Auto-Vertragsstatus: Lieferbestätigung hochladen → DRAFT auf ACTIVE
Ergänzung zum Cancellation-Trigger: wenn ein ContractDocument mit documentType "Lieferbestätigung" hochgeladen wird und der Vertrag aktuell DRAFT ist, wird er automatisch auf ACTIVE gesetzt (+ Audit-Log). Greift an beiden Upload-Pfaden: - POST /api/contracts/:id/documents (Direkt-Upload via ContractDetail) - POST /api/emails/:id/attachments/:filename/save-as-contract-document (Email-Anhang als Vertragsdokument speichern) Vergleich case-insensitive + getrimmt auf "lieferbestätigung". Andere Typen (Auftragsformular etc.) lösen keinen Wechsel aus. Nicht-DRAFT- Verträge (ACTIVE/CANCELLED/EXPIRED/DEACTIVATED) bleiben unverändert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
10ddd5118c
commit
b554c8e436
|
|
@ -11,6 +11,7 @@ import { decrypt } from '../utils/encryption.js';
|
||||||
import { ApiResponse } from '../types/index.js';
|
import { ApiResponse } from '../types/index.js';
|
||||||
import { getCustomerTargets, getContractTargets, getIdentityDocumentTargets, getBankCardTargets, documentTargets } from '../config/documentTargets.config.js';
|
import { getCustomerTargets, getContractTargets, getIdentityDocumentTargets, getBankCardTargets, documentTargets } from '../config/documentTargets.config.js';
|
||||||
import { generateEmailPdf } from '../services/pdfService.js';
|
import { generateEmailPdf } from '../services/pdfService.js';
|
||||||
|
import { maybeActivateOnDeliveryConfirmation } from '../services/contractStatusScheduler.service.js';
|
||||||
import { DocumentType } from '@prisma/client';
|
import { DocumentType } from '@prisma/client';
|
||||||
import prisma from '../lib/prisma.js';
|
import prisma from '../lib/prisma.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
@ -2001,6 +2002,9 @@ export async function saveAttachmentAsContractDocument(req: Request, res: Respon
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Falls Lieferbestätigung + Vertrag DRAFT → automatisch auf ACTIVE
|
||||||
|
await maybeActivateOnDeliveryConfirmation(contract.id, documentType, req);
|
||||||
|
|
||||||
res.json({ success: true, data: doc } as ApiResponse);
|
res.json({ success: true, data: doc } as ApiResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('saveAttachmentAsContractDocument error:', error);
|
console.error('saveAttachmentAsContractDocument error:', error);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import * as authorizationService from '../services/authorization.service.js';
|
||||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||||
import { logChange } from '../services/audit.service.js';
|
import { logChange } from '../services/audit.service.js';
|
||||||
import { canAccessContract } from '../utils/accessControl.js';
|
import { canAccessContract } from '../utils/accessControl.js';
|
||||||
|
import { maybeActivateOnDeliveryConfirmation } from '../services/contractStatusScheduler.service.js';
|
||||||
|
|
||||||
export async function getContracts(req: AuthRequest, res: Response): Promise<void> {
|
export async function getContracts(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -494,6 +495,9 @@ export async function uploadContractDocument(req: AuthRequest, res: Response): P
|
||||||
customerId: contract?.customerId,
|
customerId: contract?.customerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Falls Lieferbestätigung + Vertrag DRAFT → automatisch auf ACTIVE
|
||||||
|
await maybeActivateOnDeliveryConfirmation(contractId, documentType, req);
|
||||||
|
|
||||||
res.status(201).json({ success: true, data: doc } as ApiResponse);
|
res.status(201).json({ success: true, data: doc } as ApiResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
*/
|
*/
|
||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
import prisma from '../lib/prisma.js';
|
import prisma from '../lib/prisma.js';
|
||||||
import { createAuditLog } from './audit.service.js';
|
import { createAuditLog, logChange } from './audit.service.js';
|
||||||
|
|
||||||
async function runExpireCheck(): Promise<void> {
|
async function runExpireCheck(): Promise<void> {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
@ -83,3 +83,42 @@ export function startContractStatusScheduler(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export { runExpireCheck };
|
export { runExpireCheck };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wird nach einem ContractDocument-Upload aufgerufen. Wenn der Typ eine
|
||||||
|
* Lieferbestätigung ist UND der Vertrag aktuell DRAFT ist, wird er auf
|
||||||
|
* ACTIVE gesetzt (+ Audit-Log). Andere Typen/Status bleiben unangetastet.
|
||||||
|
*
|
||||||
|
* Schreibweise "Lieferbestätigung" stammt aus dem Frontend-Dropdown
|
||||||
|
* (SaveAttachmentModal / ContractDetail). Vergleich case-insensitive +
|
||||||
|
* getrimmt zur Robustheit.
|
||||||
|
*/
|
||||||
|
export async function maybeActivateOnDeliveryConfirmation(
|
||||||
|
contractId: number,
|
||||||
|
documentType: string,
|
||||||
|
req: unknown,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!documentType || typeof documentType !== 'string') return;
|
||||||
|
if (documentType.trim().toLowerCase() !== 'lieferbestätigung') return;
|
||||||
|
|
||||||
|
const contract = await prisma.contract.findUnique({
|
||||||
|
where: { id: contractId },
|
||||||
|
select: { status: true, contractNumber: true, customerId: true },
|
||||||
|
});
|
||||||
|
if (!contract || contract.status !== 'DRAFT') return;
|
||||||
|
|
||||||
|
await prisma.contract.update({
|
||||||
|
where: { id: contractId },
|
||||||
|
data: { status: 'ACTIVE' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await logChange({
|
||||||
|
req,
|
||||||
|
action: 'UPDATE',
|
||||||
|
resourceType: 'Contract',
|
||||||
|
resourceId: contractId.toString(),
|
||||||
|
label: `Vertrag ${contract.contractNumber} automatisch auf ACTIVE gesetzt (Lieferbestätigung hochgeladen)`,
|
||||||
|
details: { vorher: 'DRAFT', nachher: 'ACTIVE', trigger: 'Lieferbestätigung-Upload' },
|
||||||
|
customerId: contract.customerId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,10 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||||
wenn Vertrag aktuell `ACTIVE` → auf `CANCELLED` setzen (Audit-Log).
|
wenn Vertrag aktuell `ACTIVE` → auf `CANCELLED` setzen (Audit-Log).
|
||||||
Der "Optionen"-Upload löst den Wechsel bewusst NICHT aus, da er für
|
Der "Optionen"-Upload löst den Wechsel bewusst NICHT aus, da er für
|
||||||
Vertragsänderungen (nicht echte Kündigungen) gedacht ist.
|
Vertragsänderungen (nicht echte Kündigungen) gedacht ist.
|
||||||
|
- Beim Upload einer `Lieferbestätigung` (ContractDocument via direkt-Upload
|
||||||
|
oder Email-Anhang-Import): wenn Vertrag aktuell `DRAFT` → auf `ACTIVE`
|
||||||
|
setzen (Audit-Log). Schreibweise stammt aus dem Frontend-Dropdown,
|
||||||
|
Vergleich case-insensitive + getrimmt.
|
||||||
- Keine neuen Status eingeführt: `cancellationSentDate` vs.
|
- Keine neuen Status eingeführt: `cancellationSentDate` vs.
|
||||||
`cancellationConfirmationDate` genügen, um "gesendet vs. bestätigt"
|
`cancellationConfirmationDate` genügen, um "gesendet vs. bestätigt"
|
||||||
abzubilden. `ACTIVE` bleibt bis zur Bestätigung.
|
abzubilden. `ACTIVE` bleibt bis zur Bestätigung.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue