toga-ai 1.0.317 → 1.0.319
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
|
|
8
8
|
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/checker.php, _underscore/Component/Forecast/SaleImport/SaleImport.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
|
|
9
9
|
| [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/netsuite/rest.php, library/app/systemmonitor/netsuiteintegration.php |
|
|
10
|
+
| [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL | library/app/worker.php, worker/crons/worker/worker_heartbeat.php |
|
|
10
11
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
|
|
11
12
|
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OneUptime external uptime monitoring for 1.0 workers
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-13
|
|
10
|
+
owners: ["jcardinal"]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/worker.php
|
|
13
|
+
- worker/crons/worker/worker_heartbeat.php
|
|
14
|
+
related: []
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Summary
|
|
18
|
+
|
|
19
|
+
Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once
|
|
20
|
+
per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL. This is an
|
|
21
|
+
**external** observability layer that sits on top of the pre-existing **internal**
|
|
22
|
+
DB-heartbeat logic in `worker_heartbeat.php` (the `Workers` table / `dtHeartbeat`
|
|
23
|
+
self-recovery/terminate mechanism). If a box dies, OneUptime stops receiving pings and
|
|
24
|
+
fires **Degraded at 3 min** / **Offline at 5 min**, alerting the team independently of the
|
|
25
|
+
internal self-recovery path.
|
|
26
|
+
|
|
27
|
+
## Key files / entry points
|
|
28
|
+
|
|
29
|
+
- `library/app/worker.php` — `App_Worker::$oneUptimeEndpoints`: a `public static` map keyed
|
|
30
|
+
by `workerName` (`notification`, `database`, `infrastructure`, `toga`, `togadesk`, `sync`,
|
|
31
|
+
`catalog`) → that worker's OneUptime heartbeat push URL. This is where the endpoint URLs
|
|
32
|
+
(with their per-monitor UUIDs) live; treat them as a credential/secret location, not values
|
|
33
|
+
to reproduce elsewhere.
|
|
34
|
+
- `worker/crons/worker/worker_heartbeat.php` — the per-minute heartbeat cron; the OneUptime
|
|
35
|
+
push happens in the block that handles this instance's own `Workers` row.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
1. The heartbeat cron processes this instance's own `Workers` row and sets
|
|
40
|
+
`dtHeartbeat = NOW()` (existing internal-heartbeat behavior).
|
|
41
|
+
2. Immediately after, it curl-POSTs to
|
|
42
|
+
`App_Worker::$oneUptimeEndpoints[$row['workerName']]` with a 10s timeout.
|
|
43
|
+
3. On `curl_errno`, it `error_log()`s the failure (per coding-style rules — no silent
|
|
44
|
+
swallow) but does not abort the cron.
|
|
45
|
+
4. OneUptime, receiving a ping every minute, keeps the monitor "Online". Missing pings for
|
|
46
|
+
3 min → **Degraded**; 5 min → **Offline**, which triggers the team alert.
|
|
47
|
+
|
|
48
|
+
Net effect: liveness is now observed from outside the box. The internal `Workers`/`dtHeartbeat`
|
|
49
|
+
stale-cutoff (270s) self-terminate/replace logic is unchanged and still runs in the same cron.
|
|
50
|
+
|
|
51
|
+
## Provisioning the monitors
|
|
52
|
+
|
|
53
|
+
The OneUptime monitors are **Incoming Request heartbeat** monitors configured
|
|
54
|
+
1 min online / 3 min degraded / 5 min offline. They were provisioned by exporting the JSON
|
|
55
|
+
of the existing "Sync 1.0" monitor and cloning it into import files for Notification,
|
|
56
|
+
Database, Infrastructure, TOGa, TOGa Desk, and Catalog, then importing each into OneUptime
|
|
57
|
+
manually. To add a new worker's monitor, clone an existing monitor export the same way and
|
|
58
|
+
register the resulting heartbeat URL in `App_Worker::$oneUptimeEndpoints`.
|
|
59
|
+
|
|
60
|
+
## Gotchas
|
|
61
|
+
|
|
62
|
+
- Do **not** hardcode the heartbeat URLs/UUIDs anywhere but `App_Worker::$oneUptimeEndpoints`
|
|
63
|
+
— they are push credentials. Same for the AWS keys / EIP allocation IDs already present in
|
|
64
|
+
these files: do not copy them into docs or logs.
|
|
65
|
+
- A stale leftover `const ONE_UPTIME_ENDPOINTS = [];` remains near the top of
|
|
66
|
+
`worker_heartbeat.php` (dead code superseded by the `App_Worker` map). It is a known
|
|
67
|
+
pending cleanup — the live source of truth is the `App_Worker::$oneUptimeEndpoints` map.
|
|
68
|
+
|
|
69
|
+
## Change history
|
|
70
|
+
- 2026-07-13 — Added external OneUptime heartbeat push for all 1.0 workers on top of the
|
|
71
|
+
existing internal DB-heartbeat mechanism; endpoints keyed by workerName in
|
|
72
|
+
`App_Worker::$oneUptimeEndpoints`. (jcardinal)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 12 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 15 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
10
10
|
- **togadesk** (TOGa Desk) — 9 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
11
11
|
- **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
@@ -5,7 +5,7 @@ project: Worker
|
|
|
5
5
|
client: tow-foundation
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-07-
|
|
8
|
+
updated: 2026-07-13
|
|
9
9
|
owners: ["rgirish"]
|
|
10
10
|
files:
|
|
11
11
|
- worker2/Worker/Client/TowFoundation.php
|
|
@@ -25,7 +25,9 @@ Excel file per person, uploads it to SharePoint, and sends a summary email.
|
|
|
25
25
|
|
|
26
26
|
Invocation: `{"action": "Client/TowFoundation/ProcessReceipts/Run", "parameters": {}}`
|
|
27
27
|
|
|
28
|
-
Optional filter parameters: `limit` (int), `year` (string e.g. `"2026"`), `person` (string e.g. `"Brent Peterkin"`), `billingCycle` (string — matches the **end** of the billing-cycle folder name, e.g. `"06-03-2026"` matches `"Amex ending in 06-03-2026"`, `"Mastercard ending in 06-03-2026"`, etc.).
|
|
28
|
+
Optional filter parameters: `limit` (int — caps count processed, **not a dry-run**; each processed receipt still archives and the email still sends), `year` (string e.g. `"2026"`), `person` (string e.g. `"Brent Peterkin"`), `billingCycle` (string — matches the **end** of the billing-cycle folder name, e.g. `"06-03-2026"` matches `"Amex ending in 06-03-2026"`, `"Mastercard ending in 06-03-2026"`, etc.).
|
|
29
|
+
|
|
30
|
+
The monthly production procedure is **two explicit per-card runs** (Amex + Ryan Farrell's Mastercard), never a single month sweep — see *Monthly production run procedure* below.
|
|
29
31
|
|
|
30
32
|
## Key files / entry points
|
|
31
33
|
|
|
@@ -37,11 +39,11 @@ Optional filter parameters: `limit` (int), `year` (string e.g. `"2026"`), `perso
|
|
|
37
39
|
### SharePoint folder structure
|
|
38
40
|
```
|
|
39
41
|
Credit Card Receipts/
|
|
40
|
-
|
|
41
|
-
{Person}/
|
|
42
|
+
{Person} CC receipts/ ← each cardholder's own folder ("Brent Peterkin CC receipts")
|
|
42
43
|
{Year}/
|
|
43
44
|
{BillingCycleFolder}/ ← receipt files live here (e.g. "Amex ending in 06-03-2026")
|
|
44
45
|
{Person} report[s]/ ← .xlsx billing statements (folder name matched on singular "report")
|
|
46
|
+
3. QB Excel/ ← this person's generated QB import Excel uploaded here (per-person)
|
|
45
47
|
Archive/ ← successfully processed receipts land here (renamed, flat)
|
|
46
48
|
Archive/exception/ ← failed receipts land here (original name preserved, flat)
|
|
47
49
|
```
|
|
@@ -82,7 +84,8 @@ Credit Card Receipts/
|
|
|
82
84
|
- PATCH SharePoint to rename + move to `Archive/` (flat — no person subfolder)
|
|
83
85
|
- **Multi-entry docx expansion** — a `.docx` whose `extractDocxData()` produced multiple `line_items[]` (e.g. a subway-rides document with 14 dated trip entries) is expanded into one Excel row per entry
|
|
84
86
|
8. **Excel generation** — one `.xlsx` per person (PhpSpreadsheet); columns: Row #, Account Name, QB Vendor, Payment Amount, Date, Payment Method, Payment Memo, QB Description, Class, Category, Payment Account, Ref No.
|
|
85
|
-
9. **SharePoint upload** — each Excel uploaded to
|
|
87
|
+
9. **SharePoint upload** — each person's Excel is uploaded to **that person's own**
|
|
88
|
+
`Credit Card Receipts/{Person} CC receipts/{Year}/3. QB Excel/` folder via Graph API PUT (see *Per-person QB Excel upload location* below)
|
|
86
89
|
10. **Summary email** — sent with Excel files attached; To: Jheanelle, CC: Magdalena, BCC: devteam@togatech.com. `sendSummaryEmail()` takes a `$warnings` param and renders an **"Amounts to verify"** section listing rows whose amount could not be reconciled against the statement (see below).
|
|
87
90
|
|
|
88
91
|
### Amount reconciliation (flag-only)
|
|
@@ -227,6 +230,37 @@ To re-run one cardholder + cycle after a code fix, without deploying:
|
|
|
227
230
|
3. **Email is safe locally** — each `Run()` sends the summary email, but with `debug_mode=1`
|
|
228
231
|
locally all mail is redirected to `send_debug_emails_to`, so no client contact is reached.
|
|
229
232
|
|
|
233
|
+
### Monthly production run procedure (per-card, NOT a month sweep)
|
|
234
|
+
The agreed monthly ops procedure is **two explicit invocations** of
|
|
235
|
+
`Client/TowFoundation/ProcessReceipts/Run`, one per card — one for the Amex cycle and one for
|
|
236
|
+
Ryan Farrell's Mastercard cycle:
|
|
237
|
+
```json
|
|
238
|
+
{"year":"2026","billingCycle":"Amex ending in 07-03-2026"}
|
|
239
|
+
{"year":"2026","person":"Ryan Farrell","billingCycle":"Mastercard ending in 07-06-2026"}
|
|
240
|
+
```
|
|
241
|
+
- `billingCycle` is a **case-insensitive `str_ends_with` suffix match** on the folder name.
|
|
242
|
+
- **Business invariant: one card per cardholder** — everyone is Amex except **Ryan Farrell**,
|
|
243
|
+
who is Mastercard-only.
|
|
244
|
+
- **Why not a single "MM-YYYY month sweep"?** A month-sweep feature (one call processing both
|
|
245
|
+
the Amex and Mastercard cycles together) was prototyped and then **deliberately reverted before
|
|
246
|
+
release**: `PAYMENT_ACCOUNT_MAP` is keyed **per-person, not per-card**, so a sweep would
|
|
247
|
+
misattribute a cardholder's QB Payment Account if a person ever held two cards in one swept
|
|
248
|
+
month. Per-card explicit runs avoid that ambiguity. Do not reintroduce a month sweep without
|
|
249
|
+
first making `PAYMENT_ACCOUNT_MAP` (and Class) card-aware.
|
|
250
|
+
|
|
251
|
+
### Production-safety facts (there is no dry-run mode)
|
|
252
|
+
- **No dry-run.** `limit` only caps *how many* receipts are processed — each processed receipt
|
|
253
|
+
**still moves to SharePoint `Archive/`** and the **summary email still sends**. `limit` is not
|
|
254
|
+
a preview.
|
|
255
|
+
- **File moves are reversible; the email is not.** Archive moves can be undone with the `MoveBack`
|
|
256
|
+
action (see above), but once the summary email sends it **cannot be recalled**. Treat every
|
|
257
|
+
`Run()` on production data as a real send.
|
|
258
|
+
- **Recipients are compile-time constants, not request-derived.** The completion-email recipient
|
|
259
|
+
list comes entirely from the `NOTIFY_*` constants in `Worker/Client/TowFoundation.php` (see
|
|
260
|
+
*Email routing*) — the request cannot change who is emailed. Secret/config values (SharePoint
|
|
261
|
+
credentials, drive/site IDs) live in `TowFoundation.php` constants and `_Config`, never in the
|
|
262
|
+
request or this doc.
|
|
263
|
+
|
|
230
264
|
## Email routing
|
|
231
265
|
|
|
232
266
|
Defined as constants in `TowFoundation.php`:
|
|
@@ -273,9 +307,16 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
|
|
|
273
307
|
- **Archive folders are flat** — processed files land directly in `Archive/` and failed
|
|
274
308
|
files in `Archive/exception/` with no extra person subfolder. The Archive is already
|
|
275
309
|
scoped inside `{Person}/{Year}/` so the extra nesting is redundant.
|
|
276
|
-
- **QB Excel goes to
|
|
277
|
-
`Credit Card Receipts/3. QB Excel
|
|
278
|
-
|
|
310
|
+
- **QB Excel goes to each person's own `3. QB Excel/` subfolder** — each cardholder's
|
|
311
|
+
Excel is uploaded to `Credit Card Receipts/{Person} CC receipts/{Year}/3. QB Excel/`,
|
|
312
|
+
**not** a single shared root folder. `uploadExcelToSharePoint(accessToken, driveId,
|
|
313
|
+
personFolderName, year, excelName, tmpFile)` builds the path with the same per-segment
|
|
314
|
+
`rawurlencode` + `ensureFolderPath()` pattern as `renameAndMoveToArchive`/`moveFileToArchive`.
|
|
315
|
+
The `personFolderName`/`year` come from a `$personFolders` map (personName ⇒ {folderName,
|
|
316
|
+
year}) populated **first-write-wins in Pass 1**, before extraction, so the destination is
|
|
317
|
+
recorded even if that person's receipt later fails. If a person's folder/year was never
|
|
318
|
+
recorded, a fallback `error_log()`s and defaults to `{personName}/{currentYear}`. (Before
|
|
319
|
+
2026-07-13 all persons' Excels went to one shared root `Credit Card Receipts/3. QB Excel/`.)
|
|
279
320
|
|
|
280
321
|
- **Person folder name normalization** — SharePoint folders are named `"Brent Peterkin CC receipts"` but `CLASS_MAP` / `PAYMENT_ACCOUNT_MAP` keys are just `"Brent Peterkin"`. The ` CC receipts` suffix is stripped via regex in `walkReceiptsFolder()`. Without this, Class and Payment Account columns are blank for those persons.
|
|
281
322
|
- **Archive paths must use the actual SharePoint folder name, not the normalized name** — the normalized person name (` CC receipts` stripped, spaces replaced) is for **map lookups only**. When building the SharePoint archive path, use the *actual* folder name (e.g. `"Emily Tow CC receipts"`), not the normalized `"Emily Tow"` — otherwise the PATCH move 404s because the path segment does not exist. Keep the normalized name and the real folder name as separate values.
|
|
@@ -289,6 +330,21 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
|
|
|
289
330
|
folder held Jan–May statements but no `"Amex ending in 06-03-2026.xlsx"` (and even her 05-03
|
|
290
331
|
file has no `Notes` column). Resolution is a **client action**: upload Nadia's 06-03-2026
|
|
291
332
|
statement with a `Notes` column, then re-run her. Not a code fix.
|
|
333
|
+
**The match is EXACT string equality between the statement filename and the receipt's
|
|
334
|
+
billing-cycle FOLDER name — there is no date-based or fuzzy fallback, and the miss is
|
|
335
|
+
entirely silent.** `collectStatementFiles` keys each statement by its filename-without-extension
|
|
336
|
+
(`cycleKey`); the later lookup is `parsedStatements[$personName][$billingCycleFolder]`. So a
|
|
337
|
+
cardholder whose receipts live in a differently-named folder than any statement file gets **no
|
|
338
|
+
Notes memo, no exception, and no warning row** — the memo silently falls back to AI inference,
|
|
339
|
+
producing plausible-but-unverified output with zero signal. Confirmed real for **Ryan Farrell**:
|
|
340
|
+
his receipts are in a `"Mastercard ending in 07-06-2026"` folder while his only statement is named
|
|
341
|
+
`"Amex ending in ..."`, so his Notes never load. When adding/extending this worker, treat a missing
|
|
342
|
+
statement as a real defect risk, not a benign case — cross-check that `parsedStatements[$person]`
|
|
343
|
+
is populated for each cardholder in an actual run.
|
|
344
|
+
- **`computeRefNo()` hard-codes the Amex cycle-end day `03`** — so Mastercard rows (Ryan Farrell,
|
|
345
|
+
whose cycle closes on the **6th**) get a Ref No. ending in `…03…` rather than `…06…`. Pre-existing
|
|
346
|
+
data-accuracy quirk (not changed as of 2026-07-13); relevant if Mastercard Ref No. accuracy ever
|
|
347
|
+
matters for the QuickBooks import.
|
|
292
348
|
- **Vendors not in QB vendor list** — as of June 2026 these vendors are not in Tow Foundation's QuickBooks, so `qb_vendor` is blank for their charges until the client adds them to QB and the vendor list is refreshed: **Ole Mole**, **AMORE PIZZA CAFE**, **Green & Tonic (New Canaan)**, **NCFP (National Center for Philanthropy)**, **Rippling**, **Langan's**. This is a client action, not a code fix.
|
|
293
349
|
- **Duplicate detection is within-run and `fileId`-keyed only** — dedup keys on the SharePoint
|
|
294
350
|
drive-item `fileId`, so it catches only a literal re-upload of the same item present in the
|
|
@@ -318,6 +374,7 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
|
|
|
318
374
|
|
|
319
375
|
## Change history
|
|
320
376
|
|
|
377
|
+
- 2026-07-13 — **Per-person QB Excel location fix + two production findings.** (1) FIXED: `uploadExcelToSharePoint()` now writes each person's generated QB Excel to that person's own `Credit Card Receipts/{Person} CC receipts/{Year}/3. QB Excel/` subfolder (matching the docblock and client expectation) instead of a single shared root `3. QB Excel/`; signature is now `(accessToken, driveId, personFolderName, year, excelName, tmpFile)`, fed by a `$personFolders` map populated first-write-wins in Pass 1 with an `error_log` fallback to `{personName}/{currentYear}`. (2) DISCOVERED (production-critical gotcha): the statement-Notes lookup is EXACT string equality between the statement filename (`cycleKey`) and the receipt's billing-cycle FOLDER name — no date/fuzzy fallback — so a card whose folder has no identically-named statement silently skips the authoritative Notes memo and falls back to AI inference with zero signal (confirmed for Ryan Farrell's Mastercard folder vs. an Amex-named statement); also noted `computeRefNo()` hard-codes cycle-end day `03`, giving Mastercard rows a `…03…` Ref No. (pre-existing, unchanged). (3) DECIDED: monthly production procedure is two explicit per-card `Run` invocations (Amex + Ryan Farrell's Mastercard), NOT a "MM-YYYY month sweep" — the sweep prototype was deliberately reverted because `PAYMENT_ACCOUNT_MAP` is keyed per-person not per-card and would misattribute the QB Payment Account for anyone holding two cards in a swept month; recorded production-safety facts (no dry-run — `limit` still archives + emails; file moves reversible via `MoveBack` but email is not recallable; recipients are compile-time `NOTIFY_*` constants). (rgirish)
|
|
321
378
|
- 2026-07-09 — **Local reprocess-all-cardholders verification pass — three more fixes + two data/ops findings (code-only, uncommitted).** These fixes were found *after* the six-fix pass earlier the same day, while re-running every cardholder locally and inspecting the uploaded QB files. (1) **Statements-folder detection matched plural `reports` only** — `walkReceiptsFolder()` now matches `stripos(name,'report')` (singular), so `"{Name} report"` folders (Angela Leis, Kathryn Lockhart, Meghan Lowney, Eileen Wiseman) actually load their statement; without this the earlier Notes-memo fix silently did nothing for them in a real `Run()` (folder-name matching is the gate — verifying `matchStatementNotes()` in isolation is not enough). (2) **docx run-boundary word-splitting** — `</w:r>` was replaced with a space, injecting spaces mid-word (`"Innoc ence"`, `"Armstron g"`, `"202 6 . 0 6 .0 3"`) in Emily Tow's subway memos; now replaced with `''` at both sites (`extractDocxData`, `parseDocxRideLines`) — only `</w:p>` is a real line break. (3) **docx "Closing Date" header mis-parsed as first ride** — the ride regex read `"AMEX Closing Date 2026.06.03"` as a ride and swallowed the real first ride; now stripped upfront via `preg_replace('/(?:AMEX|Visa)\s+Closing\s+Date\s+\d{4}[.\/-]\d{1,2}[.\/-]\d{1,2}/i','',$text)` (kept 4-digit dot dates for Jheanelle rather than narrowing ride dates). Data/ops findings (no code change): **Nadia Alia's** June statement is absent from SharePoint → all her memos AI-inferred until the client uploads `"Amex ending in 06-03-2026.xlsx"` with a Notes column; local worker HTTP endpoint 500s on direct `{action,parameters}` calls (post 2026-07-07 `Core.WorkerJobs` dependency absent in dev) → reprocess a cycle via CLI + `MoveBack` (debug_mode redirects mail). Verified correct: Angela, Emily (incl. 14 subway line items), Kathryn, Meghan (Kellari $548.11 flagged), Michael (all 5 charges). Nadia pending client statement; Jheanelle + Diane not yet reprocessed. (rgirish)
|
|
322
379
|
- 2026-07-09 — **Client QA pass on a production run — six fixes (code-only, not deployed).** (1) Statement `Notes` column is now the **authoritative** payment memo, used verbatim; `loadStatementExcel()` detects `Notes` (exact-match first, guarded substring fallback) — replaces the `isActivityHeadingMemo()` allow-list that dropped hand-written memos (fixed Angela, Emily, Katy, Meghan, Nadia). (2) `matchStatementCandidate($strict)` returns `null` on ambiguous same-amount matches for the Notes memo instead of guessing; `matchStatementNotes()` is strict, description fallback stays best-effort. (3) Dedup now keys on SharePoint `fileId` only — the old person|date|vendor|amount key dropped distinct same-amount charges (Michael lost 2 of 5). (4) docx ride-line regex now accepts dot dates (`[\/.]`) with optional `[:\-]?` delimiter — Jheanelle's dot-format subway docx previously matched 0 lines and produced no output. (5) `buildExcelFileName()` now derives the reporting month from the billing-cycle end (`parseCycleEndDate()`/`resolvePersonCycle()`) not the charge date, so the filename agrees with the Ref No. (Diane). (6) Systemic discovery: non-docx `payment_amount` is AI-OCR only, never reconciled against the statement `Amount` (Meghan $548.11 vs $538.11); added flag-only `statementHasAmount()` + `$warnings` "Amounts to verify" email section — auto-override deferred. (rgirish)
|
|
323
380
|
- 2026-06-29 — **Reversed the over-aggressive memo override.** Replaced `isBareTranportationMemo()` (added earlier same day) with `isActivityHeadingMemo()`: the statement memo now overrides the AI memo **only** when it is a proper activity-purpose heading (e.g. `Grantee meeting:`, `Peer meeting:`), not whenever the AI memo started with `"Transportation:"`. The previous approach replaced semi-useful AI memos with raw bank strings (`"AplPay LYFT …"`, `"UBER"`) on ride-share/taxi rows where the statement has no activity heading — degraded 8 rows on Emily Tow's statement (1, 17, 18, 21, 26, 31, 32, 34). New priority: statement heading > AI memo, with AI memo kept for any non-heading statement description. (rgirish)
|
package/package.json
CHANGED