Skip to content

fix(numbering): keep order and bill numbers unique across resets - #1387

Merged
itsbkm merged 2 commits into
mainfrom
fix/unique-order-bill-numbers
Oct 27, 2026
Merged

itsbkm merged 2 commits into
mainfrom
fix/unique-order-bill-numbers

Conversation

@itsbkm

@itsbkm itsbkm commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Problem

A store with an empty invoice prefix, "include period" off and daily reset issued bills as 0001, 0002, … each day. On the next day the counter restarted at 0001, violating the UNIQUE bills.bill_number constraint, so bill.generate failed with persistence.failed after midnight. Orders had the same latent bug.

Changes

  • A resetting series (daily orders; daily, monthly or financial-year bills) always carries its date or period; only a non-resetting series may omit it. Enforced in generateOrderNumber / generateBillNumber.
  • PUT /api/settings/order-numbering stores the effective include flags and GET reports them. The settings screen hides the include toggles when the choice is forced, and previews match.
  • Number allocation checks for an already-issued number and lifts the counter past the highest issued under the same head instead of failing (mid-day setting switches, reused prefixes, restored rows).
  • Docs: new "Order and bill numbers" section in docs/architecture/data-and-migrations.md.

Verification

  • test:sequence 31/31, test:order-numbering-settings 25/25; new cases fail against the previous main/db.ts with the original UNIQUE error.
  • Integration happy path / lifecycle, bills print API, bill pagination, upgrade path pass.
  • Lint, npm run build, npm run build:frontend, npm run i18n:check, npm run docs:check pass.

🤖 Generated with Claude Code

A daily-reset series with the date/period toggle off reissued the same
number every day (e.g. 0001), which violated the UNIQUE bill_number /
order_number constraint and failed bill generation after midnight.

- Resetting series always carry their date or period; only a series that
  never resets may omit it. Enforced in the generators and normalized by
  the order-numbering settings API.
- Hide the include toggles in settings when the choice is forced.
- Allocate numbers collision-safely: an already-issued number lifts the
  counter past the highest issued under the same head instead of failing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@itsbkm
itsbkm enabled auto-merge (squash) October 27, 2026 20:00
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Keep order and bill numbers unique across resets and setting changes

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Require a date or period in resetting series so numbers remain unique after rollover.
• Advance counters past issued numbers when settings change or sequence data is stale.
• Align settings, previews, tests, and documentation with the effective numbering rules.
Diagram

graph TD
  UI["Settings screen"] --> API["Numbering API"] --> Rules["Effective flags"] --> Gen["Number generators"] --> Seq["Sequence counter"] --> Check{"Already issued?"}
  Check -->|No| Output["Unique number"]
  Check -->|Yes, lift counter| Seq
  Records["Issued records"] --> Check
Loading
High-Level Assessment

Keep globally unique printed identifiers and enforce uniqueness at generation, backed by collision recovery. Scoping the database constraint to a period would permit duplicate identifiers; changing sequence keys alone would not account for restored or imported rows.

Files changed (7) +455 / -107

Bug fix (4) +163 / -71
page.tsxKeep saved numbering form aligned with effective flags +8/-1

Keep saved numbering form aligned with effective flags

• After saving, the form records date and period inclusion as enabled whenever the corresponding series resets.

frontend/src/app/(dashboard)/settings/page.tsx

GeneralSettingsTab.tsxShow only valid numbering choices and previews +44/-39

Show only valid numbering choices and previews

• Hides inclusion toggles when a reset makes the date or period mandatory. Previews display those segments even if an older form value has inclusion disabled.

frontend/src/components/settings/GeneralSettingsTab.tsx

db.tsEnforce reset-safe segments and collision-safe allocation +68/-11

Enforce reset-safe segments and collision-safe allocation

• Order and bill generators require a date or period for resetting series. A shared allocator checks issued numbers and lifts a colliding sequence counter past the highest issued value under the same head.

main/db.ts

settings.tsNormalize numbering settings in API reads and writes +43/-20

Normalize numbering settings in API reads and writes

• GET reports effective inclusion flags, including for legacy contradictory settings. PUT transactionally persists effective flags after applying the requested settings.

main/routes/settings.ts

Tests (2) +278 / -36
order-numbering-settings.test.tsTest effective settings persistence and legacy reads +119/-21

Test effective settings persistence and legacy reads

• Covers forced inclusion for resetting series, optional inclusion for non-resetting series, stored flag values, and effective GET responses for legacy settings.

tests/order-numbering-settings.test.ts

sequence-generation.test.tsTest rollover uniqueness and issued-number collisions +159/-15

Test rollover uniqueness and issued-number collisions

• Adds day-rollover cases for orders and bills with empty prefixes and disabled inclusion flags. Covers bill and order counter recovery after setting switches, legacy bare numbers, and missing sequence rows.

tests/sequence-generation.test.ts

Documentation (1) +14 / -0
data-and-migrations.mdDocument numbering invariants and collision recovery +14/-0

Document numbering invariants and collision recovery

• Explains sequence buckets, mandatory date or period segments for resetting series, and how allocation advances past issued numbers.

docs/architecture/data-and-migrations.md

@qodo-code-review

qodo-code-review Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Oversized numbers halt future sales ✓ Resolved
Description
allocateUniqueNumber casts every matching suffix to a SQLite integer and lifts the sequence to the
highest result, even when an imported suffix reaches or exceeds the counter's representable range.
If an ordinary candidate collides under that head, the lift can issue one oversized number, after
which increments repeat that candidate and subsequent order or bill generation exhausts its three
attempts.
Code

main/db.ts[R8779-8782]

+  const highestIssued = db.prepare(`
+    SELECT MAX(CAST(substr(${column}, ?) AS INTEGER)) AS highest
+    FROM ${table}
+    WHERE ${column} GLOB ? AND substr(${column}, ?) NOT GLOB '*[^0-9]*'
Relevance

●●● Strong

Concrete overflow breaks the PR’s uniqueness guarantee; accepted precedents favor validating
imported numeric data and numbering edge cases.

PR-#783
PR-#994

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new query casts matching text suffixes to INTEGER and writes their maximum into the sequence;
SQLite clamps oversized text-to-integer casts at its signed 64-bit maximum. The sequence then
advances through SQLite arithmetic and is formatted as a JavaScript number. Neither number column
limits imported suffix length, and the import path inserts supplied values without number-format
validation.

main/db.ts[8779-8795]
main/db.ts[8626-8665]
main/db.ts[8063-8068]
main/db.ts[8133-8138]
main/routes/database.ts[576-589]
🌐 SQLite clamps a text-to-INTEGER cast above 9223372036854775807 to that maximum.
🌐 SQLite REAL values use approximate IEEE 754 binary-64 representation when arithmetic exceeds the integer range.

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An imported order or bill number with an oversized numeric suffix can lift the sequence beyond the range in which it advances, eventually preventing another number from being allocated.

## Fix Focus Areas
- main/db.ts[8779-8795]

## Recommended Fix
Handle suffixes outside the sequence's safely incrementable range without lifting the counter to them. Keep checking the exact candidate for uniqueness, and add a regression test with an occupied low number and an oversized imported number under the same head.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 5 rules
✅ Web pages:
  +6 more
✅ Cross-repo context — repo relationships
Review mode: Auto: 🧠 Deep: Number allocation, settings, UI, and persistence span many defect-prone paths.

Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread main/db.ts
…unter

An imported order or bill number with a suffix too large for the counter
could lift the sequence past its incrementable range and halt future
numbering. Only suffixes up to 9 digits now raise the counter.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@itsbkm
itsbkm merged commit 548a96b into main Oct 27, 2026
23 checks passed
@itsbkm
itsbkm deleted the fix/unique-order-bill-numbers branch October 27, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant