* fix(mcp): offer the link tool in the uncategorized-transactions VAT blocker The gnubok_vat_close_check blocker hint only named categorize/auto-match, both of which create new bookkeeping. For a transaction whose affarshandelse is already booked on an existing verifikat, following the hint would double-book, so agents dead-ended the case into "contact support" (2026-08-06 support mail from Orto Engineering). The hint now also names gnubok_link_transaction_to_journal_entry, is extracted as an exported constant pinned by a test, and the tool joins the categorize_month recommended loadout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(invoices): quarterly, half-yearly and yearly recurring schedules User request: recurring invoice schedules only supported monthly cadence. Adds interval_months (SMALLINT 1-12, default 1) to recurring_invoice_schedules; the UI offers manadsvis/kvartalsvis/ halvarsvis/arsvis presets while API and MCP accept any 1-12. The cron advances next_run_date by whole intervals from the due date, and the new rollNextRunDateForward() helper rolls missed or edited interval schedules on their own month grid so a quarterly Jan/Apr/Jul/Oct schedule missed in an outage rolls Jan 15 to Apr 15, never Feb 15. Monthly (interval 1) keeps its existing today-anchored recompute semantics unchanged. Changing the interval alone never touches next_run_date: the new cadence applies from the next run, so an edit can never pull a send earlier. Existing rows default to 1 and behave byte-identically. The MCP slice of this feature (interval_months on the three recurring-schedule tools in server.ts) was committed in d2600907f alongside the VAT-blocker hint fix by a parallel session sharing this worktree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(invoices): address PR #1438 review findings CodeRabbit round 1, all three findings: - MCP descriptions now state the full accepted interval range (any integer 1-12) instead of enumerating only the 1/3/6/12 presets, and qualify that changing ONLY interval_months leaves next_run_date untouched. - assertValidCadence rejects fractional day_of_month. - rollNextRunDateForward rejects calendar-invalid anchors that pass the shape regex (2026-13-05, 2026-02-31), with regression tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
84 lines
3.4 KiB
TypeScript
84 lines
3.4 KiB
TypeScript
/**
|
|
* Unit tests for gnubok_vat_close_check.
|
|
*
|
|
* Covers tool registration, scope mapping, the pure Skatteverket deadline math,
|
|
* and the basic output shape. The full multi-query integration is tested via
|
|
* the manual MCP smoke test described in the plan; mocking every chained
|
|
* supabase call here would couple tests to internal query order.
|
|
*/
|
|
import { describe, it, expect } from 'vitest'
|
|
import { tools, computeMomsDeadline, UNCATEGORIZED_TRANSACTIONS_HINT } from '../server'
|
|
import { TOOL_SCOPE_MAP } from '@/lib/auth/api-keys'
|
|
|
|
describe('gnubok_vat_close_check', () => {
|
|
it('is registered in the tools array', () => {
|
|
const tool = tools.find((t) => t.name === 'gnubok_vat_close_check')
|
|
expect(tool).toBeDefined()
|
|
expect(tool?.annotations.readOnlyHint).toBe(true)
|
|
expect(tool?.annotations.idempotentHint).toBe(true)
|
|
expect(tool?.annotations.destructiveHint).toBe(false)
|
|
})
|
|
|
|
it('has the required input schema', () => {
|
|
const tool = tools.find((t) => t.name === 'gnubok_vat_close_check')!
|
|
const schema = tool.inputSchema as { required?: string[]; properties?: Record<string, unknown> }
|
|
expect(schema.required).toEqual(['period_type', 'year', 'period'])
|
|
expect(schema.properties).toHaveProperty('period_type')
|
|
expect(schema.properties).toHaveProperty('year')
|
|
expect(schema.properties).toHaveProperty('period')
|
|
})
|
|
|
|
it('declares an output schema with all the intent fields', () => {
|
|
const tool = tools.find((t) => t.name === 'gnubok_vat_close_check')!
|
|
const schema = tool.outputSchema as { required?: string[] }
|
|
expect(schema.required).toContain('rutor')
|
|
expect(schema.required).toContain('payment')
|
|
expect(schema.required).toContain('blockers')
|
|
expect(schema.required).toContain('sanity')
|
|
expect(schema.required).toContain('ready_to_close')
|
|
expect(schema.required).toContain('summary')
|
|
})
|
|
|
|
it('is mapped to reports:read scope', () => {
|
|
expect(TOOL_SCOPE_MAP.gnubok_vat_close_check).toBe('reports:read')
|
|
})
|
|
|
|
it('uncategorized-transactions hint offers both resolution paths', () => {
|
|
// The blocker must not steer agents into double-booking: a transaction
|
|
// whose affärshändelse is already booked needs the link tool, not a new
|
|
// booking via categorize/auto-match. An agent that only sees the booking
|
|
// tools concludes linking requires support intervention.
|
|
expect(UNCATEGORIZED_TRANSACTIONS_HINT).toContain('gnubok_categorize_transaction')
|
|
expect(UNCATEGORIZED_TRANSACTIONS_HINT).toContain('gnubok_auto_match_period')
|
|
expect(UNCATEGORIZED_TRANSACTIONS_HINT).toContain('gnubok_link_transaction_to_journal_entry')
|
|
})
|
|
})
|
|
|
|
describe('computeMomsDeadline', () => {
|
|
it('monthly: March 2026 → 12 April 2026', () => {
|
|
const d = computeMomsDeadline('monthly', 2026, 3)
|
|
expect(d?.date).toBe('2026-04-12')
|
|
expect(d?.label).toBe('12 april 2026')
|
|
})
|
|
|
|
it('monthly: December rolls into next year', () => {
|
|
const d = computeMomsDeadline('monthly', 2026, 12)
|
|
expect(d?.date).toBe('2027-01-12')
|
|
})
|
|
|
|
it('quarterly: Q1 2026 → 26 April 2026', () => {
|
|
const d = computeMomsDeadline('quarterly', 2026, 1)
|
|
expect(d?.date).toBe('2026-04-26')
|
|
})
|
|
|
|
it('quarterly: Q4 2026 → 26 January 2027', () => {
|
|
const d = computeMomsDeadline('quarterly', 2026, 4)
|
|
expect(d?.date).toBe('2027-01-26')
|
|
})
|
|
|
|
it('yearly: 2026 → 26 February 2027', () => {
|
|
const d = computeMomsDeadline('yearly', 2026, 1)
|
|
expect(d?.date).toBe('2027-02-26')
|
|
})
|
|
})
|