Skip to content

v1.152.0 — Sign-in lockout, time off on the board, and finished jobs read finished on the phone

Latest

Choose a tag to compare

@github-actions github-actions released this 20 Oct 04:18
· 1 commit to main since this release
03ef590

Warning: Take a database backup before updating. Migrations 132 to 134 and 136 remove tables and user columns that nothing in the app uses (safety checklists, van inventory, the old tech unavailability list, and seven unused user fields). If any of them still holds data, the update stops before removing anything and the app will not start until that data is exported and cleared by hand.

Highlights

  • An account is locked for 15 minutes after 10 wrong passwords since its last successful sign-in. The Users page shows which accounts are locked, and an admin can unlock one from there.
  • On a tech's phone, finished jobs now show as finished.
  • The schedule board and the open-slot finder now account for approved time off, so a tech who is off is no longer shown as free.
  • Moving a scheduled job onto a tech past the end of their shift now asks first, both on the dispatch board and when booking from the phone.
  • Vacation can now be counted by calendar year or from each person's hire date (anyone without a hire date stays on the calendar year). Set this in Settings, and add hire dates on the Users page. Every vacation balance shows which dates it covers.

Changes you may notice

  • The job and customer pickers on appointments search every job and customer, not just the first page.
  • When a closeout books a second day, that visit is no longer labelled a callback.
  • Estimate previews, the email composer and emailed estimate PDFs show the same expiry date the send sets.
  • The dispatch drawer stays open when the board refreshes, and its times are shown in the shop's timezone.
  • Pop-up messages no longer appear twice on several pages.
  • The office inbox works on a phone-sized screen.
  • On the phone, the day summary can be opened from the Today screen, and the Job Details buttons stay inside the card.
  • Re-opening a finished job no longer leaves it marked done.
  • Delete now works for custom fields, survey templates and tech phone tags.
  • The survey list shows respondents and the last link sent.
  • Status colors on the variance report and the customer portal match each status.
  • An invoice in a locked accounting period can no longer be voided.
  • Win-back texts are now actually sent, and a campaign is only marked sent once they go out.
  • Several buttons and labels are easier to read in dark mode.
  • PDF template editor tabs that had no effect were removed.

Under the hood

  • Unused tables and user columns are removed (see the warning above).
  • Actions the AI assistant cannot perform are no longer reported as done, and its invoice void goes through the same checks as the Void button.
  • More follow-up and reminder changes are written to the audit log.
  • An old phone endpoint that marked a job done without completing it was removed.

What's Changed

  • fix(ai): _business_tz reads the shop zone through the shared pay_periods reader (GDXA-1621) by @freePerro in #1327
  • fix(bank_feeds): tenant_zoneinfo reads through the shared shop timezone reader (GDXA-1620) by @freePerro in #1329
  • fix(lint): per-site S608 noqa replaces the file-level ignores for mobile.py and the live SQL audit tool (GDXA-1617) by @freePerro in #1331
  • style(leads): rename ambiguous l to lead (E741) (GDXA-1662) by @freePerro in #1332
  • fix(install-sheet): rename ambiguous l to line in estimate_total (E741) (GDXA-1661) by @freePerro in #1333
  • fix(lint): rename ambiguous l in expenses lines-sum (E741) (GDXA-1660) by @freePerro in #1334
  • fix(statements): read the shop timezone through the shared pay_periods reader (GDXA-1619) by @freePerro in #1330
  • fix(ledger): rename ambiguous l (E741) in build_expense_lines (GDXA-1659) by @freePerro in #1335
  • chore(lint): lower ruff baseline 241 -> 221 after E741 test sweep (GDXA-1671) by @freePerro in #1336
  • fix(lint): rename ambiguous l to line in purchase_orders (GDXA-1663) by @freePerro in #1337
  • fix(install-sheet): contain the per-line catalog door read, stop on the first error, drop raw ILIKE (GDXA-1647) by @freePerro in #1338
  • refactor(mcp): one estimate-line serializer for the estimate tools (GDXA-1683) by @freePerro in #1340
  • fix(dispatch): a dated job dropped past a tech's shift asks too by @freePerro in #1341
  • fix(mobile-dispatch): ask before a booking runs past the tech's day (GDXA-1686) by @freePerro in #1342
  • fix(tech-mobile-settings): import confirmAsync so tag Delete works (GDXA-1690) by @freePerro in #1344
  • fix(surveys): template dialog sends the SurveyTemplateIn fields; unknown keys are refused (GDXA-1676) by @freePerro in #1343
  • fix(surveys): import confirmAsync so template Delete works (GDXA-1689) by @freePerro in #1345
  • fix(docker): customer compose mounts a recordings volume; guard compose app volume drift (GDXA-1682) by @freePerro in #1346
  • fix(ai): rename ambiguous l (E741) in ai-mcp estimate tools and router (GDXA-1658) by @freePerro in #1339
  • fix(webhooks): send the test-ping through the redirect-validating opener (GDXA-1703) by @freePerro in #1347
  • fix(customer-tags): admin tag delete soft-deletes; re-create revives the row (GDXA-1700) by @freePerro in #1349
  • docs(commission): standing instruction says leaving core, not yet a plugin (GDXA-1711) by @freePerro in #1350
  • fix(pay-periods): one shop-timezone reader and default, guard test (GDXA-1618) by @freePerro in #1351
  • docs(adr-013): amend - scoped plugin DB role not built; prerequisite before plugin install ships (GDXA-1716) by @freePerro in #1352
  • fix(custom-fields): import confirmAsync so field Delete works; guard unbound destructive confirms (GDXA-1691) by @freePerro in #1348
  • chore(lint): bank ruff 239 and drop the deleted register_endpoint from a docstring (GDXA-1678) by @freePerro in #1353
  • fix(surveys): Respondent and Last Link columns read fields the router now returns (GDXA-1709) by @freePerro in #1354
  • fix(mobile): a visible way in to the day summary from Today (GDXA-1726) by @freePerro in #1356
  • fix(distributor): create_dealer_order accepts the PO/ship-to envelope (GDXA-1737) by @freePerro in #1358
  • fix(customers-crm): request bodies refuse unknown keys; guard the class (GDXA-1697) by @freePerro in #1357
  • fix(portal): remove orphan GET /portal/documents (GDXA-1732) by @freePerro in #1359
  • fix(plugins): untitled plugin screens get a readable tab label (GDXA-1746) by @freePerro in #1360
  • docs(comments): drop dead routers/booking.py and routers/auth.py pointers (GDXA-1730) by @freePerro in #1361
  • fix(theme): --text-on-interactive-primary token, sidebar pin-badge contrast, guard (GDXA-1754) by @freePerro in #1362
  • fix(referrals): speak the referrals API in ReferralsView and SurveysView templates (GDXA-1693) by @freePerro in #1355
  • fix(plugins): storefront refuses a plugin whose requires this host cannot meet (GDXA-1742) by @freePerro in #1364
  • fix(theme): --text-on-p-primary token, ErrorBoundary retry contrast, guard (GDXA-1761) by @freePerro in #1363
  • fix(inbox): phone-width single-pane layout for the office inbox (GDXA-1753) by @freePerro in #1365
  • fix(dispatch): delete the unreachable Assign dialog; repoint its specs (GDXA-1772) by @freePerro in #1366
  • fix(jobs): drop JobsView page-level outlet duplicating App.vue (GDXA-1787) by @freePerro in #1367
  • fix(comms): drop page-level outlet from OutboundEmailLogView (GDXA-1788) by @freePerro in #1369
  • fix(winback): drain queued win-back sends; campaign is not 'sent' at enqueue (GDXA-1723) by @freePerro in #1368
  • fix(customers-crm): drop page-level duplicate outlets (GDXA-1785) by @freePerro in #1370
  • fix(documents): drop DocumentsView's duplicate outlet (GDXA-1786) by @freePerro in #1371
  • fix(estimates): drop page-level outlets duplicating App.vue's (GDXA-1782) by @freePerro in #1374
  • fix(billing): drop page-level Toast outlets that doubled every toast (GDXA-1784) by @freePerro in #1372
  • fix(ui): drop page-level duplicate outlets in Users and Automation Rules (GDXA-1783) by @freePerro in #1373
  • fix(reports): sales-tax Total chip text readable on emerald (GDXA-1769) by @freePerro in #1376
  • fix(mobile): capture FAB text on interactive-primary uses the contrast token (GDXA-1755) by @freePerro in #1377
  • refactor(phone_com): delete dead MessageThreadOut and MessageOut models (GDXA-1815) by @freePerro in #1375
  • fix(timeclock): drop TimeclockView page-level outlet duplicating App.vue (GDXA-1789) by @freePerro in #1378
  • fix(performance): drop the always-zero safety_checklists stat (GDXA-1832) by @freePerro in #1379
  • fix(ai-mcp): validate POST /api/next-actions body; audit next-action changes (GDXA-1808) by @freePerro in #1380
  • fix(admin): server-errors active tab text readable on emerald in dark mode (GDXA-1765) by @freePerro in #1382
  • fix(customers-crm): edit dialog PATCHes only changed keys (GDXA-1824) by @freePerro in #1381
  • fix(timeclock): user-label lookups run on SQLite; one shared helper (GDXA-1793) by @freePerro in #1383
  • feat(users,settings): wire users.hire_date and add vacation-year policy setting (GDXA-1827) by @freePerro in #1385
  • fix(pave): refuse while other clients are connected, fence option, lock_timeout on the DROP (GDXA-1811) by @freePerro in #1386
  • fix(variance): actual parts read closeout snapshot and capture rows (GDXA-1825) by @freePerro in #1389
  • fix(mobile): text on --p-primary-color fills readable in dark mode (GDXA-1763) by @freePerro in #1390
  • test(frontend): guard that only App.vue mounts a outlet (GDXA-1790) by @freePerro in #1384
  • fix(invoices): a locked period refuses the void at the transition too (GDXA-1848) by @freePerro in #1392
  • fix(ai-mcp): AI card Save & Test readable on interactive-primary in dark mode (GDXA-1756) by @freePerro in #1393
  • fix(jobs-dispatch): delete the safety_checklist router and its tests (GDXA-1833) by @freePerro in #1394
  • fix(time-off): vacation balance readers follow vacation_year_policy (GDXA-1855) by @freePerro in #1396
  • fix(winback): the drain budget bounds the texting loop, not just new claims (GDXA-1823) by @freePerro in #1387
  • refactor(models): delete dead LIFECYCLE_TRANSITIONS / validate_job_transition (GDXA-1883) by @freePerro in #1398
  • fix(plugins-host): a refused plugin request no longer crashes the page (GDXA-1873) by @freePerro in #1395
  • fix(mobile): Stop re-checks clock_out IS NULL on write; loses the sweep race cleanly (GDXA-1878) by @freePerro in #1399
  • fix(dispatch): drawer and park toast show scheduled_at in shop timezone (GDXA-1874) by @freePerro in #1400
  • fix(inbox): folder unread badge text readable on emerald in dark mode (GDXA-1762) by @freePerro in #1391
  • fix(ai-mcp): shared audited NextAction upsert/resolve for background writers (GDXA-1859) by @freePerro in #1401
  • refactor(ai-mcp): rename recommendation_routes to next_action_routes, retag next-actions (GDXA-1866) by @freePerro in #1403
  • fix(jobs): Job Details controls stay inside the card on phones (GDXA-1876) by @freePerro in #1402
  • fix(platform): drop the SafetyChecklist model and guard-drop its table (GDXA-1834) by @freePerro in #1404
  • fix(ai-mcp): open jobs include service_call; MCP tools skip soft-deleted rows; jobs.update_status is an honest preview (GDXA-1881) by @freePerro in #1405
  • fix(platform-core): SILENTFAIL1 replaces the ruff-invalid silent-failure noqa marker; CI guards invalid noqa codes (GDXA-1892) by @freePerro in #1407
  • fix(documents): drop PDF template editor tabs no renderer reads (GDXA-1909) by @freePerro in #1408
  • fix(settings): vacation-year rule lives only in core; drop stale Settings hint (GDXA-1856) by @freePerro in #1406
  • fix(timeclock): sweep takes its constants from a leaf, breaking the webhook-nulling import cycle (GDXA-1889) by @freePerro in #1410
  • fix(outlook): sync-health NextAction writes through the audited helper (GDXA-1862) by @freePerro in #1411
  • fix(comms): phone/SMS text on interactive-primary uses the contrast token in dark mode (GDXA-1757) by @freePerro in #1409
  • fix(van-inventory): retire the router, models and tables; drop them in migration 133 (GDXA-1826) by @freePerro in #1412
  • fix(people-time): vacation line labels the window, not a calendar year; shared MyVacationLine (GDXA-1902) by @freePerro in #1413
  • fix(tags): silent-failure marker becomes ruff-valid SILENTFAIL1 (GDXA-1894) by @freePerro in #1415
  • fix(jobs-dispatch): install_sheet catalog lookup uses the SILENTFAIL1 noqa marker (GDXA-1895) by @freePerro in #1414
  • fix(time-off): shared approved-time-off lookup; remove duplicate technicians unavailability routes (GDXA-1828) by @freePerro in #1397
  • fix(onboarding): step dot and Next button text on interactive-primary use the contrast token in dark mode (GDXA-1758) by @freePerro in #1417
  • fix(mobile): phone clock labels the vacation balance by its window through the shared MyVacationLine (GDXA-1903) by @freePerro in #1418
  • fix(time-off): pre-hire calendar window ends the day before a same-year hire date (GDXA-1927) by @freePerro in #1419
  • fix(quickbooks): tasks binds sync as a module and takes its errors from a leaf, breaking the webhook-nulling import cycle (GDXA-1922) by @freePerro in #1420
  • fix(time-off): silent-failure marker becomes ruff-valid SILENTFAIL1 (GDXA-1893) by @freePerro in #1416
  • fix(jobs): re-open resets dispatch_status from the crew instead of keeping 'done' (GDXA-1929) by @freePerro in #1421
  • fix(planner): own chat bubble text uses --text-on-p-primary (GDXA-1764) by @freePerro in #1388
  • fix(portal): status tag colours cancelled and every job lifecycle stage (GDXA-1943) by @freePerro in #1422
  • fix(billing): follow-up and dunning-nudge NextAction writes go through the audited helper (GDXA-1860) by @freePerro in #1423
  • fix(variance-report): status colors match the API's display labels (GDXA-1944) by @freePerro in #1424
  • fix(ai-mcp): invoices.void calls the shared void; MCP write tools stop claiming effects they do not perform (GDXA-1849) by @freePerro in #1425
  • test(mcp): pin JWT_SECRET in the bearer tests; wrong keys differ by construction (GDXA-1951) by @freePerro in #1426
  • fix(dispatch): schedule board and available-slots read approved time off; retire /api/tech-unavailability (GDXA-1829) by @freePerro in #1427
  • fix(forecasting): forecast, cash calendar and board tile share one dated job-stage set (GDXA-1939) by @freePerro in #1428
  • fix(jobs): JobStateOverrideDialog uses DatePicker, not deprecated Calendar (GDXA-1958) by @freePerro in #1429
  • fix(jobs): closeout stages job.completed through the shared completion write (GDXA-1954) by @freePerro in #1431
  • fix(payroll): PayrollView uses DatePicker, not the deprecated Calendar shim (GDXA-1959) by @freePerro in #1430
  • fix(appointments): job and customer pickers search the server instead of a capped list (GDXA-1965) by @freePerro in #1432
  • fix(db): drop the orphan tech_unavailability table and model in guarded migration 134 (GDXA-1830) by @freePerro in #1433
  • fix(dispatch): open drawer survives a board refresh for lane-only parked and off-window jobs (GDXA-1989) by @freePerro in #1434
  • fix(estimates): preview, composer and emailed PDFs print the expiry the send sets (GDXA-1981) by @freePerro in #1435
  • fix(mobile): delete dead FCM scaffolding: nativePush.js, google-services wiring (GDXA-1977) by @freePerro in #1436
  • feat(mobile): native app takes a build-time default server, VITE_DEFAULT_SERVER_URL (GDXA-1993) by @freePerro in #1437
  • fix(jobs-dispatch): a closeout's day-2 return visit is no longer labelled a callback; jobs record return_visit_source (GDXA-1946) by @freePerro in #1438
  • docs(timesheet-export): a past period's file is rebuilt on download, not frozen (GDXA-1992) by @freePerro in #1439
  • docs(CLAUDE.md): ledger entries say who hits it; tooling notes get their own file by @freePerro in #1440
  • fix(frontend): drop unused @capacitor/push-notifications dependency (GDXA-1978) by @freePerro in #1441
  • fix(mobile): delete deprecated POST /api/mobile/jobs/{id}/complete; it set dispatch_status=done without lifecycle_stage (GDXA-1937) by @freePerro in #1442
  • feat(auth): lock an account for 15 min after 10 failed logins; Users page shows and lifts it (GDXA-1995) by @freePerro in #1443
  • fix(mobile): finished jobs read finished on the phone (GDXA-1942) by @freePerro in #1444
  • docs(release): v1.152.0 release notes by @freePerro in #1445

Full Changelog: v1.151.0...v1.152.0