Files
accounted/lib/pending-operations/schemas/dimension-value.ts
T
f266c386f3 chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers (#2150)
* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers

Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n
namespaces and 4 unused dependencies; fold byte-identical helper copies
into one canonical home each (lib/utils chunk/sleep/utcDateStamp,
lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format,
lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body +
v1ValidationError rolled out to ~55 v1 routes, booking-template schemas).

No behaviour change: v1 bodies and status codes, MCP tool schemas, DB
writes and money math are untouched. Naive ore rounding was deliberately
not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list
of things left alone on purpose.

tsc, lint, 19588 unit tests and check:guards green; antipattern baseline
ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(transactions): import RawTransaction from @/types after the ingest re-export removal

CI's type ratchet (check:types, full tsconfig) caught the one test file
that still imported the type through lib/transactions/ingest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 11:51:16 +02:00

72 lines
2.6 KiB
TypeScript

/**
* Authoritative server-side validation for the create_dimension_value staged
* operation. Used by:
* - The MCP tool execute() before staging (extensions/general/mcp-server/server.ts)
* - commitCreateDimensionValue() before the dimension_values INSERT
* (lib/pending-operations/commit.ts)
*
* Defense in depth: validating at the commit boundary protects the DB even if
* a caller writes directly to pending_operations.params bypassing the MCP
* tool (ASVS V4.5 / ISO A.8.28), mirroring CreateSupplierParamsSchema.
*
* The code format is the strict Fortnox charset: deliberately tighter than
* both the DB CHECK (1..40 chars, no `"{}`) and DimensionsBagSchema: legacy
* free-text codes from the backfill/SIE import must survive on lines, but new
* registry codes minted by agents stay portable to Fortnox/Visma. Identical
* to `dimensionValueCode` in lib/api/schemas.ts (the dashboard POST route) so
* the two write paths cannot drift.
*/
import { z } from 'zod'
const FORTNOX_CODE_RE = /^[A-Za-z0-9ÅÄÖåäö_+\-]{1,20}$/
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/
/** Trim strings; normalise empty/null to undefined before the inner schema. */
function optString(inner: z.ZodTypeAny) {
return z.preprocess(
(v) => {
if (v == null) return undefined
if (typeof v !== 'string') return v
const t = v.trim()
return t === '' ? undefined : t
},
inner.optional(),
)
}
export const CreateDimensionValueParamsSchema = z
.object({
sie_dim_no: z.preprocess(
(v) => (typeof v === 'string' && /^\d+$/.test(v) ? Number(v) : v),
z
.number()
.int('sie_dim_no must be an integer SIE dimension number')
.min(1, 'sie_dim_no must be ≥ 1 (1 = kostnadsställe, 6 = projekt)'),
),
code: z.preprocess(
(v) => (typeof v === 'string' ? v.trim() : v),
z
.string()
.regex(
FORTNOX_CODE_RE,
'Koden får bara innehålla bokstäver (A-Ö), siffror, _, + och - (max 20 tecken)',
),
),
name: z.preprocess(
(v) => (typeof v === 'string' ? v.trim() : v),
z.string().min(1, 'name is required').max(120),
),
start_date: optString(z.string().regex(ISO_DATE_RE, 'start_date must be ISO yyyy-MM-dd')),
end_date: optString(z.string().regex(ISO_DATE_RE, 'end_date must be ISO yyyy-MM-dd')),
})
.strict()
.superRefine((val, ctx) => {
if (val.start_date && val.end_date && val.end_date < val.start_date) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['end_date'],
message: 'Slutdatum får inte vara före startdatum',
})
}
})