ec27228a8e
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
'use server'
|
|
|
|
import { cookies, headers } from 'next/headers'
|
|
import { revalidatePath } from 'next/cache'
|
|
import { createClient } from '@/lib/supabase/server'
|
|
import { normalizeOrgNumber } from '@/lib/company-lookup/normalize-org-number'
|
|
import { ensureTicSnapshot } from '@/lib/agent/composer/tic-fetch'
|
|
|
|
export interface RefreshCompanyProfileResult {
|
|
ok?: true
|
|
snapshot?: Record<string, unknown> | null
|
|
fetchedAt?: string
|
|
// Error *codes*, translated by the caller (same pattern as company/actions.ts):
|
|
// unauthorized | org_number_invalid | persist_failed | not_found
|
|
error?: string
|
|
}
|
|
|
|
/**
|
|
* Fetch Bolagsuppgifter on demand from the settings → Företag panel.
|
|
*
|
|
* The panel normally shows the cached `companies.tic_snapshot`. This action
|
|
* lets the user (re)fetch it live by submitting an org number / personnummer:
|
|
* the path that recovers a company whose cached snapshot is missing or wrong
|
|
* (e.g. an enskild firma whose 10-digit personnummer previously fuzzy-matched
|
|
* the wrong entity; `searchCompanyByOrgNumber` now expands it to the 12-digit
|
|
* form so Lens resolves it exactly).
|
|
*
|
|
* We persist the (normalized) number and clear `tic_snapshot_fetched_at` to
|
|
* force `ensureTicSnapshot` past its 7-day cache, then let it do the live
|
|
* /profile fetch + write. All writes are RLS-scoped to the caller's company.
|
|
*/
|
|
export async function refreshCompanyProfileAction(
|
|
companyId: string,
|
|
orgNumberRaw: string,
|
|
): Promise<RefreshCompanyProfileResult> {
|
|
const supabase = await createClient()
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
if (!user) return { error: 'unauthorized' }
|
|
|
|
// Refuse malformed input at the boundary rather than storing a value that
|
|
// would later break SIE/SRU exports (same rule as createCompanyFromOnboarding).
|
|
const cleaned = normalizeOrgNumber(orgNumberRaw)
|
|
if (!cleaned) return { error: 'org_number_invalid' }
|
|
|
|
// Persist the (possibly corrected) number and force staleness so
|
|
// ensureTicSnapshot re-fetches instead of returning the poisoned cache.
|
|
const { error: updateError } = await supabase
|
|
.from('companies')
|
|
.update({ org_number: cleaned, tic_snapshot_fetched_at: null })
|
|
.eq('id', companyId)
|
|
if (updateError) return { error: 'persist_failed' }
|
|
|
|
// Keep the settings form (which reads company_settings.org_number) in sync,
|
|
// best-effort; the TIC fetch reads companies.org_number, updated above.
|
|
await supabase
|
|
.from('company_settings')
|
|
.update({ org_number: cleaned })
|
|
.eq('company_id', companyId)
|
|
|
|
// Self-fetch needs the caller's session cookie and the current origin so it
|
|
// reaches this same instance (dev / preview / prod); see ensureTicSnapshot.
|
|
const cookieStore = await cookies()
|
|
const cookieHeader = cookieStore.getAll().map((c) => `${c.name}=${c.value}`).join('; ')
|
|
const hdrs = await headers()
|
|
const host = hdrs.get('host')
|
|
const proto = hdrs.get('x-forwarded-proto') ?? 'https'
|
|
const origin = host ? `${proto}://${host}` : undefined
|
|
|
|
const { snapshot, source } = await ensureTicSnapshot({
|
|
supabase,
|
|
companyId,
|
|
cookieHeader,
|
|
origin,
|
|
// The user is watching a spinner; give the ~7-13 call Lens fan-out room to
|
|
// finish (the 5s default aborted every fetch during the May quota incident).
|
|
timeoutMs: 10_000,
|
|
})
|
|
|
|
// 'fetched' = a fresh live fetch was persisted. 'fallback' = TIC returned
|
|
// nothing / errored: surface it and leave the existing snapshot untouched
|
|
// rather than blanking a good panel on a transient outage.
|
|
if (source !== 'fetched' || !snapshot) {
|
|
return { error: 'not_found' }
|
|
}
|
|
|
|
revalidatePath('/settings')
|
|
return { ok: true, snapshot, fetchedAt: new Date().toISOString() }
|
|
}
|