d86262740 |
Tim Richardson |
2026-02-27T23:07:27+11:00 |
feat: Auto-commit by deployer
|
982fda425 |
Tim Richardson |
2026-02-27T23:06:53+11:00 |
fix(shopify): prioritize Loop exchange order names in pass processing
|
0ebf8e5dc |
Tim Richardson |
2026-02-27T22:36:14+11:00 |
fix(shopify): harden handling-fee money task idempotency checks
|
9b97b926c |
Tim Richardson |
2026-02-27T22:36:08+11:00 |
fix(shopify): tighten loop pagination and credit-note repair guards
|
9058dc9d2 |
Tim Richardson |
2026-02-27T22:36:02+11:00 |
chore: migrate code review tooling to heavy-review agents
|
05ddc5ad2 |
Tim Richardson |
2026-02-27T20:29:57+11:00 |
chore: enhance code-review skill with parallel reviewers
|
716a465c9 |
Tim Richardson |
2026-02-27T20:29:06+11:00 |
fix(shopify): clarify pass-one finished-state CN message
|
1bf2c4fb8 |
Tim Richardson |
2026-02-27T20:10:09+11:00 |
chore: update code review skill templates
|
7115c68d8 |
Tim Richardson |
2026-02-27T20:06:08+11:00 |
fix(core): stop duration timer when websocket closes
|
6662a9710 |
Tim Richardson |
2026-02-27T20:02:05+11:00 |
fix(shopify): harden loop pass-one credit note refunds
|
d3887101f |
Tim Richardson |
2026-02-27T17:00:49+11:00 |
fix(shopify): remove landing page tutorial video
|
916096c37 |
Tim Richardson |
2026-02-27T16:33:32+11:00 |
chore(jaggad): enable loop returns and clarify bg command docs
|
e8184d912 |
Tim Richardson |
2026-02-27T09:40:21+11:00 |
fix(integration): ensure Dermapen autofill columns migrate
|
b94726a25 |
Tim Richardson |
2026-02-27T09:18:07+11:00 |
feat: improve b2b portal performance monitoring reliability
|
49f7e4d2d |
Tim Richardson |
2026-02-27T09:14:51+11:00 |
feat(three_pl_dermapen): add help menu and operator guides
|
7c2931d32 |
Tim Richardson |
2026-02-26T17:44:49+11:00 |
feat: Add background run visibility and perf monitor
Improve background orchestration with stricter dispatch validation,
richer lifecycle checkpoints, and a new `bg-list` command so operators
can discover and track detached runs more reliably.
Add a Playwright-based B2B portal performance monitor (script, env
example, tests, and dependency updates) to measure login-to-catalog and
all-orders latency for sales-rep customer flows.
Extend Dermapen autofill queue records with `location` and `notes`
fields, and persist location during queue upserts to improve
observability and audit context for skipped or errored processing.
|
a0d99ad3c |
Tim Richardson |
2026-02-26T16:55:00+11:00 |
chore(bg): remove redundant status/tail agents
|
c4b20eca7 |
Tim Richardson |
2026-02-26T15:23:03+11:00 |
chore: add quick-start for background deploy usage
|
f5c9122f9 |
Tim Richardson |
2026-02-26T15:21:23+11:00 |
chore: document background opencode commands
|
af127e1b0 |
Tim Richardson |
2026-02-26T14:55:07+11:00 |
chore: add bg-tail command and background deploy docs
|
87bbca064 |
Tim Richardson |
2026-02-26T14:38:39+11:00 |
chore: add background fanout orchestration commands
|
a054f0b54 |
Tim Richardson |
2026-02-26T12:47:30+11:00 |
chore: add synthesis agent and worktree override skill
|
ff68416b9 |
Tim Richardson |
2026-02-26T12:41:24+11:00 |
feat(shopify): Add OAuth fallback connection check
Fallback to `oauth/access_scopes.json` when configured scopes do not map
to supported REST resources, so connection checks still validate auth
instead of failing with a scope-compatibility error.
Update the OAuth connection view to build `/admin/oauth/...` URLs for
OAuth endpoints and keep versioned `/admin/api/...` URLs for standard
resources. Add tests for fallback resource selection and end-to-end view
behavior.
Also add new `.opencode` review agents (software engineer, sales manager,
accountant) and a `background-deploy` skill to support structured plan
reviews and non-blocking deploy workflows.
|
2eefc2794 |
Tim Richardson |
2026-02-26T11:03:26+11:00 |
feat(integration): add DotWMS product sync workflow
|
d3b415386 |
Tim Richardson |
2026-02-26T11:03:18+11:00 |
feat(integration): add DearCache LLM query pilot workflow
|
b5b8fa2af |
Tim Richardson |
2026-02-26T11:03:06+11:00 |
chore: expand background deployment agent guidance
|
c0a42d4ad |
Tim Richardson |
2026-02-26T11:03:01+11:00 |
fix(integration): add Shopify OAuth connection health check
|
b250e5cc6 |
Tim Richardson |
2026-02-26T11:02:56+11:00 |
chore: remove stale planning docs
|
c9dc8b303 |
Tim Richardson |
2026-02-25T17:13:31+11:00 |
feat(integration): add DearCache object schema catalog
|
e6e2fbd2f |
Tim Richardson |
2026-02-25T13:56:18+11:00 |
Merge branch 'feat/shopify-offline-oauth'
|
607d754cd |
Tim Richardson |
2026-02-25T13:27:45+11:00 |
chore: add untracked environment files
|
f80f404d0 |
Tim Richardson |
2026-02-25T13:27:19+11:00 |
chore: standardize subagent model IDs
|
5798afeba |
Tim Richardson |
2026-02-25T13:20:51+11:00 |
fix(integration): open embedded Shopify links in new tabs
|
52f4b1586 |
Tim Richardson |
2026-02-25T12:48:32+11:00 |
feat(integration): add embedded Shopify landing page
|
f5bc26092 |
Tim Richardson |
2026-02-25T12:01:25+11:00 |
chore: add Shopify app install instructions
|
207960235 |
Tim Richardson |
2026-02-25T12:01:17+11:00 |
feat(integration): add Shopify offline OAuth token flow
|
2fe9f8392 |
Tim Richardson |
2026-02-25T09:29:38+11:00 |
chore: reformat sale bundle expansion logic
|
49142bf8d |
Tim Richardson |
2026-02-25T09:29:32+11:00 |
fix(integration): use OpenCode CLI for commit messages
|
417de0ed0 |
Tim Richardson |
2026-02-25T09:29:29+11:00 |
chore: update Serena project metadata
|
bed707bf1 |
Tim Richardson |
2026-02-24T17:13:21+11:00 |
chore: Add Serena memory for GLM-5 provider preference
Documents that GLM-5 requests should use the Z.AI Coding Plan
provider with the exact model identifier `zai-coding-plan/glm-5`,
preventing substitution with other GLM variants.
|
7ffef4496 |
Tim Richardson |
2026-02-23T11:58:13+11:00 |
chore: add opencode GLM quota plugin dependency
|
0be6a2399 |
Tim Richardson |
2026-02-23T11:58:09+11:00 |
fix(integration): simplify opencode headless invocation
|
b1392a69f |
Tim Richardson |
2026-02-23T11:57:14+11:00 |
fix(integration): guide Xero tenant recovery workflow
|
ee5996f01 |
Tim Richardson |
2026-02-22T11:52:55+11:00 |
fix(integration): reduce duplicate bug tickets in job error analysis
|
25a8c7267 |
Tim Richardson |
2026-02-22T11:28:49+11:00 |
chore: update local workspace metadata
|
2903b5fad |
Tim Richardson |
2026-02-22T11:28:35+11:00 |
chore: add celery task change playbook guidance
|
b49562ea0 |
Tim Richardson |
2026-02-22T11:28:29+11:00 |
chore: refresh opencode agent configs
|
e80367cff |
Tim Richardson |
2026-02-22T11:25:49+11:00 |
fix(integration): keep websocket job duration ticking smoothly
|
c881316b5 |
Tim Richardson |
2026-02-21T16:52:30+11:00 |
chore: harden deploy agent site parsing rules
|
59e4f58f7 |
Tim Richardson |
2026-02-21T16:40:18+11:00 |
fix(integration): add eager-mode job log fallback
|
44dfce008 |
Tim Richardson |
2026-02-21T16:02:40+11:00 |
chore: update zoho analytics connector submodule
|
fc4775597 |
Tim Richardson |
2026-02-21T16:01:58+11:00 |
chore: persist serena symbol info budget setting
|
31db34dd9 |
Tim Richardson |
2026-02-20T20:06:50+11:00 |
fix(integration): parse cached invoice JSON and normalize lookup
|
98865d8ce |
Tim Richardson |
2026-02-20T18:10:28+11:00 |
feat(integration): add manual cantontea invoice PDF batch download
|
03ee19973 |
Tim Richardson |
2026-02-20T12:16:51+11:00 |
feat: Auto-commit by deployer
|
c59a0bd75 |
Tim Richardson |
2026-02-20T12:16:41+11:00 |
fix(cin7_sync): Bypass "recently refreshed" throttle for manual cache updates
Manual cache rebuilds triggered via the UI were silently skipping the orders
refresh when the periodic scheduler had already refreshed within the last 60s.
The user's explicit date parameter was ignored. Now all three object types
(orders, credit notes, payments) bypass the staleness check on manual runs.
|
937553e34 |
Tim Richardson |
2026-02-20T08:07:27+11:00 |
chore: Reduce sales cache update progress logging from every 25 to every 100 rows
|
333bf0158 |
Tim Richardson |
2026-02-20T07:54:08+11:00 |
fix(status_anxiety): Convert auto_pick_locations from string to list in settings reader [DAS-449]
The auto_pick_locations setting is stored as a comma-separated string via
the CommaSeparatedInput admin widget, but get_status_anxiety_settings_per_dear_entity()
was not converting it to a list (unlike click_and_collect_carriers and email_error_list).
This caused the autopick task to iterate over individual characters instead of location
names, producing 92 repeated errors in production.
Also fixes all legacy ruff E501 (31) and mypy (29) errors in the file:
- Modernize typing imports (Dict→dict, Tuple→tuple, Optional→X|None)
- Fix implicit Optional parameters (PEP 484 no_implicit_optional)
- Add type: ignore[abstract] for DearCachedAPI instantiations
- Add type guards for nullable JSONField access on WebhookTransaction
- Guard against None return from get_product_by_sku in stock_check
- Add explicit return None for missing return path
- Wrap all long lines under 120 chars
|
f76ff8d36 |
Tim Richardson |
2026-02-20T07:39:45+11:00 |
feat: Auto-commit by deployer
|
022d36a75 |
Tim Richardson |
2026-02-20T07:34:59+11:00 |
fix(core): Use JS form submission for Send Invite button to avoid nested form
The admin change form template's after_related_objects block renders
inside the main <form> tag. HTML forbids nested forms — browsers silently
ignore the inner <form> and submit the outer admin form instead, so the
send-invite URL was never hit.
Replace the nested <form> with a button that dynamically creates and
submits a standalone form via JavaScript, appended to document.body
outside the admin form. Reads the CSRF token from the existing form.
|
cb13bfef3 |
Tim Richardson |
2026-02-19T23:01:07+11:00 |
fix(core): Hide invitation inline on the admin Add User form
The UserInvitationInline requires a saved User instance for its FK.
On the two-step Add User form (username+password first), there is no
saved user yet, causing ManagementForm validation errors. Switch from
a static `inlines` attribute to `get_inlines()` that returns the
inline only when editing an existing user (obj is not None).
|
e977032dd |
Tim Richardson |
2026-02-19T22:52:05+11:00 |
feat(core): Add email invitation system for admin user management
Add a complete user invitation flow accessible from /admin/auth/user/:
- UserInvitation model (OneToOneField to User) with 256-bit URL-safe tokens,
status tracking (Pending/Accepted), expiry computation, and resend support
- send_invitation_email() helper using EmailMultiAlternatives for HTML+text
emails with adaptive subject (new user vs returning user)
- accept_invitation_view for unauthenticated password setup via token link,
with validation for expired/used/invalid tokens
- CustomUserAdmin replacing default User admin with:
- "Invite Status" column on user list (Never invited/Pending/Accepted/Expired)
- Bulk "Send/resend invitation email" action
- Per-user "Send Invite" button on change form with status display
- Read-only UserInvitationInline showing send history
- INVITATION_EXPIRY_WEEKS setting (default: 4 weeks)
- HTML/text email templates, set-password form, and error page templates
Resending regenerates the token, immediately invalidating previous links.
After setting password, user is redirected to the login page for 2FA setup.
|
507da642d |
Tim Richardson |
2026-02-19T21:59:52+11:00 |
feat: Auto-commit by deployer
|
72b0ea781 |
Tim Richardson |
2026-02-19T21:59:41+11:00 |
fix(core): Add lock vanishing diagnostics and protect scheduler greenlets
- Log ERROR with verdict (EXTERNAL DELETION vs LIKELY EXPIRED) when the
scheduler discovers a lock key has vanished from Valkey, including
renewal count, time since last renewal, and expire settings
- Log WARNING in stale lock cleanup before deleting, with lock TTL,
holder job_id, and active hosts list
- Register scheduler and heartbeat greenlets as protected so post-task
cleanup does not kill lock infrastructure
- Refresh host heartbeat from scheduler loop (every 30s) instead of
relying on one-shot set at lock acquisition time
|
91cd239cf |
Tim Richardson |
2026-02-19T20:19:48+11:00 |
feat: Auto-commit by deployer
|
91f7244bb |
Tim Richardson |
2026-02-19T20:19:01+11:00 |
feat(statusanxiety): Add Reset to Defaults button to settings form
Add a reusable reset_url mechanism to DynamicConfigurationView — subclasses
override get_reset_url() to enable a "Reset to Defaults" button on the generic
form template, with a JS confirm dialog before navigating.
For Status Anxiety, the reset view overwrites the KeyValueJson row with the
class-defined default_values and default_schema, then redirects back to the
settings form.
Also updates the form default_values to rename Paddington Store to Claremont
Store (matching the runtime defaults updated in the previous commit).
|
3c9da2631 |
Tim Richardson |
2026-02-19T20:18:54+11:00 |
fix(statusanxiety): Rename Paddington Store to Claremont Store in hardcoded defaults
The physical store has been renamed. Update the click_and_collect_carriers
fallback default list and the matching test fixture.
|
d797fc239 |
Tim Richardson |
2026-02-19T20:12:15+11:00 |
feat(statusanxiety): Register settings under Starshipit navbar with Configuration menu link
Register StatusAnxiety_FormSettings as a second URL under the starshipit app
namespace at /starshipit/status_anxiety_settings/. The DynamicConfigurationView
auto-detects the app from the URL resolver, so the starshipit-registered URL
renders with the Starshipit navbar instead of the cached_dear navbar.
Add a "Status Anxiety > Settings" entry under the Starshipit Configuration
dropdown menu, gated on SHORT_HOSTNAME == 'statusanxiety'.
Update both settings links in the statusanxiety namespace fragment from
cached_dear:status_anxiety_settings to starshipit:status_anxiety_settings so
they point to the new Starshipit-namespaced URL.
Also wires up the namespace fragment include system for the Starshipit landing
page, loading namespace_fragments/{SHORT_HOSTNAME}.html when present.
|
4412a9e22 |
Tim Richardson |
2026-02-19T20:11:54+11:00 |
fix(statusanxiety): Use entity-prefixed key for settings form and add missing config keys
The settings form was writing to KeyValueJson key "status_anxiety_settings" but
runtime code in get_status_anxiety_settings_per_dear_entity() reads from
"{dear_entity}-status_anxiety_settings". This meant any values saved via the form
were invisible to the running autopick, webhook, and fulfilment tasks.
Fix: Override get_initial/get_form_kwargs/form_valid to use entity_setting_key
property that produces the entity-prefixed key matching the runtime reader.
Also adds 4 missing config keys to the form that were previously only available
as hardcoded defaults scattered across the codebase:
- auto_pick_locations (comma-separated list of pick locations)
- NewZealandLocation (Cin7 Core location for NZ fulfilment)
- UnitedStatesLocation (Cin7 Core location for US fulfilment)
- AutoPickHealthCheckURL (healthchecks.io ping URL)
Existing DB rows are seeded with missing keys on first form access via a merge
loop in get_initial(), and the form_schema is kept in sync with the class
definition.
|
974fb4856 |
Tim Richardson |
2026-02-19T14:59:52+11:00 |
fix(deploy): Gate dear_analytics entrypoint migration on postgresql_analytics flag
The entrypoint in django-api-sync-base.yaml only checked analytics_db_password
before running `migrate dear_zoho_analytics --database=dear_analytics`, but
setup_new_overlay.py generates analytics_db_password unconditionally for all sites.
Sites without postgresql_analytics enabled (e.g. jaggad) have no dear_analytics
entry in DATABASES, so the migrate command fails with 'invalid choice: dear_analytics'
causing a crash-loop.
Now mirrors the local_settings.py guard: requires BOTH postgresql_analytics AND
analytics_db_password before running dear_analytics migrations.
|
b938c861d |
Tim Richardson |
2026-02-19T14:52:53+11:00 |
fix(deploy): Gate analytics DB connection on postgresql_analytics feature flag
The dear_analytics DATABASES entry was guarded only by
analytics_db_password being present, but setup_new_overlay.py generates
this password unconditionally for all sites. Sites without analytics
enabled (postgresql_analytics= empty) would crash on startup trying to
connect to a non-existent database.
Now requires BOTH postgresql_analytics AND analytics_db_password to be
set before adding the dear_analytics database connection.
Fixes jaggad crash-loop: dear_analytics_jaggad role does not exist
because analytics was never enabled for this site.
|
3405a2ff5 |
Tim Richardson |
2026-02-19T14:39:13+11:00 |
feat(core): Sort module tiles by ascending priority, Admin always leftmost
Flip Module.ordering from descending to ascending sort_priority so lower
values appear first (leftmost). Admin tile moved before the for-loop in
the template so it is always the first tile regardless of DB state.
Added MinValueValidator(1) on sort_priority — 0 is reserved for the
hardcoded Admin tile. Data migration resets all existing rows to
sort_priority=1 (alphabetical tiebreaker); admins can reassign via
Django admin post-deploy.
|
e62447460 |
Tim Richardson |
2026-02-19T14:02:25+11:00 |
feat(deploy): conditional analytics migrations, idempotent overlay setup
- Analytics DB migrations now conditional on password env vars being set
instead of unconditional || true (django-api-sync-base.yaml)
- setup_new_overlay.py: skip overlay+DB setup if site-secret.values already
exists, proceed directly to deploy; --pgpassword no longer required for
existing overlays
|
2a2dc574f |
Tim Richardson |
2026-02-19T14:02:16+11:00 |
chore: consolidate code review tooling into /code-review skill
- Enhanced SKILL.md with parallel domain expert agents, verification
guidelines, and data flow verification checklist
- Removed .opencode/agents/review.md (consolidated into skill)
- Simplified AGENTS.md code review section to reference /code-review skill
|
18d5986e3 |
Tim Richardson |
2026-02-19T13:58:16+11:00 |
fix(loop_returns): eval removal, type safety, exception logging, idempotency guards
Security: Replace eval() with ast.literal_eval() in views_loop.py for safe
dict-literal parsing.
Logging: Replace bare print() with logger.debug(); add logger.warning() to
previously silent except-pass block; add logger.exception() to 8 broad
except Exception blocks to capture full stack traces in container logs
alongside existing user-facing job messages.
Type safety: Add type hints to 5 untyped function signatures (process_returns,
pass_one, pass_one_no_exchange, find_exchange_order_name, returns_processing_journal).
Fix 34 legacy mypy errors including: null guards for dear_settings_row and
dear_base_currency, SaleCreditNoteModel narrowing after get_first_auth_credit_note(),
refund variable shadowing (float vs Refund TypedDict), linked_invoice Optional
typing, and values_list None filtering in views.
Idempotency: Wrap refund-paid handling fee post_money_task in try/except API_error
(matching existing Stripe-paid pattern); add debug logging when duplicate handling
fees are detected and skipped.
All files now pass ruff check, ruff format, and mypy with zero errors.
|
9f07c5cb7 |
Tim Richardson |
2026-02-19T13:58:04+11:00 |
chore(loop_returns): modernize typing imports, fix lint violations
Replace deprecated typing.List/Dict with builtin list/dict across loop_models,
loop_connector, loop_configurations, and fix_bad_credit_notes. Fix all E501
line-length violations, trailing whitespace, implicit Optional parameters, and
== None comparison (now is None). Pure mechanical changes with zero behavior
impact. All files now pass ruff check and mypy with zero errors.
|
7c7aff733 |
Tim Richardson |
2026-02-19T13:25:34+11:00 |
feat(deploy): Add non-interactive deploy mode and wire setup_new_overlay.py end-to-end
tui_deployer.py: Add PlainOutput duck-type class for curses.window so
run_deployment() and wait_for_ingress() work without a terminal UI.
Add --non-interactive flag (skips prompts, uses PlainOutput instead of
curses TUI) and --skip-build flag (skips version bump and Docker
build/push). Non-interactive mode exits 1 on first failure.
reset_django_passwords.py: Add --non-interactive flag that bypasses the
confirmation prompt, proceeding automatically instead of aborting.
setup_new_overlay.py: Add --skip-deploy escape hatch for old behaviour
(overlay + DB only). Without it, main() now chains: invoke_deployment()
→ wait_for_pod_ready() → invoke_superuser_creation(), producing a fully
provisioned site from a single command. Route53 DNS remains a manual
step printed in the completion summary.
|
1fa9bbd91 |
Tim Richardson |
2026-02-19T11:50:44+11:00 |
feat(deploy): Generate readonly_main_db_password for jaggad
|
ca5d441bf |
Tim Richardson |
2026-02-19T11:50:44+11:00 |
feat: Auto-commit by deployer
|
324b9371c |
Tim Richardson |
2026-02-18T19:01:53+11:00 |
feat: Auto-commit by deployer
|
7dfb22695 |
Tim Richardson |
2026-02-18T18:54:59+11:00 |
feat: Auto-commit by deployer
|
f0fa18a71 |
Tim Richardson |
2026-02-18T18:54:45+11:00 |
fix(three_pl_dermapen): COA attachment dedup broken by Dear URL-encoding filenames
Dear URL-encodes the FileName field when storing attachments (spaces become +,
# becomes %23, etc.). The dedup check compared these URL-encoded names against
plain filenames from SharePoint, so they never matched. This caused every COA
to be re-attached on every 15-minute hexspoor processing cycle.
For sale DPW-25430 this produced 12,061 attachments (43 unique files × ~850
duplicates each over ~9 days of reprocessing).
Fix: decode Dear's FileName with unquote_plus() before comparing, and track
newly-attached names within a single invocation to prevent cross-batch
duplication.
|
62d4e986b |
Tim Richardson |
2026-02-18T10:38:40+11:00 |
fix: disambiguate "fulfilment" terminology in emails, logs, and UI to avoid confusion with Cin7 Fulfillment Orders
Error emails and log messages that used "fulfilment" were being misinterpreted by downstream
LLM processing as referring to Cin7 Core's niche "Fulfillment Orders" feature (used with
Amazon FBA / external fulfilment), when they actually refer to the standard Pick/Pack/Ship
workflow. This caused incorrect remediation advice.
Changes across 65 files in starshipit/, three_pl/, three_pl_skyzer/, three_pl_dermapen/,
three_pl_torque/, cached_dear/, and shopify/:
- "fulfilment N" (as an identifier) → "shipment N" (the Ship step number)
- "fulfilment" (general process) → "pick/pack/ship" (the full workflow)
- "Dear" in user-facing strings → "Cin7" (current brand name)
- Emailed error messages now explicitly reference "Pick/Pack/Ship workflow" and
"Cin7 Core warehouse" to prevent ambiguity
- Variable names, function names, field names, API references, and migrations left unchanged
|
12a6e293b |
Tim Richardson |
2026-02-18T10:37:31+11:00 |
fix(three_pl): recover from Dear API duplicate fulfilments instead of always raising (DAS-443)
The Dear API occasionally creates duplicate fulfilments from a single POST request
(observed with 105s response times, suggesting internal retry). Previously, the code
detected and voided duplicates but always raised RuntimeError even when the void
succeeded, causing the order to be skipped until the next autopick cycle.
Now: after voiding duplicates, re-fetch the sale and re-validate. If duplicates are
resolved, continue processing normally. Only raise if duplicates persist after void.
Also improves void_duplicate_fulfillments() with diagnostic WARNING logs when a
duplicate cannot be voided (non-empty or wrong status), and fixes validate_unique_fulfilments()
which previously logged "Duplicate found" unconditionally even when validation passed.
Includes incidental log message renames (fulfilment → pick/pack/ship terminology).
|
67403f926 |
Tim Richardson |
2026-02-18T09:27:55+11:00 |
fix(three_pl): guard EDI index creation against missing table
EDIX12TransactionJournal table only exists on entities with EDI
integrations. Wrap CREATE INDEX in a DO block that checks
information_schema.tables first, so the migration succeeds on all
namespaces. Also drops CONCURRENTLY since it cannot run inside DO blocks.
|
a7e8bec69 |
Tim Richardson |
2026-02-18T09:14:22+11:00 |
fix: downgrade operational false-positive errors to WARNING across multiple apps
Demote non-actionable log messages from ERROR to WARNING so they no longer
pollute the analyze_job_errors.py bug detection pipeline. These are all
operational/data-quality issues requiring human action on external systems,
not code defects:
- statusanxiety: "Pick is not authorised" in dear_fulfilment_helper and
ship_fulfilment (same pattern as equipmed fix in 65642accb)
- djcity: Zoho/Dear customer name mismatch requiring manual merge
- vv: Pepperi API rejecting product image uploads (400 "Upload file error")
- 1300tempfence: Zoho account creation failure, lock-not-acquired now uses
LOCK_NOT_ACQUIRED status (consistent with other lock handlers)
|
5cc8713f7 |
Tim Richardson |
2026-02-18T09:08:21+11:00 |
refactor(xero_sync): decompose invoice_sync.py and credit_note_sync.py into focused modules
Extract shared code from two monolithic files (5,925 + 3,753 lines) into
five new modules with clear responsibilities:
- common.py: shared utilities, error classes, tax mapping, lock dates
- contacts.py: Xero contact sync (create, update, find, sync orchestration)
- accounts.py: Xero account management (get, create, ensure)
- line_items.py: unified Dear→Xero line item conversion with include_tax_amount param
- correction.py: void-and-recreate framework (payments, allocations, correction numbers)
Key improvements:
- Eliminates circular dependency between invoice_sync and credit_note_sync
(deferred imports replaced with direct imports from correction.py)
- Deduplicates get_xero_organization_lock_dates (was copy-pasted in both files)
- Merges 4 line item converters into 2 parameterized functions
- Merges generate_correction_invoice_number + generate_correction_cn_number
- invoice_sync.py reduced from 5,925 to 3,071 lines
- credit_note_sync.py reduced from 3,753 to 3,025 lines
- All consumers updated to import from canonical locations
|
7003ce797 |
Tim Richardson |
2026-02-18T09:08:09+11:00 |
fix(cached_dear): use entity name instead of account UUID for DearLocationRenameHistory
DearLocationRenameHistory.dear_instance_id is a CharField expecting the
entity name (e.g. 'orbitkey'), not the Dear account UUID. Using
self.entity instead of dear_account_id fixes rename history tracking.
|
65642accb |
Tim Richardson |
2026-02-18T09:08:04+11:00 |
fix(three_pl): downgrade non-actionable log messages from ERROR to WARNING
Operational conditions like multiple draft picks, already-authorised
shipments, un-authorised picks, COA attachment failures, and Hoxton 404s
are not bugs — they are expected scenarios that resolve themselves or
require user action in Dear. Logging them as ERROR pollutes error dashboards.
Also adds explicit RuntimeError handling in task_create_dear_shipments to
catch pick/ship status issues already logged with detail by the caller,
and handles Hoxton/CartonCloud 404 (deleted consignment) gracefully.
|
2a50e53cf |
Tim Richardson |
2026-02-18T09:07:54+11:00 |
chore(three_pl): update dependent_product_ids field help text and defaults
AlterField migration for ThreePL_order_fulfilments.dependent_product_ids —
clarifies the help text to document the NULL/empty-list/populated semantics
(NULL = unknown, [] = no dependency, [ids] = stock-dependent).
|
cc5dedb43 |
Tim Richardson |
2026-02-18T09:07:49+11:00 |
feat: add performance indexes for WebhookTransaction, JobMaster, JobLog, MiscFileStore, ShopifyInstanceSettings, XeroBankStatement, ThreePL_order_fulfilments, and EDI journal
Non-blocking CONCURRENTLY index creation for core models. Adds composite
indexes on heavily-queried columns: status+source, job+status, job+created,
warehouse_name (case-insensitive), process_status+adoption_attempts, and
more. All migrations use atomic=False for safe production deployment.
|
8d74be43e |
Tim Richardson |
2026-02-18T08:10:33+11:00 |
feat(xero_sync): add standalone credit note sync support
Add filter_is_standalone_cn parameter to sync_b2b_sales_modified_since
task and B2BXeroSyncView form, enabling targeted sync of standalone
credit notes (e.g., CR-34162) via the admin UI. When enabled, the task
skips regular sales processing and filters standalone CNs by
string_uniqueID instead of date range.
Also adds find_active_xero_credit_note() to credit_note_sync.py which
walks the void-and-recreate revision chain (-R1, -R2, etc.) to locate
the active non-voided credit note in Xero. Includes formatting cleanup
across credit_note_sync.py (line-length consolidation).
Updates DearCache.string_uniqueID help text to mention credit note
numbers alongside sale order numbers.
|
db3ba26f9 |
Tim Richardson |
2026-02-18T08:10:24+11:00 |
chore: restructure AGENTS.md with clearer guiding principles
Expand the Guiding Principles section into distinct subsections for
Question the Premise, Distributed System Concerns, and Verify Data
Structures Before Querying. Streamline build commands and formatting.
|
48a3804d0 |
Tim Richardson |
2026-02-17T22:39:30+11:00 |
feat: Auto-commit by deployer
|
e2e4b33e1 |
Tim Richardson |
2026-02-17T22:33:55+11:00 |
feat: Auto-commit by deployer
|
1f640e149 |
Tim Richardson |
2026-02-17T20:50:47+11:00 |
feat: Auto-commit by deployer
|
bc7952f17 |
Tim Richardson |
2026-02-17T20:50:37+11:00 |
fix(xero_sync): Fix standalone credit note already_synced check and fingerprint upgrade
Two bugs fixed:
1. already_synced check used TaskID UUID instead of CN number (e.g. CR-34162)
as dear_entity_key, causing the duplicate-prevention check to never match.
Standalone CNs store sync records keyed by CN number, not TaskID.
2. Fingerprint upgrade for standalone CNs queried DearCache with wrong
object_type (SALE_CREDITS instead of SALE_CREDITS_STAND_ALONE), causing
all standalone CN fingerprint upgrades to silently fail.
|
fcc9c8067 |
Tim Richardson |
2026-02-17T18:46:13+11:00 |
feat: Auto-commit by deployer
|
d274b2789 |
Tim Richardson |
2026-02-17T18:46:04+11:00 |
fix(xero_sync): Remove Name/Description from invoice fingerprint lines_hash to eliminate false drift
Invoice drift detection was triggering on 100% of Orbitkey invoices due to Dear
truncating product names inconsistently between API calls. The lines_hash included
Name (for lines) and Description (for charges) alongside financial fields (Account,
Price, Quantity, TaxRule). When Dear returned a slightly different truncation of a
long product name, the hash changed despite zero financial impact.
Investigation of 20 recent drift records confirmed all were LINE_CONTENT_ONLY —
totals, tax, line counts, due dates, currencies all identical. 51 false-positive
DRIFT records were cleared in production.
Changes:
- Exclude Name/Description from lines_hash tuple in both calculate and extract
functions (commented out with explanation for future re-enablement)
- Bump FINGERPRINT_VERSION to 5 (triggers automatic upgrade on next sync)
- Add DearInstanceSettings lookup patterns to test-harness skill
|
ba2003684 |
Tim Richardson |
2026-02-16T22:25:34+11:00 |
fix(cin7_sync): Prevent infinite pagination loop in SizeRanges and Users cache updates
The Cin7 Omni SizeRanges and Users API endpoints do not support pagination
(no page/rows query parameters). When called via yield_next_page(), the API
ignores the page parameter and returns the full dataset on every request,
creating an infinite loop that exhausts all 3 API keys' daily limits (15,000
calls/day) within hours.
This was the root cause of the retrojan healthcheck failure for "RJ Living
Cin7 Order Cache Daily Rebuild" — task_refresh_omni_analytics consumed 14,817
API calls (98.8% of budget) fetching size ranges ~27,000 times.
Both methods now make a single API call and process the complete response
directly, matching how these non-paginated endpoints actually behave.
|
8d97243ee |
Tim Richardson |
2026-02-16T22:13:22+11:00 |
fix(three_pl): Process EDI 850 CN (consignment) orders normally
BEG02=CN means "Consignment Order" — a standard order type that should
be auto-processed like OS and DS. The removed code incorrectly treated
CN as a "Change Order" requiring human review, causing valid POs to be
emailed instead of processed. Only one CN order has ever been received
(PO 27493, 2026-01-06) and it was blocked by this code.
|
ce2b2b2d5 |
Tim Richardson |
2026-02-16T21:50:37+11:00 |
fix: Harden Django admin across project to prevent OOM and N+1 queries
Project-wide audit found the same class of bug that OOM-killed the
cultiver web pod: ForeignKey fields rendered as <select> dropdowns
loading thousands of records into memory, and N+1 queries in list views.
Critical OOM fixes (raw_id_fields added):
- xysense: Box.order FK to DearCache (50K-200K+ records) had NO admin
config at all — bare admin.site.register(Box) loaded entire DearCache
into dropdown. Created proper BoxAdmin and BoxLineAdmin.
- cached_dear: SaleIntercompanyJournalAdmin.dear_sale_guid FK to
DearCache — same table, same risk.
- shopify: ShopifyPaymentXeroTransactionAdmin had two unprotected FK
dropdowns (shopify_payout, xero_bank_statement). ShopifyPaymentXeroPayout
also missing raw_id_fields for xero_bank_statement.
- dear_purchasing: Both WarehouseReceiptBatchPart and CreatedPurchaseReceipt
had receipt_batch FK without raw_id_fields.
N+1 query fixes (list_select_related added):
- core: JobLogAdmin (job FK in list_display)
- cached_dear: DearCacheAdmin (dear_account_id FK in list_display)
- cached_dear/xero_sync: XeroSyncRecordAdmin, DearCustomerXeroContactMapping,
XeroSyncSettingsAdmin (dear_instance FK in list_display)
- shopify: ShopifyLocationMappingAdmin (shopify_instance_settings FK)
- revel_pos: RevelOrderImportLogAdmin (establishment FK)
Also fixed: removed FK object references from search_fields where invalid,
removed duplicate list_filter entries, removed JSONField from list_display
in dear_purchasing.
|
1dfb9881b |
Tim Richardson |
2026-02-16T21:44:36+11:00 |
fix(three_pl): Prevent OOM in Django admin for EDIX12 models
EDIX12TransactionJournal and EDIX12FunctionalGroup admin change forms
loaded all 15,377 EDIX12Interchange records (~10KB serialized_content each)
into memory via FK select dropdowns, consuming ~155MB per dropdown per page
load. Over time this accumulated past the 2GB pod memory limit, causing
OOMKilled crashes.
Changes:
- Add raw_id_fields for all FK fields on TransactionJournal and FunctionalGroup
admins, replacing memory-heavy <select> dropdowns with ID input + lookup
- Add readonly_fields for large TextFields (serialized_content, edi_transaction_string)
- Use interchange_id in list_display to avoid N+1 queries on list views
- Remove duplicate transaction_set_identifier_code from TransactionJournal list_filter
|
a02eec856 |
Tim Richardson |
2026-02-16T12:24:28+11:00 |
chore(dear_zoho_analytics): Remove unused Shippit integration
Shippit webhook, task scheduling, CSV processing, and analytics model were only
configured for CultiverAus and are no longer needed. Removes the module, model,
Celery tasks, view, URL pattern, and generates a migration to drop the
shippit_transactions table.
|
683c53955 |
Tim Richardson |
2026-02-16T11:44:41+11:00 |
fix(interentity): Include expected format example in stock transfer location error messages
When a stock transfer location like 'Melbourne Discrepancy' lacks the required
RI/RH prefix, the error now shows the expected format (e.g. 'RH Melbourne Discrepancy')
so the user knows exactly how to fix the location name in Dear.
|
491cd5d2f |
Tim Richardson |
2026-02-16T09:32:32+11:00 |
fix(three_pl): Keep process status popover open while hovering over its content
Switch Bootstrap popover trigger from "hover focus" to "manual" with a JS-managed
hover bridge: a 150ms delayed hide is cancelled if the mouse enters the popover body.
This lets users reach interactive elements like the "View & Adopt" button inside the
popover without it disappearing on mouseout from the trigger element.
|
86bd73b2c |
Tim Richardson |
2026-02-16T08:55:35+11:00 |
fix(dear_zoho_analytics): Consolidate currency string parsing to prevent ValueError on Zoho numeric fields (DAS-440)
Zoho Analytics SQL export can return currency-formatted strings (e.g. '$2.03')
for numeric columns. The conversion rate lookup in ZohoBackendDear.get_conversion_rate()
used raw float() which crashed on aurora-lites. Previous fixes (DAS-428, DAS-436) only
covered Dear API supplier prices and AverageCost fields but missed Zoho query results.
Promoted _parse_currency_value() from dear_analytics_logic_sales_tables to a shared
parse_currency_value() in common_definitions.py. Applied it to all raw float() calls
on Zoho query results across zoho_backend_dear.py (conversion rates, net movements),
cogs_timing.py (excess revenue/COGS values), and zoho_helpers.py (revenue quantities,
unit net — replacing a fragile [1:] slice hack). Removed duplicate nested definition
from zoho_helpers.get_exchange_rate().
|
12219fd1e |
Tim Richardson |
2026-02-15T13:24:51+11:00 |
fix(dear_zoho_analytics): Validate TypedDict Literals and model choices against djcity production data
Queried 840K orders, 21K products, 13K purchases, 28K stock transfers from
djcity DearCache to cross-check every Literal type alias and choices list
against real Dear API values.
Critical fix: FulFilmentStatus used underscores (PARTIALLY_FULFILLED) but
Dear API returns spaces (PARTIALLY FULFILLED). This caused a silent bug in
dear_analytics_logic_sales_tables.py:799 where the fulfilment status check
never matched, potentially excluding partially-fulfilled orders from BI.
TypedDict changes:
- FulFilmentStatus: underscores to spaces in SaleListRow and DearSale
- Sale Status: add CLOSED
- StockTransferStatus: add ORDERED, PICKING
- PurchaseGlobalStatus: add 4 compound statuses (RECEIVING / CREDITED,
RECEIVED / CREDITED, PARTIALLY INVOICED, COMPLETED / CREDIT NOTE CLOSED)
- CombinedPaymentStatus: add OVERPAID, OVERPAID / CREDITED
- CombinedInvoiceStatus: add PARTIALLY INVOICED / CREDITED
- ProductMovementType: add Assembly, Assembly Cost Change, Cost Adjustment,
Production Run (discovered values, not API-documented)
- Purchase CombinedInvoiceStatus/CombinedPaymentStatus: typed from bare str
to Optional[Literal[...]] using production data
Model choices updated to match. Migration 0073 covers 16 field alterations.
|
88d936096 |
Tim Richardson |
2026-02-15T13:24:34+11:00 |
fix(cached_dear): Harden Xero void/credit-note exception handling (DAS-438)
Replace broad Exception catches with specific Xero SDK exception types
(AccountingBadRequestException, HTTPStatusException, RateLimitException).
Add _format_http_status_error() helper for structured diagnostic logging
of Xero API errors including HTTP status, reason, and truncated body.
Reformat imports and string literals for ruff compliance.
|
b5c2c499a |
Tim Richardson |
2026-02-15T11:57:43+11:00 |
Merge branch 'feature/analytics-model-choices'
|
34b4d11b0 |
Tim Richardson |
2026-02-15T11:57:12+11:00 |
feat(dear_zoho_analytics): Add choices metadata to analytics models and consolidate schema extraction
Add choices= metadata to all analytics model fields with enumerated values
(sale status, fulfilment status, payment status, stock status, etc.) sourced
from Dear API TypedDicts and API reference documentation. These are metadata-only
constraints (no DB enforcement) that enable schema introspection for both the
MCP server and the HTML documentation page.
Consolidate duplicated schema extraction logic into a single source of truth at
dear_zoho_analytics/schema_utils.py. The MCP server's schema_extractor.py becomes
a thin re-export shim for backwards compatibility. The AnalyticsTableDocumentationView
now uses the shared get_table_schema() API instead of inline field iteration, ensuring
any future metadata additions (e.g. db_index, validators) are automatically reflected
in both the MCP server and the documentation page.
Add blank and unique fields to FieldSchema TypedDict for complete field introspection.
|
2c47d601e |
Tim Richardson |
2026-02-14T16:04:39+11:00 |
fix(dear_zoho_analytics): Harden views security and reformat analytics logic
views.py security fixes:
- Use hmac.compare_digest for timing-safe token comparison (shippit webhook, zoho proxy)
- Remove hardcoded fallback password from zoho_proxy_password lookup
- Require configured shippit_webhook_token, return 503 if missing
- Add @login_required to ShowTableDefs and zoho_table_update_timestamps
- Prevent XSS in error responses: replace <pre> HTML with content_type="text/plain"
- Remove duplicate exception handler in verify_product_availability_integrity_view
dear_analytics_logic.py:
- Ruff formatting (line wrapping, whitespace)
- Widen summarise_lines_by_product_id param to Sequence for type covariance
|
fde8eb0e8 |
Tim Richardson |
2026-02-14T15:59:56+11:00 |
fix(dear_zoho_analytics): Resolve all 269 mypy errors in dear_analytics_logic_sales_tables.py
Fix long-standing mypy type errors that accumulated when DearSale and related TypedDicts
were introduced without updating existing code. Changes are type-annotation-only with no
logic modifications.
Key fixes:
- Annotate 19 dict construction sites (row, header, new_row, totals_per_doc, etc.) as
dict[str, Any] to prevent mypy from inferring overly-narrow types from first assignment
- Modernize legacy "# type:" comments to inline annotations (set[str], list[dict])
- Widen consolidate_service_lines and summarise_lines_by_product_id params from list[T]
to Sequence[T] to fix list covariance errors
- Change prepare_analytics_sales_order_payments and prepare_analytics_sales_line_lifecycle
param types from dict to DearSale
- Cast dynamic TypedDict key access (f"AdditionalAttribute{i}") through Any intermediates
- Convert TypedDict mutation in payments prep to dict() copies instead of in-place mutation
- Annotate float initializers (revenue_qty_total, restock_qty, max_cost_price_base_cur)
- Remove redundant type annotations on redefined variables (new_row, row, uniq_key)
- 5 targeted # type: ignore comments for genuine mypy limitations (nonlocal binding,
Django model field assignment, structurally-compatible TypedDict args)
|
da7cbd514 |
Tim Richardson |
2026-02-14T15:06:51+11:00 |
fix(cin7_sync): Use defensive Id access in Cin7 cache update methods (DAS-437)
The Cin7 Omni API can return records without an 'Id' field, causing KeyError
crashes in update_size_ranges, update_production_jobs, update_bom_masters, and
update_serial_numbers. This crashed the entire analytics refresh task since
KeyError is not caught by the API_error exception handler.
Applied the same defensive .get('Id') + skip pattern already used by
update_users. Skipped records are counted and logged for diagnostics.
|