/** * 小区管理模块 — 业务服务层 * * 对应规范文档: * §5.5「录入小区基础档案」 * §5.6「维护小区—群—门店对应关系」 * * 业务规则: * 1. 小区户数无法从 QiWe 自动获取,需人工录入 * 2. 群与小区/门店的绑定关系禁止自动猜测——必须人工确认 * 3. 未绑定群在看板中标记为「待维护」 * 4. 报表按小区/门店聚合时,通过绑定表 INNER JOIN */ import { AppError } from '../../../../shared/errors/app-error.js'; import type { Community, CreateCommunityRequest, CommunityRoomBinding, CreateBindingRequest, } from '../models/community.model.js'; // ---------- 内存存储(TODO: 替换为数据库)---------- const communityStore = new Map(); const bindingStore = new Map(); let nextCommunityId = 1; let nextBindingId = 1; // ============================================================ // 小区档案 CRUD // ============================================================ /** * 录入小区基础档案 * * 户数是触达率计算的分母,必须准确。 * 房价、交房时间等字段供看板和经营分析使用。 */ export function createCommunity(data: CreateCommunityRequest): Community { const now = new Date().toISOString(); const community: Community = { id: nextCommunityId++, name: data.name, totalHouseholds: data.totalHouseholds, avgPrice: data.avgPrice || 0, deliveryYear: data.deliveryYear || 0, storeId: data.storeId, address: data.address || '', remark: data.remark || '', createdAt: now, updatedAt: now, }; communityStore.set(community.id, community); return community; } /** * 查询小区列表(支持按门店筛选) */ export function listCommunities(storeId?: number): Community[] { let result = Array.from(communityStore.values()); if (storeId !== undefined) { result = result.filter((c) => c.storeId === storeId); } return result; } /** * 查询单个小区详情 */ export function getCommunityById(id: number): Community { const community = communityStore.get(id); if (!community) { throw new AppError(404, 'COMMUNITY_NOT_FOUND', `小区 ID=${id} 不存在`); } return community; } /** * 更新小区信息 */ export function updateCommunity(id: number, data: Partial): Community { const community = getCommunityById(id); if (data.name !== undefined) community.name = data.name; if (data.totalHouseholds !== undefined) community.totalHouseholds = data.totalHouseholds; if (data.avgPrice !== undefined) community.avgPrice = data.avgPrice; if (data.deliveryYear !== undefined) community.deliveryYear = data.deliveryYear; if (data.storeId !== undefined) community.storeId = data.storeId; if (data.address !== undefined) community.address = data.address; if (data.remark !== undefined) community.remark = data.remark; community.updatedAt = new Date().toISOString(); communityStore.set(id, community); return community; } /** * 删除小区 */ export function deleteCommunity(id: number): void { getCommunityById(id); communityStore.delete(id); // 同时清理关联的绑定关系 for (const [key, binding] of bindingStore) { if (binding.communityId === id) { bindingStore.delete(key); } } } // ============================================================ // 小区-群-门店绑定 // ============================================================ /** * 创建小区-群-门店绑定关系 * * 业务约束: * - 同一个 roomId 不可重复绑定到不同的小区 * - 绑定禁止自动猜测,必须人工确认 * * @throws {AppError} 当 roomId 已被绑定时 */ export function createBinding(data: CreateBindingRequest): CommunityRoomBinding { // 检查 roomId 是否已绑定 for (const binding of bindingStore.values()) { if (binding.roomId === data.roomId) { throw new AppError( 409, 'BINDING_DUPLICATE', `群 ${data.roomId} 已绑定到小区 ID=${binding.communityId},不可重复绑定`, ); } } const binding: CommunityRoomBinding = { id: nextBindingId++, communityId: data.communityId, roomId: data.roomId, storeId: data.storeId, createdAt: new Date().toISOString(), }; bindingStore.set(binding.id, binding); return binding; } /** * 查询绑定关系列表(按小区或门店筛选) */ export function listBindings(communityId?: number, storeId?: number): CommunityRoomBinding[] { let result = Array.from(bindingStore.values()); if (communityId !== undefined) { result = result.filter((b) => b.communityId === communityId); } if (storeId !== undefined) { result = result.filter((b) => b.storeId === storeId); } return result; } /** * 删除绑定关系(解绑) */ export function deleteBinding(id: number): void { const binding = bindingStore.get(id); if (!binding) { throw new AppError(404, 'BINDING_NOT_FOUND', `绑定 ID=${id} 不存在`); } bindingStore.delete(id); } /** * 按 roomId 查询绑定(用于合规等模块关联查询) */ export function getBindingByRoomId(roomId: string): CommunityRoomBinding | undefined { for (const binding of bindingStore.values()) { if (binding.roomId === roomId) { return binding; } } return undefined; }