feat(document): EntityDocumentsService + autoTitle + soft-delete cascade
list/create/delete for Document attached to ClientContract, DeliveryLot, ClientInvoice. Auto-titles from entity fields. Soft-delete cascades to DocumentFile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dc0e40a4b6
commit
08a5e36582
109
src/modules/documents/entity-documents.service.ts
Normal file
109
src/modules/documents/entity-documents.service.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { PrismaClient, DocumentType, DocumentDirection, Prisma } from '@prisma/client';
|
||||
|
||||
export type EntityKind = 'client-contract' | 'delivery-lot' | 'client-invoice';
|
||||
|
||||
interface CreateDto {
|
||||
documentType: DocumentType;
|
||||
title?: string;
|
||||
direction?: DocumentDirection;
|
||||
parentDocumentId?: number;
|
||||
responsibleId?: string;
|
||||
}
|
||||
|
||||
const FK_MAP: Record<EntityKind, string> = {
|
||||
'client-contract': 'clientContractId',
|
||||
'delivery-lot': 'deliveryLotId',
|
||||
'client-invoice': 'clientInvoiceId',
|
||||
};
|
||||
|
||||
const DEFAULT_DIRECTION: Record<EntityKind, DocumentDirection> = {
|
||||
'client-contract': 'CLIENT',
|
||||
'delivery-lot': 'SUPPLIER',
|
||||
'client-invoice': 'CLIENT',
|
||||
};
|
||||
|
||||
const DOCUMENT_INCLUDE = {
|
||||
responsible: { select: { id: true, name: true, nameRu: true } },
|
||||
files: { where: { deletedAt: null }, select: { id: true, originalName: true, version: true, isLatest: true, mimeType: true, fileSize: true, createdAt: true } },
|
||||
} as const;
|
||||
|
||||
export function autoTitleForEntity(kind: EntityKind, entity: any): string {
|
||||
switch (kind) {
|
||||
case 'client-contract': return `Контракт ${entity.contractNumber ?? ''}`.trim();
|
||||
case 'delivery-lot': return `УПД ${entity.updNumber ?? ''}`.trim();
|
||||
case 'client-invoice': return `Счёт ${entity.invoiceNumber ?? ''}`.trim();
|
||||
}
|
||||
}
|
||||
|
||||
export class EntityDocumentsService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
async list(kind: EntityKind, entityId: number) {
|
||||
const fk = FK_MAP[kind];
|
||||
return this.prisma.document.findMany({
|
||||
where: { [fk]: entityId, deletedAt: null },
|
||||
include: DOCUMENT_INCLUDE,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async create(kind: EntityKind, entityId: number, dto: CreateDto, userId?: string) {
|
||||
const entity = await this.loadEntity(kind, entityId);
|
||||
if (!entity) {
|
||||
const err = new Error('Entité non trouvée') as any;
|
||||
err.statusCode = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (dto.parentDocumentId) {
|
||||
const parent = await this.prisma.document.findFirst({
|
||||
where: { id: dto.parentDocumentId, [FK_MAP[kind]]: entityId, deletedAt: null },
|
||||
});
|
||||
if (!parent) {
|
||||
const err = new Error('Document parent introuvable ou appartient à une autre entité') as any;
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const title = dto.title || autoTitleForEntity(kind, entity);
|
||||
const direction = dto.direction ?? DEFAULT_DIRECTION[kind];
|
||||
|
||||
return this.prisma.document.create({
|
||||
data: {
|
||||
documentType: dto.documentType,
|
||||
title,
|
||||
direction,
|
||||
[FK_MAP[kind]]: entityId,
|
||||
responsibleId: dto.responsibleId ?? userId,
|
||||
parentDocumentId: dto.parentDocumentId,
|
||||
},
|
||||
include: DOCUMENT_INCLUDE,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(documentId: number) {
|
||||
const now = new Date();
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.document.update({
|
||||
where: { id: documentId },
|
||||
data: { deletedAt: now },
|
||||
}),
|
||||
this.prisma.documentFile.updateMany({
|
||||
where: { documentId, deletedAt: null },
|
||||
data: { deletedAt: now },
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
private async loadEntity(kind: EntityKind, entityId: number) {
|
||||
switch (kind) {
|
||||
case 'client-contract':
|
||||
return this.prisma.clientContract.findUnique({ where: { id: entityId } });
|
||||
case 'delivery-lot':
|
||||
return this.prisma.deliveryLot.findUnique({ where: { id: entityId } });
|
||||
case 'client-invoice':
|
||||
return this.prisma.clientInvoice.findUnique({ where: { id: entityId } });
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user