Commit Graph

2762 Commits

Author SHA1 Message Date
acc5a7aa1b Merge branch 'staging' of gitea.story-basking.ts.net:notid/integreat into staging 2026-08-04 22:59:46 -07:00
46fdc29712 Merge pull request 'integreat-send-email' (#15) from integreat-send-email into staging
Reviewed-on: #15
2026-08-04 22:59:33 -07:00
0df9a29022 feat(ssr): add report email hand-off to ledger export modal
The SSR ledger reports could generate and download a PDF but had lost the
SPA's ability to hand the report off to the client's email contacts. This
restores it for Profit and Loss, Balance Sheet and Cash Flows.

The server still sends nothing: the modal offers a mailto: link, pre-filled
with the client's email contacts, subject and body, that opens in the user's
own mail client so they can review before sending.

Extracts the modal the three reports duplicated into a shared
auto-ap.ssr.ledger.export-modal namespace, and tightens the SPA's rules
along the way:

- admin-only, uniformly (the SPA's Cash Flows page skipped this check)
- single client only, uniformly (the SPA's Balance Sheet page did not check,
  so a multi-client report could be mailed to one client's contacts)
- recipients joined with "," per RFC 6068 rather than Outlook's ";"
- subject percent-encoded, like the body already was
- body links built from :base-url and bidi routes, fixing the dead
  /reports/ link (the page now lives at /company/reports) and the
  hardcoded prod domain in the requires-feedback link

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:55:53 -07:00
a17e16e31b feat(ledger): remove a whole ledger entry from the external import grid
The external ledger import review grid is one row per line item, so a
journal entry spans two or more rows. When one entry fails validation the
whole paste is rejected, and the only way forward was to re-paste without
it.

Each row now carries its entry id (client-source-externalId, the same key
table->entries groups on) and the last column gets a trash button that
drops every row sharing that id. The rows are the form inputs, so removing
them from the DOM removes them from the next import post -- which also
works for rows that fail schema validation. Index gaps left behind are
compacted by coerce-vector on the way back in.

Also drops two leftover pprint calls that dumped form-errors to stdout on
every render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:44:52 -07:00
e6507d5387 merged 2026-08-04 22:15:59 -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
7155e199e8 Merge branch 'master' into staging 2026-07-29 10:25:04 -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
604d1ee1cf changes 2026-07-25 21:13:44 -07:00
e095cb94e4 fix(config): propagate rotated Plaid secret to worker configs
The rotation in 111eca41 updated the Plaid secret-key only in prod.edn,
leaving prod-background-worker.edn, prod-cloud-background-worker.edn, and
prod-cloud.edn on the old (now-invalidated) secret. Background worker jobs
loading those files failed with INVALID_API_KEYS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 20:29:15 -07:00
9d007ee8e2 Merge branch 'master' into staging 2026-07-22 21:50:12 -07:00
fa25620b7a olo fixes 2026-07-22 21:49:24 -07:00
d012283362 olo fixes 2026-07-22 21:43:31 -07:00
14726d0208 re-synced secrets. 2026-07-22 21:17:31 -07:00
6429cf823d fixes 2026-07-12 20:45:36 -07:00
111eca413e rotates passwords 2026-07-12 20:44:05 -07:00
80e45c026e feat(ssr): add sales summary csv download
Exports one row per sales summary item so each category is individually
auditable, rather than one row per daily summary. Uses the grid helper's
page->csv-entities hook, the same seam the ledger export uses to fan a
journal entry out into its line items.

Includes the GL account code alongside the name so rows tie back to the
chart of accounts, and formats amounts to cents to keep float noise
(36.900000000000006) out of the export. Accounts are resolved in a single
batched pull and clientized per summary so name overrides are respected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:51:46 -07:00
9cd3d9c962 fixed. 2026-07-08 23:33:37 -07:00
5f5686c39c fix(ssr): link to the real transaction route from paperclip menus
::transaction-routes/all-page does not exist -- the transactions index
is registered as ::transaction-routes/page ("/transaction2"). bidi's
path-for returns nil for an unknown handler, so (hu/url nil {...})
produced the relative "?exact-match-id=123". Clicking a Transaction
link in the ledger paperclip tooltip therefore stayed on the ledger and
re-filtered it by a transaction entity id, matching nothing.

Repoint all five call sites (ledger, payments and invoice paperclip
menus, the insights breadcrumb, and the expected-deposit row button) at
::transaction-routes/page.

Also add the missing conj in the ledger :invoice/source-url cond->
branch. The bare map made cond-> invoke it as a function against the
accumulated vector, yielding nil, so any journal entry whose original
entity was an invoice with a file rendered no paperclip at all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:15:17 -07:00
d0846f91aa fix(ssr): use form-validation-error for transaction edit validations
Untyped (ex-info ... {:validation-error ...}) throws escaped the
wrap-form-4xx-2 middleware, surfacing as a 500 / unexpectedError box
instead of an inline form error. Route them through
utils/form-validation-error, which throws with :type :form-validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:12:00 -07:00
56addf8c70 fix(ssr): order coerced vector form params by numeric index
Indexed form fields like periods[0]..periods[13] parse into a
string-keyed map; coerce-vector rebuilt the vector with a plain
lexicographic sort, so "10".."13" sorted before "2". Reports with
10+ periods (e.g. the 13/14-period option) rendered columns in the
order 0,1,10,11,12,13,2..9. Sort index keys numerically instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 05:22:52 -07:00
ebb48a0de4 feat(ssr): previous calendar year period + external register filters
- P&L period dropdown: replace "Calendar year (YYYY)" with
  "Previous Calendar Year (YYYY-1)" pulling the full prior year
- External register: title now reads "External Register"; add
  Source, External Id, and Location filters

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:22:31 -07:00
e2ccfc8d2c Merge branch 'ledger-bulk' into staging 2026-06-23 22:03:29 -07:00
e8cbd2760c fixes 2026-06-23 22:03:26 -07:00
e0da8e1866 feat(ssr): add delete selected to external ledger
Replicate the master CLJS "delete external ledger" feature on the SSR
external ledger page: an admin-only bulk delete that retracts the
selected journal entries, skipping any in a client's locked period and
capping at 1000 per request.

Return the result via modal-response (retargets the persistent
#modal-content shell) and target #modal-content from the button so the
request never relies on the outerHTML swap inherited from the data-grid
card, which previously replaced #modal-holder and broke the next click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:48:39 -07:00
2e3c1e3646 feat(ssr): add clear filters button to transactions
Shows a "Clear filters" button in the transactions action bar whenever a
non-date filter is active. It's a boosted link back to the transactions
page that preserves the date range (and any implied status), so the
sidebar filters and table both reset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:43:43 -07:00
a7e9fbaf6b feat(ssr): disable bulk action buttons until a transaction is selected
Disable Code, Delete, and Suppress until at least one row is checked or
"select all" is active, matching the existing selection-aware UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:19:39 -07:00
8a676718a7 feat(ssr): reset transaction selection after bulk code
Bulk coding left the checked items selected after the table refreshed.
Add a dedicated reset-selection event that the grid's Alpine state
listens for, and fire it alongside refreshTable on bulk-code submit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:06:37 -07:00
3ffb661da3 fix(ssr): stop unresolved filter flipping to true on transactions navigation
The unresolved/potential-duplicates query-param decoders fell through to
(boolean %) for unrecognized strings. A round-tripped "false" (pushed into the
URL, re-read via HX-Current-URL) decoded to true since any non-nil string is
truthy, so navigating pages silently turned on the "Unresolved only" filter.

Handle "false" and already-boolean values symmetrically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:40:31 -07:00
f9438ba983 fix(ssr): only require account coding for manual transaction edits
Account coding lived in the always-applied base map of edit-form-schema, so
every action (including the link/apply-rule/unlink actions) required a valid
transaction-account/account. The edit modal always submits the Manual tab's
(usually blank) account row, so link submits failed validation before reaching
their save-handler and silently no-op'd. Move account validation into the
:manual branch of the action :multi so link actions validate without it.

Also surface whole-form validation errors in the wizard footer error bar:
default-step-footer only handled top-level/sequential error shapes, so nested
field-error maps (e.g. a hidden tab's account error) produced an empty bar and
a silent failure. Add flatten-form-errors to flatten the humanized error tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:30:29 -07:00
7d34b8a5f6 money 2026-06-18 20:26:07 -07:00
c09d85ede6 fix(ssr): fix Client Review (requires-feedback) status in bulk-code dialog
The bulk-code "Requires Feedback" option submitted "requires_feedback"
(underscore), which decoded to an enum keyword not present in the
schema (idents use a hyphen), so selecting it failed validation. Use
the hyphenated value and relabel the option, the reconciliation report
header to "Client Review" to unify with the sidebar terminology.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:38:00 -07:00
ec4f88b7fc fix(ssr): hide P&L warning box when there is no warning
The profit-and-loss report always passed :warning as a [:div ...] hiccup
vector, which is truthy even when empty. The shared report table renders
its red warning box with (when warning ...), so a clean report with no
warning and no unresolved entries still showed an empty red error box.

Only build the warning div when there is actual warning text or sample
links, matching how the balance-sheet and cash-flows reports pass nil.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:56:43 -07:00
8ca5e75c4d fix(ssr): hide client column in edited transaction row for single client
The save-handler re-rendered the edited row via row* without passing
:request, so the Client column's :hide? predicate received a nil request
and never hid the column. Pass :request request like table* does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:40:50 -07:00
4aed27b204 feat(ssr): add bank account column to transactions table
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:36:08 -07:00
d0028f403c fix(ssr): allow picking bank account when editing a transaction rule
The bank-account filter rendered "Please select a client" even when a
client was set on the rule. Two causes:

- Inside (fc/with-field :transaction-rule/bank-account ...) the cursor is
  rebound to the bank-account field, so (:transaction-rule/client
  (fc/field-value)) read the nil bank-account value and the server
  rendered the placeholder. The clientId watcher only fires on change, so
  when editing (client preset, unchanged) the htmx swap never corrected
  it. Read the client from the form root before entering the field.
- The clientId-change swap used innerHTML, nesting a fresh typeahead
  inside the stale one and breaking its Alpine refs. Use outerHTML so the
  typeahead is replaced in place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:32:30 -07:00
6b4392b74b fix(ssr): keep top bar to a fixed-height single row
The top bar grew vertically on narrower viewports when the environment
badge and company-selector labels wrapped, pushing content under the
fixed navbar (which the layout offsets with a fixed pt-16).

Rework the navbar into a fixed h-16 row with a priority-based responsive
layout:
- search fills the middle (flex-1) and shrinks first when space is tight
- company selector holds its size and truncates long names
- environment badge degrades full pill -> compact letter badge -> hidden
- harmonize control heights (40px controls, 32px badge/avatar accents) so
  the search no longer renders as a cramped thin strip

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:01:32 -07:00
cdc87d3710 fix 2026-06-16 20:36:00 -07:00
1e3952a7fb fix(auth): login error-details pre escaping Alpine scope
The error-details <pre> lived inside a <span x-data="{e:false}"> that was
itself inside a <p>. Since <pre> is block content, the HTML parser closed
the <p> and reparented the <pre> out of the span, so Alpine evaluated
x-show="e" with e no longer in scope ("e is not defined"). Use a <div>
wrapper instead of <p> so the pre stays within the e scope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:05:43 -07:00
e099714af1 fix(ssr): transaction edit dropdown duplication and advanced->simple toggle
- Location field hx-target "find *" resolved to the <label> (first child),
  so changing an account swapped the reloaded <select> over the label and
  left a duplicate dropdown. Target "find select" instead (simple + advanced).
- edit-wizard-toggle-mode-handler read mode only from step-params, but the
  hidden "mode" field is a top-level form param, so current-mode always
  defaulted to "simple" and the toggle could never return from advanced.
  Read it from form-params too, matching edit-vendor-changed-handler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:02:41 -07:00
11024b7b89 fix(ssr): transaction rule wizard drops fields on Next
The EditModal step body wrapped all rule fields in a nested
<form id="my-form"> inside the wizard's own #wizard-form. By HTML
form-ownership rules those fields belonged to the inner form, so when
htmx serialized #wizard-form on Next, none of the step-params fields
were sent. The server saw an empty rule, reported "required" for
description/accounts, and re-rendered a blank wizard (losing input).

Replace the nested <form> with a plain <div>; the wizard form already
owns submission, so the inner form and its htmx attributes were
redundant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:58:36 -07:00
de2a1ab850 fixes pnl file name 2026-06-16 14:37:13 -07:00
fc54b92ddb fixes 2026-06-04 22:59:51 -07:00
019a1b4cd8 fixes 2026-06-04 22:56:01 -07:00
38575aa5bd data(sysco): add missing line-item GL mappings for paper & other items
The Sysco importer codes line items by exact-matching the item description
against resources/sysco_line_item_mapping.csv, falling back to GL 50000
(Food Costs) when no entry exists. On master, 8 of the paper-product
descriptions on recent invoices (e.g. BAG PAPER 250 CT, NAPKIN 2PLY INTR
FOLD 6.3X8.26, CONTAINER PAPER 4/110OZ NTG) were missing, so they
defaulted to 50000 instead of 55000 (Paper Costs).

Append the 34 curated mappings (Ids 1762-1795) covering these paper items
(-> 55000) plus the other new items from the same invoices, so they code
correctly on re-import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:28:23 -07:00
85aaf7b759 mcp repl fixes 2026-06-02 23:40:05 -07:00
3641846f70 Merge pull request 'docs: SSR rendering modernization rollout plan' (#12) from docs/ssr-rendering-modernization-plan into staging
Reviewed-on: #12
2026-06-02 23:26:45 -07:00
d360316590 docs: add swap-target selector strategy consideration
Note in 3.1 that targeted hx-select/hx-target swaps in repeated/nested
structures may want a consistent scheme -- semantic markup + data-attributes,
or a form-path->selector helper (mirroring cursors) -- instead of hand-minting
a unique id per element. Framed as a consideration for advanced cases, with a
Phase 5 task to settle the convention into the skill cookbook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:17:52 -07:00