## Summary
`GoalsProgressAlertsCron` only consulted the per-department
`slack_webhook`
field when dispatching to Slack. For internal departments — Taulov and
Taastrup are configured as internal via `Slack >
internal_department_ids`
— those per-department webhooks are intentionally empty, so the cron had
no destination to post to and the daily DHL goal never reached the
internal Slack channel.
The dedicated `internal_department_goal_progress_webhook_url` is the
correct destination for these alerts. This change routes the dispatch
through it when **all** of a goal's departments are internal, with a
clean fallback to the existing per-department webhook loop when the
dedicated URL is empty or when the goal includes any non-internal
department. Operator-facing echo lines were added so the cron log
shows exactly which webhook was used for each goal.
## Why "in progress since 21/7"
The legacy cron flow is intact, the new cron-worker is wired up, and
the schedule fires every 60 s as expected. The goal records the right
department IDs. The destination check in the dispatcher silently
matched nothing — the per-department webhook was empty, the fallback to
the default webhook pointed to the wrong channel, and nothing in the
log indicated *which* dispatch path had been taken. Switching the
internal-department branch to the dedicated goal-progress webhook is
the real fix; the diagnostic echo lines prevent this from being silent
in the future.
## Changes
- `services/nginx/app/cron/Cron.php` (GoalsProgressAlertsCron SLACK
branch): when all linked departments are flagged as internal and the
dedicated `internal_department_goal_progress_webhook_url` is
configured, post to that webhook instead of the per-department
webhooks. Otherwise behave exactly as before.
-
`services/nginx/app/tests/Unit/Cron/GoalsProgressAlertsInternalWebhookTest.php`:
pin the new dispatch behaviour with four targeted tests covering
the happy path, the empty-webhook fallback, the mixed/external
goal path, and the Slack config helper calls.
## Test plan
- `vendor/bin/pest
tests/Unit/Cron/GoalsProgressAlertsInternalWebhookTest.php`
→ 4 passed, 18 assertions.
- `vendor/bin/pest tests/Unit/Cron/` → 34 passed (full cron suite
still green).
- Manual: after deploy, force-run the task via the existing
`POST /api/superuser/cron/run` endpoint with body
`{"job": "goals.progress_alerts"}` and confirm the
`[CRON] GoalsProgressAlertsCron: goal #N sent to internal goal
progress webhook (departments: …)` line appears in the cron log and
the message lands in the configured internal Slack channel.
Fixes TRU-76 (DRIFT 15).
Co-authored-by: backend-subagent <backend@truck-wash.local>
## TRU-70: DRIFT 9 — Customer rule "auto-send invoice toggle (3rd
business day each month)"
Adds a per-customer rule that auto-sends the customer's invoice on the
3rd business day of each month. The rule is implemented as a new
customer attribute (`autoSendInvoiceThirdBusinessDay`) that can be
toggled through the existing `POST /customer/attributes` endpoint, plus
a daily cron task that, on the trigger day, enqueues every ready
collected invoice for export via the existing `economic_transfer_queue`.
## Linear
- **TRU-70** (DRIFT 9)
## Changes
- **Customer attribute (TRU-70 / DRIFT 9)**
- `classes/customer_rule_product_restriction_service.php`
Adds `'autoSendInvoiceThirdBusinessDay'` to `SUPPORTED_ATTRIBUTES` so
the existing customer-attributes route can persist the toggle.
- **3rd-business-day service**
- `classes/auto_send_invoice_third_business_day_service.php` (new)
- Computes the 3rd business day of any month (weekend-aware, holiday
provider override).
- `runOnce()` is a no-op on every day except the 3rd business day.
- On the trigger day, scans `customer_attributes` for opted-in customers
and loads their ready `collected_order_invoices` (not booked, not
closed, has at least one order, not deleted).
- Enqueues each via `economic_transfer_queue` and returns a summary
`{triggered, customers, collections_scanned, jobs_enqueued,
skipped_already_queued, errors[]}`.
- Public hooks (`loadEligibleCustomerNumbers`,
`loadReadyInvoiceCollections`) and protected `createTransferQueue()` are
designed for unit-test isolation so no live database is required.
- **Cron task (TRU-70 / DRIFT 9)**
- `modules/economic/cron/tasks.php`
Registers `'economic.auto_send_invoices_third_business_day'` with a 24h
interval, 15 min timeout, priority 25. Anchored in the economic module
because the actual export goes through `economic_transfer_queue`.
- `cron/Cron.php`
New `AutoSendInvoicesThirdBusinessDay()` handler. Mirrors the
surrounding cron-task conventions (`warn` + `error_log` breadcrumb on
failure) and only logs a one-liner when the trigger fires.
## Tests
- `tests/Unit/Cron/AutoSendInvoiceThirdBusinessDayTest.php` (new, 16
tests)
- The 3rd-business-day computation (weekday-start, weekend-start,
Saturday, 4th-business-day, holiday skip, holiday forward-shift).
- `thirdBusinessDayOfMonth()` helper for the three reference months used
in the spec (Aug/Sep 2026 and Jul 2026).
- `runOnce()` no-op path on non-trigger days.
- `runOnce()` summary on trigger day with zero opted-in customers.
- `runOnce()` summary with customers and ready collections.
- `runOnce()` per-collection error recording when enqueue throws.
- `clearOverrides()` reset.
- The new attribute is in
`customer_rule_product_restriction_service::SUPPORTED_ATTRIBUTES` and
exposes a stable `ATTRIBUTE` constant.
- `tests/Unit/Cron/CronTaskRegistryTest.php`
Updated the discovery assertion from 22 to 23 definitions and added
coverage that the new `'economic.auto_send_invoices_third_business_day'`
task is discovered with the expected schedule and module.
## Backwards compatibility
- The new attribute is additive; existing `customer_attributes` rows are
unaffected. The default customer has no auto-send rule.
- The cron task is registered in the standard task registry; the
existing `cron_worker` (15s poll) handles the trigger without any new
infrastructure.
- The service gracefully no-ops when the transfer queue is unavailable
in unit-test contexts; the production cron task will surface a `warn()`
breadcrumb if e-conomic is unreachable, exactly like the other economic
cron tasks.
## Verification
```
vendor/bin/pest tests/Unit/Cron/ tests/Unit/Customers/
# 43 passed (199 assertions)
```
---------
Co-authored-by: openclaw bugfix <openclaw@copenhagentruckwash.local>
Co-authored-by: bugfix <bugfix@truckwash.local>
Co-authored-by: bugfix-subagent <[email protected]>
## Summary
Removes the XLVask autopilot / automation / MiniMax / OpenAI pipeline
and the related module config, CLI, cron, and migration scaffolding. The
Selvvask view (Superuser -> Fakturaer -> Periode -> Selvvask) is reduced
to a single read-only listing of usage logs plus operator-driven ignore
/ unignore / accept / reject endpoints gated on the
`review_xlvask_usage_order` permission.
See `inventory/self-serve-inventory.md` for the full surface map.
## Test plan
- [x] `vendor/bin/pest --testsuite=Unit` -> **1266 passed**, 1 unrelated
pre-existing failure (`BirdControlPlaneActivationTest`, needs
`PLENO_REPO_ROOT_FOR_TESTS`).
- [x] `php -l` on every modified PHP file -> no syntax errors.
- [x] Grep validation -> zero production-code references to removed
surfaces (`xlvask_autopilot_service`, `xlvask_automation_service`,
`xlvask_automation_policy_service`, `EnsureXLVaskAutomationSchema`,
`runScheduledAutomationIfReady`, `processAutopilotQueue`, `MiniMax`,
`minimax`, ...).
- [ ] Qodana + Tests workflows green on this PR.
Co-authored-by: openhands <openhands@all-hands.dev>
---------
Co-authored-by: openhands <openhands@all-hands.dev>
## 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)
## Summary
- Add self-service deletion for the authenticated customer or subuser
identity only.
- Preserve shared customer grants, reset keys, bookings, order bookings,
vehicles, invoices, and legally required history.
- Require password/TOTP or a fresh deletion-specific, five-minute,
single-use WebAuthn assertion.
- Reject support impersonation and expired legacy plain-session tokens.
- Use durable database throttling, transactional request processing, a
durable outbox, and terminal `manual_review` state.
- Keep API and worker default-off behind separate
`account_deletion.api_enabled` and `account_deletion.worker_enabled`
module-config flags.
## Safe rollout
1. Keep both flags disabled.
2. Run `php scripts/account-deletion-schema.php check`.
3. If needed, run `php scripts/account-deletion-schema.php apply --yes`,
then rerun `check` until `ready:true`.
4. Deploy the frontend companion PR while the API remains disabled.
5. Enable `api_enabled` for a controlled canary; verify password and
passwordless request flows plus immediate authentication revocation.
6. Inspect queued request/outbox state, then enable `worker_enabled`.
7. Verify anonymization, preserved tenant/history data, outbox delivery,
retries, and manual-review behavior before broad rollout.
## Verification
- Account deletion unit tests: 2 passed, 43 assertions.
- PHP lint, both OpenAPI YAML parses, runtime-DDL scan,
destructive-scope scan, and `git diff --check` passed.
- Full API/unit/integration evidence is required from exact-head CI;
local Docker is unavailable and shared-vendor tests were explicitly
discarded.
## Security notes
- Schema mutation is CLI-only; web and cron paths perform read-only
readiness checks.
- Runtime behavior fails closed when schema/config/throttle/delivery
prerequisites are unavailable.
Resolve recommended-profile Critical and High findings, retain narrow analyzer exceptions, and update the edge-broker WebSocket dependency to a non-vulnerable release.
- Added a test to ensure correct round-tripping of default distribution department config value through economic config updates.
- Improved department handling by adding fallback logic to use the default economic distribution department id when a customer's department id is missing.
- Enhanced weather API routes to fetch, cache, and return detailed employee contributions per department for a given time slot.
- Added methods `enqueueInvoicePeriodWarming` and `consumeInvoicePeriodWarmingQueue` to the `Redis` interface for managing warming periods.
- Modified `invoice_period_flag_service` to enqueue warming periods on cache misses.
- Updated cron job logic to process invoice period warming queues and ensure flags are warmed effectively.
- Introduced methods for caching, retrieving, and clearing manual and automatic invoice period flags, as well as order item rows, using the Redis interface.
- Implemented `warmManualFlagsCache` and `warmAutomaticFlagsForPeriod` methods in `invoice_period_flag_service` to enhance performance by loading flags and order items into cache.
- Added new cron jobs `WarmInvoicePeriodManualFlagsCron` and `WarmInvoicePeriodAutomaticFlagsCron` to regularly update cached data for improved access speeds.
- Add `DepartmentLaneDynamicImageRouteTest` to verify dynamic image preview handling for studio lanes.
- Introduce `selfserve_machine_signal` class to standardize signal normalization, recording, and gateway signal management workflows.
- Add `selfserve_virtual_hardware` class to handle virtual hardware configurations, including gateway and binding management.
- Enhance structure with auxiliary methods for payload normalization, workspace merging, and validation warnings.
- Replace `send_webhook_message` with `send_message` for improved Slack notification rendering using `goals_progress_alert_renderer`.
- Add methods for calculating and retrieving departmental progress and distribution in `goals_criteria`.
- Update `renderDanishPeriodSummary` and Slack cron logic to support departmental-specific summaries.
- Include departmental progress in serialized goal objects for better reporting.
- Introduce a new cron job to send progress alerts for department goals based on criteria.
- Support alert destinations: Slack (with fallback webhook), Email, and SMS (currently manual only).
- Evaluate frequency rules (daily, weekly, monthly, or change-based) with configurable time-of-day and weekdays.
- Deduplicate notifications using Redis keys for each goal and alert slot.
- Implement detailed alert scheduling, rendering, and dispatch logic.
Commented out unused cron tasks 'CheckUnfulfilledBookings' and 'SyncBookings' in the cron configuration. Also removed a debug print statement from the booking wash form to clean up the code.
Previously defined constants could cause errors if redefined, so checks are added to ensure they are defined only once. Additionally, the `syncAllUsersEconomicCustomerDetails` call in the Cron job is commented out, likely to prevent unintended executions.
Enhanced PDF generation with dynamic booking, department, and customer data, improving template variability. Introduced a new cron job and script to sync Economic invoice statuses, ensuring data consistency and error handling in automated tasks.
Introduced customer attribute-based filtering for individual invoicing and enhanced collected order invoice processing with proper associations to customers and orders. Added new endpoints, fields, and utility methods to streamline data retrieval, ensure consistency, and support new use cases like 'Ready to Invoice'. Includes minor fixes, validations, and optimizations throughout the affected modules.
Changed the execution interval of `SyncUserEconomicCustomerDiscounts` from 10 minutes to 1 minute. This ensures more frequent updates and improves data synchronization accuracy.
Introduced a new "backup" task that runs every 12 hours. The task uses the backup_store class to create backups and handles exceptions gracefully by logging errors. This ensures regular backups and improves system reliability.