fix(c3): record 坐标 Decimal 返回前转字符串,防泄漏 {s,e,d}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-15 01:00:06 +08:00
parent ab09210d28
commit c00c4ee007
2 changed files with 37 additions and 5 deletions

View File

@ -647,6 +647,27 @@ describe('RecordService.detail / list / prepaidList', () => {
expect(res).toMatchObject({ id: 'r1', items: [{ id: 'ri1' }], photos: [{ id: 'rp1' }], abnormals: [{ id: 'ra1' }] }); expect(res).toMatchObject({ id: 'r1', items: [{ id: 'ri1' }], photos: [{ id: 'rp1' }], abnormals: [{ id: 'ra1' }] });
}); });
it('detail/list 将 Decimal 坐标序列化为字符串(null 保持 null)', async () => {
const recordFindUnique = vi.fn().mockResolvedValue({
id: 'r1',
recordStatus: 'submitted',
checkinLat: { toString: () => '25.010000' },
checkinLng: null,
});
const recordFindMany = vi.fn().mockResolvedValue([
{ id: 'r2', checkinLat: { toString: () => '26.500000' }, checkinLng: { toString: () => '103.100000' } },
]);
const { service } = makeService({ recordFindUnique, recordFindMany });
const detail = await runWithRequestContext(ctx, () => service.detail('r1'));
expect(detail.checkinLat).toBe('25.010000');
expect(detail.checkinLng).toBeNull();
const list = await runWithRequestContext(ctx, () => service.list({}));
expect(list[0].checkinLat).toBe('26.500000');
expect(list[0].checkinLng).toBe('103.100000');
});
it('list 按 elder/assignee/状态筛选', async () => { it('list 按 elder/assignee/状态筛选', async () => {
const recordFindMany = vi.fn().mockResolvedValue([{ id: 'r1' }]); const recordFindMany = vi.fn().mockResolvedValue([{ id: 'r1' }]);
const { service } = makeService({ recordFindMany }); const { service } = makeService({ recordFindMany });

View File

@ -267,11 +267,11 @@ export class RecordService {
this.prisma.client.recordPhoto.findMany({ where: { recordId: id } }), this.prisma.client.recordPhoto.findMany({ where: { recordId: id } }),
this.prisma.client.recordAbnormal.findMany({ where: { recordId: id } }), this.prisma.client.recordAbnormal.findMany({ where: { recordId: id } }),
]); ]);
return { ...record, items, photos, abnormals }; return this.serializeRecord({ ...record, items, photos, abnormals });
} }
/** 列表:按老人/护理员/日期/状态筛选。 */ /** 列表:按老人/护理员/日期/状态筛选。 */
list(q: ListRecordQuery) { async list(q: ListRecordQuery) {
const where: Record<string, any> = {}; const where: Record<string, any> = {};
if (q.elderId) where.elderId = q.elderId; if (q.elderId) where.elderId = q.elderId;
if (q.assigneeId) where.assigneeId = q.assigneeId; if (q.assigneeId) where.assigneeId = q.assigneeId;
@ -282,12 +282,14 @@ export class RecordService {
lte: new Date(`${q.serviceDate}T23:59:59.999+08:00`), lte: new Date(`${q.serviceDate}T23:59:59.999+08:00`),
}; };
} }
return this.prisma.client.record.findMany({ where, orderBy: { checkinTime: 'desc' } }); const rows = await this.prisma.client.record.findMany({ where, orderBy: { checkinTime: 'desc' } });
return rows.map((r) => this.serializeRecord(r));
} }
/** 垫付台账:按老人返回 is_prepaid=1 的记录。 */ /** 垫付台账:按老人返回 is_prepaid=1 的记录。 */
prepaidList(q: PrepaidListQuery) { async prepaidList(q: PrepaidListQuery) {
return this.prisma.client.record.findMany({ where: { elderId: q.elderId, isPrepaid: true } }); const rows = await this.prisma.client.record.findMany({ where: { elderId: q.elderId, isPrepaid: true } });
return rows.map((r) => this.serializeRecord(r));
} }
/** 结转:复制垫付 record → 正式 record(is_prepaid=0 + source_prepaid_of),原记录不改。 */ /** 结转:复制垫付 record → 正式 record(is_prepaid=0 + source_prepaid_of),原记录不改。 */
@ -387,6 +389,15 @@ export class RecordService {
return record; return record;
} }
/** Prisma Decimal 经 TransformInterceptor 不会转,泄漏为 {s,e,d};返回前把坐标转字符串(null 保持 null)。 */
private serializeRecord(record: Record<string, any>): Record<string, any> {
return {
...record,
checkinLat: record.checkinLat != null ? record.checkinLat.toString() : null,
checkinLng: record.checkinLng != null ? record.checkinLng.toString() : null,
};
}
/** 校验 exec_result 枚举 + svc_id 落本机构 L4,并解析快照;photo_required 从 order.item_list 推导。 */ /** 校验 exec_result 枚举 + svc_id 落本机构 L4,并解析快照;photo_required 从 order.item_list 推导。 */
private async resolveItems(items: RecordItemDto[], order?: Record<string, any> | null) { private async resolveItems(items: RecordItemDto[], order?: Record<string, any> | null) {
return Promise.all( return Promise.all(