fix(c2): plan create 置 active + confirm 不改状态 + change 校验同老人
- R7: create 置 planStatus=active(spec 权威);confirm 仅写确认字段不改状态 - R8: change 校验 oldPlan.elderId === dto.elderId,不匹配拒绝 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
418dfb9dae
commit
161a1253ee
@ -136,7 +136,7 @@ describe('PlanService', () => {
|
|||||||
elderId: 'e1',
|
elderId: 'e1',
|
||||||
itemCount: 18,
|
itemCount: 18,
|
||||||
itemCountRule: 'inst_medical/care_1 ≥18 项',
|
itemCountRule: 'inst_medical/care_1 ≥18 项',
|
||||||
planStatus: 'draft',
|
planStatus: 'active',
|
||||||
tenantCode: 'T001',
|
tenantCode: 'T001',
|
||||||
createdBy: 'u1',
|
createdBy: 'u1',
|
||||||
}),
|
}),
|
||||||
@ -208,12 +208,12 @@ describe('PlanService', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(planCreate).toHaveBeenCalledWith({
|
expect(planCreate).toHaveBeenCalledWith({
|
||||||
data: expect.objectContaining({ careMode: 'home_org', itemCount: 0, planStatus: 'draft' }),
|
data: expect.objectContaining({ careMode: 'home_org', itemCount: 0, planStatus: 'active' }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('变更留旧版:旧计划转 changed + 新计划 change_from_plan_id', async () => {
|
it('变更留旧版:旧计划转 changed + 新计划 change_from_plan_id', async () => {
|
||||||
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p-old', planStatus: 'active' });
|
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p-old', elderId: 'e1', planStatus: 'active' });
|
||||||
const planCreate = vi.fn().mockResolvedValue({ id: 'p-new' });
|
const planCreate = vi.fn().mockResolvedValue({ id: 'p-new' });
|
||||||
const planUpdate = vi.fn().mockResolvedValue({ id: 'p-old' });
|
const planUpdate = vi.fn().mockResolvedValue({ id: 'p-old' });
|
||||||
const { service } = makeService({ planFindUnique, planCreate, planUpdate });
|
const { service } = makeService({ planFindUnique, planCreate, planUpdate });
|
||||||
@ -233,8 +233,8 @@ describe('PlanService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('确认置 confirm_time + elder/guardian 布尔 + 转 active', async () => {
|
it('确认置 confirm_time + elder/guardian 布尔,不改变 plan_status', async () => {
|
||||||
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p1', planStatus: 'draft' });
|
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p1', planStatus: 'active' });
|
||||||
const planUpdate = vi.fn().mockResolvedValue({ id: 'p1' });
|
const planUpdate = vi.fn().mockResolvedValue({ id: 'p1' });
|
||||||
const { service } = makeService({ planFindUnique, planUpdate });
|
const { service } = makeService({ planFindUnique, planUpdate });
|
||||||
|
|
||||||
@ -242,15 +242,30 @@ describe('PlanService', () => {
|
|||||||
|
|
||||||
expect(planUpdate).toHaveBeenCalledWith({
|
expect(planUpdate).toHaveBeenCalledWith({
|
||||||
where: { id: 'p1' },
|
where: { id: 'p1' },
|
||||||
data: expect.objectContaining({
|
data: {
|
||||||
confirmElder: true,
|
confirmElder: true,
|
||||||
confirmGuardian: false,
|
confirmGuardian: false,
|
||||||
confirmTime: expect.any(Date),
|
confirmTime: expect.any(Date),
|
||||||
planStatus: 'active',
|
},
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('变更计划 elder 不匹配 → 拒绝,不写新计划', async () => {
|
||||||
|
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p-old', elderId: 'e-other', planStatus: 'active' });
|
||||||
|
const planCreate = vi.fn();
|
||||||
|
const planUpdate = vi.fn();
|
||||||
|
const { service } = makeService({ planFindUnique, planCreate, planUpdate });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
runWithRequestContext(ctx, () =>
|
||||||
|
service.change({ ...baseDto, careMode: 'home_org', items: [], changeFromPlanId: 'p-old' }),
|
||||||
|
),
|
||||||
|
).rejects.toMatchObject({ response: { code: ErrorCodes.PLAN_NOT_FOUND } });
|
||||||
|
|
||||||
|
expect(planCreate).not.toHaveBeenCalled();
|
||||||
|
expect(planUpdate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('detail 返回计划头 + 计划项', async () => {
|
it('detail 返回计划头 + 计划项', async () => {
|
||||||
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p1', elderId: 'e1', planStatus: 'active' });
|
const planFindUnique = vi.fn().mockResolvedValue({ id: 'p1', elderId: 'e1', planStatus: 'active' });
|
||||||
const planItemFindMany = vi.fn().mockResolvedValue([{ id: 'pi1', planId: 'p1' }]);
|
const planItemFindMany = vi.fn().mockResolvedValue([{ id: 'pi1', planId: 'p1' }]);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ interface ResolvedPlanItem extends PlanItemInputDto {
|
|||||||
* 服务计划(svc_plan/svc_plan_item)业务服务。
|
* 服务计划(svc_plan/svc_plan_item)业务服务。
|
||||||
*
|
*
|
||||||
* - 制定计划:校验老人存在 + 每个 svc_id 落本机构 L4 + 属地项数(昆明校验/州市提示);
|
* - 制定计划:校验老人存在 + 每个 svc_id 落本机构 L4 + 属地项数(昆明校验/州市提示);
|
||||||
* 快照 item_count/item_count_rule,plan_status 默认 draft;写 plan_item(svc_name/code 快照)。
|
* 快照 item_count/item_count_rule,plan_status=active;写 plan_item(svc_name/code 快照)。
|
||||||
* - 变更计划:新建 Plan + change_from_plan_id 指向旧计划,旧计划转 changed,不原地改。
|
* - 变更计划:新建 Plan + change_from_plan_id 指向旧计划,旧计划转 changed,不原地改。
|
||||||
* - 确认:置 confirm_time + elder/guardian 布尔,计划转 active。
|
* - 确认:置 confirm_time + elder/guardian 布尔,计划转 active。
|
||||||
*/
|
*/
|
||||||
@ -25,20 +25,23 @@ interface ResolvedPlanItem extends PlanItemInputDto {
|
|||||||
export class PlanService {
|
export class PlanService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
/** 制定服务计划(draft)。 */
|
/** 制定服务计划(active)。 */
|
||||||
create(dto: CreatePlanDto) {
|
create(dto: CreatePlanDto) {
|
||||||
return this.buildPlan(dto, { planStatus: 'draft' });
|
return this.buildPlan(dto, { planStatus: 'active' });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 变更服务计划:新建 active 计划并指向旧计划,旧计划转 changed。 */
|
/** 变更服务计划:新建 active 计划并指向旧计划,旧计划转 changed。 */
|
||||||
async change(dto: ChangePlanDto) {
|
async change(dto: ChangePlanDto) {
|
||||||
await this.requireExists(dto.changeFromPlanId);
|
const oldPlan = await this.requireExists(dto.changeFromPlanId);
|
||||||
|
if (oldPlan.elderId !== dto.elderId) {
|
||||||
|
throw new BusinessException(ErrorCodes.PLAN_NOT_FOUND, '被变更计划不属于该老人', HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
const newPlan = await this.buildPlan(dto, { planStatus: 'active', changeFromPlanId: dto.changeFromPlanId });
|
const newPlan = await this.buildPlan(dto, { planStatus: 'active', changeFromPlanId: dto.changeFromPlanId });
|
||||||
await this.prisma.client.plan.update({ where: { id: dto.changeFromPlanId }, data: { planStatus: 'changed' } });
|
await this.prisma.client.plan.update({ where: { id: dto.changeFromPlanId }, data: { planStatus: 'changed' } });
|
||||||
return newPlan;
|
return newPlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 确认计划:置确认时间与老人/家属布尔,计划转 active。 */
|
/** 确认计划:仅写确认时间与老人/家属布尔(不改变 plan_status)。 */
|
||||||
async confirm(id: string, dto: ConfirmPlanDto) {
|
async confirm(id: string, dto: ConfirmPlanDto) {
|
||||||
await this.requireExists(id);
|
await this.requireExists(id);
|
||||||
return this.prisma.client.plan.update({
|
return this.prisma.client.plan.update({
|
||||||
@ -47,7 +50,6 @@ export class PlanService {
|
|||||||
confirmElder: dto.confirmElder,
|
confirmElder: dto.confirmElder,
|
||||||
confirmGuardian: dto.confirmGuardian ?? false,
|
confirmGuardian: dto.confirmGuardian ?? false,
|
||||||
confirmTime: new Date(),
|
confirmTime: new Date(),
|
||||||
planStatus: 'active',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user