Mcp/entry bug (#580)
* feat: restore document version supersession on posted entries * refactor: update VAT handling logic to remove dependency on seller registration status * feat: update VAT handling to respect seller registration status across invoice processing * feat: add no-op migration placeholder for document supersession
This commit is contained in:
@@ -67,11 +67,13 @@ describe('getAvailableVatRates', () => {
|
||||
expect(rates).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('collapses to single 0% exempt option when seller is NOT VAT-registered', () => {
|
||||
// ML 1 kap. 1§ — a non-skattskyldig seller may not charge VAT, so the
|
||||
// picker must offer 0% only, regardless of customer type.
|
||||
for (const ct of ['individual', 'swedish_business', 'eu_business', 'non_eu_business'] as const) {
|
||||
const rates = getAvailableVatRates(ct, true, false)
|
||||
it('collapses to 0%/exempt for non-registered sellers regardless of customer type', () => {
|
||||
// ML 1 kap. 1§ — only a skattskyldig may charge output VAT. The picker
|
||||
// must never offer non-zero rates when vat_registered=false; the API
|
||||
// route (route.ts) and preview-pdf route enforce the same gate so a
|
||||
// client bypassing the UI also gets rejected.
|
||||
for (const customerType of ['individual', 'swedish_business', 'eu_business', 'non_eu_business'] as const) {
|
||||
const rates = getAvailableVatRates(customerType, false, false)
|
||||
expect(rates).toHaveLength(1)
|
||||
expect(rates[0]).toEqual({
|
||||
rate: 0,
|
||||
@@ -81,10 +83,10 @@ describe('getAvailableVatRates', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('defaults vatRegistered to true (current behavior preserved)', () => {
|
||||
// Existing callers omit the third arg — they must still see the full
|
||||
// rate set for Swedish customers.
|
||||
const rates = getAvailableVatRates('swedish_business')
|
||||
it('defaults vatRegistered to true (legitimate VAT path)', () => {
|
||||
// Explicit default-arg pin so a future signature change doesn't silently
|
||||
// flip the default and strip VAT from invoices for registered sellers.
|
||||
const rates = getAvailableVatRates('swedish_business', false)
|
||||
expect(rates).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
@@ -177,11 +179,12 @@ describe('getVatRules', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('short-circuits to exempt/0/empty momsRuta when seller is NOT VAT-registered', () => {
|
||||
// ML 1 kap. 1§ — no output VAT, no momsdeklaration row, regardless of
|
||||
// customer type. Verified for all four customer types.
|
||||
for (const ct of ['individual', 'swedish_business', 'eu_business', 'non_eu_business'] as const) {
|
||||
const rules = getVatRules(ct, true, false)
|
||||
it('short-circuits to exempt / rate 0 / empty momsRuta for non-registered sellers', () => {
|
||||
// ML 1 kap. 1§ — non-skattskyldig cannot charge output VAT. momsRuta is
|
||||
// intentionally empty so a downstream momsdeklaration generator never
|
||||
// mis-files a non-registered seller's revenue into ruta 05.
|
||||
for (const customerType of ['individual', 'swedish_business', 'eu_business', 'non_eu_business'] as const) {
|
||||
const rules = getVatRules(customerType, false, false)
|
||||
expect(rules).toEqual({
|
||||
treatment: 'exempt',
|
||||
rate: 0,
|
||||
@@ -190,12 +193,12 @@ describe('getVatRules', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('defaults vatRegistered to true (current behavior preserved)', () => {
|
||||
// Existing callers omit the third arg — they must still see standard_25
|
||||
// for Swedish customers.
|
||||
const rules = getVatRules('swedish_business')
|
||||
it('defaults vatRegistered to true (legitimate VAT path)', () => {
|
||||
// Explicit default-arg pin (see corresponding getAvailableVatRates test).
|
||||
const rules = getVatRules('swedish_business', false)
|
||||
expect(rules.rate).toBe(25)
|
||||
expect(rules.treatment).toBe('standard_25')
|
||||
expect(rules.momsRuta).toBe('05')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -873,7 +873,7 @@ export function InvoicePDF({ invoice, customer, items, company, originalInvoiceN
|
||||
// charge output VAT, so a "Moms 0%" line would imply VAT
|
||||
// accounting that doesn't exist. The notice block below the
|
||||
// payment section explains the absence of VAT.
|
||||
!(company.vat_registered === false && invoice.vat_amount === 0) && (
|
||||
company.vat_registered !== false && (
|
||||
<View style={styles.totalRow}>
|
||||
<Text style={styles.totalLabel}>{L.vatRow(invoice.vat_rate ?? (vatByRate.size === 1 ? (vatByRate.keys().next().value ?? 0) : 0))}</Text>
|
||||
<Text style={styles.totalValue}>{formatCurrency(invoice.vat_amount, invoice.currency, lang)}</Text>
|
||||
@@ -1069,7 +1069,10 @@ export function InvoicePDF({ invoice, customer, items, company, originalInvoiceN
|
||||
don't apply, and a single dedicated notice is clearer for the
|
||||
customer than reusing the exempt notice (which implies the sale
|
||||
specifically is exempt while the seller is otherwise within the
|
||||
VAT system). */}
|
||||
VAT system). Server-side enforcement (api/invoices/route.ts +
|
||||
preview-pdf/route.ts) coerces vat_amount to 0 for non-registered
|
||||
sellers, so this branch always lines up with what's in the
|
||||
totals block. */}
|
||||
{company.vat_registered === false ? (
|
||||
<View style={styles.reverseChargeBox}>
|
||||
<Text style={styles.reverseChargeText}>{L.notVatRegisteredNotice}</Text>
|
||||
|
||||
@@ -15,7 +15,9 @@ export interface VatRateOption {
|
||||
* When the seller is not VAT-registered (`vatRegistered=false`), every customer
|
||||
* type collapses to a single 0% / exempt option. ML 1 kap. 1§ — only a
|
||||
* skattskyldig person may charge VAT, so the picker must never offer non-zero
|
||||
* rates in that mode.
|
||||
* rates in that mode. ML 16 kap. 23 § (faktureringsmoms) imposes liability for
|
||||
* VAT erroneously stated on a document, but does NOT grant the right to charge
|
||||
* it — so we block at source rather than allow + warn.
|
||||
*/
|
||||
export function getAvailableVatRates(
|
||||
customerType: CustomerType,
|
||||
@@ -82,7 +84,8 @@ export interface VatRule {
|
||||
* When the seller is not VAT-registered (`vatRegistered=false`), the rules
|
||||
* short-circuit to `{ treatment: 'exempt', rate: 0, momsRuta: '' }` regardless
|
||||
* of customer type — ML 1 kap. 1§ bars a non-skattskyldig from charging output
|
||||
* VAT. `momsRuta` is empty so the invoice doesn't claim a momsdeklaration row.
|
||||
* VAT. `momsRuta` is empty so a downstream momsdeklaration generator never
|
||||
* mis-files a non-registered seller's "sales" into ruta 05.
|
||||
*/
|
||||
export function getVatRules(
|
||||
customerType: CustomerType,
|
||||
|
||||
@@ -2198,7 +2198,7 @@ async function commitCorrectEntry(
|
||||
// commit-time gate matches the staging-time signal.
|
||||
const { data: original, error: origErr } = await supabase
|
||||
.from('journal_entries')
|
||||
.select('id, status, entry_date, fiscal_period_id, fiscal_periods!inner(is_closed, locked_at)')
|
||||
.select('id, status, entry_date, fiscal_period_id, fiscal_periods!journal_entries_fiscal_period_id_fkey!inner(is_closed, locked_at)')
|
||||
.eq('id', entryId)
|
||||
.eq('company_id', companyId)
|
||||
.maybeSingle()
|
||||
@@ -2281,7 +2281,7 @@ async function commitReverseEntry(
|
||||
// via resolvePeriodStatusForDate, matching the staging-time signal.
|
||||
const { data: original, error: origErr } = await supabase
|
||||
.from('journal_entries')
|
||||
.select('id, status, entry_date, fiscal_period_id, fiscal_periods!inner(is_closed, locked_at)')
|
||||
.select('id, status, entry_date, fiscal_period_id, fiscal_periods!journal_entries_fiscal_period_id_fkey!inner(is_closed, locked_at)')
|
||||
.eq('id', entryId)
|
||||
.eq('company_id', companyId)
|
||||
.maybeSingle()
|
||||
|
||||
Reference in New Issue
Block a user