Files
app/src/services/numbering.service.ts
2026-03-24 19:59:14 +01:00

76 lines
2.5 KiB
TypeScript

import prisma from "../config/database";
/**
* Shared number generator for orders and projects.
* Format: YYtypeCode + 4-digit sequence (e.g., 26710003)
* Queries MAX from both orders and projects tables.
*/
export async function generateSharedNumber(): Promise<string> {
const settings = await prisma.company_settings.findFirst({
select: { order_type_code: true },
});
const typeCode = settings?.order_type_code || "71";
const yy = String(new Date().getFullYear()).slice(-2);
const prefix = `${yy}${typeCode}`;
const prefixLen = prefix.length;
const likePattern = `${prefix}%`;
const result = await prisma.$queryRaw<[{ max_seq: bigint | null }]>`
SELECT COALESCE(MAX(seq), 0) as max_seq FROM (
SELECT CAST(SUBSTRING(order_number, ${prefixLen} + 1) AS UNSIGNED) AS seq
FROM orders WHERE order_number LIKE ${likePattern}
UNION ALL
SELECT CAST(SUBSTRING(project_number, ${prefixLen} + 1) AS UNSIGNED) AS seq
FROM projects WHERE project_number LIKE ${likePattern}
) combined
`;
const nextNum = Number(result[0]?.max_seq ?? 0) + 1;
return `${prefix}${String(nextNum).padStart(4, "0")}`;
}
/**
* Next offer number. Queries MAX from quotations table.
* Format: YEAR/PREFIX/NNN (e.g., 2026/NA/008)
*/
export async function generateOfferNumber(): Promise<string> {
const settings = await prisma.company_settings.findFirst({
select: { quotation_prefix: true },
});
const prefix = settings?.quotation_prefix || "NA";
const year = new Date().getFullYear();
const likePattern = `${year}/${prefix}/%`;
const result = await prisma.$queryRaw<[{ max_num: bigint | null }]>`
SELECT COALESCE(MAX(CAST(SUBSTRING_INDEX(quotation_number, '/', -1) AS UNSIGNED)), 0) as max_num
FROM quotations
WHERE quotation_number LIKE ${likePattern}
`;
const nextNum = Number(result[0]?.max_num ?? 0) + 1;
return `${year}/${prefix}/${String(nextNum).padStart(3, "0")}`;
}
/**
* Next invoice number via atomic sequence table.
*/
export async function generateInvoiceNumber(year: number): Promise<number> {
return prisma.$transaction(async (tx) => {
const existing = await tx.number_sequences.findFirst({
where: { type: "invoice", year },
});
if (existing) {
const nextNum = (existing.last_number ?? 0) + 1;
await tx.number_sequences.update({
where: { id: existing.id },
data: { last_number: nextNum },
});
return nextNum;
}
await tx.number_sequences.create({
data: { type: "invoice", year, last_number: 1 },
});
return 1;
});
}