* feat(mcp): article-aware invoice updates with gnubok_get_invoice round trip and rebooking preview gnubok_update_invoice items are a FULL REPLACE, had no article fields, and no MCP tool returned invoice lines, so a quantity fix rebuilt from memory wrote article_id/revenue_account null and reverted vat_rate to the customer default: revenue silently moved from the article account (3041) to the VAT-derived default, invisible in the approval preview. - gnubok_get_invoice (invoices:read, search-only): header plus every line with article_id, revenue_account, vat_rate, dimensions, editable_draft - gnubok_update_invoice lines accept article_id with the same prefill and default-set VAT adoption guard as create; permitted-set VAT gate at staging; preview carries the new lines' effective booking and a snapshot of the lines being replaced - commitUpdateInvoice scope-checks staged article ids like create does - OperationPreview: update_invoice preview (current vs new lines, header diffs, totals); create_invoice lines show VAT rate and posting account - invoicing skill points at the read-before-replace round trip Closes #1642 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkUfWtuFCUkNtRAgMQCse2 * fix(mcp): use roundOre for update-invoice preview totals so the ore ratchet stays at baseline The preview-building code in gnubok_update_invoice introduced five naive Math.round(x * 100) / 100 occurrences, tripping check:guards (naive-ore-round 627 vs baseline 622) and failing Core Build on PR #1993. roundOre from @/lib/money is the sanctioned helper and was already imported in this file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkUfWtuFCUkNtRAgMQCse2 * fix(mcp): make the invoice round trip lossless for text, ROT/RUT and accrual lines Skeptic review of #1993 found three round-trip breaks for web-created drafts edited via MCP (the exact silent-loss class issue #1642 reports): - Text rows: the update pre-gate and resolveInvoiceLineFromArticle rejected quantity <= 0 before looking at line_type, so any draft with a free-text spacer row could not be edited at all, and the natural agent recovery (drop the row and retry the FULL REPLACE) silently deleted invoice content. Text rows are now exempt from the quantity/description/unit/price gates (CreateInvoiceItemSchema parity), normalized to the zeroed stored shape, excluded from the staged totals and the VAT gate (commitCreateInvoice billableItems parity), and line_type is declared on both the create and update item schemas. - ROT/RUT: gnubok_get_invoice omitted housing_designation, apartment_number and brf_org_number, so an items replace on a ROT draft either failed AFTER approval ('Fastighetsbeteckning krävs för ROT-avdrag') or, for a schema-conformant agent, silently stripped the avdrag and the stored personnummer. The three property columns (property identifiers, never the personnummer ciphertext) are now returned per line, the deduction fields are declared on the update item schema, deduction_type rides on the current_items snapshot and the new-lines preview, and a staging-time completeness gate (arbetstyp/timmar via validateDeductionLines, fastighetsbeteckning for ROT, personnummer availability on the invoice or the individual's kundkort) surfaces the failure to the agent instead of the approver. - Declared-schema gap: revenue_account and the accrual fields were accepted on pass-through but undeclared, so a schema-conformant agent dropped a manual posting-account override or a periodisering on pass-back. They are now declared on the update item schema (revenue_account also on create; create deliberately does NOT declare deduction/accrual fields because commitCreateInvoice drops them), and the approval preview shows ROT/RUT-avdrag and the periodisering period per line. tools/list ceiling check after the two new create-schema properties: 63337 of 63400. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkUfWtuFCUkNtRAgMQCse2 --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
156 lines
8.9 KiB
TypeScript
156 lines
8.9 KiB
TypeScript
import type { Skill } from './types'
|
|
|
|
const body = `# Invoicing Rules: Accounted
|
|
|
|
How to send a Swedish-compliant invoice from start to finish.
|
|
|
|
## When to use
|
|
|
|
- "Skicka faktura till [kund]"
|
|
- "Invoice [customer] for [amount]"
|
|
- "Create a credit note"
|
|
- "How do I invoice an EU customer?"
|
|
|
|
## Mandatory invoice fields (ML 17 kap. 24 §)
|
|
|
|
Every Swedish invoice (faktura) must contain:
|
|
|
|
1. **Datum för utfärdande** (issue date)
|
|
2. **Löpnummer** (sequential invoice number: system-assigned at approval)
|
|
3. **Säljarens momsregistreringsnummer** (seller's VAT number)
|
|
4. **Köparens momsregistreringsnummer** (for EU B2B; otherwise name + address)
|
|
5. **Säljarens fullständiga namn och adress**
|
|
6. **Köparens fullständiga namn och adress**
|
|
7. **Mängd och slag av varor / omfattning av tjänster**
|
|
8. **Datum då varorna levererats / tjänsterna utförts** (if different from invoice date)
|
|
9. **Beskattningsunderlag per momssats**
|
|
10. **Tillämpad momssats**
|
|
11. **Momsbelopp**
|
|
12. **Vid omvänd betalningsskyldighet:** notation "omvänd betalningsskyldighet" or "reverse charge"
|
|
13. **Vid undantag:** referens till relevant ML-paragraph or article in Direktivet
|
|
14. **F-skatt / FA-skatt notation** ("Innehar F-skattsedel" or "F-skattebevis") for B2B services
|
|
|
|
The \`gnubok_create_invoice\` tool handles all of these automatically, but always provide \`our_reference\`/\`your_reference\` if known.
|
|
|
|
## Workflow
|
|
|
|
### Step 1: Customer ready
|
|
|
|
Customers with their full data already in the system: \`gnubok_list_customers\`. Find the one. Note the \`customer_id\`.
|
|
|
|
If the customer doesn't exist:
|
|
|
|
\`gnubok_create_customer\` with at minimum \`{ name, customer_type }\`. \`customer_type\` must be one of:
|
|
|
|
- \`individual\`: physical person
|
|
- \`swedish_business\`: AB / HB / KB / EF with Swedish org-number
|
|
- \`eu_business\`: EU company. **Provide \`vat_number\`** so VIES validation runs (otherwise reverse-charge eligibility fails).
|
|
- \`non_eu_business\`: outside EU
|
|
|
|
### Step 2: Determine VAT treatment
|
|
|
|
| Customer | VAT treatment | Default rate |
|
|
|----------|---------------|--------------|
|
|
| Swedish individual | \`standard_25\` (or 12/6 by goods) | 25 % |
|
|
| Swedish business | \`standard_25\` | 25 % |
|
|
| EU business with valid VAT number | \`reverse_charge\` | 0 % (with notation) |
|
|
| EU business without VAT number | \`standard_25\` | 25 % (treat as B2C) |
|
|
| Non-EU business / private | \`export\` | 0 % |
|
|
| Books, newspapers, transport | \`reduced_6\` | 6 % |
|
|
| Restaurant, hotel | \`reduced_12\` | 12 %: see footnote below |
|
|
|
|
**Footnote on the 1 April 2026 livsmedel rate change** (Prop. 2025/26:55):
|
|
|
|
- **Livsmedel sold in other forms** (grocery, takeaway sold by retailer, etc.) drops from 12 % → **6 %** from 1 April 2026.
|
|
- **Restaurang och servering** (sit-down food and beverage service) **stays at 12 %** even after 1 April 2026.
|
|
- Hotels: room nights remain at 12 %; on-site restaurant service is restaurang (12 %); minibar / shop is sale of varor (6 % if food, 25 % otherwise).
|
|
|
|
When in doubt for an invoice issued on or after 1 April 2026, classify the supply per the above rather than defaulting to one rate for "restaurang/hotell".
|
|
|
|
Use \`getAvailableVatRates(customerType, vatNumberValidated)\` semantics: Accounted handles this. Per-line override possible via \`vat_rate\` on each item.
|
|
|
|
### Step 3: Create the invoice
|
|
|
|
\`gnubok_create_invoice({ customer_id, items: [{ description?, quantity, unit?, unit_price?, vat_rate?, article_id? }], invoice_date?, due_date?, currency? })\`
|
|
|
|
Set \`article_id\` (from \`gnubok_list_articles\`) to invoice a catalog article: it prefills description, unit, unit_price and revenue account from the article, and any value set on the line wins. Without \`article_id\`, description, unit and unit_price are required. The article's stored \`vat_rate\` is a DOMESTIC rate: it is adopted only when it is in the customer's default VAT set, so a reverse-charge or export customer keeps 0% unless the line sets \`vat_rate\` explicitly (taxed-where-performed supplies like hotel nights).
|
|
|
|
Returns staged operation. User approves in web app → invoice number is allocated atomically (gap-free) and journal entry posted (under accrual / faktureringsmetoden).
|
|
|
|
### Step 4: Send
|
|
|
|
\`gnubok_send_invoice(invoice_id)\`: emails the PDF to the customer. Requires email service configured (Resend) and customer email on file.
|
|
|
|
If the user delivered the invoice manually (printed or sent through an external e-invoice provider), use \`gnubok_mark_invoice_as_sent\` instead: same booking effect, no email.
|
|
|
|
### Step 5: Record payment
|
|
|
|
When money arrives in 1930:
|
|
|
|
- **Match to bank transaction** (preferred): \`gnubok_match_transaction_to_invoice({ transaction_id, invoice_id })\`: links the payment, marks invoice paid (or partially_paid), books JE.
|
|
- **Manual mark**: \`gnubok_mark_invoice_as_paid({ invoice_id, payment_date })\`: when payment arrived but isn't in the bank feed yet.
|
|
|
|
### Step 6: Reverse if needed
|
|
|
|
If the invoice was wrong: \`gnubok_credit_invoice({ invoice_id, reason })\` creates a \`KR-\` mirror invoice with negated amounts and reverses the original JE. Original status → \`credited\`. **Never edit a sent invoice**: kreditfaktura is the only legal path.
|
|
|
|
The kreditfaktura **itself** consumes a sequential number from the same (or a dedicated KR-) fakturaserie per BFL 5 kap. 6-7 § / ML 17 kap. 22-23 §. The \`KR-\` prefix is a display convention; the underlying löpnummer must be unbroken just like the regular invoice series. \`gnubok_credit_invoice\` allocates this atomically at approval: agents shouldn't try to set or skip the number manually.
|
|
|
|
## ROT/RUT (consumer services)
|
|
|
|
For consumer-targeted services (RUT: städning, RUT) or construction (ROT):
|
|
|
|
- Use \`fakturamodellen\` (the customer pays the discounted amount; you reclaim the rest from Skatteverket)
|
|
- Customer must have **personnummer** (or coordination number) on file
|
|
- Add the property's **fastighetsbeteckning** (real estate ID) for ROT
|
|
- **RUT**: 50 % deduction, max 75 000 SEK/year/person (2025).
|
|
- **ROT**: rate and ceiling have shifted year by year: verify against Skatteverket for the invoice date before applying:
|
|
- **Standard rate**: 30 %, max 50 000 SEK/year/person.
|
|
- **2024 H2 (1 Jul to 31 Dec 2024)**: temporary doubled ceiling, separate caps applied.
|
|
- **2025 May-Dec**: enhanced 50 % rate (still 50 000 SEK ceiling). Reverts to 30 % from 2026 unless extended.
|
|
- When in doubt for an invoice issued in May 2025 or later, default to the current Skatteverket-published rate rather than the 30 % baseline.
|
|
|
|
This data goes on the invoice; Accounted's invoice template renders it automatically when set on the customer.
|
|
|
|
## External e-invoicing (including B2G)
|
|
|
|
Accounted currently creates PDF invoices and can send them by email. It does not generate e-invoice XML or deliver invoices through Peppol. If the customer requires an e-invoice, deliver it through an external e-invoice provider, then use \`gnubok_mark_invoice_as_sent\` to record the delivery and apply the same booking effect without sending another email.
|
|
|
|
## Critical rules
|
|
|
|
- **Invoice numbers are sequential and gap-free.** Allocated atomically at approval. If you change your mind, use \`gnubok_credit_invoice\`, never delete or skip a number: Skatteverket will audit.
|
|
- **F-skatt notation is mandatory** for B2B services. Accounted adds it automatically when company settings have F-skatt = true.
|
|
- **Currency:** SEK is default but the invoice itself can be issued in any of SEK/EUR/USD/GBP/NOK/DKK. The bookkeeping JE is always in SEK at issue-date Riksbanken rate.
|
|
|
|
## Common errors
|
|
|
|
- **EU customer charged 25 %**: missing \`vat_number\` or VIES validation failed. Fix: re-validate, then re-issue as \`reverse_charge\`.
|
|
- **Sent before approval**: not possible: \`gnubok_send_invoice\` stages too. The user must approve.
|
|
- **Edit instead of credit**: blocked by DB triggers. Use \`gnubok_credit_invoice\`.
|
|
|
|
## Tools
|
|
|
|
- \`gnubok_list_customers\` / \`gnubok_create_customer\`: customer setup
|
|
- \`gnubok_create_invoice\`: stage new invoice
|
|
- \`gnubok_send_invoice\`: email PDF
|
|
- \`gnubok_mark_invoice_as_sent\`: manual delivery
|
|
- \`gnubok_mark_invoice_as_paid\`: manual payment
|
|
- \`gnubok_match_transaction_to_invoice\`: link bank payment
|
|
- \`gnubok_credit_invoice\`: kreditfaktura (legal undo)
|
|
- \`gnubok_convert_invoice\`: proforma → real invoice
|
|
- \`gnubok_list_invoices\`: find existing invoices
|
|
- \`gnubok_get_invoice\`: one invoice with its lines (article_id, revenue_account, vat_rate); read it before editing
|
|
- \`gnubok_update_invoice\`: edit a draft; \`items\` is a FULL REPLACE, so pass every line back (with \`article_id\`) from \`gnubok_get_invoice\`
|
|
`
|
|
|
|
export const invoicingRulesSkill: Skill = {
|
|
slug: 'invoicing-rules',
|
|
name: 'Invoicing Rules',
|
|
summary: 'Mandatory invoice fields (ML 17 kap. 24 §), VAT treatment, ROT/RUT, external e-invoicing, kreditfaktura.',
|
|
tags: ['invoicing', 'vat', 'compliance', 'eu', 'rot-rut'],
|
|
body,
|
|
tier: 'workflow',
|
|
// Universal: both AB and EF send invoices.
|
|
applicability: { entity_type: 'both' },
|
|
}
|