feat: add safe owner-only migration reset (#1682)

* feat: add safe company migration reset

* fix: harden company reset eligibility

* fix: close company reset compliance gaps

* test: fix migration reset pg-real probes

* fix: preserve migration archive access

* docs: explain migration numbering continuity

* fix: block reset with VAT workflow state

* fix: block externally staged reset data

* fix: address migration reset review findings

* fix: clear stale migration archive estimate

* fix: retry migration archive estimates
This commit is contained in:
Mattsson
2026-08-19 12:04:24 +02:00
committed by GitHub
parent b07a4a4bca
commit 3a1b842e4a
24 changed files with 5017 additions and 57 deletions
+29
View File
@@ -3640,3 +3640,32 @@ export const SkattekontoFileExecuteSchema = z.object({
variant: z.enum(['csv', 'skv']),
closing_saldo: z.number().finite().nullable().optional(),
})
// ============================================================
// Company migration reset
// ============================================================
/**
* POST /api/company/[id]/migration-reset
*
* Every confirmation is required independently. The database repeats these
* checks inside the atomic RPC, including the exact display-name match.
*/
export const CompanyMigrationResetSchema = z.object({
confirm_name: z
.string()
.trim()
.min(1, 'Ange företagsnamnet exakt som det visas')
.max(200, 'Företagsnamnet får vara högst 200 tecken'),
reason: z
.string()
.trim()
.min(20, 'Beskriv varför migreringen behöver göras om med minst 20 tecken')
.max(1000, 'Beskrivningen får vara högst 1 000 tecken'),
confirm_no_filed_declarations: z.literal(true, {
error: 'Bekräfta att inga deklarationer eller årsredovisningar har lämnats in',
}),
confirm_retained_archive: z.literal(true, {
error: 'Bekräfta att den tidigare företagskopian sparas som arkiv',
}),
})
+35
View File
@@ -2698,6 +2698,41 @@ const COMPANY: Record<string, StructuredErrorEntry> = {
message_sv: 'Företaget kunde inte skapas.',
message_en: 'Failed to create company.',
},
COMPANY_RESET_NOT_FOUND: {
httpStatus: 404,
message_sv: 'Företaget kunde inte hittas.',
message_en: 'Company not found.',
},
COMPANY_RESET_FORBIDDEN: {
httpStatus: 403,
message_sv: 'Endast företagets ägare kan starta om en migrering.',
message_en: 'Only the company owner can reset a migration.',
},
COMPANY_RESET_INELIGIBLE: {
httpStatus: 409,
message_sv: 'Företaget kan inte återställas med självservice. Kontakta supporten för en individuell bedömning.',
message_en: 'The company is not eligible for a self-service reset. Contact support for an individual review.',
},
COMPANY_RESET_CONFIRMATION_MISMATCH: {
httpStatus: 400,
message_sv: 'Företagsnamnet stämmer inte överens.',
message_en: 'The company name does not match.',
},
COMPANY_RESET_REASON_INVALID: {
httpStatus: 400,
message_sv: 'Beskriv varför migreringen behöver göras om med 20 till 1 000 tecken.',
message_en: 'Explain why the migration must be redone using 20 to 1,000 characters.',
},
COMPANY_RESET_CONFIRMATION_REQUIRED: {
httpStatus: 400,
message_sv: 'Alla säkerhetsbekräftelser krävs.',
message_en: 'All safety confirmations are required.',
},
COMPANY_RESET_FAILED: {
httpStatus: 500,
message_sv: 'Migreringen kunde inte startas om. Inga ändringar har sparats.',
message_en: 'The migration reset failed. No changes were saved.',
},
}
const API_KEY: Record<string, StructuredErrorEntry> = {