74 lines
2.5 KiB
TypeScript
74 lines
2.5 KiB
TypeScript
/**
|
||
* Structured PO number generator.
|
||
* Format: COMPANY_CODE/VESSEL_CODE/PO_ID/FY
|
||
* - COMPANY_CODE: company.code (fallback "PMS")
|
||
* - VESSEL_CODE: vessel.code (fallback "GEN")
|
||
* - PO_ID: globally sequential integer, starting from 200
|
||
* - FY: Indian financial year "XXYY" e.g. "2526" for Apr 2025–Mar 2026
|
||
*
|
||
* Example: PMS/HNR1/200/2526
|
||
*/
|
||
|
||
import { db } from "@/lib/db";
|
||
|
||
/** Indian financial year string. April–March cycle. */
|
||
function currentFY(): string {
|
||
const now = new Date();
|
||
const month = now.getMonth() + 1; // 1-indexed
|
||
const year = now.getFullYear();
|
||
const fyStart = month >= 4 ? year : year - 1;
|
||
const fyEnd = fyStart + 1;
|
||
return `${fyStart}-${String(fyEnd).slice(-2)}`;
|
||
}
|
||
|
||
/** Find the next sequential PO ID (min 200) by scanning existing structured PO numbers. */
|
||
async function nextPoId(): Promise<number> {
|
||
const pos = await db.purchaseOrder.findMany({ select: { poNumber: true } });
|
||
// Floor at 8999 so the first generated ID is 9000, avoiding clashes with
|
||
// imported POs that retain their original IDs (which typically start from 1).
|
||
let maxId = 8999;
|
||
for (const { poNumber } of pos) {
|
||
const parts = poNumber.split("/");
|
||
if (parts.length === 4) {
|
||
const n = parseInt(parts[2], 10);
|
||
if (!isNaN(n) && n > maxId) maxId = n;
|
||
}
|
||
}
|
||
return maxId + 1;
|
||
}
|
||
|
||
/**
|
||
* Generate a structured PO number.
|
||
* Pass vesselId and companyId so we can resolve their codes from the DB.
|
||
* Either may be null — sensible defaults are used.
|
||
*/
|
||
export async function generatePoNumber(
|
||
vesselId?: string | null,
|
||
companyId?: string | null,
|
||
): Promise<string> {
|
||
const [vessel, company, id] = await Promise.all([
|
||
vesselId ? db.vessel .findUnique({ where: { id: vesselId }, select: { code: true } }) : null,
|
||
companyId ? db.company.findUnique({ where: { id: companyId }, select: { code: true } }) : null,
|
||
nextPoId(),
|
||
]);
|
||
|
||
const companyCode = company?.code ?? "PMS";
|
||
const vesselCode = vessel?.code ?? "GEN";
|
||
const fy = currentFY();
|
||
|
||
return `${companyCode}/${vesselCode}/${id}/${fy}`;
|
||
}
|
||
|
||
/** Parse a structured PO number into its parts. Returns null for old-format numbers. */
|
||
export function parsePoNumber(poNumber: string): {
|
||
companyCode: string;
|
||
vesselCode: string;
|
||
poId: number;
|
||
fy: string;
|
||
} | null {
|
||
const parts = poNumber.split("/");
|
||
if (parts.length !== 4) return null;
|
||
const poId = parseInt(parts[2], 10);
|
||
if (isNaN(poId)) return null;
|
||
return { companyCode: parts[0], vesselCode: parts[1], poId, fy: parts[3] };
|
||
}
|