| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import Parse from '../../../../shared/db/parse-client.js';
- import {
- getInternalContactList,
- getContactDetailsBatch,
- } from './qiwe-api.service.js';
- import { listStores } from './organization.service.js';
- export interface OrgDepartmentDto {
- id: string;
- partyId: string;
- name: string;
- parentPartyId: string;
- regionCode: string;
- storeId: string;
- orderIndex: number;
- }
- export interface OrgMemberDto {
- id: string;
- userId: string;
- name: string;
- partyIds: string[];
- mobile: string;
- position: string;
- }
- export interface OrgTreeNode {
- partyId: string;
- name: string;
- regionCode: string;
- storeId: string;
- children: OrgTreeNode[];
- members: OrgMemberDto[];
- }
- const SEED_DEPARTMENTS = [
- { partyId: 'root', name: '拉迷总部', parentPartyId: '', regionCode: '', storeId: '', orderIndex: 0 },
- { partyId: 'region_east', name: '华东区域', parentPartyId: 'root', regionCode: '华东', storeId: '', orderIndex: 1 },
- { partyId: 'region_north', name: '华北区域', parentPartyId: 'root', regionCode: '华北', storeId: '', orderIndex: 2 },
- { partyId: 'region_south', name: '华南区域', parentPartyId: 'root', regionCode: '华南', storeId: '', orderIndex: 3 },
- ];
- function deptToDto(obj: Parse.Object): OrgDepartmentDto {
- return {
- id: obj.id!,
- partyId: obj.get('partyId') || '',
- name: obj.get('name') || '',
- parentPartyId: obj.get('parentPartyId') || '',
- regionCode: obj.get('regionCode') || '',
- storeId: obj.get('storeId') || '',
- orderIndex: obj.get('orderIndex') ?? 0,
- };
- }
- function memberToDto(obj: Parse.Object): OrgMemberDto {
- return {
- id: obj.id!,
- userId: obj.get('userId') || '',
- name: obj.get('name') || '',
- partyIds: (obj.get('partyIds') as string[]) || [],
- mobile: obj.get('mobile') || '',
- position: obj.get('position') || '',
- };
- }
- export async function listOrgDepartments(): Promise<OrgDepartmentDto[]> {
- const query = new Parse.Query('OrgDepartment');
- query.ascending('orderIndex');
- query.limit(500);
- const results = await query.find({ useMasterKey: true });
- return results.map(deptToDto);
- }
- export async function listOrgMembers(): Promise<OrgMemberDto[]> {
- const query = new Parse.Query('OrgMember');
- query.limit(1000);
- const results = await query.find({ useMasterKey: true });
- return results.map(memberToDto);
- }
- export async function getOrgMemberName(userId: string): Promise<string> {
- if (!userId) return '';
- const query = new Parse.Query('OrgMember');
- query.equalTo('userId', userId);
- const member = await query.first({ useMasterKey: true });
- return member?.get('name') || '';
- }
- export async function buildOrgTree(): Promise<OrgTreeNode[]> {
- const [departments, members] = await Promise.all([
- listOrgDepartments(),
- listOrgMembers(),
- ]);
- const nodeMap = new Map<string, OrgTreeNode>();
- for (const dept of departments) {
- nodeMap.set(dept.partyId, {
- partyId: dept.partyId,
- name: dept.name,
- regionCode: dept.regionCode,
- storeId: dept.storeId,
- children: [],
- members: [],
- });
- }
- for (const member of members) {
- const primaryParty = member.partyIds[0];
- const node = primaryParty ? nodeMap.get(primaryParty) : undefined;
- if (node) node.members.push(member);
- }
- const roots: OrgTreeNode[] = [];
- for (const dept of departments) {
- const node = nodeMap.get(dept.partyId)!;
- if (!dept.parentPartyId) {
- roots.push(node);
- continue;
- }
- const parent = nodeMap.get(dept.parentPartyId);
- if (parent) parent.children.push(node);
- else roots.push(node);
- }
- return roots;
- }
- async function upsertDepartment(data: Omit<OrgDepartmentDto, 'id'>): Promise<void> {
- const query = new Parse.Query('OrgDepartment');
- query.equalTo('partyId', data.partyId);
- const existing = await query.first({ useMasterKey: true });
- const obj = existing ?? new Parse.Object('OrgDepartment');
- obj.set('partyId', data.partyId);
- obj.set('name', data.name);
- obj.set('parentPartyId', data.parentPartyId);
- obj.set('regionCode', data.regionCode);
- obj.set('storeId', data.storeId);
- obj.set('orderIndex', data.orderIndex);
- obj.set('syncedAt', new Date());
- await obj.save(null, { useMasterKey: true });
- }
- async function upsertMember(data: Omit<OrgMemberDto, 'id'>): Promise<void> {
- const query = new Parse.Query('OrgMember');
- query.equalTo('userId', data.userId);
- const existing = await query.first({ useMasterKey: true });
- const obj = existing ?? new Parse.Object('OrgMember');
- obj.set('userId', data.userId);
- obj.set('name', data.name);
- obj.set('partyIds', data.partyIds);
- obj.set('mobile', data.mobile);
- obj.set('position', data.position);
- obj.set('syncedAt', new Date());
- await obj.save(null, { useMasterKey: true });
- }
- export async function bootstrapOrgTree(): Promise<void> {
- const stores = await listStores();
- let order = 10;
- for (const seed of SEED_DEPARTMENTS) {
- await upsertDepartment({ ...seed, orderIndex: seed.orderIndex });
- }
- for (const store of stores) {
- const partyId = `store_${store.code.toLowerCase()}`;
- const parentPartyId = SEED_DEPARTMENTS.find((d) => d.regionCode === store.region)?.partyId || 'root';
- await upsertDepartment({
- partyId,
- name: store.name,
- parentPartyId,
- regionCode: store.region,
- storeId: store.id,
- orderIndex: order++,
- });
- }
- console.log('[Org] 组织树种子已同步');
- }
- export async function syncOrgFromQiWe(): Promise<{
- departments: number;
- members: number;
- source: 'qiwe' | 'seed';
- errors: string[];
- }> {
- const errors: string[] = [];
- try {
- const contacts = await getInternalContactList();
- if (contacts.length === 0) {
- return { departments: 0, members: 0, source: 'seed', errors: ['企微未返回内部联系人,保留本地组织树'] };
- }
- const userIds = contacts.map((c) => c.userId).filter(Boolean);
- const details = await getContactDetailsBatch(userIds);
- const partyIds = new Set<string>();
- for (const contact of contacts) {
- if (contact.partyId) partyIds.add(contact.partyId);
- }
- let deptCount = 0;
- for (const partyId of partyIds) {
- await upsertDepartment({
- partyId,
- name: `部门 ${partyId.slice(-6)}`,
- parentPartyId: 'root',
- regionCode: '',
- storeId: '',
- orderIndex: deptCount,
- });
- deptCount++;
- }
- let memberCount = 0;
- for (const contact of contacts) {
- const detail = details.find((d) => d.userId === contact.userId);
- await upsertMember({
- userId: contact.userId,
- name: detail?.name || contact.userId,
- partyIds: contact.partyId ? [contact.partyId] : [],
- mobile: detail?.mobile || '',
- position: detail?.position || '',
- });
- memberCount++;
- }
- return { departments: deptCount, members: memberCount, source: 'qiwe', errors };
- } catch (err: unknown) {
- const msg = err instanceof Error ? err.message : String(err);
- errors.push(msg);
- return { departments: 0, members: 0, source: 'seed', errors };
- }
- }
|