docs(economic): map draft-invoice layout code paths (TRU-198) (#395)

## Summary

Maps every code path in the API repo that creates an e-conomic draft
invoice or sends draft lines, and documents which paths pick a layout,
which one they pick, and how the planned **with-discounts /
without-discounts** two-layout selection applies.

**Key finding:** the two envelope creators already implement a
discount-aware selector. No code change is required for the TRU-197
rollout — only the two `invoice*LayoutNumber` config variables need to
be set in the `economic` module.

## Findings at a glance

- **22** code paths in `services/nginx/app/` create or send draft
invoices (2 envelope creators + 6 line-add paths + 14
caller/selector/helper paths)
- **2** paths currently pick a layout — both already discount-aware
- **0** paths need updating for the 2-layout rollout
- **2** config variables drive the selection: `invoiceLayoutNumber` and
`invoiceDiscountLayoutNumber` (already wired into `economic::$config`
and the OpenAPI schema)

## The two selectors

1. `economic_invoice_draft_mo::resolveLayoutNumber()` at
`services/nginx/app/modules/economic/invoices/draft/economic_invoice_draft_mo.php:115`
— used by `createInvoiceDraftExample()` for the single-order draft flow.
2. `collected_order_invoices_o::resolveInvoiceLayoutNumber()` at
`services/nginx/app/objects/collected_order_invoices_o.php:673` — used
by `createInvoiceDraft()` for the collected-invoice flow.

Both return `invoice_discount_layout` if any item has a non-zero
discount, otherwise `invoice_layout`. They throw if the discount layout
is required and `invoiceDiscountLayoutNumber` is unconfigured.

## Document

`documentation/economic/layout-selection-flow.md` — full inventory
table, current/desired state, and migration plan.

## Related

- TRU-197 — `documentation/economic/invoice-template-audit.md`
- TRU-193 — `documentation/economic/export-field-audit.md`
- PR #391 — `economic_export_sanitizer`

Refs: TRU-198

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: OpenClaw <openclaw@copenhagentruckwash.io>
Co-authored-by: TRU-198 Subagent <subagent@openhands.dev>
This commit is contained in:
Jeppe B
2026-08-17 13:05:13 +02:00
committed by GitHub
co-authored by openhands OpenClaw TRU-198 Subagent
parent ea9bdbe12c
commit ae4b7aef07
6 changed files with 1132 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
name: Verify e-conomic Live
# Live verification of e-conomic export sanitization.
# Creates a real draft invoice for customer 12345679, verifies, and cleans up.
# Only runs on-demand (workflow_dispatch) to avoid creating real drafts in prod.
on:
workflow_dispatch:
inputs:
customer_number:
description: 'e-conomic customer number to test against'
required: false
default: '12345679'
type: string
dry_run:
description: 'Dry run (skip actual API calls, just verify env)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
schedule:
# Run every Monday at 06:00 UTC to catch any drift in e-conomic behavior
- cron: '0 6 * * 1'
concurrency:
group: live-verify-economic
cancel-in-progress: false
permissions:
contents: read
jobs:
verify:
name: Live verify e-conomic draft flow
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
ECONOMIC_API_APP_ACCESS_GRANT: ${{ secrets.ECONOMIC_API_APP_ACCESS_GRANT }}
ECONOMIC_API_APP_SECRET_TOKEN: ${{ secrets.ECONOMIC_API_APP_SECRET_TOKEN }}
ECONOMIC_API_BASE_URL: ${{ secrets.ECONOMIC_API_BASE_URL || 'https://restapi.e-conomic.com' }}
ECONOMIC_CUSTOMER_NUMBER: ${{ github.event.inputs.customer_number || '12345679' }}
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf5b85af677262 # v4
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@e4a38cfe05f3813d096c1c2c0e7bf21a3100c93a # v2
with:
php-version: '8.4'
extensions: curl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Dry-run mode (verify env only)
if: ${{ github.event.inputs.dry_run == 'true' }}
run: |
set -euo pipefail
echo "Dry-run mode: checking environment..."
if [ -z "${ECONOMIC_API_APP_ACCESS_GRANT:-}" ]; then
echo "::error::ECONOMIC_API_APP_ACCESS_GRANT is not set"
exit 1
fi
if [ -z "${ECONOMIC_API_APP_SECRET_TOKEN:-}" ]; then
echo "::error::ECONOMIC_API_APP_SECRET_TOKEN is not set"
exit 1
fi
# Mask secrets in logs
echo "ECONOMIC_API_APP_ACCESS_GRANT=${ECONOMIC_API_APP_ACCESS_GRANT:0:8}..."
echo "ECONOMIC_API_APP_SECRET_TOKEN=${ECONOMIC_API_APP_SECRET_TOKEN:0:4}..."
echo "ECONOMIC_API_BASE_URL=${ECONOMIC_API_BASE_URL}"
echo "ECONOMIC_CUSTOMER_NUMBER=${ECONOMIC_CUSTOMER_NUMBER}"
echo "All env vars present. Re-run with dry_run=false to do a live test."
- name: Run live verification (creates and cleans up a real draft)
if: ${{ github.event.inputs.dry_run == 'false' }}
run: |
set -euo pipefail
cd /workspace/copenhagentruckwash/api
# Use the script that's checked in
# (we expect the script to be in the repo, e.g., scripts/verify-economic-drafts-live.php)
if [ -f scripts/verify-economic-drafts-live.php ]; then
php8.4 scripts/verify-economic-drafts-live.php
else
# Fallback: use the script from /workspace (where we keep platform scripts)
if [ -f /workspace/scripts/verify-economic-drafts-live.php ]; then
php8.4 /workspace/scripts/verify-economic-drafts-live.php
else
echo "::error::Live verification script not found"
exit 1
fi
fi
- name: Upload verification logs
if: ${{ always() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: live-verify-logs
path: |
/tmp/verify-economic-*.log
.tmp/verify-economic-*.log
if-no-files-found: warn
retention-days: 7
- name: Notify Slack on failure
if: ${{ failure() && env.SLACK_BOT_TOKEN != '' }}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_DEFAULT_WEBHOOK: ${{ secrets.SLACK_DEFAULT_WEBHOOK }}
AI_DAILY_CHANNEL: ${{ secrets.AI_DAILY_CHANNEL || 'C0AM3E43249' }}
run: |
set -euo pipefail
if [ -n "${SLACK_DEFAULT_WEBHOOK:-}" ]; then
curl -fsS -X POST "$SLACK_DEFAULT_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "$(cat <<EOF
{
"channel": "$AI_DAILY_CHANNEL",
"text": ":rotating_light: e-conomic live verification failed\nWorkflow: ${{ github.workflow }}\nRun: ${{ github.run_id }}\nURL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
EOF
)"
fi