diff --git a/extensions/general/mcp-server/__tests__/connect-links.test.ts b/extensions/general/mcp-server/__tests__/connect-links.test.ts index 059f5f8c..36d2900e 100644 --- a/extensions/general/mcp-server/__tests__/connect-links.test.ts +++ b/extensions/general/mcp-server/__tests__/connect-links.test.ts @@ -74,6 +74,18 @@ describe('onboarding connect-link tools', () => { expect(result.connect_url).toBe('https://app.example.test/import?mode=psd2') }) + it('bank: nudges the agent to ask for the bank when none was passed, and not when one was', async () => { + const bare = (await bankTool.execute({}, COMPANY_ID, 'user-1', { + from: listClient([]).from, + } as never)) as Record + expect(bare.instructions).toContain('BETTER LINK AVAILABLE') + + const named = (await bankTool.execute({ bank: 'Swedbank' }, COMPANY_ID, 'user-1', { + from: listClient([]).from, + } as never)) as Record + expect(named.instructions).not.toContain('BETTER LINK AVAILABLE') + }) + it('bank: reports an active connection', async () => { const { from } = listClient([ { id: 'c1', bank_name: 'Swedbank', status: 'active', created_at: '2026-08-01T00:00:00Z' }, diff --git a/extensions/general/mcp-server/__tests__/create-company.test.ts b/extensions/general/mcp-server/__tests__/create-company.test.ts index b31e7f96..8d182124 100644 --- a/extensions/general/mcp-server/__tests__/create-company.test.ts +++ b/extensions/general/mcp-server/__tests__/create-company.test.ts @@ -118,6 +118,35 @@ describe('gnubok_create_company', () => { expect((result.next as Record).tool).toBe('gnubok_load_skill') }) + it('orders history import before the bank when the fiscal period started over 90 days ago', async () => { + // The default calendar-year setup starts 1 January: from February on the + // period has >90 days of history the bank cannot deliver, so the created + // result must point at SIE-first ordering. Frozen mid-year so the test + // does not flip in January. + vi.useFakeTimers() + vi.setSystemTime(new Date(`${new Date().getFullYear()}-08-15T12:00:00Z`)) + try { + const supabase = supabaseWithTeam(TEAM_ID) + mocks.createCompanyCore.mockImplementation( + async (_client: unknown, _input: unknown, createRow: () => Promise<{ data: unknown; error: unknown }>) => { + const { data } = await createRow() + return { companyId: data as string } + } + ) + const result = (await tool.execute( + { ...setup, confirm: true }, + '', + 'user-1', + supabase as never + )) as Record + + expect(result.history_note).toContain('gnubok_sie_preflight') + expect(result.message).toContain('IN ORDER') + } finally { + vi.useRealTimers() + } + }) + it('refuses a VAT-registered company without a moms period before touching the database', async () => { const supabase = supabaseWithTeam(TEAM_ID) await expect( diff --git a/extensions/general/mcp-server/__tests__/lookup-company.test.ts b/extensions/general/mcp-server/__tests__/lookup-company.test.ts index 5a021c8c..af2a3838 100644 --- a/extensions/general/mcp-server/__tests__/lookup-company.test.ts +++ b/extensions/general/mcp-server/__tests__/lookup-company.test.ts @@ -86,6 +86,10 @@ describe('gnubok_lookup_company', () => { // accounting_method defaults by form in create_company (flagged in its // preview) and is deliberately not a question here. expect(ask.some((q) => q.startsWith('accounting_method'))).toBe(false) + // The two flow questions (E2E #5): bank for the direct consent link, + // previous system for the import-before-bank ordering. + expect(ask.some((q) => q.startsWith('bank ('))).toBe(true) + expect(ask.some((q) => q.startsWith('previous_system'))).toBe(true) // Registry facts are confirmed, never re-asked. expect(ask.some((q) => q.startsWith('entity_type'))).toBe(false) expect(ask.some((q) => q.startsWith('vat_registered'))).toBe(false) diff --git a/extensions/general/mcp-server/server.ts b/extensions/general/mcp-server/server.ts index a155c0d9..9698bbc3 100644 --- a/extensions/general/mcp-server/server.ts +++ b/extensions/general/mcp-server/server.ts @@ -3211,6 +3211,16 @@ export const tools: McpTool[] = [ // accounting_method is deliberately NOT in this list: gnubok_create_company // defaults it by form (AB accrual, EF cash) and flags the default in the // preview, where the user confirms or overrides it in the same "ja". + // + // Two flow questions the agent must ask in the SAME round (E2E #5: the + // agent skipped both and the user landed on a generic bank picker with + // no history import): + stillToAsk.push( + "bank (vilken bank har företaget? pass it to gnubok_connect_bank as bank so the connect link opens that bank's consent directly)" + ) + stillToAsk.push( + 'previous_system (har bokföringen legat i ett annat system? if yes: SIE import comes FIRST, before any bank connect: PSD2 history rarely covers the fiscal year)' + ) const startMonth = parseStartMonthDay(lookup.fiscalYear?.startMonthDay) // No closed fiscal period in the registry = no annual report filed yet @@ -3382,15 +3392,30 @@ export const tools: McpTool[] = [ throw Object.assign(new Error(result.error), { code }) } + // A fiscal period that started well before today means bookkeeping + // already happened somewhere: history import comes BEFORE the bank + // (PSD2 reaches ~90 days back; the rest only arrives via SIE). + const periodStartMs = Date.parse(plan.fiscalPeriod.startDate) + const daysOfHistory = Number.isFinite(periodStartMs) + ? Math.floor((Date.now() - periodStartMs) / 86_400_000) + : 0 + const historyFirst = daysOfHistory > 90 + return { created: true, company_id: result.companyId, ...preview, trial: 'A 30-day trial with every paid capability (bank sync, Skatteverket, AI, e-mail) is active from now.', - message: - 'Company created and ready for bookkeeping. This connection uses it automatically from the next call. Remaining setup: bank connection, Skatteverket connection, and the first transactions.', + ...(historyFirst + ? { + history_note: `The fiscal period started ${daysOfHistory} days ago but bank PSD2 history reaches ~90 days: ask which system the bookkeeping lived in and run the SIE import (gnubok_sie_preflight) BEFORE connecting the bank.`, + } + : {}), + message: historyFirst + ? 'Company created; this connection uses it automatically from the next call. Remaining setup IN ORDER: (1) import existing bookkeeping via SIE (see history_note), (2) bank connection (pass bank=), (3) Skatteverket.' + : 'Company created and ready for bookkeeping. This connection uses it automatically from the next call. Remaining setup: bank connection (pass bank= for a direct consent link), Skatteverket connection, and the first transactions.', next: { - description: 'Load the onboarding skill for the remaining setup steps (bank, Skatteverket, first transactions).', + description: 'Load the onboarding skill for the remaining setup steps (history import, bank, Skatteverket, first transactions).', tool: 'gnubok_load_skill', args: { slug: 'onboarding' }, }, @@ -3458,7 +3483,10 @@ export const tools: McpTool[] = [ instructions: active.length > 0 ? 'At least one bank is connected and syncing. To add another bank, give the user the connect_url.' - : 'On claude.ai/Claude Desktop a connect card with an open-in-browser button is rendered with this result; on other clients give the user the connect_url as a link. They must be logged in to Accounted there. With bank passed, the link starts that bank\'s consent directly; otherwise they pick the bank first. They approve with BankID (consent up to 180 days), then CONFIRM WHICH ACCOUNTS to sync in the dialog that opens; the first transactions arrive within a minute of that save. Banks cap PSD2 history (often ~90 days): older history comes via SIE import, not the bank. When the user is back, call this tool again to verify status=active, then continue straight to gnubok_list_uncategorized_transactions without asking.', + : (requestedBank + ? '' + : 'BETTER LINK AVAILABLE: if you know (or can ask) which bank the company uses, call this tool again with bank=; the link then opens that bank\'s consent directly instead of a picker. ') + + 'On claude.ai/Claude Desktop a connect card with an open-in-browser button is rendered with this result; on other clients give the user the connect_url as a link. They must be logged in to Accounted there. They approve with BankID (consent up to 180 days), then CONFIRM WHICH ACCOUNTS to sync in the dialog that opens; the first transactions arrive within a minute of that save. Banks cap PSD2 history (often ~90 days): older history comes via SIE import, not the bank. When the user is back, call this tool again to verify status=active, then continue straight to gnubok_list_uncategorized_transactions without asking.', } }, },