* feat(mcp): ask the bank first: connect link deep-starts the named bank's consent The connect card used to open the generic picker page; the user then chose the bank there. The agent now asks 'vilken bank har företaget?' among the opening questions and passes it to gnubok_connect_bank, whose connect_url becomes /import?mode=psd2&bank=<name>. BankSelector resolves the name (exact, then unique prefix, then unique substring: ambiguous names fall back to the prefilled picker rather than guessing an institution) and auto-starts that bank's consent through the same onConnect handler, so the duplicate-pending and renew-instead guards stay fully interactive. The param is stripped via history.replaceState after the one-shot so an aborted bank flow plus back-navigation does not silently relaunch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): first-year suggestion keeps the AB vs enskild firma distinction Compliance-review finding on #1949: the suggestion text collapsed both forms onto a 31 December end. Only an enskild firma's first year MUST end 31 December; an AB may pick any end within BFL 3 kap 3 §'s 18-month cap, with 31 December as the common default. The lookup tool's still_to_ask line and the skill now say so explicitly, and first-year-defaults documents that fiscalYear-null is a strong-not-perfect filed-report signal that must only ever feed confirm-question suggestions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
/**
|
|
* First-fiscal-year defaults derived from TIC lookup data.
|
|
*
|
|
* Extracted from WelcomeOnboarding so both the wizard and the journey
|
|
* onboarding can share them (dev_docs/onboarding_migration_plan.md, PR A).
|
|
*/
|
|
|
|
/**
|
|
* Parse TIC v2's `startMonthDay` ("MM-DD": e.g. "07-01") into a month
|
|
* number 1-12. Returns null on missing / malformed input so the caller
|
|
* can fall through to the manual picker default.
|
|
*/
|
|
export function parseStartMonthDay(value: string | null | undefined): number | null {
|
|
if (!value) return null
|
|
const match = /^(\d{1,2})-\d{1,2}$/.exec(value)
|
|
if (!match) return null
|
|
const month = Number(match[1])
|
|
if (!Number.isInteger(month) || month < 1 || month > 12) return null
|
|
return month
|
|
}
|
|
|
|
/**
|
|
* Derive the first-year defaults from TIC's `registrationDate`.
|
|
* A company is treated as "first year" when registered less than 12 months
|
|
* ago, or less than 18 months ago when the registry shows NO closed fiscal
|
|
* period (`noClosedPeriod`): a company that has never filed an annual
|
|
* report is still in its first räkenskapsår, and BFL 3 kap 3 § allows that
|
|
* first year to run up to 18 months (no minimum). The 12-month floor alone
|
|
* missed exactly the extended-first-year companies the signal exists for
|
|
* (Arcim, registered 13 months before onboarding, first year to 31 Dec).
|
|
*
|
|
* `noClosedPeriod` comes from TIC's `fiscalYear` being null, which is
|
|
* derived from `mostRecentFinancialSummary` (the latest filed report): a
|
|
* strong but not perfect signal (a filed report can lag in the data).
|
|
* Acceptable because every consumer only feeds a confirm-question
|
|
* suggestion; never auto-set first_fiscal_year from this.
|
|
* Returns both the toggle state and a seeded `first_year_start` (always the
|
|
* 1st of the registration month, the format the date inputs expect).
|
|
*
|
|
* `now` exists for tests; production callers omit it.
|
|
*/
|
|
export function deriveFirstYearDefaults(
|
|
registrationDate: number | null | undefined,
|
|
now: number = Date.now(),
|
|
opts?: { noClosedPeriod?: boolean },
|
|
): {
|
|
isFirstFiscalYear: boolean
|
|
firstYearStart: string | undefined
|
|
} {
|
|
if (!registrationDate || !Number.isFinite(registrationDate)) {
|
|
return { isFirstFiscalYear: false, firstYearStart: undefined }
|
|
}
|
|
const regDate = new Date(registrationDate)
|
|
if (Number.isNaN(regDate.getTime())) {
|
|
return { isFirstFiscalYear: false, firstYearStart: undefined }
|
|
}
|
|
const windowMonths = opts?.noClosedPeriod ? 18 : 12
|
|
const monthsAgo = (now - regDate.getTime()) / (1000 * 60 * 60 * 24 * 30.44)
|
|
if (monthsAgo >= windowMonths) return { isFirstFiscalYear: false, firstYearStart: undefined }
|
|
const year = regDate.getUTCFullYear()
|
|
const month = String(regDate.getUTCMonth() + 1).padStart(2, '0')
|
|
return { isFirstFiscalYear: true, firstYearStart: `${year}-${month}-01` }
|
|
}
|