Reverse caller-ID lookup: replace Elasticsearch with a direct iDempiere-backed in-memory directory — server/utils/telephony/cidDirectory.ts fetches the small active ad_user set with phone/phone2 (SERVICE token, same pattern as phoneActivity.ts/extensions.ts) into a 60s TTL stale-while-revalidate cache so cid-lookup/[number].get.ts has zero network calls on the hot path (was querying a Postgres-trigger-fed ES index that could lag/stall behind iDempiere edits); server/plugins/warm-cid-lookup.ts pre-warms the cache at server boot so the first call after a deploy/restart is instant too; recipe note in reference/recipes.md (details)
Fix attachment-lookup N+1: skip the live ad_table query for known table names (details)
Tickets: show staff replies to limited merchant roles (service-token reads) (details)
Ticket detail: wrap toolbar instead of overlapping in the split view (details)
Fix unbounded open-shipments query: replace child-table $expand with direct-Postgres list + batched line fetch (details)
Mobile app: bump version to 1.0.49 (versionCode 50) (details)
Change log modal (admin only) for orders, shipments, products and partners (details)
Shipments list: link document numbers directly to the shipment edit page (details)
Order edit: restructure the action toolbar, rename reactivation to 'Auftrag wiederöffnen' (details)
Order tab: fix column overlap (30% / auto / 22%); change log table: truncate line labels, hide technical fields by default (details)
Order tab: make the 'General' column really 22% wide, stack its paired fields (details)
Order tab: move the partner link into the shipping-address header as a right-aligned 'Kunde' link (details)
Order tab 'General' column: Organization first, compact German-formatted date row, Tenant as tooltip (details)
Order edit toolbar: ticket button in the app-wide yellow (same as .button.is-ticket) (details)
Order edit: no stale 'Abort' button when opening an order (details)
Return label: tighten header band, put doc number next to title
Shrinks the top black header-band height and moves the document number onto the same row as the RETOURE/KUNDENRÜCKSENDUNG title (top-aligned, right of it) instead of stacking them, freeing vertical space for more article rows before a continuation label is needed. Title is now bold and slightly larger; company/date text in the band keeps its prior size.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013G7aDXCyYUwGgTG81QTP8E
Return label: shrink header band further, lift doc number 1mm
Continues the earlier band/title tightening: band height reduced again (10/7mm -> 9/6mm) and the document number nudged 1mm above the title's baseline for a slightly better visual balance on the row.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013G7aDXCyYUwGgTG81QTP8E
Mobile return: fix qty locked at 0 when scanning an Amazon-imported RMA
Scanning a return parcel's external RMA number (M_RMA.externalRmaNumber) finds the matching pre-imported Amazon RMA and loads its linked shipment, but that RMA is already DocStatus=CO with the expected qty on its line — getReturnedQtyMap counted it as "already returned" against its own shipment, zeroing maxQty and locking the qty stepper at 0 for every line. Confirmed against a live prod record (RMA 1000688 / externalRmaNumber "DydTw8rHRRMA": CO, one M_RMALine Qty=1 on the exact line whose remaining qty then computed to 0).
Fix: exclude the matched RMA's own id from the already-returned tally — it's the incoming authorization being fulfilled right now, not a separate prior return. Searching by bpartner and selecting the shipment manually was unaffected since it doesn't go through this RMA-number match at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013G7aDXCyYUwGgTG81QTP8E
Customer Returns: link doc number to detail view, add label reprint button
The list grid's Doc Number column was plain text — no way to open a return's detail view from it. Add AgCustomLink (mirrors the MaterialReceipt/Shippers pattern) linking to /sales/returns/[id]/view.
On that detail view, add a "Etikett erneut drucken" button that re-fetches the return's lines (GET /api/inouts/[id]/return-detail) and reprints the return label via POST /api/print/return-label (kind: 'customer-return', using the printer queue from useReturnLabelPrinter) — for when the label didn't come out at intake for any reason.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013G7aDXCyYUwGgTG81QTP8E
Dev: pre-bundle AG Grid + vue-pdf deps to fix grid never rendering
Every AG Grid page (any of them — confirmed on both Customer Returns and Shippers) rendered a completely empty <div class="ag-theme-balham">: no rows, no "No Rows" overlay, no console error. Root cause was the same class of issue the surrounding optimizeDeps.include comment already documents for other packages, just not yet applied to these: ag-grid-community, ag-grid-enterprise, ag-grid-vue3, and @tato30/vue-pdf were never in the list, so Vite only discovered them when the first page using <AgGrid>/ <PrintViewer> actually rendered — triggering a disruptive re-optimize + forced reload mid-render. That reload left AgGridVue's internal grid creation desynced from AgGrid.vue's provideGlobalGridOptions({theme: "legacy"}) call, surfacing as AG Grid's own "error #239" (Theming API vs CSS File Theme conflict) — and, on the very first hit of a session, as a totally blank grid with no error at all.
Also adds @capacitor/preferences (pulled in by plugins/00.capacitor-api.client.ts for the dual web/Capacitor build, which stays in the dev module graph even though it early-returns on web) — same failure mode, same fix.
Confirmed fixed: clean nuxt dev restart + rm -rf .nuxt node_modules/.cache, Customer Returns list and detail view both rendered fully on first load, no "Vite discovered new dependencies at runtime" warning, no AG Grid error. Dev-only change (vite.optimizeDeps has no effect on the production build).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013G7aDXCyYUwGgTG81QTP8E
Requests: push tickets to mobile workers as priority tasks
Lets dispatchers push a ticket to a specific mobile worker's device (native FCM + web push) so it pops up and stays pinned as a priority-task banner on the mobile dashboard until the worker starts the timer on it. Opening it reuses the existing ticket instructions/chat/timer flow in tasks.vue; a new global running-timer banner keeps the timer visible while navigating other mobile pages.
Needs two new custom columns on R_Request in iDempiere (IsPushedTask Yes-No, PushedTaskAt Date+Time) — stays inert until those exist.
The "Etikett erneut drucken" button on the return detail page always printed the customer-return's own DocumentNo, but /mobile/reconditioning only scans the linked RMA's DocumentNo (571/572 live returns have one). Now prints the RMA's number when present, matching what intake already prints, and falls back to the return's own number only when there's no linked RMA.
Reverse caller-ID lookup: replace Elasticsearch with a direct iDempiere-backed in-memory directory — server/utils/telephony/cidDirectory.ts fetches the small active ad_user set with phone/phone2 (SERVICE token, same pattern as phoneActivity.ts/extensions.ts) into a 60s TTL stale-while-revalidate cache so cid-lookup/[number].get.ts has zero network calls on the hot path (was querying a Postgres-trigger-fed ES index that could lag/stall behind iDempiere edits); server/plugins/warm-cid-lookup.ts pre-warms the cache at server boot so the first call after a deploy/restart is instant too; recipe note in reference/recipes.md
Fix attachment-lookup N+1: skip the live ad_table query for known table names
server/api/attachments/[table]/[id]/index.get.ts hit iDempiere's ad_table model on every call to resolve a table name to its numeric id, even though the very same route already had a complete hardcoded fallback map for the common tables. Every AgGrid row with an attachment cell (AgCustomAttachment.vue) fires this once, so a large order/shipment grid render turned into hundreds of redundant REST calls, each going through the synchronized DefaultQueryConverter singleton (see the "iDempiere REST load rules" recipe / 2026-09-10 incident) — a live capture on prod showed 287 of these in one grid render. Now the static map is checked first and iDempiere is only queried for a genuinely unmapped table name. Behavior-preserving: also drops a dead duplicate M_Product branch that was unreachable (208 always won).
Tickets: show staff replies to limited merchant roles (service-token reads)
Staff replies on merchant tickets were stamped with the staff member's active org (LogYou) instead of the ticket's org, so iDempiere's org row-level security hid them from the merchant's limited role — customers saw only their own messages (85 such rows across 5 merchants on prod, e.g. Wonderhome ticket 1000439 with both LogYou replies missing). AD_Org_ID is not updateable on R_Request/R_RequestUpdate/R_RequestAction, so the rows cannot be re-stamped.
- server/utils/ticketViewer.ts: role FrontendMenu resolved server-side (service token, cached), caller-token ticket access check, server-side twin of the client's internal-entry filter ('I' / [Zeitabrechnung]). - request-update / request-history GET: a proven-limited viewer reads the ticket's rows with the service token AFTER the caller-token access check; internal entries are dropped server-side. Both lists now ordered + capped. - request-update POST: AD_Org_ID derived from the ticket server-side (body.organizationId ignored) so every caller stamps the right org; limited roles can never post an 'I' entry. Write stays on the caller's token so CreatedBy is correct. - author-orgs: collect update/action authors with the service token for limited viewers so the newly visible staff rows get their LogYou label. - latest-updates: limited viewers get updates of their visible tickets via chunked `R_Request_ID in (...)` with the service token, filtered.
Ticket detail: wrap toolbar instead of overlapping in the split view
The ticket header bar was a single non-wrapping row with hidden horizontal scroll; in the my-tickets split view (and small windows) the action group painted over the metadata chips (ticket no., date, type, category, assignee). The bar now wraps into a metadata row and an action row when space runs out, chips wrap inside each group, and in narrow bars the secondary actions (push to mobile, accounting flag) collapse to icon buttons with tooltips via a container query.
Fix unbounded open-shipments query: replace child-table $expand with direct-Postgres list + batched line fetch
useOpenShipments.ts (backing components/integrations/OpenShipments.vue, polled every 2 minutes per open tab on the commissioning page) queried m_inout with a 5-way $expand including the CHILD table m_inoutline($expand=m_product_id) and no $top — same shape as the 2026-09-10 incident's m_product $expand=M_Storage, hitting the synchronized DefaultQueryConverter singleton once per shipment per line. A live capture returned 2MB in one response; only 67 rows today but it only gets heavier as open-shipment volume grows.
Fix, following the same pattern already used for products+storage (productStorageHelper.ts): - Shipment list now comes from the existing direct-Postgres endpoint server/api/inouts/open-to-commission-list.get.ts (already used by the dashboard "Offene Aufträge" card and the commissioning modal), extended with the fields the widget needs (location/shipper name, PO reference, line-qty sum) so it's purely additive for its existing consumers. - Line items (needed for the barcode-scan-to-shipment search in commission.vue) are fetched in one bounded REST call: m_inoutline top-level list with $filter=M_InOut_ID in (...) and $expand=M_Product_ID as a to-one FK expand, not a child expand under m_inout, so it takes the cheap code path per the "iDempiere REST load rules" recipe. Verified directly against prod (SQL query + REST filter) before committing.
Change log modal (admin only) for orders, shipments, products and partners
Adds an iDempiere AD_ChangeLog viewer like the ZK 'Change Log' / 'Time Line' tab: - server/utils/changeLogHelper.ts + GET /api/changelog/detail/{table}/{id}: paged changelog read with column metadata, list values resolved via AD_Ref_List, foreign keys resolved to record labels (bounded, cached), child lines included (order/shipment lines, partner locations + contacts), record creation event; role must be IsClientAdministrator (checked server-side) - components/ChangeLogModal.vue: timeline (grouped per transaction, day separators) + table view, search, line/technical-field toggles, DE/EN/ES - components/ChangeLogButton.vue: admin-gated toolbar button, wired into the order, shipment, product and business-partner edit pages
Order edit: restructure the action toolbar, rename reactivation to 'Auftrag wiederöffnen'
The order edit bar had grown to ~20 coloured icon-only buttons. It is now a neutral, text-labelled toolbar: Save, Files (with badge), Complete / Reopen order, Create shipment, Create invoice, Void, and a grouped 'More actions' dropdown (print / labels / duplicate / video / reminders / compare / change log). All visibility rules live in one data-driven action list; on phones (<= 768px) only Save, Files and the status action stay outside the dropdown. Right side: Abort, share, Back, Ticket as labelled buttons.
- 'Reaktivieren' is now 'Auftrag wiederöffnen' (Reopen order); the orders list batch action 'Auf Entwurf' uses the same wording - new translation keys (en/de/es): reopen_order, more_actions, create_shipment_action, create_invoice_action, shipment_created, invoice_created, void_not_possible_shipped, force_void_short, manual_commission_short, reprint_label, order_reminders, show_video
Order tab: fix column overlap (30% / auto / 22%); change log table: truncate line labels, hide technical fields by default
- OrderForm: explicit flex bases + min-width:0 on the three order-tab columns (numbers/refs wider, shipping address flexible, 'General' column narrower) so the row can no longer overflow under the side cards; long e-mails wrap - ChangeLogModal: fixed table layout with column widths, line badges truncated with full text as tooltip, 'Hide technical fields' on by default
Order tab: make the 'General' column really 22% wide, stack its paired fields
The earlier width rule lost to Bulma's .column.is-one-third (higher specificity), so the column was still a third of the row. The selector now outranks it and the Date Ordered/Promised and Organization/Tenant pairs stack vertically so nothing truncates in the narrow column.
Order tab: move the partner link into the shipping-address header as a right-aligned 'Kunde' link
The partner row above the recipient name is replaced by a compact 'Kunde' link (with open-link icon) on the right of the 'Lieferanschrift' header; the partner name is shown as tooltip only. Limited roles get a static label.
Order tab 'General' column: Organization first, compact German-formatted date row, Tenant as tooltip
- Organization is the first field; Tenant is no longer a field, it shows as a tooltip on hover over Organization - Date Ordered / Date Promised sit side by side in one compact row (smaller label + input, short labels 'Ordered' / 'Promised' with full title); on completed/voided orders the read-only values display as dd.mm.yyyy
Order edit: no stale 'Abort' button when opening an order
- loadOrderData() resets the global useActiveSave flag before loading — a fresh record has nothing unsaved; the flag is shared by every edit page and modal, so a leftover true (e.g. from a dismissed quick-add) showed 'Abort' and blocked navigation on the next order opened from the grid - the Abort button additionally requires the form to be really dirty (activeSave && !noEdit) - OrderQuickAddModal clears the flag when it is closed without saving
Pushed tasks: instruction note, worker actions, take-back and deep-link fix
Rounds out the push-to-mobile-worker flow into one coherent workflow:
- Desktop push modal: the note becomes an instruction for the worker (up to 1000 chars) and is stored as an INTERNAL chat note "[Anweisung] …" — never visible to the merchant and no merchant notification. The modal shows which order/product is linked (rendered on the device with the instruction). - Desktop ticket: a prominent banner while the ticket lives on a device ("An Mobilgerät gepusht · wartet" / "In Arbeit auf dem Mobilgerät · X"), a state pill in the toolbar and a "Zurückholen" button (optional take-back). - New route [id]/push-task-status.post.ts: start → TaskStatus 20 %, return → IsPushedTask cleared (+ internal note, staff toast when from mobile), complete → IsComplete + TaskStatus 100 % + IsPushedTask cleared. push-task sets TaskStatus 0 %; the pending list now includes started tasks (TaskStatus ≠ 0 → "In Arbeit") plus linked order/product. - Mobile ticket modal for a pushed task: instruction card, order/product cards, "Timer starten" (keeps the task in the list as "In Arbeit"), "Arbeit abschließen" (stops the timer, books the time, completes and hands the ticket back to the desktop) and "Zurück an Desktop" (optional reason). Full history collapsed behind "Verlauf anzeigen". - Dashboard banner separates new vs. "in Arbeit" pushed tasks. - Fix: opening a pushed task from the dashboard again after leaving the keep-alive'd tasks page re-showed the state it was left in (wizard step, open list). Deep links are now handled on every activation/query change and reset the page first (a running timer is never discarded).
Same pattern as the order edit toolbar: state chips (open/completed, owner, pushed, accounted) on the left of the action group, the two workflow actions (hand over / take back, close / reopen) as text buttons, and everything else (push to mobile, take back from mobile, accounting flag, PDF report, open full page) grouped in a data-driven "Weitere Aktionen" dropdown — so the bar fits one row in the split view instead of a wall of buttons.
Lead questionnaire: initial-call form (DE/EN) with blank print, draft and PDF attachment
New "Fragebogen" on the lead page (hero CTA + toolbar icon): a catalog-driven questionnaire staff fill in while talking to a prospect — current situation, order volume, articles (barcode, MHD, serials), additional services (customisation, kitting, Amazon Prime/FBA/Pre-FBA), inbound & storage, organisational. German by default, EN toggle switches labels and PDF.
- Question catalog in app/utils/leadQuestionnaire.ts, shared by the modal and the server renderer; adding a question is one entry. - server/utils/offers/questionnairePdf.ts (pdf-lib): blank mode = fillable AcroForm (text fields + check boxes) for hand-written answers, filled mode = drawn values; the free-notes box grows to the rest of its page. - Routes: offers/questionnaire/pdf (render) and offers/questionnaire/save (answers on offer_conditions.questionnaire; with attach: filled PDF attached to Lead_User via Strapi + C_ContactActivity with the answer summary). - Draft save / reopen prefilled, progress chip, conversation meta (date, channel, interviewer). - Leads list: "Leerer Fragebogen" (+ EN) prints the blank form without any lead prefill. - offerConditions section type widened to string (new 'questionnaire' section).
Pushed tasks: refresh the mobile dashboard live on a foreground push
Android hands a foreground FCM message to the app without a tray notification, so a worker sitting on the dashboard saw nothing until they left and came back. The native plugin now refetches the pushed-task list on a 'pushed_task' receipt, plays the warning feedback and shows a toast.
The banner stays sticky at the top but drops the red block: rows are neutral and only priority 1 (Dringend) / 3 (Hoch) carry a tag. The native app adds a 60 s refetch while the dashboard is visible as a safety net for missed pushes. The desktop "Gepusht · wartet" pill uses the blue working-state colours.
Pushed tasks: task modal — back to dashboard, return fix, linked record cards
- Closing a task opened from the dashboard/notification deep link returns to the dashboard (history entry replaced) instead of the new-task wizard. - "Zurück an Desktop" confirmation replaces the action row in place (it used to render below the fold), books a running timer instead of refusing, and hands back to the dashboard afterwards. - Linked product (thumbnail, codes, stock per locator, open shortcut), order (customer, ship-to, lines, shipments) and shipment (status, tracking, lines) are rendered as cards; the pushed-task list now carries M_InOut_ID. - Pinned list uses neutral colours with Dringend/Hoch tags.
Dashboard banner rows and the pinned list now carry the ticket's priority tier on border/background plus a solid tag: 1/3 (Urgent/High) red, 5 (the default) orange, 7/9 (Low/Minor) light blue. Theme tokens only, so dark mode follows automatically.