- feat: order confirmation PDF generation with VAT support
- feat: order confirmation modal with custom item editing
- fix: attendance negative duration clamping and switchProject timing
- fix: Quill editor locked to Tahoma 14px, PDF heading sizes
- fix: invoice/offer PDF font consistency (Tahoma enforcement)
- fix: invoice alert cron improvements
- fix: NAS financials manager edge cases
- refactor: numbering service with unique sequence constraints

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-04-23 17:23:10 +02:00
parent b197017644
commit 07cb428287
36 changed files with 2233 additions and 480 deletions

View File

@@ -1,5 +1,9 @@
import prisma from "../config/database";
import { generateOfferNumber } from "./numbering.service";
import {
generateOfferNumber,
previewOfferNumber,
releaseOfferNumber,
} from "./numbering.service";
interface QuotationItemInput {
description?: string;
@@ -18,7 +22,7 @@ interface ScopeSectionInput {
}
// Re-export for convenience
export { generateOfferNumber as getNextOfferNumber } from "./numbering.service";
export { previewOfferNumber as getNextOfferNumber } from "./numbering.service";
const ALLOWED_SORT_FIELDS = [
"id",
@@ -133,11 +137,14 @@ export async function getOffer(id: number) {
}
export async function createOffer(body: Record<string, any>) {
const quotationNumber =
body.quotation_number !== undefined && body.quotation_number !== null
? String(body.quotation_number)
: await generateOfferNumber();
const quotation = await prisma.quotations.create({
data: {
quotation_number: body.quotation_number
? String(body.quotation_number)
: null,
quotation_number: quotationNumber,
project_code: body.project_code ? String(body.project_code) : null,
customer_id: body.customer_id ? Number(body.customer_id) : null,
valid_until: body.valid_until ? new Date(String(body.valid_until)) : null,
@@ -190,13 +197,16 @@ export async function updateOffer(id: number, body: Record<string, any>) {
if (existing.status === "invalidated")
return { error: "invalidated" as const };
if (
body.quotation_number !== undefined &&
String(body.quotation_number) !== existing.quotation_number
) {
return { error: "Číslo nabídky nelze změnit", status: 400 } as const;
}
await prisma.quotations.update({
where: { id },
data: {
quotation_number:
body.quotation_number !== undefined
? String(body.quotation_number)
: undefined,
customer_id:
body.customer_id !== undefined ? Number(body.customer_id) : undefined,
valid_until:
@@ -281,6 +291,12 @@ export async function deleteOffer(id: number) {
if (!existing) return null;
await prisma.quotations.delete({ where: { id } });
const year = existing.created_at
? new Date(existing.created_at).getFullYear()
: new Date().getFullYear();
await releaseOfferNumber(year);
return existing;
}