The supplier-invoice editor issued four requests on every mount (suppliers,
accounts, settings, fiscal periods) and defaulted vatRegistered=true,
entity type and rounding until /api/settings landed, so the moms controls
visibly flipped. The register forms fetched the whole chart of accounts to
fill one konto combobox, and each transaction review dialog refetched
accounts, cash accounts or settings per open.
- use-supplier-invoice-data: thin composition of useSuppliers, useAccounts,
useCompanySettings and useFiscalPeriods; the settings-driven gates come
from a pure deriveSupplierInvoiceDefaults() (tested) instead of state
that flips when the fetch returns; the per-invoice öresavrundning toggle
is the one local override. Inline supplier create invalidates the shared
list instead of patching local state.
- SupplierForm, ArticleForm (posting accounts), QuickReviewDialog,
InvoiceMatchDialog, supplier-invoices/[id] (payment dialog chart):
useAccounts; ArticleForm's inline account create invalidates the chart.
- BulkBookDialog, MatchVoucherDialog, DuplicateBookingDialog: cash
accounts from useCashAccounts (resolveAccount over the cached list; an
empty list still resolves to 1930 with the fallback note).
- QuickReviewDialog, BulkBookDialog, NewEmployeeDialog, customers list
(default payment terms), salary run page (payment format, bank, IBAN,
dimensions): derived from useCompanySettings; the salary page's
post-settings-modal refetch becomes a cache invalidation.
raw-reference-fetch ratchet: 45 -> 35 files.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(suppliers): stop requiring standardkonto that was never meant to be required
The supplier form initializes every optional field to '' and sent them
as-is, while CreateSupplierSchema validates default_expense_account
with the 4-digit account rule behind .optional(): an empty string is a
present string, so saving a supplier with the field untouched failed
with "Kontonummer måste vara 4 siffror" even though the field carries
no required mark (reported by Björn with a screen recording; the edit
page failed the same way for any supplier without a default account).
Schemas now own the normalization, split by verb: on create '' becomes
undefined (key dropped, column NULL), on update '' becomes null,
because update routes pass fields straight into .update() where
undefined means "leave unchanged" and clearing must actually write
NULL. Email gets the same treatment and the form's old client-side
email strip is removed; stripping empty strings client-side would
break exactly the clear path.
The free-text Standardkonto input is replaced with the shared
AccountCombobox (browsable list filtered to cost classes 4-7, the same
rule the agent-path expenseAccountField enforces), with the selected
account name shown under the field and a clear button when set.
Standardkonto itself stays optional: it only prefills supplier-invoice
lines and the ledger-context suggestion covers the empty case.
Verified end to end against the running app: saving a supplier without
a default account succeeds on the update path, and the combobox
search/select/clear cycle works inside the create dialog.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(api-spec): render preprocess pipes by output side, required-ness by undefined-acceptance
The minimal Zod-to-JSON-schema walker described every pipe by its input
side. For .transform() that is right (the caller sends the input), but
z.preprocess() is the mirror image: the callable sits on the input side,
so the supplier schemas' new empty-string normalization rendered email
and default_expense_account as required untyped fields in the OpenAPI
spec and the generated accounted-api skill. Describe the output side
when the input is a transform.
Required-ness now derives from schema.safeParse(undefined) instead of a
top-level discriminator check: a field may be omitted exactly when the
schema accepts undefined. Besides the preprocess pipes, this corrects
several fields the old check misrendered as required (z.unknown()
bodies, union-with-empty-string settings fields, preprocessed
personal_number), so the regenerated skill references only flip
required to optional where runtime validation already allowed omission.
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>
* fix(design): stop synthesizing bold on Hedvig display headings
Hedvig Letters Serif ships weight 400 only, but the h1-h3 base rule
forced font-weight 500 and DialogTitle/SheetTitle stacked font-semibold
on top, so every display heading rendered browser-synthesized bold: the
smudged heavy look on dialog titles and page headings. Drop the base
rule to 400, remove the weight utilities from the title primitives, and
sweep the 41 files that hand-set font-medium/semibold/bold on serif
headings (a pattern design.md already forbids). Headings that opt into
font-sans keep their weight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(design): drop empty className left by the weight sweep
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>
* feat(payments): betalfil UI (betalfil 3/3)
Bulk-select + Skapa betalfil bulkbar on the supplier-invoices list,
preview dialog with per-line editable amount/date and exclusion reasons,
payment-files history page with re-download, cancel and a sequential
bulk mark-paid (duplicate guard respected, never forced), I betalfil
chip on rows in active batches, clearing/kontonummer fields on the
supplier form, and the supplier_payment_files namespace in sv+en.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sandbox): make the demo AP data betalfil-ready
Demo supplier bankgiro numbers were not Luhn-valid, the unpaid demo
invoice had remaining_amount 0 (no trigger derives it, so the list said
0 kr kvar att betala), and the company had no IBAN/BIC, all of which
excluded the seeded data from the betalfil flow. Numbers swapped for
Luhn-valid ones (991-2346 is Bankgirot's test number), a valid OCR added,
and both bulk-insert rows set remaining_amount explicitly per the
PostgREST normalization rule already documented inline.
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>
* feat: add user locale preference to user_preferences table
- Introduced a new column 'locale' in the user_preferences table to store per-user UI language preferences.
- Added a CHECK constraint to ensure only supported locales ('sv', 'en') are allowed.
- Triggered a schema reload notification for the changes.
chore: declare CSS module support in TypeScript
- Added a declaration for CSS modules in globals.d.ts to enable TypeScript support for importing CSS files.
* feat: add Swish as an invoice payment method in company settings
* feat(import): add customer and supplier parsing functionality
- Implemented customer file parsing in `lib/import/customers/parser.ts` with support for Excel and CSV formats.
- Created types for detected customer columns and parsed customer rows in `lib/import/customers/types.ts`.
- Added tests for customer classification logic in `lib/import/shared/__tests__/classify.test.ts`.
- Developed classification functions for customers and suppliers in `lib/import/shared/classify.ts`.
- Introduced shared column utility functions in `lib/import/shared/column-utils.ts`.
- Implemented supplier file parsing in `lib/import/suppliers/parser.ts` with validation for various fields.
- Created types for detected supplier columns and parsed supplier rows in `lib/import/suppliers/types.ts`.
- Added tests for supplier column detection and parsing in `lib/import/suppliers/__tests__/column-detector.test.ts` and `lib/import/suppliers/__tests__/parser.test.ts`.
* fix(labels): update 'Svenskt företag' to 'Svenskt företag eller organisation' for clarity
* feat(import): refactor encoding handling for Swedish files and add tests for character preservation
* feat(recapt): implement clearRecaptIdentity function and integrate into logout flow
* feat(bookkeeping): implement copy functionality and next voucher sequence retrieval
* feat(import): enhance customer and supplier import functionality with normalization and event handling
* Implement company and account deletion features
- Add event types for company and account deletion to CoreEvent.
- Enhance Supabase middleware to handle company context resolution and cookie management for archived companies.
- Create API routes for deleting accounts and companies, including necessary validations and event emissions.
- Implement tests for account and company deletion endpoints to ensure proper functionality and error handling.
- Add retention notice component to inform users about bookkeeping data retention during destructive actions.
- Create database migrations to support soft deletion of companies and anonymization of user accounts, ensuring compliance with retention laws.
* feat: enhance account deletion process and update user notifications
* Add service client for onboarding completion check and update escape hatch visibility
* Enhance invite flow and email handling for company members
* Refactor company context and RLS policies for active company isolation
- Update `switchCompany` to remove unnecessary revalidation as client handles navigation.
- Revise `getActiveCompanyId` to prioritize `user_preferences` and validate against non-archived memberships.
- Modify `setActiveCompany` to ensure `user_preferences` is the authoritative source while maintaining cookie compatibility.
- Enhance middleware to resolve active company using `user_preferences` and fallback to first non-archived membership.
- Introduce new API route `/api/company/current` to fetch the active company ID for cross-tab synchronization.
- Implement `CompanyTabSync` component for real-time active company enforcement across tabs.
- Create migration for RLS policies to enforce single-active-company isolation using `current_active_company_id()`.
* feat: implement viewer role enforcement for write permissions
- Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company.
- Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions.
- Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers.
- Created tests to verify the behavior of the viewer role and write permissions.
- Added database migration to enforce read-only access for viewers at the database level.
* fix: enhance validation for moms_period and conditionally set vat_number and moms_period based on vat_registered
* Enhance preprocessing for first year fields, fiscal year end month, and accounting method in schema validation
* fix: simplify schema validation by removing unnecessary preprocessors
* fix: prevent setting invalid values in select components across multiple forms
* fix: improve layout responsiveness and conditional rendering in onboarding steps
* feat: mobile UX improvements across dashboard pages
- Responsive layouts with stacked mobile forms and grid desktop views
- Safe area handling (viewportFit cover, bottom nav insets, main padding)
- Touch-friendly dialogs, tabs, and page headers
- Mobile nav drawer open/close animation
- Dashboard layout parallel Supabase queries
- SIE parser: handle quoted VER/TRANS fields
- Skip account override for template-based transaction booking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review feedback from Greptile
- Fix nav drawer close timer race condition with useRef + clearTimeout
- Show locked VAT rate as read-only label on mobile instead of hiding it
- Remove dead bottom-16 class overridden by inline style
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>