feat(shopify): port the order sync from the transactions feed to webshop_orders (#1676)

* feat(shopify): port the order sync from the transactions feed to webshop_orders

Shopify orders now land as rich rows on the Orders page (platform
'shopify'), the same surface WooCommerce uses, instead of opaque
bank-feed rows on the 1584 cash account:

- order-sync.ts writes through the shared upsertWebshopOrders service;
  the 1584/ensureManualCashAccount wiring is gone (prod has zero Shopify
  feed rows). Cursor/overlap/dedup, revoked classification and the
  frozen external_id formats are unchanged.
- vat_breakdown is reconstructed from the order-level taxLines
  (net = tax/rate, remainder as a 0%-bucket, refuse on unusable data);
  refund VAT is prorated from the parent order's mix. The line-item
  snapshot is stored only when it reconstructs the charged total to the
  ore, else the invoice conversion falls back to one aggregate line.
- GraphQL query gains createdAt, taxesIncluded, taxLines, lineItems and
  shippingLines (all non-PII; page size 100 -> 25 for query cost).
- Nav gate counts active shopify_connections; the Orders empty-state CTA
  goes to the platform-neutral /import hub; panel/manifest copy now
  points at the Orders page (sv + en).
- Paid-only qualification and the 90-day backfill stay; the
  bookkeeping-lock row filter is dropped (lock is enforced at booking,
  parity with WooCommerce).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(shopify): carry the prorated parent tax on refunds when per-rate bucketing is refused

A refund whose parent vat_breakdown was refused (unreported rates) stored
total_tax 0 and prefilled a 0%-refund with no moms reversal. The parent's
total tax is now prorated into the refund row, so the booking dialog's
ratio-inference fallback presents an editable bucket with the reversal
instead (CodeRabbit + Swedish review + skeptic finding). Adds the
mixed-rate line and truncated shipping-page tests CodeRabbit asked for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-18 10:57:01 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent cfdddb2d7e
commit bc357531cc
17 changed files with 888 additions and 493 deletions
+7 -7
View File
@@ -507,7 +507,7 @@
},
"shopify": {
"title": "Shopify",
"description": "Connect your Shopify store to fetch paid orders and refunds into the transaction inbox, as a bank-style feed for the store. You book the rows from the inbox as usual. Note that an order can mix VAT rates (25/12/6%); split the VAT accordingly when you book the row.",
"description": "Connect your Shopify store to fetch paid orders and refunds to the Orders page every night, with amounts, payment methods and VAT per rate. You book the orders from there.",
"not_configured": "The Shopify integration is not configured on this installation. Contact your administrator.",
"load_failed": "Could not read the Shopify status. Check your connection and try again.",
"action_timeout": "The action took too long. Reload the page to see whether it went through.",
@@ -536,16 +536,16 @@
"sync_now": "Sync now",
"syncing": "Syncing…",
"sync_done_title": "Sync complete",
"sync_done_feed": "{fetched} order(s) fetched: {imported} new rows in the inbox.",
"sync_done_feed": "{fetched} order(s) fetched: {imported} new on the Orders page.",
"sync_done_empty": "The store returned no orders for the period. Check that the right store is connected if you expected orders.",
"sync_done_feed_errors": "{fetched} order(s) fetched: {imported} new rows in the inbox. {errors} row(s) could not be imported: sync again.",
"sync_done_feed_errors": "{fetched} order(s) fetched: {imported} new on the Orders page. {errors} row(s) could not be imported: sync again.",
"sync_partial_title": "Sync paused",
"sync_partial": "{fetched} order(s) fetched so far: {imported} new rows in the inbox.{errors, plural, =0 {} other { # row(s) could not be imported.}} Not all orders were fetched in time: sync again to continue where it stopped.",
"sync_partial": "{fetched} order(s) fetched so far: {imported} new on the Orders page.{errors, plural, =0 {} other { # row(s) could not be imported.}} Not all orders were fetched in time: sync again to continue where it stopped.",
"sync_failed_title": "Sync failed",
"sync_revoked": "The store rejected the app credentials, so no orders could be fetched. Connect the store again.",
"transaction_sync_title": "Orders from Shopify",
"transaction_sync_description": "Fetch the store's paid orders and refunds into the transaction inbox every night, as a bank-style feed for the store. You book the rows from the inbox as usual.",
"transaction_sync_backfill_note": "The first sync fetches up to 90 days of history, but never before the bookkeeping lock.",
"transaction_sync_description": "Fetch the store's paid orders and refunds to the Orders page every night. You book the orders from there.",
"transaction_sync_backfill_note": "The first sync fetches up to 90 days of history.",
"transaction_sync_last_synced": "Last synced {date}",
"transaction_sync_never_synced": "Not synced yet",
"transaction_sync_enabled_toast": "Order sync enabled. History is fetched on the next sync.",
@@ -7089,7 +7089,7 @@
"woocommerce_not_enabled_title": "The WooCommerce extension is not enabled",
"woocommerce_not_enabled_description": "Enable the WooCommerce extension to connect your store and fetch orders automatically.",
"shopify_title": "Shopify",
"shopify_description": "Connect your Shopify store to fetch paid orders and refunds into the transaction inbox.",
"shopify_description": "Connect your Shopify store to fetch paid orders and refunds to the Orders page.",
"shopify_not_enabled_title": "The Shopify extension is not enabled",
"shopify_not_enabled_description": "Enable the Shopify extension to connect your store and fetch orders automatically.",
"migration_title": "Import from another system",
+7 -7
View File
@@ -507,7 +507,7 @@
},
"shopify": {
"title": "Shopify",
"description": "Koppla din Shopify-butik så hämtas betalda ordrar och återbetalningar till transaktionsinkorgen, som ett bankflöde för butiken. Du bokför raderna som vanligt från inkorgen. Observera att en order kan innehålla flera momssatser (25/12/6 %); dela upp momsen därefter när du bokför raden.",
"description": "Koppla din Shopify-butik så hämtas betalda ordrar och återbetalningar till Ordersidan varje natt, med belopp, betalsätt och moms per sats. Du bokför ordrarna därifrån.",
"not_configured": "Shopify-integrationen är inte konfigurerad på den här installationen. Kontakta administratören.",
"load_failed": "Kunde inte läsa Shopify-statusen. Kontrollera din uppkoppling och försök igen.",
"action_timeout": "Åtgärden tog för lång tid. Ladda om sidan för att se om den gick igenom.",
@@ -536,16 +536,16 @@
"sync_now": "Synka nu",
"syncing": "Synkar…",
"sync_done_title": "Synkronisering klar",
"sync_done_feed": "{fetched} order/ordrar hämtade: {imported} nya rader i inkorgen.",
"sync_done_feed": "{fetched} order/ordrar hämtade: {imported} nya på Ordersidan.",
"sync_done_empty": "Butiken returnerade inga ordrar för perioden. Kontrollera att rätt butik är ansluten om du väntade dig ordrar.",
"sync_done_feed_errors": "{fetched} order/ordrar hämtade: {imported} nya rader i inkorgen. {errors} rad(er) kunde inte importeras: synka igen.",
"sync_done_feed_errors": "{fetched} order/ordrar hämtade: {imported} nya på Ordersidan. {errors} rad(er) kunde inte importeras: synka igen.",
"sync_partial_title": "Synkroniseringen pausades",
"sync_partial": "{fetched} order/ordrar hämtade hittills: {imported} nya rader i inkorgen.{errors, plural, =0 {} other { # rad(er) kunde inte importeras.}} Alla ordrar hann inte hämtas: synka igen för att fortsätta där det stannade.",
"sync_partial": "{fetched} order/ordrar hämtade hittills: {imported} nya på Ordersidan.{errors, plural, =0 {} other { # rad(er) kunde inte importeras.}} Alla ordrar hann inte hämtas: synka igen för att fortsätta där det stannade.",
"sync_failed_title": "Synkroniseringen misslyckades",
"sync_revoked": "Butiken avvisade appens uppgifter, så inga ordrar kunde hämtas. Anslut butiken igen.",
"transaction_sync_title": "Ordrar från Shopify",
"transaction_sync_description": "Hämta butikens betalda ordrar och återbetalningar till transaktionsinkorgen varje natt, som ett bankflöde för butiken. Du bokför raderna som vanligt från inkorgen.",
"transaction_sync_backfill_note": "Vid första synkningen hämtas upp till 90 dagars historik, dock inte före bokföringslåset.",
"transaction_sync_description": "Hämta butikens betalda ordrar och återbetalningar till Ordersidan varje natt. Du bokför ordrarna därifrån.",
"transaction_sync_backfill_note": "Vid första synkningen hämtas upp till 90 dagars historik.",
"transaction_sync_last_synced": "Senast synkad {date}",
"transaction_sync_never_synced": "Inte synkad ännu",
"transaction_sync_enabled_toast": "Ordersynk aktiverad. Historiken hämtas vid nästa synkning.",
@@ -7089,7 +7089,7 @@
"woocommerce_not_enabled_title": "WooCommerce-tillägget är inte aktiverat",
"woocommerce_not_enabled_description": "Aktivera tillägget WooCommerce för att koppla din butik och hämta ordrar automatiskt.",
"shopify_title": "Shopify",
"shopify_description": "Koppla din Shopify-butik så hämtas betalda ordrar och återbetalningar till transaktionsinkorgen.",
"shopify_description": "Koppla din Shopify-butik så hämtas betalda ordrar och återbetalningar till Ordersidan.",
"shopify_not_enabled_title": "Shopify-tillägget är inte aktiverat",
"shopify_not_enabled_description": "Aktivera tillägget Shopify för att koppla din butik och hämta ordrar automatiskt.",
"migration_title": "Hämta från annat system",