Add a flips ledger with fee presets, sales record and limit tracking #21

Merged
kubus merged 4 commits from feat/flip-ledger into main 2026-10-01 12:35:58 +00:00
Owner

Stacked on #20 (migration numbering and shared files). Merge #20 first, then switch this PR's base to main.

Step 3 of the reseller roadmap: a private Flips page for what you buy and resell.

What's in it

  • Ledger: each flip records the buy (date, platform, price, extra costs such as shipping in, buyer fees or repairs), where it's listed, and the sale (date, platform, price, fee, your selling costs). Net profit is computed per flip.

  • Seller fee presets, editable. Defaults are private-seller rates as of 2026-09:

    • OLX: 0% with Przesyłka. Set a rate if you use the optional Zapłać, jeśli sprzedasz, which is about 6–10% in Elektronika.
    • Allegro Lokalnie: 4,9% for Kup teraz or auctions in Elektronika, 7,9% elsewhere.
    • Vinted: 0%.

    The fee is stored with each sale, so editing a preset never rewrites past sales, and an entered fee always wins.

  • Delist checklist: Mark sold lists the other platforms the item is still up on. The page warns about sold items that are still listed somewhere.

  • Działalność nierejestrowana:

    • Revenue this quarter against the 2026 quarterly limit of 10 813,50 zł, with a bar that turns orange at 80%.
    • DAC7 sales per platform against 30 sales a year.
    • The uproszczona ewidencja sprzedaży for any quarter: one row per day with that day's sales and the quarter-to-date total, downloadable as CSV. Purchase costs stay out of it, as the rules expect.
  • Listing drawer: a Sell on select, so the expected profit subtracts that platform's fee, and an I bought this button that adds a prefilled flip.

Boundaries

  • Nothing in scanning, scoring, bands or alerts reads the ledger, so your own prices never mix with asking-price statistics. It lives in its own table and module (server/flips.ts, src/profit.ts).
  • Retention never prunes it. It is included in /api/export and backups.
  • Hidden from the Debug API: left out of the table list, refused in read-only SQL (a text check plus a SQLite authorizer DENY), and emptied in debug snapshots.
  • Profit is display-only and forward-only: there is no "max buy price".

Verification

  • npm run typecheck, npm test (258/258, with new tests/flips.test.ts and a debug-exclusion test), npm run build, and npm audit --omit=dev (0 vulnerabilities) all pass.
  • Live smoke test on a temporary DB:
    • A create → sell cycle stored the 63,70 zł fee.
    • A bad date and a sale before the purchase date both returned 400.
    • Presets saved.
    • Debug SQL on flips returned 403, and the table isn't in the debug schema.
    • The export includes flips, and /flips serves the app.
  • Not covered: the UI wasn't checked visually, iOS has no Flips screen, and MCP has no ledger tool.

🤖 Generated with Claude Code

**Stacked on #20** (migration numbering and shared files). Merge #20 first, then switch this PR's base to `main`. Step 3 of the reseller roadmap: a private **Flips** page for what you buy and resell. ## What's in it - **Ledger:** each flip records the buy (date, platform, price, extra costs such as shipping in, buyer fees or repairs), where it's listed, and the sale (date, platform, price, fee, your selling costs). Net profit is computed per flip. - **Seller fee presets**, editable. Defaults are private-seller rates as of 2026-09: - OLX: 0% with Przesyłka. Set a rate if you use the optional *Zapłać, jeśli sprzedasz*, which is about 6–10% in Elektronika. - Allegro Lokalnie: 4,9% for Kup teraz or auctions in Elektronika, 7,9% elsewhere. - Vinted: 0%. The fee is **stored with each sale**, so editing a preset never rewrites past sales, and an entered fee always wins. - **Delist checklist:** *Mark sold* lists the other platforms the item is still up on. The page warns about sold items that are still listed somewhere. - **Działalność nierejestrowana:** - Revenue this quarter against the 2026 quarterly limit of 10 813,50 zł, with a bar that turns orange at 80%. - DAC7 sales per platform against 30 sales a year. - The *uproszczona ewidencja sprzedaży* for any quarter: one row per day with that day's sales and the quarter-to-date total, downloadable as CSV. Purchase costs stay out of it, as the rules expect. - **Listing drawer:** a *Sell on* select, so the expected profit subtracts that platform's fee, and an **I bought this** button that adds a prefilled flip. ## Boundaries - Nothing in scanning, scoring, bands or alerts reads the ledger, so your own prices never mix with asking-price statistics. It lives in its own table and module (`server/flips.ts`, `src/profit.ts`). - Retention never prunes it. It is included in `/api/export` and backups. - **Hidden from the Debug API:** left out of the table list, refused in read-only SQL (a text check plus a SQLite authorizer `DENY`), and emptied in debug snapshots. - Profit is display-only and forward-only: there is no "max buy price". ## Verification - `npm run typecheck`, `npm test` (258/258, with new `tests/flips.test.ts` and a debug-exclusion test), `npm run build`, and `npm audit --omit=dev` (0 vulnerabilities) all pass. - Live smoke test on a temporary DB: - A create → sell cycle stored the 63,70 zł fee. - A bad date and a sale before the purchase date both returned 400. - Presets saved. - Debug SQL on `flips` returned 403, and the table isn't in the debug schema. - The export includes flips, and `/flips` serves the app. - Not covered: the UI wasn't checked visually, iOS has no Flips screen, and MCP has no ledger tool. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add a flips ledger with fee presets, sales record and limit tracking
Some checks failed
ci / verify (pull_request) Has been cancelled
022688debd
A private bookkeeping page for what the operator buys and resells:

- Flips record the buy (date, platform, price, extra costs), where the item
  is listed, and the sale (date, platform, price, fee, selling costs). Net
  profit is computed per flip.
- Editable seller fee presets (defaults: OLX 0% with Przesyłka, Allegro
  Lokalnie 4,9% Elektronika, Vinted 0%). The fee is stored with each sale,
  so preset edits never rewrite history.
- Marking sold shows a delist checklist, and the page warns about sold
  items still listed elsewhere.
- Działalność nierejestrowana: quarterly revenue against the 2026 limit
  (10 813,50 zł), DAC7 sales per platform, and the uproszczona ewidencja
  sprzedaży per quarter with CSV download.
- The listing drawer's estimate subtracts the chosen platform's fee, and
  "I bought this" adds a prefilled flip.

The ledger never feeds scanning, scoring, bands or alerts, is never pruned,
is exported with /api/export, and is hidden from the Debug API (table
list, read-only SQL and snapshots).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep ledger columns aligned and show the platform's fee rate before a resale price
Some checks failed
ci / verify (pull_request) Has been cancelled
401ce7e1a2
Each ledger row sized its own actions column, so the header and rows drifted
apart. The drawer's Sell on hint said "no seller fee" for Allegro Lokalnie
until a resale price was entered; it now shows the preset rate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge main into feat/flip-ledger
Some checks failed
ci / verify (pull_request) Has been cancelled
a973a842bf
FlipsPage now subscribes to its own 'flips' events (main moved live refresh
to the shared event stream), flips join the streamed export tables, and
031_flips follows main's 030_perf_indexes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
kubus changed target branch from feat/olx-listing-signals to main 2026-10-01 12:34:48 +00:00
kubus merged commit 8089cc655e into main 2026-10-01 12:35:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kubus/scout!21
No description provided.