* 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>
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { serverErrorMessage, syncSummary, type ShopifySyncPayload } from '../lib/settings-actions'
|
|
|
|
describe('syncSummary', () => {
|
|
const base = { fetched: 5, refundsFetched: 1, inserted: 4, updated: 1, unchanged: 0, errors: 0 }
|
|
|
|
it('classifies a revoked run before anything else', () => {
|
|
expect(syncSummary({ transactions: { ...base, revoked: true } })).toEqual({
|
|
reason: 'revoked',
|
|
})
|
|
})
|
|
|
|
it('classifies a truncated run as partial, keeping the error count', () => {
|
|
expect(
|
|
syncSummary({ transactions: { ...base, errors: 2, deadlineReached: true } }),
|
|
).toEqual({ reason: 'partial', values: { fetched: 5, imported: 4, errors: 2 } })
|
|
})
|
|
|
|
it('classifies an empty window as its own outcome', () => {
|
|
expect(
|
|
syncSummary({ transactions: { ...base, fetched: 0, inserted: 0 } }),
|
|
).toEqual({ reason: 'empty' })
|
|
})
|
|
|
|
it('reports both halves when rows landed and rows failed', () => {
|
|
expect(syncSummary({ transactions: { ...base, errors: 2 } })).toEqual({
|
|
reason: 'errors',
|
|
values: { fetched: 5, imported: 4, errors: 2 },
|
|
})
|
|
})
|
|
|
|
it('classifies a clean run as feed', () => {
|
|
expect(syncSummary({ transactions: base })).toEqual({
|
|
reason: 'feed',
|
|
values: { fetched: 5, imported: 4 },
|
|
})
|
|
})
|
|
|
|
it('classifies an unreadable body as unknown', () => {
|
|
expect(syncSummary(null)).toEqual({ reason: 'unknown' })
|
|
expect(syncSummary({} as ShopifySyncPayload)).toEqual({ reason: 'unknown' })
|
|
expect(syncSummary({ transactions: {} })).toEqual({ reason: 'unknown' })
|
|
})
|
|
})
|
|
|
|
describe('serverErrorMessage', () => {
|
|
it('prefers route copy over the generic map', () => {
|
|
expect(serverErrorMessage({ error: 'Ingen ansluten Shopify-butik.' }, 404, 'sv')).toBe(
|
|
'Ingen ansluten Shopify-butik.',
|
|
)
|
|
})
|
|
|
|
it('prefers the English variant when present and the locale is en', () => {
|
|
expect(
|
|
serverErrorMessage({ error: 'Fel.', error_en: 'An error.' }, 500, 'en'),
|
|
).toBe('An error.')
|
|
})
|
|
})
|