You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
39 lines
1.8 KiB
TypeScript
39 lines
1.8 KiB
TypeScript
import { formatDocNo } from '@sims/domain'
|
|
import type { DB } from './db'
|
|
|
|
export const TYPE_PREFIX: Record<string, string> = {
|
|
QUOTATION: 'QT', PROFORMA: 'PI', INVOICE: 'INV', RECEIPT: 'RCT', CREDIT_NOTE: 'CN',
|
|
}
|
|
|
|
export async function nextDocNo(db: DB, docType: string, fy: string): Promise<string> {
|
|
const prefix = `${TYPE_PREFIX[docType]}/${fy.slice(2)}`
|
|
await db.run(
|
|
// Portability quirk: ON CONFLICT upsert is SQLite/Postgres dialect (standard SQL has MERGE).
|
|
`INSERT INTO doc_series (doc_type, fy, prefix, next_seq) VALUES (?, ?, ?, 1)
|
|
ON CONFLICT (doc_type, fy) DO NOTHING`,
|
|
docType, fy, prefix,
|
|
)
|
|
// D24 (red-team): allocate the number in ONE atomic UPDATE ... RETURNING. This takes a
|
|
// row lock, so concurrent issuers serialize on the row and can never read the same seq
|
|
// twice (the old read-then-bump raced → duplicate numbers / burned sequence on Postgres).
|
|
// Both engines support UPDATE ... RETURNING (SQLite ≥3.35, node pg). Runs inside the
|
|
// caller's issueDocument transaction.
|
|
const row = (await db.get<{ prefix: string; seq: number }>(
|
|
`UPDATE doc_series SET next_seq = next_seq + 1 WHERE doc_type=? AND fy=?
|
|
RETURNING prefix, next_seq - 1 AS seq`,
|
|
docType, fy,
|
|
))!
|
|
return formatDocNo(row.prefix, row.seq, 4)
|
|
}
|
|
|
|
/** Mid-FY cutover (spec §4): continue after the last APEX-issued number. */
|
|
export async function seedSeries(db: DB, docType: string, fy: string, lastUsedSeq: number): Promise<void> {
|
|
const prefix = `${TYPE_PREFIX[docType]}/${fy.slice(2)}`
|
|
await db.run(
|
|
// Portability quirk: ON CONFLICT upsert is SQLite/Postgres dialect (standard SQL has MERGE).
|
|
`INSERT INTO doc_series (doc_type, fy, prefix, next_seq) VALUES (?, ?, ?, ?)
|
|
ON CONFLICT (doc_type, fy) DO UPDATE SET next_seq = excluded.next_seq`,
|
|
docType, fy, prefix, lastUsedSeq + 1,
|
|
)
|
|
}
|