Vertragsstatus-Trigger: Datum beim Upload miterfassen
Beim automatischen Status-Wechsel wird jetzt auch das passende Datum gesetzt, damit Status und Datumsfeld konsistent sind (Cockpit-Warnung "Datum fehlt" verschwindet sofort nach Upload). Backend: - Upload-Handler für Kündigungsbestätigung(s-Optionen) nimmt optional `confirmationDate` aus multipart an, speichert als cancellationConfirmationDate / cancellationConfirmationOptionsDate. Fallback: heute (nur falls Feld noch leer war). - maybeActivateOnDeliveryConfirmation nimmt optional deliveryDate, setzt Contract.startDate falls leer. Fallback: heute. Frontend: - ContractDetail: neues kleines Modal beim Kündigungsbestätigungs-Upload fragt das Bestätigungs-Datum ab (Default: heute oder bereits gesetzter Wert). Der bestehende inline-Datums-Editor bleibt für spätere Korrekturen. - ContractDocumentsSection: Datums-Input erscheint conditional im Upload-Bereich, sobald Typ "Lieferbestätigung" gewählt ist. - SaveAttachmentModal (E-Mail-Anhang → Vertragsdokument): gleicher Datums-Input conditional für "Lieferbestätigung". - API-Methoden uploadCancellationConfirmation / uploadDocument / saveAttachmentAsContractDocument nehmen optional Datum entgegen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2002,8 +2002,9 @@ export async function saveAttachmentAsContractDocument(req: Request, res: Respon
|
||||
},
|
||||
});
|
||||
|
||||
// Falls Lieferbestätigung + Vertrag DRAFT → automatisch auf ACTIVE
|
||||
await maybeActivateOnDeliveryConfirmation(contract.id, documentType, req);
|
||||
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer
|
||||
const deliveryDate = typeof req.body?.deliveryDate === 'string' ? req.body.deliveryDate : null;
|
||||
await maybeActivateOnDeliveryConfirmation(contract.id, documentType, req, deliveryDate);
|
||||
|
||||
res.json({ success: true, data: doc } as ApiResponse);
|
||||
} catch (error) {
|
||||
|
||||
@@ -462,7 +462,7 @@ export async function getContractDocuments(req: AuthRequest, res: Response): Pro
|
||||
export async function uploadContractDocument(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const contractId = parseInt(req.params.id);
|
||||
const { documentType, notes } = req.body;
|
||||
const { documentType, notes, deliveryDate } = req.body;
|
||||
|
||||
if (!req.file) {
|
||||
res.status(400).json({ success: false, error: 'Keine Datei hochgeladen' } as ApiResponse);
|
||||
@@ -495,8 +495,8 @@ export async function uploadContractDocument(req: AuthRequest, res: Response): P
|
||||
customerId: contract?.customerId,
|
||||
});
|
||||
|
||||
// Falls Lieferbestätigung + Vertrag DRAFT → automatisch auf ACTIVE
|
||||
await maybeActivateOnDeliveryConfirmation(contractId, documentType, req);
|
||||
// Falls Lieferbestätigung: DRAFT → ACTIVE + startDate setzen falls leer
|
||||
await maybeActivateOnDeliveryConfirmation(contractId, documentType, req, deliveryDate);
|
||||
|
||||
res.status(201).json({ success: true, data: doc } as ApiResponse);
|
||||
} catch (error) {
|
||||
|
||||
@@ -563,10 +563,30 @@ async function handleContractDocumentUpload(
|
||||
}
|
||||
}
|
||||
|
||||
// Bei Kündigungsbestätigung(s-Optionen): optionales Datum aus multipart
|
||||
// übernehmen. Ohne Angabe: falls Feld noch leer → heute, sonst nicht anfassen.
|
||||
const updateData: Record<string, unknown> = { [fieldName]: relativePath };
|
||||
if (fieldName === 'cancellationConfirmationPath' || fieldName === 'cancellationConfirmationOptionsPath') {
|
||||
const dateField = fieldName === 'cancellationConfirmationPath'
|
||||
? 'cancellationConfirmationDate'
|
||||
: 'cancellationConfirmationOptionsDate';
|
||||
const provided = typeof req.body?.confirmationDate === 'string' ? req.body.confirmationDate : null;
|
||||
let target: Date | null = null;
|
||||
if (provided) {
|
||||
const parsed = new Date(provided);
|
||||
if (!isNaN(parsed.getTime())) target = parsed;
|
||||
}
|
||||
if (target) {
|
||||
updateData[dateField] = target;
|
||||
} else if (!contract[dateField]) {
|
||||
updateData[dateField] = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
// Vertrag in der DB aktualisieren
|
||||
await prisma.contract.update({
|
||||
where: { id: contractId },
|
||||
data: { [fieldName]: relativePath },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
// Wenn eine Kündigungsbestätigung (nicht "Optionen") hochgeladen wurde und
|
||||
|
||||
@@ -86,8 +86,9 @@ 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.
|
||||
* Lieferbestätigung ist:
|
||||
* - Contract.status von DRAFT auf ACTIVE setzen (falls DRAFT)
|
||||
* - Contract.startDate auf deliveryDate (oder heute) setzen, falls noch leer
|
||||
*
|
||||
* Schreibweise "Lieferbestätigung" stammt aus dem Frontend-Dropdown
|
||||
* (SaveAttachmentModal / ContractDetail). Vergleich case-insensitive +
|
||||
@@ -97,19 +98,43 @@ export async function maybeActivateOnDeliveryConfirmation(
|
||||
contractId: number,
|
||||
documentType: string,
|
||||
req: unknown,
|
||||
deliveryDate?: Date | string | null,
|
||||
): 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 },
|
||||
select: { status: true, contractNumber: true, customerId: true, startDate: true },
|
||||
});
|
||||
if (!contract || contract.status !== 'DRAFT') return;
|
||||
if (!contract) return;
|
||||
|
||||
// deliveryDate parsen, Fallback auf heute
|
||||
let parsedDate: Date | null = null;
|
||||
if (deliveryDate) {
|
||||
const parsed = new Date(deliveryDate);
|
||||
if (!isNaN(parsed.getTime())) parsedDate = parsed;
|
||||
}
|
||||
const effectiveDate = parsedDate || new Date();
|
||||
|
||||
const updateData: Record<string, unknown> = {};
|
||||
const changes: Record<string, { vorher: unknown; nachher: unknown }> = {};
|
||||
|
||||
if (contract.status === 'DRAFT') {
|
||||
updateData.status = 'ACTIVE';
|
||||
changes.status = { vorher: 'DRAFT', nachher: 'ACTIVE' };
|
||||
}
|
||||
|
||||
if (!contract.startDate) {
|
||||
updateData.startDate = effectiveDate;
|
||||
changes.startDate = { vorher: null, nachher: effectiveDate.toISOString().split('T')[0] };
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) return;
|
||||
|
||||
await prisma.contract.update({
|
||||
where: { id: contractId },
|
||||
data: { status: 'ACTIVE' },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
@@ -117,8 +142,8 @@ export async function maybeActivateOnDeliveryConfirmation(
|
||||
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' },
|
||||
label: `Vertrag ${contract.contractNumber} automatisch aktualisiert (Lieferbestätigung hochgeladen)`,
|
||||
details: { ...changes, trigger: 'Lieferbestätigung-Upload' },
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
}
|
||||
|
||||
+8
-4
@@ -102,12 +102,16 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
`status=ACTIVE` und `endDate < heute` → `EXPIRED` (mit Audit-Log).
|
||||
- Beim Upload der Kündigungsbestätigung (`cancellationConfirmationPath`):
|
||||
wenn Vertrag aktuell `ACTIVE` → auf `CANCELLED` setzen (Audit-Log).
|
||||
Der "Optionen"-Upload löst den Wechsel bewusst NICHT aus, da er für
|
||||
Vertragsänderungen (nicht echte Kündigungen) gedacht ist.
|
||||
Frontend fragt per Modal das Bestätigungs-Datum ab (Default: heute),
|
||||
wird direkt als `cancellationConfirmationDate` gespeichert.
|
||||
Der "Optionen"-Upload löst den Status-Wechsel bewusst NICHT aus, da er
|
||||
für Vertragsänderungen (nicht echte Kündigungen) gedacht ist, setzt
|
||||
aber `cancellationConfirmationOptionsDate` analog.
|
||||
- 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.
|
||||
setzen + `startDate` auf das erfasste Lieferdatum (falls leer).
|
||||
Frontend zeigt Datums-Input conditional, wenn Typ "Lieferbestätigung"
|
||||
ausgewählt ist.
|
||||
- Keine neuen Status eingeführt: `cancellationSentDate` vs.
|
||||
`cancellationConfirmationDate` genügen, um "gesendet vs. bestätigt"
|
||||
abzubilden. `ACTIVE` bleibt bis zur Bestätigung.
|
||||
|
||||
Reference in New Issue
Block a user