76 lines
2.5 KiB
TypeScript
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;
|
|
});
|
|
}
|