Commit Graph

2360 Commits

Author SHA1 Message Date
21e62d1a7c docs(reports): rerun the ninety-day reconciliation from a fresh restore
Rebuilt the whole analysis from nothing: fresh restore of backup point
209608347, deactivate the ten shared locations, re-key and split every
one of 19,040,785 orders, re-import from Square, then two full ninety-day
recomputes — one with the fixes off, one with them on.

The baseline is now a no-fix recompute rather than production's stored
summaries. That is the stricter comparison: production's figures are in
places months stale, and crediting the fixes with repairing ordinary
staleness flattered them. On the fairer footing the two arithmetic fixes
are worth 979 client-days and $61,769.56, taking the window from 1,258
days out of balance ($69,560.10) to 279 ($7,790.54), with zero days
knocked out of balance and zero already-balanced days altered at line
level.

The migration now runs to completion database-wide: 17,047,142 payments
scoped, nothing left to rename, nothing unscopable, and no payment owned
by more than one order across 400,000 orders checked. The earlier
"transactor-bound, two days" diagnosis was wrong — the bottleneck was GC
in the driving process; the full pass takes about thirteen minutes.

Also corrects compare-sales-summaries: :ledger-mapped/amount, ledger-side
and account are :db/noHistory, so as-of cannot recover past amounts and
a rewritten summary reads back as a false balanced day. Every figure in
the report comes from live captures taken straight after each pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 18:35:47 -07:00
e05f20c135 fix(square): split per client, and say why same-client shares stay shared
Running the split across all 19,040,296 orders left 104 payments with two parent
orders in a 250,000-order sample. Every one of them is two orders of the SAME
client; cross-client sharing is gone entirely.

Attempting to split those too was wrong twice over. Both orders compute the same
name, so there is no second name to give a copy and the transaction conflicts.
And a copy would double that client's takings for the day — where Square splits
one tender across two of a client's own orders, one payment covering both is the
truthful record.

So the rule is now explicit: split per client, not per order. The component
cascade still reaches these, which is why remove-voided-orders needs its own
guard regardless of how complete this migration is — that was already the
recommendation and this makes it load-bearing rather than belt-and-braces.

Batch bookkeeping now records which name each charge was claimed under, so a
second order in the same batch computing that same name is left alone instead of
attempting a colliding copy.

Migration state on the restore, measured rather than asserted:

  charges     17,046,418 scoped · 0 legacy · 0 unscopable
  refunds         51,986 scoped · 0 legacy
  payouts        144,652 scoped · 0 legacy · 36 with no owner
  shifts          69,291 scoped · 0 legacy

A second full pass walked all 19M orders in 7.8 minutes and changed nothing,
which is the idempotency the tests assert, confirmed at full scale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 16:24:39 -07:00
c3d95cba6a feat(square): make the re-key complete, and measure whether it is
The migration had been run over a chosen subset — the ten clients that share a
Square location today, within a date window. Measuring the whole database showed
how partial that was:

  charges   259,763 scoped · 14,241,890 legacy · 2,122,161 with no owner
  refunds, payouts, shifts: complete

So 1.6% of charges carried a client-scoped key. The importer's tolerance of both
key schemes was not easing a transition, it was holding the system together.

unscoped-report counts, per entity type, how many keys are scoped, how many are
still legacy, and how many have no owner to scope by. That is the gate: while
legacy is above zero the database is in a mixed state and a stray unscoped
record can still be adopted by whichever client imports it first. At zero, the
fallback lookup in square.core3/existing-id can be removed and the guarantee
becomes structural rather than conventional.

migrate-all! runs it over every order rather than a subset. Splitting has to be
driven from orders because a payment's rightful owner is whichever order refers
to it — and nine client pairs contended in the past without sharing a location
today, so a migration scoped to the current configuration misses all of them.

Two changes were needed to run at that scale. The split no longer remembers
every charge it has seen; whether a charge is claimed is read from the charge,
which needs a map of the whole table otherwise. And claiming a charge now
records the claiming client on it, which is how a later order recovers the
Square id from an already-scoped key — the regression test caught the omission
immediately.

Re-running is now a true no-op rather than a repeated rename.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 15:39:04 -07:00
088dade112 fix(square): do not double-scope a charge that is already scoped
The split derived the underlying Square id by stripping only the
"square/charge/" prefix. That is correct the first time a charge is seen, but
once it has been re-keyed to one client, a second order processing the same
charge in a later batch reads NGCC-CC-<id> as the id and scopes it again:

  square/charge/NGCD-CD-NGCC-CC-hW59pSj5hAsFBUIQi6cMyY36kN7YY

The importer then computes the correct single-scoped key, does not find it, and
creates a second charge — doubling the tender on exactly the days the import
touched. Found on a restored backup where five contended clients showed
imbalances of $3,000 to $7,000 on Aug 5-7.

Client codes may contain dashes, so the scope cannot be recognised by pattern.
raw-square-id recovers it from the entity instead: whoever the charge currently
belongs to is whose scope its key carries. The run also remembers each charge's
raw id when it first claims it, so later clones reuse it rather than re-deriving
from a mutated key.

Covered by a test that runs the split with a batch size of one, which is the
arrangement that made the second order observe an already-scoped key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 13:32:50 -07:00
784c221f99 fix(square): split shared charges instead of re-keying them to one client
Re-keying alone could not undo an existing shared charge. It handed the single
entity to whichever client was looked at first and left the other order pointing
at a charge it does not own. Because :sales-order/charges is a component
attribute, that is not untidy but dangerous: retracting either order deletes a
charge the other still needs. It also produced double tender when the second
client re-imported and created its own.

split-and-rekey-charges! now gives every order its own charge. The first order
to claim a shared charge keeps it, re-keyed to that order's client and location;
every other order gets a copy carrying the same amounts, scoped to itself, with
its reference repointed. Afterwards no charge has more than one parent order and
the component relationship means what it says.

charges-with-multiple-parents is the §3.3 gate, which must read zero before any
historical cleanup or voided-order retraction is safe.

Four tests cover it: that the shared condition exists to begin with, that the
split produces two distinct entities with amounts copied and one parent each,
that an unshared charge is only re-keyed, and that re-running changes nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 13:17:59 -07:00
41a1eb04a8 feat(square): date arity for cash shift import, and verify the re-key is book-neutral
Adds a [client location start end] arity to upsert-cash-shifts, matching
upsert-payouts, so a backfill can cover a chosen window rather than the fixed
75-day default.

Re-keyed and re-imported the two newly scoped types on the restore, then checked
what it cost:

  144,652 expected deposits and 69,291 cash drawer shifts re-keyed
  entity counts unchanged on all four Square types, 0 collisions, 0 legacy keys
  live re-import over 66 of 102 clients wrote NOTHING — no entities created and
    no values rewritten, because every record resolved to the importing client's
    own entity through the legacy-key fallback
  0 ownership changes since the re-key, across deposits, shifts and refunds
  12,044 summaries recomputed afterwards and compared: 0 differences

So re-keying 213,943 entities moved no money at all, which is the result you
want from a migration whose only job is to make contention impossible.

The re-key does not retrospectively re-attribute records claimed by the wrong
client while a location was shared; those stay where they were last written.
Correcting them is a separate exercise, gated on the business deciding which
client owns each location.

Report updated with the isolation audit and a plain-language explanation of the
remove-voided-orders component cascade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 13:03:15 -07:00
87f7fd564b fix(square): client-scope payout and cash-drawer-shift keys too
An audit of every unique-identity entity the importers create found two more
that carry no client scoping:

  :expected-deposit/external-id   "square/payout/" id
  :cash-drawer-shift/external-id  "square/cash-drawer-shift/" id

Both endpoints are location-filtered, so two clients configured on one Square
location import the same payout or shift and collide on a single entity, exactly
as refunds and charges did.

This is not hypothetical. Reading ownership out of the history of the restored
backup, the client has actually changed on:

  4,069 of 144,688 expected deposits   (2.8%)
  2,628 of  69,291 cash drawer shifts  (3.8%)
  3,387 of  51,990 refunds             (6.5%, before the earlier fix)

across 19 distinct client pairs — and only 10 of those pairs share a location in
today's configuration. Nine, including NGMJ/NGSC with 1,546 affected entities
and NGAK/NGMH with 952, are invisible to any point-in-time check because the
configuration has since changed. Deactivating what looks shared today is
therefore a snapshot fix; scoping the keys is what makes contention structurally
impossible.

Verified on the restore: 144,652 deposits and 69,291 shifts re-keyed with all
four entity counts unchanged, zero collisions, zero legacy keys remaining. 36
deposits carry neither client nor location and were left alone.

ezCater orders and sales orders were already scoped and need no change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 12:01:43 -07:00
7e4ff93b68 feat(sales-summaries): add a summary comparison tool, and report 30-day impact
compare-sales-summaries diffs summaries between two points in the same database
using as-of, so a recompute can be audited against exactly what was there before
without a snapshot or scratch copy.

It answers a question that balance-status counting hides: a day can stay
balanced while its line amounts move, which is a real change to the books even
though no red turns green. Amounts are compared at the cent so floating-point
noise does not read as a change.

Run over 2026-07-15 to 08-13 against production's own summaries at basis-t
209608347:

  out of balance -> balanced          309
  balanced -> out of balance            0
  balanced -> balanced, numbers moved 237
  no summary in production          4,058

Broken down by fix, neither calculation change touches a healthy day: R1 fixes
86 with 0 unbalanced and 0 balanced-day edits, R2 fixes 276 on the same terms.
Every one of the 237 balanced-but-changed days comes from the data work, which
is also the only step that unbalances anything — 19 days, all of them absorbed
by R1 and R2, which is why the end-to-end count is zero.

The 4,058 missing summaries include eight consecutive days, Jul 30 to Aug 6,
missing for all 210 clients — the coverage hole the plan predicted from the
scheduler's seven-day lookback, confirmed here independently from the data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:06:54 -07:00
f5f6602873 docs(sales-summaries): compare fixes to baseline over the last 30 days
Recomputed every summary in the database — 14,458 client-days spanning
2024-04-01 to 2026-08-14 — through sales-summaries-v2, zero failures, none left
dirty. Compared the fixes against baseline over the last 30 days
(2026-07-15 to 2026-08-13), both arms on the same deduplicated data so this
isolates what the calculation fixes are worth on top of the dedup work.

Excluding the deactivated twins, 2,842 client-days:

  baseline  398 days off, 86.00% clean, $22,527.40, 336 material
  fixed      67 days off, 97.64% clean,    $405.66,   5 material

331 client-days fixed, 0 regressed — not one balanced day stopped balancing.

Of the $405.66 left, $399 sits in five material days, three of which the plan
predicted; the remaining 62 days total $1.62 with the largest at 9.00c, so the
10c threshold separates rounding from real variance with nothing near the
boundary.

Baseline was derived as fixed + untendered tip + service charges, since R1 and
R2 only add credits; the identity was verified against a from-scratch baseline
recomputation on 20 random client-days.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 08:53:04 -07:00
4882c1c9cf perf(sales-summaries): stop the dirty-summary scan at the client boundary
dirty-sales-summaries index-pulled from [client-id true] and then filtered by
client. index-pull returns a lazy seq running to the END of the index, and a
lazy filter does not stop it, so for every client the job walked every summary
belonging to every client sorting after it — pulling their items along the way.
Quadratic in the number of summaries.

take-while stops at the client boundary instead, which is safe because
:sales-summary/client+dirty sorts by client first, so a client's dirty
summaries are contiguous from that start point.

Measured on a 14,458-summary database: 1,321ms -> 5.6ms per client, a 237x
improvement, with identical results. A full refresh had been degrading from
~180 client-days a minute to ~3 as summaries accumulated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 08:44:52 -07:00
42a8207be9 feat(square): client-scope refund and charge external ids
Sales order keys already carry client and location; refund and charge keys do
not. That is why two clients configured on the same Square location share one
entity: the refund's owner flips every time either client imports, and a single
charge ends up referenced by both clients' orders. Scoping the keys the same way
makes contention structurally impossible — each client gets its own entity.

The hazard is the cutover. These ids are :db.unique/identity and the import
relies on upsert-by-identity, so changing the key format alone would match
nothing and Datomic would create a SECOND entity for every refund and charge,
orphaning the originals under their legacy keys. square.core3/existing-id
resolves the entity explicitly, scoped key first and legacy key second, and pins
the result as :db/id so the write lands on the existing entity whichever scheme
it currently carries.

All three construction sites are covered: order tenders, refunds, and the payout
path, which mints bare charge stubs from an external id alone.

The migration job re-keys whatever the importer has not yet touched. It recovers
scope from the referencing sales order or expected deposit for the ~12.8% of
charges that carry neither :charge/client nor :charge/location, detects
already-scoped entities by comparing against the key they should have rather
than pattern-matching ids that may themselves contain dashes, and is therefore
idempotent and re-runnable over a partially migrated database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 07:49:31 -07:00
9e4cb851ff fix(sales-summaries): count reversed tips, credit Square service charges
Two calculation defects that leave a day out of balance, plus the first tests to
cover sales summaries.

Tips: get-tip joins through :sales-order/charges, so it only sees tips that
settled on a tender. A return-only order has no tender — it carries the reversal
on :sales-order/tip, which nothing read — so the day credited a tip that had
been handed back. Now additive: tendered tips plus the tips on orders that have
no tender at all. Deliberately not a swap, because where an order does have a
tender the tender is the correct source; real orders exist whose tender carries
a tip their :sales-order/tip does not (auto-gratuity booked as a service charge,
wallet tips missing from the net amounts), and reading the order would drop them.

Service charges: nothing reads :sales-order/service-charge. The charge is
collected inside the card tender but no line credits it, so every order carrying
one leaves the day short by exactly that amount. Both signs count — a returned
catering fee arrives as a negative service charge and is subtracted back out of
:sales-order/returns, so dropping negatives would lose the reversal.

The vendor gate is load-bearing: ezCater service charges are commission deducted
from the restaurant rather than collected from the diner, and crediting those
makes the day worse. It matches on :sales-order/vendor where that is set and
falls back to the external id prefix where it is not — whole eras of Square
orders carry no vendor attribute at all, and a gate on vendor alone would have
silently credited nothing.

Service charges stay behind the per-client "summary-service-charges" flag, and
the account they map to (49000 Service Income) needs accounting sign-off before
that flag is enabled anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 23:35:29 -07:00
dc991df89a feat(sales-summaries): log a summary's imbalance and its suspect terms
Adds d-ss/imbalance alongside balanced?, and logs it from sales-summaries-v2
together with the categories a balancing investigation keeps returning to.

balanced? answers yes or no; when the answer is no there is currently nothing
recording by how much or in which direction, so an out-of-balance day is only
visible by opening the grid. This turns it into something queryable, and gives
the calculation changes that follow a before-and-after signal rather than an
opinion. No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 23:29:14 -07:00
e0b0186b62 fix(datomic): install tuple attributes after the attributes they compose
A composite tuple can only be created once its member attributes exist, and the
pieces are split across the two schema files: the tuple
:journal-entry-line/running-balance-tuple is declared in schema.edn while one of
its members, :journal-entry-line/running-balance, is declared in
cloud-migration-schema.edn, which is transacted afterwards.

Against a long-lived database this never surfaced, because those attributes were
installed by separate transactions years apart. Against an empty one it fails
outright with :db.error/invalid-tuple-attrs, which meant transact-schema could
not build a fresh database at all — and since every test fixture starts from an
empty datomic:mem database, the entire test suite died in setup rather than in
any individual test.

Install every plain attribute first, then every tuple.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 23:29:00 -07:00
8af9bfdee2 Merge branch 'feat/scheduled-sales-summary-refresh' 2026-08-12 16:10:44 -07:00
95d0537c69 feat(sales-summaries): refresh on a schedule, skipping accepted days
sales-summaries-v2 recomputes every dirty summary, but nothing set the dirty
flag on a schedule: mark-all-dirty was only ever called by hand from the
comment block, and the job was registered in neither server.clj's
INTEGREAT_JOB dispatch nor terraform/deploy.tf, so -main was dead code that
could never run in production. Summaries were only recalculated when someone
remembered to do it in the REPL, and POS data keeps arriving after a business
day closes, so a summary computed once on the day was routinely wrong and
stayed wrong.

Add a daily job that marks the trailing 7 days dirty and recomputes them,
leaving finished work alone. "Finished" is the condition the app already calls
Balanced -- debits equal credits and every line is mapped to an account. Since
that is derived rather than stored, a summary that later falls out of balance
is picked up again on the next run.

Extract the Balanced predicate into auto-ap.datomic.sales-summaries so the
grid's pill and the job share one definition, rather than a background job
requiring an SSR namespace. total-debits/total-credits resolve the ledger side
from either a plain keyword or the {:db/ident ...} map a pull returns, and
accepted? requires every item to declare a side: un-normalized pulled items
otherwise sum to 0.0 on both sides, read as balanced, and get skipped
silently and permanently.

Also fix sales-summaries-v2 destroying user-entered line items. It filtered
for :sales-summary-item/manual? to preserve them, but dirty-sales-summaries'
index-pull selector never fetched :sales-summary/items, so manual-items was
always empty. Because items is a component attribute upserted via
[:reset-rels ...], every recompute deleted the hand-entered lines -- often the
very lines that make a summary balance. Harmless while nothing ran on a
schedule; destructive the moment this does.

Register the job in the admin Background Jobs dropdown too, with a days
field: schedules are prod-only, so the admin page is the only way to run it
on staging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:17:54 -07:00
5126f3799d data(sysco): apply client category verification sheet to line-item mapping
Recategorizes 131 Sysco line-item descriptions per the client-reviewed
"Product Category Verification with codes" sheet: 96 existing rows re-coded
and 35 new rows appended (ids 1796-1830).

Root cause this addresses: get-line-account matches on exact description
string and silently defaults anything unmapped to 50000 Food Costs. Only 147
of the sheet's 321 reviewed rows coded the way the client expected. Note the
sheet's "change" column understates the work -- 20 of its 53 change rows are
no-ops (Paper -> Paper, confirming the gloves/liners/hairnets) and 3 were
already fixed in 38575aa5 / 7a0e256f, while 214,398 lines of movement come
from rows the client ticked as correct against a suggestion that already
differed from production.

Moves, replayed over all 1,022,732 DET lines in sysco-poller:

  50000 -> 51500 Dry Goods            90,481 ln   $5,215,011.82  105 clients
  50000 -> 51450 Dressing & Sauce     58,191 ln   $4,445,726.56   98
  50000 -> 51400 Bread and Bun        37,705 ln   $3,964,462.24   96
  50000 -> 52000 Soft Beverage        36,244 ln   $1,033,497.52   94
  50000 -> 51200 Produce               6,519 ln     $460,629.32   98
  55000 -> 51500 Dry Goods             6,516 ln     $259,750.60   97
  50000 -> 74100 Cleaning Supplies     5,630 ln     $205,233.19   98
  55000 -> 74100 Cleaning Supplies     5,222 ln     $125,946.32   99
  50000 -> 51120 Chicken/Poultry         265 ln      $42,482.50    8
  50000 -> 51300 Dairy                    36 ln       $5,941.69    6
  50000 -> 55000 Paperware                54 ln       $1,751.22   18
  54400 -> 51450 Dressing & Sauce         24 ln       $1,413.26    1
  total                               246,887 ln  $15,761,846.24

Only three source accounts are touched: 50000 and 55000 (the two silent
defaults) plus the single intended 54400 -> 51450 vinaigrette row. Nothing
else leaves a deliberately assigned account.

The 7 Misc Charges descriptions are deliberately left alone per Bryce,
including PICKLE CHIP KOSH 1/4 KK, which therefore stays at the 50000
default rather than moving to Produce as the sheet originally suggested.

Also corrects the PAPER & DISP fallback comment in sysco.clj: 440 of its 455
mapped descriptions point at 55000, not all 454. The 15 exceptions (foil
pans -> 51500, scour pads -> 74100) are mapped explicitly, so the
description map still wins ahead of the fallback. No logic changed.

Affects only clients with the code-sysco-items feature flag, and only at
import time -- already-imported invoices keep their existing splits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:00:57 -07:00
7a0e256f07 fix(sysco): fall back to PAPER & DISP category when description is unmapped
The Sysco importer codes each line item by exact description match against
resources/sysco_line_item_mapping.csv, silently defaulting to GL 50000 (Food
Costs) when the description is absent. Every new or renamed Sysco SKU
therefore leaks into Food Costs until someone hand-patches the CSV, which is
what 38575aa5 did for 34 descriptions.

Add a category-level fallback consulted after the description map and before
the 50000 default, enabled for PAPER & DISP only. The description mapping
still wins wherever it exists, so nothing already mapped changes.

PAPER & DISP is safe to generalize: all 454 mapped PAPER & DISP rows point at
55000, with no exceptions. Of the 852 distinct descriptions ever invoiced
under that category, only 3 resolved elsewhere, each because a row with a
different category shared the description and won the later-wins (into {}).
One of those, DESSERT CUP, was simply mis-categorized -- it is paper, and its
own lid (id 1782 LID DOME DESSERT CUP) was already 55000 -- so correct id 1772
to PAPER & DISP / 55000. The remaining two stay at 50000 on purpose, since
they are not paper: PAD SCRUB S-S 35 GRAM 1.25 OZ (SUPP & EQUIP) and TEST
STRIP SANITIZER QUAT (CHEMICAL/JANTRL).

Verified by replaying both changes over all 1,022,732 DET lines in the 56,010
CSVs under sysco-poller/: every resulting transition is 50000 -> 55000 (10,994
lines, $728,106.62). No line that already resolved to a non-default account
moved.

Note this only affects clients carrying the code-sysco-items feature flag, and
only on import -- already-imported invoices need a separate recode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 07:43:58 -07:00
2b5fbaca00 Add template for new Reel Produce statement layout
The statement no longer prints "Reel Produce" as text (only
orders@reelproduce.com), switched to MM/DD/YYYY dates, and moved the
invoice number into an "INV #..." transaction description, so no
template matched and the file fell through to the glimpse2 fallback.

Adds a QuickBooks-statement-style template (same shape as Suncrest /
Ocean Queen) keyed on reelproduce.com + Statement, placed after the
existing Reel Produce statement template so the old layout still wins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 10:24:31 -07:00
473556a45d Backfill script for olo 2026-07-29 09:59:02 -07:00
fa25620b7a olo fixes 2026-07-22 21:49:24 -07:00
08b948c24b fixes 2026-05-19 20:55:21 -07:00
2c8985203e improvements 2026-05-18 23:18:02 -07:00
66b0b611e4 stuff 2026-05-18 18:29:44 -07:00
baef2afc63 fixes 2026-05-18 16:21:07 -07:00
a156ac99fe tries sales changes 2026-05-18 15:38:07 -07:00
de1c154706 Polish sales summary grid and edit dialog
Aligns debit/credit amounts to a right column with tabular-nums;
replaces the in-cell delta and balanced text with chip-style status
indicators; shortens the edit dialog and clarifies its totals/unbalanced
footer rows; gives manual line items a subtle accent so they're
distinguishable from auto-generated rows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 11:13:32 -07:00
31179278e4 Adds a more actionable view 2026-05-17 08:16:11 -07:00
Bryce
1b2e2e4da7 Merge branch 'master' of gitea.story-basking.ts.net:notid/integreat 2026-05-16 00:17:01 -07:00
cc31d8849b Feat/Complete Sales Summaries (#5)
## Summary

Completes the automatic sales summary pipeline end-to-end: the `sales-summaries-v2` job now calculates aggregate totals, preserves manual adjustments, and automatically posts balanced journal entries to the ledger.

## What Changed

**New Datomic transaction function** (`upsert-sales-summary-ledger`)
- Transforms detailed `sales-summary-item`s into aggregated `journal-entry` lines grouped by account and ledger side
- Handles the full upsert: posts a new journal entry for summaries with mapped accounts, or retracts the orphaned entry if items no longer qualify

**Enhanced `sales-summaries-v2` job**
- Calculates and stores 13 aggregate total attributes (card/cash/food-app/gift-card payments, refunds, fees, discounts, tax, tip, returns, unknown, net)
- Preserves manual items (`manual? true`) during recalculation — only auto-calculated items are replaced

**Ledger reconciliation**
- `reconcile-ledger` now queries for sales summaries missing journal entries and repairs them via `:upsert-sales-summary-ledger`, alongside existing invoice and transaction repairs

**Schema**
- Added 13 `total-*` attributes on `sales-summary` (all `db.type/double`, no history)
- Registered the new transaction function in `tx.clj` and `datomic.clj`

**Admin UI cleanup**
- Resolved "clientize" and HTMX `client-id` TODOs in the sales summaries admin page
- `new-summary-item` now correctly passes `client-id` via `hx-vals`
- Removed stale TODO comments and placeholder code

## Files Changed (8)

| File | Purpose |
|------|---------|
| `iol_ion/.../upsert_sales_summary_ledger.clj` | New Datomic tx function |
| `iol_ion/.../tx.clj` | Register new tx function |
| `resources/schema.edn` | 13 new `total-*` attributes |
| `src/.../datomic.clj` | Load new tx namespace |
| `src/.../jobs/sales_summaries.clj` | Aggregate totals + manual item preservation |
| `src/.../ledger.clj` | Sales summary repair in `reconcile-ledger` |
| `src/.../ssr/admin/sales_summaries.clj` | UI TODO cleanup |
| `docs/plans/...plan.md` | Implementation plan document |

Co-authored-by: Bryce <bryce@integreatconsult.com>
Reviewed-on: #5
Co-authored-by: Bryce <bryce@brycecovertoperations.com>
Co-committed-by: Bryce <bryce@brycecovertoperations.com>
2026-05-16 00:16:44 -07:00
Bryce
04bc7cae78 total column 2026-04-09 14:32:39 -07:00
297464c188 Merge branch 'master' of codecommit://integreat 2026-03-30 22:38:34 -07:00
6e3a024f66 adds stuff for dough burger 2026-03-30 22:36:12 -07:00
Bryce
28a755e9a9 fixes invoice date filtering 2026-03-02 23:20:14 -08:00
Bryce
01347ff3f5 fixes balance sheet 2026-03-02 22:53:53 -08:00
53625e4583 Makes invoices use the closed_at date. 2026-02-21 22:53:05 -08:00
8a0395dc4a Add Bonanza Produce multi-invoice statement template
- Added multi-invoice template for Bonanza Produce with :multi and :multi-match? flags
- Template uses keywords for statement header to identify multi-invoice format
- Extracts invoice-number, date, customer-identifier (from RETURN line), and total
- Parses 4 invoices from statement PDF 13595522.pdf
- All tests pass (29 assertions, 0 failures, 0 errors)

- Added test: parse-bonanza-produce-statement-13595522
- Updated invoice-template-creator skill: emphasized test-first approach
2026-02-08 07:56:14 -08:00
98a3e0dda6 Extract customer name and address for Bonanza Produce
- customer-identifier field: customer name (e.g., 'NICK THE GREEK')
- account-number field: street address (e.g., '600 VISTA WAY')
- Combined they provide full customer identification with address
- Updated test to verify both fields and their concatenation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:55:26 -08:00
f4366fe98e Add location extraction for Bonanza Produce invoices
- Extract city/state/zip in location field
- Customer address now split across 3 fields:
  - customer-identifier: customer name
  - account-number: street address
  - location: city, state zip
- All components verified in test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:55:12 -08:00
d95e24a1d7 Improve Bonanza Produce customer identifier extraction
- Extract customer name in customer-identifier field
- Extract street address in account-number field
- Use non-greedy regex with lookahead to capture clean values
- Update test to verify both name and address extraction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:50:55 -08:00
37351e5f92 Add Bonanza Produce invoice template
- Add new PDF template for Bonanza Produce vendor
- Template uses phone number 530-544-4136 as unique identifier
- Extracts invoice number, date, customer identifier, and total
- Includes passing test for invoice 03881260

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:50:42 -08:00
7fae3717f7 expense account fix 2026-01-16 16:24:43 -08:00
a2e31d6966 1099 expens account 2026-01-16 15:50:36 -08:00
795189ddd3 csv for worldwide produce 2026-01-07 11:32:49 -08:00
4ef2190570 template for sushi. 2026-01-07 11:26:20 -08:00
b1273835d5 2025 1099s 2026-01-07 10:45:35 -08:00
68444d6311 allows importing only the invoices that were successful. 2026-01-05 21:34:24 -08:00
8511d30715 pnl changes 2026-01-03 22:44:58 -08:00
269fa45923 improvements 2025-12-04 11:18:36 -08:00
af7f9355e8 Fixes issues 2025-11-19 23:53:30 -08:00