|
|
@@ -8,9 +8,14 @@ describe('CustomersService', () => {
|
|
|
const prisma = {
|
|
|
customer: {
|
|
|
create: jest.fn(),
|
|
|
+ findFirst: jest.fn(),
|
|
|
findMany: jest.fn(),
|
|
|
+ update: jest.fn(),
|
|
|
count: jest.fn(),
|
|
|
},
|
|
|
+ customerTreatmentRecord: {
|
|
|
+ findMany: jest.fn(),
|
|
|
+ },
|
|
|
aiHairSimulation: {
|
|
|
findMany: jest.fn(),
|
|
|
},
|
|
|
@@ -172,4 +177,150 @@ describe('CustomersService', () => {
|
|
|
}),
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ it('returns customer detail with 未记录 visit frequency when fewer than two treatment records exist', async () => {
|
|
|
+ prisma.customer.findFirst.mockResolvedValue({
|
|
|
+ id: BigInt(8),
|
|
|
+ hairstylistId: BigInt(1),
|
|
|
+ name: 'Sato Misaki',
|
|
|
+ namePronunciation: 'さとう みさき',
|
|
|
+ phone: '090-0000-0000',
|
|
|
+ email: 'misaki@example.com',
|
|
|
+ birthday: new Date('1998-04-10T00:00:00.000Z'),
|
|
|
+ avatar: 'https://example.com/avatar.png',
|
|
|
+ expectContact: 'LINE',
|
|
|
+ hairLength: 'MEDIUM',
|
|
|
+ hairTexture: 'NORMAL',
|
|
|
+ notice: '注意保湿',
|
|
|
+ allergy: '无',
|
|
|
+ innerRemark: '常客',
|
|
|
+ nextFollowDate: new Date('2026-07-12T00:00:00.000Z'),
|
|
|
+ recommendVisitDate: new Date('2026-07-20T00:00:00.000Z'),
|
|
|
+ firstSuggestHair: 'AUTO',
|
|
|
+ planDate: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ isFirstRegister: 1,
|
|
|
+ visitPurpose: 'CONSULT_SIM',
|
|
|
+ trouble: '发量少',
|
|
|
+ consultContent: '希望轻盈',
|
|
|
+ expectLook: '自然层次',
|
|
|
+ createTime: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ updateTime: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ isDeleted: 0,
|
|
|
+ });
|
|
|
+ prisma.customerTreatmentRecord.findMany.mockResolvedValue([
|
|
|
+ { treatmentTime: new Date('2026-05-01T00:00:00.000Z') },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const result = await service.getById(BigInt(1), BigInt(8));
|
|
|
+
|
|
|
+ expect(result).toMatchObject({
|
|
|
+ id: '8',
|
|
|
+ name: 'Sato Misaki',
|
|
|
+ visitFrequencyLabel: '未记录',
|
|
|
+ recommendVisitDate: '2026-07-20',
|
|
|
+ nextFollowDate: '2026-07-12',
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('maps treatment intervals to a visit frequency label', async () => {
|
|
|
+ prisma.customer.findFirst.mockResolvedValue({
|
|
|
+ id: BigInt(8),
|
|
|
+ hairstylistId: BigInt(1),
|
|
|
+ name: 'Sato Misaki',
|
|
|
+ namePronunciation: '',
|
|
|
+ phone: '090-0000-0000',
|
|
|
+ email: '',
|
|
|
+ birthday: null,
|
|
|
+ avatar: 'https://example.com/avatar.png',
|
|
|
+ expectContact: 'LINE',
|
|
|
+ hairLength: 'MEDIUM',
|
|
|
+ hairTexture: 'NORMAL',
|
|
|
+ notice: null,
|
|
|
+ allergy: '',
|
|
|
+ innerRemark: null,
|
|
|
+ nextFollowDate: null,
|
|
|
+ recommendVisitDate: null,
|
|
|
+ firstSuggestHair: 'AUTO',
|
|
|
+ planDate: null,
|
|
|
+ isFirstRegister: 1,
|
|
|
+ visitPurpose: null,
|
|
|
+ trouble: null,
|
|
|
+ consultContent: null,
|
|
|
+ expectLook: null,
|
|
|
+ createTime: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ updateTime: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ isDeleted: 0,
|
|
|
+ });
|
|
|
+ prisma.customerTreatmentRecord.findMany.mockResolvedValue([
|
|
|
+ { treatmentTime: new Date('2026-01-01T00:00:00.000Z') },
|
|
|
+ { treatmentTime: new Date('2026-03-02T00:00:00.000Z') },
|
|
|
+ { treatmentTime: new Date('2026-05-01T00:00:00.000Z') },
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const result = await service.getById(BigInt(1), BigInt(8));
|
|
|
+
|
|
|
+ expect(result.visitFrequencyLabel).toBe('约 2 个月');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('updates only the current stylist customer', async () => {
|
|
|
+ prisma.customer.findFirst.mockResolvedValueOnce({
|
|
|
+ id: BigInt(8),
|
|
|
+ hairstylistId: BigInt(1),
|
|
|
+ isDeleted: 0,
|
|
|
+ });
|
|
|
+ prisma.customer.update.mockResolvedValue({
|
|
|
+ id: BigInt(8),
|
|
|
+ hairstylistId: BigInt(1),
|
|
|
+ name: 'Sato Misaki Updated',
|
|
|
+ namePronunciation: 'さとう みさき',
|
|
|
+ phone: '090-0000-0001',
|
|
|
+ email: '',
|
|
|
+ birthday: null,
|
|
|
+ avatar: 'https://example.com/avatar.png',
|
|
|
+ expectContact: 'PHONE',
|
|
|
+ hairLength: 'LONG',
|
|
|
+ hairTexture: 'CURLY',
|
|
|
+ notice: null,
|
|
|
+ allergy: '',
|
|
|
+ innerRemark: null,
|
|
|
+ nextFollowDate: null,
|
|
|
+ recommendVisitDate: null,
|
|
|
+ firstSuggestHair: 'AUTO',
|
|
|
+ planDate: null,
|
|
|
+ isFirstRegister: 1,
|
|
|
+ visitPurpose: null,
|
|
|
+ trouble: null,
|
|
|
+ consultContent: null,
|
|
|
+ expectLook: null,
|
|
|
+ createTime: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ updateTime: new Date('2026-06-28T00:00:00.000Z'),
|
|
|
+ isDeleted: 0,
|
|
|
+ });
|
|
|
+ prisma.customerTreatmentRecord.findMany.mockResolvedValue([]);
|
|
|
+
|
|
|
+ await service.update(
|
|
|
+ BigInt(1),
|
|
|
+ BigInt(8),
|
|
|
+ {
|
|
|
+ name: 'Sato Misaki Updated',
|
|
|
+ phone: '090-0000-0001',
|
|
|
+ expectContact: 'PHONE',
|
|
|
+ hairLength: 'LONG',
|
|
|
+ hairTexture: 'CURLY',
|
|
|
+ } as never,
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(prisma.customer.update).toHaveBeenCalledWith(
|
|
|
+ expect.objectContaining({
|
|
|
+ where: { id: BigInt(8) },
|
|
|
+ data: expect.objectContaining({
|
|
|
+ name: 'Sato Misaki Updated',
|
|
|
+ phone: '090-0000-0001',
|
|
|
+ expectContact: 'PHONE',
|
|
|
+ hairLength: 'LONG',
|
|
|
+ hairTexture: 'CURLY',
|
|
|
+ }),
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ });
|
|
|
});
|