Commit Graph
95 Commits
Author SHA1 Message Date
Jeppe B 58d5e177a9 fix(api): order order_items so primary precedes addons in getOrderItems (#364)
Defensive ORDER BY in orders_o.php::getOrderItems so primary items render before their addons (related_item_id IS NULL DESC, related_item_id ASC, id ASC). Pinned with OrderItemsListingOrderingTest which locates orders_o.php via worktree-aware resolver.
2026-08-11 07:05:59 +02:00
Jeppe BandWorktree Fix Verifier 82a3684f05 fix(api): order getOrdersWithRegistrationNumberInDateRange by id ASC (#362)
## Summary

orders_o::getOrdersWithRegistrationNumberInDateRange() selects orders
matching a registration number within a date range without an explicit
ORDER BY clause. MySQL is free to return rows in any order. The endpoint
at routes/orderInvoicesRoute.php then iterates the result and calls
assignToInvoiceCollection() on each row, so the audit-log +
invoice-collection numbering depend on the arbitrary backend row order.

Add a stable `ORDER BY id ASC` to the SELECT and pin the contract with a
new Pest unit test.

## Test plan

- New Pest test `OrdersRegistrationDateRangeQueryTest` asserts the
SELECT still carries `ORDER BY id ASC`.
- Existing tests in the same file still pass unchanged (they don't
assert on ordering).
- Manual php -l on both modified files shows no syntax errors.

## Commits

- bbd50239 fix(api): order getOrdersWithRegistrationNumberInDateRange by
id ASC

Co-authored-by: Worktree Fix Verifier <agent@truckwash.local>
2026-08-10 20:33:10 +02:00
Jeppe BandTruck Wash Agent d850075397 fix(api): order order_items so primary precedes addons in getOrderItems (#361)
## Summary

orders_o::getOrderItems() selected order items without an explicit ORDER
BY clause, so MySQL was free to return rows in any order. On the POS
Fuldfør click and the superuser invoice tree, addons (related_item_id !=
NULL) were sometimes returned before their primary item, which broke the
FE tree-builder and the OrderContentTable render.

Add a stable ordering: primary items first (related_item_id IS NULL
DESC), addons grouped by their parent (related_item_id ASC), and
insertion order as the final tiebreaker (id ASC).

## Commits

- 7ec64ec8 fix(api): order order_items so primary precedes addons in
getOrderItems
- 68e19bee test(api): pin order_items listing ordering in getOrderItems

## Test plan

- Wiring unit test asserts the SELECT inside getOrderItems still carries
ORDER BY (related_item_id IS NULL) DESC, related_item_id ASC, id ASC.
- Verified locally with php -l on the modified file.
- Existing OrderItemReasonPolicyTest, CustomerOrderProductPolicyTest,
OrdersIncludeInInvoiceOverrideTest continue to pass in the worktree
setup (no DB fixtures touched).

---------

Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
2026-08-10 20:16:04 +02:00
Jeppe B 0aaf32efa4 Surface silent-skip paths and additional silent failures in email/booking/order flows (#352)
## Why

Customer `k.sand@ksand.dk` reported never receiving wash certificates for completed bookings. Two methods contained silent early-return guards so the actual reason was unobservable from container logs:

- `order_bookings_o::sendWashCertificateToCustomer()` — 5 silent returns
- `email::sendWashCertificateEmailToCustomer()` — 1 silent return

The most likely root cause: `email_notifications_enabled` defaults to `0` in the schema and `users.add()` does not set it on insert, so newly imported customers have notifications off until toggled. `wantsEmailNotifications()` then returns false and the email silently skips.

## What changed

### Original commit (`0ead5de5`)
- `objects/order_bookings_o.php` — all 5 silent early-returns now log via new `logWashCertificateSkip()` helper (Redis stream `module=email / action=WASH_CERT_SKIP` + `error_log('[wash-cert-skip] …')`).
- `classes/email.php` — silent `hasTransaction()` return in `sendWashCertificateEmailToCustomer()` now logs too.
- `objects/bookings_o.php` — emits `WASH_CERT_SKIP` (legacy_no_wash_certificate_email) when `washCertificateEmail` is empty; no behavioural change.
- **New** `routes/washCertificateDebugRoute.php` — `GET /debug/wash-certificates/diagnose?customer_number=&from=&to=` (404 in prod via $DEBUG; superuser-auth otherwise) replays the decision tree and reports `blocking_reason` per booking.

### Follow-up commit (`46a59e4e`) — silent-failure sweep

**PART A — silent returns / silent errors (10 fixes):**
- `email::sendEmailMailerSend()` — blacklisted-recipient skip now logs with context.
- `email::sendNewCustomerRegistrationNotifications()` — empty-email skip + per-recipient try/catch with error_log (was unprotected; a single MailerSend error broke the loop).
- `bookings_new_o::generateWashCertificate()` — wrapped `sendWashCertificateEmail()` in try/catch with error_log and re-throw (same pattern as the k.sand fix).
- `users_o::getCustomerName()` — replaced catch-and-swallow with structured error_log.
- `users_o::getCustomerEcocomicData()` — same.
- `bookingsRoute.php` — added booking-id context to 4 × `$response->error('Booking not found', 404)` calls.

**PART B — cron paths (10 files):** Added error_log breadcrumb + try/catch to `CheckUnfulfilledBookings`, `ClearAllUsersEconomicCustomerDetails`, `ClearAllUsersEconomicCustomerDiscounts`, `RunXLVaskModuleCron`, `SyncBookings`, `SyncEconomicInvoiceStatus`, `SyncLogs`, `BackfillEconomicV2History`, `EnsureXLVaskAutomationSchema`, and 3 functions in `Cron.php`. Each uses a distinct `[cron-…]` prefix for grep-ability.

**PART C — real bugs (2 fixed):**
1. `email::sendEmailMailerSend()` attachment `array_map` — the previous exception message emitted a binary blob because `$attachment[0]` was already overwritten by `file_get_contents()`. Now captures $path first.
2. `bookings_new_o::generateWashCertificate()` — booking persisted as `completed` before email was sent, with no try/catch. Fixed (see PART A).

## How to verify

1. Deploy to staging.
2. Hit `/debug/wash-certificates/diagnose?customer_number=<k.sand's customer_number>` as a superuser — the response lists every booking's `blocking_reason`.
3. Tail container logs for `[wash-cert-skip]`, `[email-skip]`, `[cron-…]`, and Redis stream `module=email` action `WASH_CERT_SKIP` to see real-world skips going forward.

## Follow-ups (out of scope)

- Schema migration to default `email_notifications_enabled` to `1` and backfill non-empty-email customers.
- Move `error_log` to a proper PSR-3 logger.

## Risk

- Logging only + new debug endpoint (404-gated in prod). No behavioural change for any path that previously sent mail successfully. `php -l` could not be run in the original sandbox; please verify on your CI box before deploying.

🤖 Generated with [OpenClaw](https://openclaw.ai)
2026-08-09 00:21:04 +02:00
Jeppe B 068f9e254f Complete selected-customer invoice period tree (#338)
Add the authoritative revision-bound invoice collection tree and guarded cleanup, merge, price-reset, and transfer operations.
2026-08-03 12:02:34 +02:00
Jeppe B 1e0e051775 Harden Sæby demo registration and department scope (#335)
Complete and secure public customer/driver registration, authoritative limited-backoffice department scope, one-time employee QR login, and pricing concurrency for the Sæby demo.
2026-08-02 11:50:56 +02:00
Jeppe BandJeppe Bundgaard 42ddce84bc Serialize VAT collection mutations with payment operations (#326)
## Summary

- Makes Stripe Terminal card payment intents always use 25% moms in the
API, independent of any client-supplied `tax_percentage`.
- Updates amount calculation, metadata persistence, stored-intent reuse
matching, the authoritative OpenAPI contracts, and operation-specific
Writerside outputs.
- Prevents double charging and false order closure across stale,
concurrently succeeded, partially recorded, or mismatched intents.
- Serializes payment create/capture/closure with order-item changes and
every order-to-invoice-collection reassignment through shared database
locks.
- Converts expected lock contention and reconciliation cases into
deliberate 409 responses.

## Exact-head evidence

Current head: `3a0f70d315a94d2efe586a2188d2c54f8ff11cd4`

- PHP syntax passed for all changed runtime files.
- Focused Orders suite: **42 tests / 293 assertions passed**.
- `git diff --check` passed.
- Fresh exact-head Tests and Qodana are running.
- Every Codex finding has a concrete reply; a fresh exact-head review is
requested below.

## Safety behavior

- Caller-controlled VAT is absent from request contracts; fixed 25% moms
is server-owned.
- A succeeded payment is preserved, requires the full expected
`amount_received`, and cannot close a changed/mismatched or
already-claimed collection.
- A compatible partially recorded Stripe closure is completed
idempotently; conflicting partial state fails closed for manual
reconciliation.
- Every cancellation/delete caller honors a concurrent-success result
and never falsely reports a completed payment as cleared.
- Price changes and invoice-collection reassignment share the payment
lock through validation, capture, post-capture reload, and closure.
- Reader changes are persisted only for reusable matching intents, so
stale intent cancellation targets the original terminal.
- Accepted legacy succeeded intents normalize stored tax to 25% before
response construction.

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-28 22:00:59 +02:00
Jeppe B 879dfcf79a Improve invoice period data and POS add-on validation 2026-07-15 17:04:52 +02:00
Jeppe Bundgaard dcef993f12 Implement department wash count service and refactor related reporting functions 2026-07-08 10:35:24 +02:00
Jeppe Bundgaard 172a21c517 Implement department-specific customer pricing functionality 2026-07-07 17:27:56 +02:00
Jeppe Bundgaard 248a901f24 Merge master into fixed price override branch 2026-07-06 16:54:01 +02:00
Jeppe Bundgaard f02dfd8c9c Add customer product fixed price overrides 2026-07-06 12:52:33 +02:00
Jeppe Bundgaard d47ea1d659 Add custom-only department pricing enforcement 2026-07-06 10:15:11 +02:00
copilot-swe-agent[bot] 1065973b33 Merge master into fix-sql-injection-in-vehicle-plate-lookup 2026-06-01 21:07:49 +00:00
Jeppe B d1871f1420 Fix SQL injection in vehicle plate order history lookup 2026-06-01 23:03:18 +02:00
copilot-swe-agent[bot] 6995c3d1bc Merge origin/master and resolve orders_o conflict 2026-06-01 20:57:44 +00:00
Jeppe B bc4b7bde15 Fix SQL injection in vehicle plate order history lookup 2026-06-01 22:55:06 +02:00
copilot-swe-agent[bot] eedde6c6d7 Merge origin/master and resolve orders_o conflict 2026-06-01 20:41:18 +00:00
Jeppe B c43618351e Escape plate in recommended order SQL lookup 2026-06-01 22:37:55 +02:00
Jeppe Bundgaard 6c40810caf Add unit tests for invoicing period pagination, normalization, and filtering logic
- Implemented `InvoicingPeriodPaginationTest` for testing period pagination modes, normalization of options, search functionality, and visibility filters.
- Added comprehensive tests to validate scenarios such as active period views, exact counts, and customer-card level search.
- Improved cURL timeout settings with `CURLOPT_CONNECTTIMEOUT` and `CURLOPT_TIMEOUT` adjustments.
- Introduced and documented helper classes/methods for local caching, pagination response structure, and customer name retrieval.
2026-05-12 03:37:47 +02:00
Jeppe Bundgaard 22fcb5cd0e - Refactor machine_1 drawing logic: optimize highlighted button rendering and deferred processing.
- Add branding management feature: API routes, payload handling, and OpenAPI schema updates.
- Implement department branding logic: CRUD operations, validation, and permissions.
- Add order deletion confirmation support with conflict handling and OpenAPI schema updates.
- Enhance tests and API methods for improved order handling and branding workflows.
2026-05-07 10:44:00 +02:00
Jeppe Bundgaard 7d450e285e Remove outdated edge gateway object classes, add new agent implementation
Transitioned from obsolete gateway object classes (`edge_gateway_shell_action_jobs_o`, `edge_gateway_shell_events_o`, `edge_gateway_shell_sessions_o`, `edge_gateway_update_jobs_o`) to the new agent implementation (`edge-gateway-agent/agent.php`).
2026-04-21 14:13:17 +02:00
Jeppe Bundgaard ebf7e820d5 Add safety seal support to orders and related logic for wash certificates
- Introduced `safety_seal` column in the `orders` table.
- Updated order creation and completion logic to handle safety seal values.
- Enhanced order and booking classes to manage safety seal attachment and retrieval.
- Added tests to validate safety seal functionality in order processing.
2026-04-14 10:51:25 +02:00
Jeppe Bundgaard 22f856c62a Add unit tests for invoicing, orders normalization, gateway commands, and department complaints. Update schema bootstraps and improve agent command execution logic. 2026-04-09 12:24:22 +02:00
Jeppe Bundgaard ba23ad6e8f Add economic_transfer_executor and economic_transfer_queue classes for handling e-conomic invoice transfer logic, queue management, and processing. Include unit tests for Redis cache validation. 2026-04-08 11:20:08 +02:00
Jeppe Bundgaard 6fdc8d466e Add unit tests for various modules: attachments grouping, department weather caching behaviors, economic module order sanitization, enriched order batching, and user cashier name lookups. Update related route logic for enhanced data fetching and caching integrations. 2026-03-24 14:59:16 +01:00
Jeppe Bundgaard 4523115584 Introduce centralized date normalization and duplicate filtering for invoicing logic, and add comprehensive unit tests 2026-03-12 16:09:54 +01:00
Jeppe Bundgaard 2ca29a77f1 Optimize order and transaction processing logic with caching, and implement opt-in Slack summaries to reduce request latency. 2026-03-12 16:00:57 +01:00
Jeppe Bundgaard 330ac4370f Add getWashesInTimeRange method to orders_o for fetching orders within specified daily time range
- Introduce filtering by time range and optional department ID.
- Validate inputs and ensure robust exception handling for invalid ranges.
- Optimize SQL queries for performance with joins and distinct order retrieval.
2026-02-10 14:05:22 +01:00
Jeppe Bundgaard 08077df618 Add getOrderItemObjects and getProduct methods, and enhance Slack notifications in invoicing routes
- Introduce `getOrderItemObjects` in `orders_o` to retrieve item objects for an order.
- Add `getProduct` in `order_items_o` for product retrieval by item.
- Enhance Slack notification logic in invoicing routes for better error reporting and fixed pricing summaries.
- Temporarily comment out permission checks in certain invoicing endpoints for debugging purposes.
2026-02-02 12:57:27 +01:00
Jeppe Bundgaard 9b41f80ea5 Add methods to retrieve and analyze product sales data, send department statistics to Slack, and implement scheduled messaging logic 2026-01-21 13:42:19 +01:00
Jeppe Bundgaard 0eddf69ae4 Replace getCustomerByIdOrCustomerNumber with getUserByCustomerNumber across services and remove deprecated method. 2026-01-06 15:50:59 +01:00
Jeppe Bundgaard 49589de98e Update getUserByCustomerNumber references to getCustomerByIdOrCustomerNumber and enhance null-checking in requireSelected. 2026-01-06 15:40:05 +01:00
Jeppe Bundgaard bcca9dd319 Add getOrderBooking method to fetch linked booking object for orders 2026-01-02 11:18:06 +01:00
Jeppe Bundgaard d3d3657dfd Generate and attach wash certificate to orders
- Added `generateWashCertificate` method to `orders_o` for wash certificate PDF generation and attachment.
- Introduced `OTHER_TYPE_WASH_CERTIFICATE` constant in `attachment_content` for new attachment type.
- Added `/order/wash-certificate` route to provide wash certificate generation functionality via API.
2025-12-03 08:45:13 +01:00
Jeppe Bundgaard ecd44a455a - Add Redis mget method and cache management improvements
- Update cache expiration times for `economicCustomerName` and `isBooked` objects
- Introduce `getCachedForMultipleObjects` for batch cache retrieval
- Optimize `isBooked` with optional caching and update to store results
- Implement `getCustomerNames` in `users_o` with caching for bulk name retrieval
- Refactor customer transaction handling in `InvoicingPeriodRoute` for efficiency
- Filter orders excluded from invoicing in `collected_order_invoices_o`
2025-11-26 10:01:24 +01:00
Jeppe Bundgaard 3dba2dedac Add isIncludedInInvoicing method and implement invoicing exclusions
- Introduced `isIncludedInInvoicing` in `orders_o` to check department-based invoicing exclusions.
- Updated routes and methods to skip orders excluded from invoicing.
- Refined transaction and order handling to respect invoicing settings, ensuring correct filtering.
2025-11-25 14:39:49 +01:00
Jeppe Bundgaard a519478788 Implement wash certificate handling for bookings and order processing
- Added functionality to create and attach wash certificates during booking completion.
- Enhanced `completeBooking` to generate and associate wash certificates with orders based on booking items.
- Updated safety seal parameter handling in relevant methods.
- Integrated wash certificate PDF generation with customer branding and order attachment logic.
- Improved file retrieval logic in the `file_server` to handle missing certificates via alternative store lookup.
2025-11-10 15:45:24 +01:00
Jeppe Bundgaard 30b94e89d7 Add PO number support and enhance permissions for order management
- Introduced `po` property in `orders_o` for handling Purchase Order (PO) numbers, including API integration for retrieval and validation.
- Enhanced customer permissions to allow limited order editing (`po` updates) and attachment downloads for their own orders.
- Added new helper methods to `users_o` for attributes like `showPricesOnBookingPage` and `usePONumbers`.
- Improved order item listing logic with distinct permissions for customers' own orders and price visibility.
- Implemented numeric value casting in filters within `db_object_t`.
2025-10-22 11:38:35 +02:00
Jeppe Bundgaard f59852375f Refactor department handling in invoice drafting and order retrieval
- Simplified department retrieval in `economic_invoice_draft.php` by streamlining logic for default department usage.
- Enhanced `getDepartmentByOrderId` in `orders_o` to ensure correct department is fetched based on given `order_id`.
- Updated discount line logic to leverage appropriate department dimension values.
2025-10-06 12:28:35 +02:00
Jeppe Bundgaard 86acc3a183 Add invoicing distribution endpoints and subscription processing features
- Introduced new endpoints for invoicing distribution: `/superuser/invoicing/period/distribution/fixed-pricing` and `/superuser/invoicing/period/distribution/wash-subscriptions`.
- Enhanced `customer_vehicles_o` with `getSubscriptionAppliedTransactionsFromList` method for transaction filtering based on subscriptions.
- Added `getNetAmountForOrderItemsOriginal` to `orders_o` for calculating the original net amount of order items.
- Introduced parsing utilities for department-based aggregation in invoicing.
- Extended `products_o` with `getSubscriptionMonthlyPrice` to calculate subscription pricing.
- Added `getFieldsWhereIn` to `db_object_t` for advanced database querying.
2025-10-01 10:21:58 +02:00
Jeppe Bundgaard dfc7738a62 Add handheld order processing indicator and caching functionality
- Introduced `is_handheld` field in order creation to identify orders from handheld devices.
- Added logic to set, cache, and expire pending handheld orders automatically at midnight.
- Enhanced `orders_o` class with `setPendingHandheldIndicator` and `isPendingHandheld` methods.
- Updated order processing flow to include `pending_handheld` indicator in responses.
2025-09-25 12:33:09 +02:00
Jeppe Bundgaard 5f400e1dd0 Add advanced invoice handling, cached result lookups, and customer-specific pricing
- Enhanced `orderInvoicesRoute` with `reset`, `unlink`, and `remove-special-arrangements` handlers for invoice actions, improving E-Conomic integration.
- Added `resetPricesOfItemsNotIncludedInInvoice`, `setAllItemsToBeIncludedInInvoice`, and `unlinkFromEconomic` methods in `collected_order_invoices_o` for robust invoice control.
- Introduced `getCustomerProductPrice` in `orders_o` to calculate customer-specific product pricing with discounts.
- Added `getCachedResultsBySubstring` in `motorapi_lookups_o` for efficient cached lookup based on result substrings.
2025-08-25 15:59:03 +02:00
Jeppe Bundgaard f281b1c4d6 Skip orders without registration number and simplify order queries for department-based filtering 2025-08-07 12:19:15 +02:00
Jeppe Bundgaard f98ca5f199 Update getTransactionsForCustomersInDateRange to include exception handling for invalid date ranges and no transactions found 2025-08-04 11:47:29 +02:00
Jeppe Bundgaard c397c20ea7 Refactor discount calculation and integrate MotorAPI checks in XL Vask module
- Updated `orders_o` to use `round` for consistent discounted price calculations.
- Refined `xlvask_parser_stor_bil` with MotorAPI-based vehicle type validation and added `checkMotorAPI` method.
- Optimized `motorapi_lookups_o` license plate validation to streamline database queries.
2025-07-29 15:13:09 +02:00
Jeppe Bundgaard f5ba9dd891 Add product discount calculation and update handling for dynamic usage logs
- Introduced `getProductDiscountPercentage` method to calculate accurate discounts for users across products, categories, and global settings.
- Adjusted `importUsageLogs` to refine date modifier parameter and enhance `usage log` checks.
- Enhanced `orders_o` to include product discount logic with stricter type casting for consistency.
- Added `updateFieldsWhere` method for selective database updates based on specific conditions.
- Updated `customers_o` to handle updates for existing customers while adding new ones.
2025-07-23 09:31:43 +02:00
Jeppe Bundgaard 16198f49e6 Refactor XL Vask module for better null handling and primary item validation
- Improved null safety by updating product ID retrieval logic in parsers and usage logs.
- Enhanced primary wash item selection with stricter checks for valid product types.
- Adjusted import date modifier in `importUsageLogs` for extended range (`-7 day`).
- Streamlined product and user association in order creation with additional validations.
- Removed redundant checks and refined permission handling in import tasks.
2025-07-21 16:47:05 +02:00
Jeppe Bundgaard 4cc270e258 Refactor order creation to use addArray method and sanitize inputs
- Introduced `addArray` method in `orders_o` for streamlined order creation using an associative array.
- Updated `ordersRoute` to utilize `addArray`, replacing older implementation for better manageability.
- Improved input handling by trimming whitespaces from registration numbers.
- Enhanced product pricing by incorporating department-specific prices and discount percentages.
2025-07-21 13:10:15 +02:00
Jeppe Bundgaard af3b0666fb Add import tasks and new parsers to XL Vask module
- Introduced `runImportTasks` method for vehicle, user, and usage log imports.
- Added new product parsers: `ht_b_rstel_s`, `ht_turbo`, and `ikke_b_rster_p_kabinen`.
- Implemented simulation and duplicate detection for usage logs via order objects.
- Updated usage log handling to include default customer checks and user association.
- Enhanced request handling with fast-link functionality for usage orders.
2025-07-21 10:32:56 +02:00