fix: remove manual project creation, smart sequence release, received-invoices schema fix
- Remove ProjectCreate page, POST /projects endpoint, and next-number endpoint - Projects can only be created through orders (shared numbering sequence) - Remove dead CreateProjectSchema and createProject service function - Delete 'order' row from number_sequences (unused; code uses 'shared') - Smart sequence release: decrement last_number only when deleting the highest number - Fix received-invoices stats referencing non-existent is_deleted and amount_czk columns - Update deploy instructions in CLAUDE.md (npm install, prisma migrate deploy) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -110,13 +110,51 @@ async function previewNextSequence(
|
||||
|
||||
/**
|
||||
* Release a sequence number back to the pool.
|
||||
* NOTE: Blindly decrementing can cause duplicate numbers if numbers were
|
||||
* allocated after this one. Sequence numbers are consumed but not returned
|
||||
* to the pool — this is intentionally a no-op.
|
||||
* Only decrements if the deleted entity held the current highest number,
|
||||
* preventing gaps while avoiding duplicate re-allocation.
|
||||
*/
|
||||
async function releaseSequence(_type: string, _year: number) {
|
||||
// No-op: decrementing can cause duplicate sequence numbers.
|
||||
// Sequence numbers are consumed but never returned to the pool.
|
||||
async function releaseSequence(
|
||||
type: string,
|
||||
year: number,
|
||||
deletedNumber?: string,
|
||||
) {
|
||||
if (!deletedNumber) return;
|
||||
|
||||
const existing = await prisma.$queryRaw<Array<{ last_number: number }>>`
|
||||
SELECT last_number FROM number_sequences
|
||||
WHERE \`type\` = ${type} AND \`year\` = ${year}
|
||||
`;
|
||||
if (existing.length === 0) return;
|
||||
|
||||
const settings = await getSettings();
|
||||
const pattern =
|
||||
type === "shared"
|
||||
? settings?.order_number_pattern || DEFAULT_ORDER_PATTERN
|
||||
: type === "invoice"
|
||||
? settings?.invoice_number_pattern || DEFAULT_INVOICE_PATTERN
|
||||
: settings?.offer_number_pattern || DEFAULT_OFFER_PATTERN;
|
||||
const code =
|
||||
type === "shared"
|
||||
? settings?.order_type_code || "71"
|
||||
: type === "invoice"
|
||||
? settings?.invoice_type_code || "81"
|
||||
: "";
|
||||
const prefix = type === "offer" ? settings?.quotation_prefix || "NA" : "";
|
||||
|
||||
const highestNumber = applyPattern(pattern, {
|
||||
year,
|
||||
prefix,
|
||||
code,
|
||||
seq: existing[0].last_number,
|
||||
});
|
||||
|
||||
if (deletedNumber === highestNumber && existing[0].last_number > 0) {
|
||||
await prisma.$executeRaw`
|
||||
UPDATE number_sequences
|
||||
SET \`last_number\` = ${existing[0].last_number - 1}
|
||||
WHERE \`type\` = ${type} AND \`year\` = ${year}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/** Verify a shared number is not already used by an order or project. */
|
||||
@@ -265,19 +303,40 @@ export async function previewInvoiceNumber(
|
||||
return { number, next_number: number };
|
||||
}
|
||||
|
||||
/** Release an offer number back to the pool (decrement sequence). */
|
||||
export async function releaseOfferNumber(year?: number) {
|
||||
await releaseSequence("offer", year || new Date().getFullYear());
|
||||
/** Release an offer number back to the pool (decrement if highest). */
|
||||
export async function releaseOfferNumber(
|
||||
year?: number,
|
||||
deletedNumber?: string,
|
||||
) {
|
||||
await releaseSequence(
|
||||
"offer",
|
||||
year || new Date().getFullYear(),
|
||||
deletedNumber,
|
||||
);
|
||||
}
|
||||
|
||||
/** Release a shared number back to the pool (decrement sequence). */
|
||||
export async function releaseSharedNumber(year?: number) {
|
||||
await releaseSequence("shared", year || new Date().getFullYear());
|
||||
/** Release a shared number back to the pool (decrement if highest). */
|
||||
export async function releaseSharedNumber(
|
||||
year?: number,
|
||||
deletedNumber?: string,
|
||||
) {
|
||||
await releaseSequence(
|
||||
"shared",
|
||||
year || new Date().getFullYear(),
|
||||
deletedNumber,
|
||||
);
|
||||
}
|
||||
|
||||
/** Release an invoice number back to the pool (decrement sequence). */
|
||||
export async function releaseInvoiceNumber(year?: number) {
|
||||
await releaseSequence("invoice", year || new Date().getFullYear());
|
||||
/** Release an invoice number back to the pool (decrement if highest). */
|
||||
export async function releaseInvoiceNumber(
|
||||
year?: number,
|
||||
deletedNumber?: string,
|
||||
) {
|
||||
await releaseSequence(
|
||||
"invoice",
|
||||
year || new Date().getFullYear(),
|
||||
deletedNumber,
|
||||
);
|
||||
}
|
||||
|
||||
/** Preview what a pattern would produce (for settings UI) */
|
||||
|
||||
Reference in New Issue
Block a user