docs: correct stale product facts (arkivplan, architecture, agents, self-hosting, extensions, database map) (#1931)
Every statement was verified against the code on main before editing; the docs had drifted from the product in ways a customer or agent would act on. - public/docs/arkivplan-mall.md: product named erp-base; magic-link login; BAS 2025/2026; eu-central/eu-west region; US subprocessors for AI. Now Accounted, e-mail + password + TOTP (BankID optional), BAS 2026, eu-north-1 Stockholm, Bedrock in EU with Resend as the only US subprocessor; adds rättelselogg, Peppol inbound, skattekonto imports and the säkerhetsbackup ZIP to the räkenskapsinformation tables. - ARCHITECTURE.md: adds the inline-rättelse correction path, OAuth 2.1 and lazy MCP auth, accounted-mcp and claude-plugin, 150+ tools. - AGENTS.md: defers to CLAUDE.md instead of a drifted copy; keeps the Codex-only constraints with the Supabase project name fixed (erp-base). - README.md: drops LangChain/OpenAI (not dependencies), Node 20/22 facts, 150+ tools, adds betalfil, Peppol, skattekonto and the Claude plugin. - docs/PEPPOL_FOUNDATION.md: the two sentences denying network delivery and inbound support now describe the live Qvalia path. - docs/SELF-HOSTING.md, docs/DOCKER.md: clone URLs and directory names, Sentry DSNs are not read by the app, image pinning uses the 7-char SHA tags the workflow actually publishes (no semver tag has been cut). - docs/EXTENSIONS.md: replaces the fictional sector tree with the 19 real extensions/general directories; lib/reports/sru-encoding.ts. - .claude/rules/database.md: 680+ migrations, ~170 live tables, adds the tables and RPCs that matter since July, drops sandbox_users. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
188816652d
commit
9396e54965
@@ -8,7 +8,7 @@ paths:
|
||||
|
||||
Use the `/supabase-migration` skill for new migrations.
|
||||
|
||||
**Location**: `supabase/migrations/`: 330+ files. Early migrations use sequential numbering (`20240101000001`-`20240101000038`), later ones use real timestamps.
|
||||
**Location**: `supabase/migrations/`: 680+ files. Early migrations use sequential numbering (`20240101000001`-`20240101000038`), later ones use real timestamps.
|
||||
|
||||
## Migration Rules
|
||||
|
||||
@@ -23,35 +23,43 @@ Use the `/supabase-migration` skill for new migrations.
|
||||
|
||||
**pg-real tests**: any PR touching a trigger/RPC/RLS/DEFERRABLE must include or extend a `*.pg.test.ts`. Parallel Vitest project against real Postgres (CI: `supabase/postgres:15`, migrations replayed). Local: `npm run test:pg`. Helpers: `tests/pg/setup.ts` (`getPool()`, `withUserContext()`), `tests/pg/fixtures.ts` (`seedCompany()`, `insertDraftJournalEntry()`, etc.).
|
||||
|
||||
## Key Tables (~60)
|
||||
## Key Tables (~170 live tables; the ones that matter)
|
||||
|
||||
- **Multi-tenant**: `companies`, `company_members`, `company_invitations`, `teams`, `team_members`, `team_invitations`, `user_preferences`, `profiles`
|
||||
- **Bookkeeping**: `chart_of_accounts`, `fiscal_periods`, `journal_entries`, `journal_entry_lines`, `voucher_sequences`, `voucher_gap_explanations`
|
||||
- **Bookkeeping**: `chart_of_accounts`, `fiscal_periods`, `journal_entries`, `journal_entry_lines`, `voucher_sequences`, `voucher_gap_explanations`, `journal_entry_rattelse_log` (immutable who/when log for inline rättelse)
|
||||
- **Invoicing**: `customers`, `invoices`, `invoice_items`, `invoice_payments`, `invoice_inbox_items`
|
||||
- **Suppliers**: `suppliers`, `supplier_invoices`, `supplier_invoice_items`
|
||||
- **Suppliers**: `suppliers`, `supplier_invoices`, `supplier_invoice_items`, `supplier_payment_batches`, `supplier_payment_batch_items` (betalfil, pain.001)
|
||||
- **Peppol**: `peppol_registrations`, `peppol_deliveries`, `peppol_delivery_events`, `peppol_delivery_evidence`, `peppol_inbound_documents`, `peppol_access`
|
||||
- **Banking**: `bank_connections`, `transactions`, `bank_file_imports`, `payment_match_log`
|
||||
- **Reconciliation**: `account_reconciliations`, `account_reconciliation_attachments`
|
||||
- **Documents**: `document_attachments` (WORM), `receipts`, `receipt_line_items`
|
||||
- **Settings**: `company_settings`, `mapping_rules`, `categorization_templates`, `booking_template_library`, `extension_data`
|
||||
- **Dimensions**: `cost_centers`, `projects`
|
||||
- **Tax/Deadlines**: `tax_rates`, `tax_table_rates`, `deadlines`, `calendar_feeds`, `skatteverket_tokens`
|
||||
- **Skattekonto**: `skattekonto_transactions`, `skattekonto_file_imports`, `skattekonto_rules`, `tax_assessment_notices`
|
||||
- **API/Auth**: `api_keys`, `oauth_used_codes`, `bankid_identities`
|
||||
- **Audit/Ops**: `audit_log` (immutable), `event_log` (30d TTL), `pending_operations`, `processing_history`, `ai_usage_tracking`, `automation_webhooks`
|
||||
- **Audit/Ops**: `audit_log` (immutable), `event_log` (30d TTL), `pending_operations`, `processing_history`, `ai_usage_tracking`, `automation_webhooks`, `company_migration_resets`
|
||||
- **Inbox**: `invoice_inbox_items`, `company_inboxes`, `email_connections`
|
||||
- **Salary**: `employees`, `salary_runs`, `salary_run_employees`, `salary_line_items`, `salary_payroll_config`, `agi_declarations`
|
||||
- **WhatsApp**: `whatsapp_phone_links`, `whatsapp_link_codes`, `whatsapp_conversations`, `whatsapp_messages`, `whatsapp_sender_rate_counters`
|
||||
- **Webshop**: `webshop_orders` (Shopify/WooCommerce orders)
|
||||
- **Salary**: `employees`, `salary_runs`, `salary_run_employees`, `salary_line_items`, `salary_payroll_config`, `agi_declarations`, `employee_vacation_balances`, `vacation_year_closures`
|
||||
- **Annual report**: `annual_report_profiles`, `annual_report_versions`, `annual_report_validation_runs`
|
||||
- **Providers**: `provider_consents`, `provider_consent_tokens`, `provider_otc`
|
||||
- **Agent**: `agent_atom_registry` (inlined skill bodies, see below)
|
||||
- **Other**: `sandbox_users`
|
||||
- **Agent**: `agent_atom_registry` (inlined skill bodies, see below), `mcp_tasks`
|
||||
|
||||
## Key RPC Functions
|
||||
|
||||
- `create_company_with_owner()`: Atomic company + owner creation
|
||||
- `commit_journal_entry()`: Atomic draft→posted with voucher number
|
||||
- `correct_entry_metadata()`, `correct_entry_lines_inline()`: Inline rättelse (BFL 5 kap. 5 §) inside the same voucher; audited SECURITY DEFINER, refused in locked/closed periods, logged to `journal_entry_rattelse_log`
|
||||
- `next_voucher_number()`: Concurrent-safe voucher generation
|
||||
- `detect_voucher_gaps()`: BFNAR 2013:2 gap detection
|
||||
- `generate_invoice_number()`, `get_next_arrival_number()`, `generate_delivery_note_number()`: Sequence generators
|
||||
- `seed_chart_of_accounts()`: BAS chart seeding per entity type
|
||||
- `validate_and_increment_api_key()`: Atomic rate limiting
|
||||
- `user_company_ids()`: RLS helper returning user's company IDs
|
||||
- `current_active_company_id()`: RLS-side read of `user_preferences.active_company_id`; the same value the middleware resolves, so Next.js and RLS agree
|
||||
- `claim_due_webhook_deliveries()`: Concurrent-safe claim of due `automation_webhooks` deliveries for the cron sender
|
||||
- `get_unlinked_1930_lines()`: Bank reconciliation helper
|
||||
- `cleanup_sandbox_user()`, `cleanup_expired_sandbox_users()`: Sandbox lifecycle
|
||||
|
||||
|
||||
@@ -1,100 +1,30 @@
|
||||
# CLAUDE.md: Accounted
|
||||
# AGENTS.md: Accounted
|
||||
|
||||
Swedish accounting SaaS: double-entry bookkeeping under Swedish accounting law (Bokföringslagen) for sole traders (enskild firma) and limited companies (aktiebolag). Multi-tenant: users belong to companies via `company_members`; `teams` group companies for consultants.
|
||||
|
||||
**Stack**: Next.js 16 (App Router), React 19, TypeScript 5 strict, Zod 4, Supabase (Postgres + RLS + auth), Tailwind 4 + shadcn/ui. Vercel-hosted is the primary target; Docker self-hosted must keep working but never at hosted's expense. Path alias `@/*` = repo root. All code, comments, and commits in English.
|
||||
This file is the entry point for Codex and other agents that do not read `CLAUDE.md` on their own. It deliberately does not duplicate the project rules: an earlier copy drifted from `CLAUDE.md` within weeks (missing the inline-rättelse correction path, a stale tenancy resolution order, an outdated MCP tool count). Read `CLAUDE.md` first and follow it as written.
|
||||
|
||||
---
|
||||
## Source of truth: `CLAUDE.md`
|
||||
|
||||
## Hard Rules
|
||||
`CLAUDE.md` at the repo root holds everything that applies to every agent, and it wins whenever this file and it disagree:
|
||||
|
||||
The accounting rules are Swedish law, enforced by DB triggers. Code that violates them fails at runtime; code that works around the triggers breaks legal compliance. Never do either.
|
||||
- **Hard Rules**: the seven accounting invariants (the two sanctioned correction paths, storno and inline rättelse; engine-only journal writes; balanced entries; period locks; document retention; money math; account numbers as strings) and the general prohibitions (migrations, extension imports, dependencies, the gnubok → Accounted rename, `.env.local`, diff scope, no em or en dashes).
|
||||
- **When Uncertain**: stop and ask; Swedish domain questions go through the `swedish-*` skills, never training data.
|
||||
- **Definition of Done**: all nine items, including the last one: the last mile is verified in-session (migration applied, PR merged, routine observed firing), or the final output states exactly what is not live yet.
|
||||
- **Commands**, **Architecture** (tenancy resolution, application-side MFA plus server-enforced session limits, event bus, Supabase clients, extensions, the 150+ MCP tools), **Repository Map**, **Testing**, and the **Decision Log** (`DECISIONS.md`).
|
||||
|
||||
1. **Never edit or delete a posted journal entry.** Committed vouchers are immutable. Cancel with `reverseEntry()`; correct with `correctEntry()` (`lib/core/bookkeeping/storno-service.ts`). Storno, never edit.
|
||||
2. **All journal writes go through `lib/bookkeeping/engine.ts`.** Never insert into journal tables directly: voucher numbers are assigned atomically by the `commit_journal_entry` RPC and must stay sequential, and gaps require documented explanations (BFNAR 2013:2, `voucher_gap_explanations`).
|
||||
3. **Every entry balances**: `sum(debits) === sum(credits)`, both `> 0`.
|
||||
4. **Respect period locks.** DB triggers block writes to closed/locked periods and behind the company lock date. Don't work around them: fix the flow that tried to write there.
|
||||
5. **Never delete documents linked to posted entries**: 7-year retention is a legal requirement.
|
||||
6. **Money math is `Math.round(x * 100) / 100`.** Never `toFixed()`: it returns strings and rounds incorrectly, causing öre-level drift that breaks entry balance.
|
||||
7. **Account numbers are strings** (`'1930'`, never `1930`). They are identifiers, not quantities; arithmetic on them is always a bug.
|
||||
Do not copy sections from `CLAUDE.md` into this file; link to them instead.
|
||||
|
||||
General prohibitions:
|
||||
## Codex-specific working constraints
|
||||
|
||||
- **Never modify an existing migration**: schemas already shipped; create a new migration. Never touch the enforcement triggers (migration 017); they are legally required.
|
||||
- **Apply migrations only to the `erpbase` Supabase project's `staging` branch.** Never apply migrations to a local database or a locally hosted Supabase instance.
|
||||
- **Never write to the `erpbase` Supabase production database without Emil's explicit approval for the specific write.** Production reads are allowed, including fetching data for a requested account, but no INSERT, UPDATE, DELETE, DDL, migration, mutating RPC, repair, seed, or other state-changing operation may run until Emil has clearly said okay. Do not infer approval from a request to investigate, diagnose, fix code, or fetch data.
|
||||
These apply to Codex sessions run by Emil (Mattsson) and were added 2026-07-21; they are not in `CLAUDE.md` because they describe one operator's environment rather than the product. Keep them here.
|
||||
|
||||
- **Apply migrations only to the `erp-base` Supabase project's `staging` branch.** Never apply migrations to a local database or a locally hosted Supabase instance.
|
||||
- **Never write to the `erp-base` Supabase production database without Emil's explicit approval for the specific write.** Production reads are allowed, including fetching data for a requested account, but no INSERT, UPDATE, DELETE, DDL, migration, mutating RPC, repair, seed, or other state-changing operation may run until Emil has clearly said okay. Do not infer approval from a request to investigate, diagnose, fix code, or fetch data.
|
||||
- **Never write directly to the `main` branch without Emil's explicit approval.** Do not commit, push, merge, or otherwise update `main`; use a feature branch unless Emil clearly approves the specific main-branch write.
|
||||
- **Never leave a remote DB ahead of the repo.** If you `apply_migration` (or run any DDL) against prod, staging, or a preview branch, write the byte-identical SQL into `supabase/migrations/` under the exact applied version in the same change. An applied version with no committed file is an orphan: Supabase branching aborts the next merge to `main` with "Remote migration versions not found in local migrations directory" and blocks every pending migration behind it. The PR preview passes anyway (preview branches fork from prod's history, which already has the orphan), so this only surfaces at merge.
|
||||
- **Core code must never import from `@/extensions/`.** CI builds core with zero extensions enabled; a direct import breaks that build. Extensions cannot use dynamic imports (the registry generates static imports via `setup:extensions`).
|
||||
- **Don't add dependencies without asking.** This is an AGPL-3.0 project; license compatibility matters, and the dependency surface is audited.
|
||||
- **Don't "finish" the gnubok → Accounted rename.** Wire-format identifiers keep the old name on purpose: `gnubok-company-id` cookie, `gnubok_sk_`/`gnubok_inv_` prefixes, and the `gnubok-mcp` compatibility package. Renaming or removing them breaks live sessions, API keys, invites, and existing MCP connections. New MCP installs use the additive `accounted-mcp` package and `accounted_*` aliases.
|
||||
- **Treat `.env.local` as pointing at the production database.** Never run seed/cleanup/repair scripts against it without explicit confirmation.
|
||||
- **Never open, start, or run Docker locally.** Do not run Docker commands or commands that start Docker-managed services.
|
||||
- **Keep the diff scoped to the request.** No drive-by refactors of untouched code.
|
||||
- **Never use em dashes (—) or en dashes (–)** in code, comments, commit messages, or docs. Use a colon, comma, semicolon, or plain hyphen instead, whichever fits the sentence. Exception: a dash character that is the literal subject being parsed, matched, or documented (e.g. mojibake byte-mapping tables, a date-range separator regex) stays as-is; don't launder those into a colon.
|
||||
- Never create a NUL/nul file: `\Accounted\NUL`.
|
||||
|
||||
## When Uncertain
|
||||
|
||||
- **Stop and ask; do not guess.** Especially for anything touching posted entries, the production database, money math, or Swedish tax law.
|
||||
- **Swedish domain questions are never answered from training data.** Load the matching `swedish-*` skill (vat, accounting-compliance, invoice-compliance, payroll, year-end-closing, sie-import-export, sru-filing, financial-reporting, asset-accounting, project-accounting, tax-planning, e-invoicing).
|
||||
- Scaffolding has skills; use them instead of improvising: `/erp-api-route` (API routes), `/supabase-migration` (migrations), `/create-extension` (extensions), `/frontend-design` (new UI), `vercel:deploy` (deployment).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
A change is done when all of these hold; iterate until they do:
|
||||
|
||||
1. `npm run lint` is clean and `npm test` passes (`npx vitest run <dir>` while iterating).
|
||||
2. New or changed logic in `lib/` or `app/api/` has tests: auth 401, validation 400, 404, happy path; mock `@/lib/supabase/server`.
|
||||
3. Any change to a trigger, RPC, RLS policy, or DEFERRABLE constraint ships with a `*.pg.test.ts` (`npm run test:pg`).
|
||||
4. New UI strings exist in **both** `messages/sv.json` and `messages/en.json`.
|
||||
5. If you edited an atom `SKILL.md`, `npm run skills:generate` was run (CI's `skills:check` fails otherwise).
|
||||
6. `npm run check:guards` passes if you touched API routes.
|
||||
7. Commit is conventional (`feat:`/`fix:`/`refactor:`/`test:`/`docs:`), atomic, branched from `main`.
|
||||
8. If the change touches migrations, local and prod are reconciled: every version in prod's `schema_migrations` has a matching file in `supabase/migrations/`, and vice versa. Check before opening the PR (e.g. `list_migrations` / `select version from supabase_migrations.schema_migrations`); a remote-only version means an uncommitted orphan that will fail the merge.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm run dev # Dev server (runs setup:extensions first)
|
||||
npm run build # Production build (runs setup:extensions first)
|
||||
npm run lint # ESLint
|
||||
npm test # All Vitest tests
|
||||
npx vitest run <dir> # Tests in one directory
|
||||
npm run test:pg # pg-real tests against real Postgres
|
||||
npm run check:guards # Ratchet guard (e.g. no hand-rolled route auth)
|
||||
npm run setup:extensions # Regenerate extension registry from extensions.config.json
|
||||
npm run skills:generate # Regenerate agent_atom_registry seed after editing an atom SKILL.md
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Journal entry lifecycle**: `createDraftEntry()` → `commitEntry()` (atomic voucher via `commit_journal_entry` RPC); `createJournalEntry()` does both. Everything accounting-shaped routes through this engine.
|
||||
- **Tenancy**: every business table has `company_id`. Active company resolves in `lib/supabase/middleware.ts`: `gnubok-company-id` cookie → `user_preferences.active_company_id` → first membership. RLS uses `user_company_ids()`; queries still filter by `company_id` explicitly (defense in depth: service-role paths have no RLS).
|
||||
- **Auth**: Supabase email+password + TOTP MFA, enforced **application-side**, not in RLS. `NEXT_PUBLIC_REQUIRE_MFA=true` on hosted; `NEXT_PUBLIC_SELF_HOSTED=true` disables MFA. API routes wrap `withRouteContext`: it is the only path that enforces MFA, so never hand-roll `supabase.auth.getUser()` in a route.
|
||||
- **Events**: `lib/events/bus.ts` is a module-level singleton. Any route that emits events must call `ensureInitialized()` (`lib/init.ts`) at module level: otherwise extension handlers are never wired and events silently go nowhere.
|
||||
- **Supabase clients**: browser `client.ts`, server `createClient()`, service role `createServiceClient()`, cookieless service role `createServiceClientNoCookies()` (lives in `lib/auth/api-keys.ts`; for API-key/MCP paths). Paginate with `fetchAllRows()`: PostgREST silently caps at 1000 rows.
|
||||
- **Extensions**: opt-in plugins in `extensions/general/<name>/`; `extensions.config.json` is the source of truth for what's enabled. Core must run with zero extensions.
|
||||
- **MCP server**: the bookkeeping engine is exposed as 100+ MCP tools (`extensions/general/mcp-server/`), authenticated by `gnubok_sk_` API keys (SHA-256, scoped, default 100 RPM per key).
|
||||
- **Types**: import from `@/types` (`types/index.ts`); event types in `lib/events/types.ts`.
|
||||
- **User-facing errors are Swedish**: map through `lib/errors/get-error-message.ts`.
|
||||
- **Cron**: hosted cron jobs live in `vercel.json`, authenticated via `verifyCronSecret()` (`lib/auth/cron.ts`).
|
||||
|
||||
## Repository Map
|
||||
|
||||
- `lib/bookkeeping/`: engine, entry generators, mapping, templates, BAS 2026 data (`bas-data/`)
|
||||
- `lib/core/`: period, year-end, storno, tax codes, audit, documents
|
||||
- `lib/events/`, `lib/auth/`, `lib/supabase/`, `lib/api/` (Zod `validateBody`/`validateQuery`)
|
||||
- `lib/reports/`: balance sheet, income statement, trial balance, GL, ledgers, VAT, SIE, INK2, NE-bilaga, salary, …
|
||||
- `lib/invoices/`, `lib/transactions/`, `lib/import/`, `lib/documents/`, `lib/salary/`, `lib/reconciliation/`, `lib/tax/`, `lib/vat/`, `lib/providers/` (Fortnox/Bokio/Briox/BL/Visma), `lib/skatteverket/`, `lib/currency/`, `lib/bankgiro/`, `lib/deadlines/`, `lib/calendar/`
|
||||
- `lib/utils.ts`: `cn()`, `formatCurrency()`, `formatDate()`, `formatOrgNumber()`; `lib/logger.ts`
|
||||
- `app/(dashboard)/*` pages; `app/api/*` routes; `supabase/migrations/` schema; `extensions/general/*` plugins
|
||||
|
||||
## Testing
|
||||
|
||||
Vitest 4, `node` env, tests in `__tests__/`, scope `lib/` + `app/api/` (no component/E2E tests). Helpers in `tests/helpers.ts`: `createMockSupabase()`, `createQueuedMockSupabase()`, `createMockRequest()`, `parseJsonResponse()`, plus fixture factories (`makeTransaction`, `makeJournalEntry`, `makeInvoice`, …). `vi.clearAllMocks()` + `eventBus.clear()` in `beforeEach`. Trigger/RPC/RLS behavior is tested in `*.pg.test.ts` against real Postgres, not with mocks.
|
||||
|
||||
## Detail Loads On Demand
|
||||
## Path-specific guidance (`.claude/rules/`)
|
||||
|
||||
The files below are the shared source of truth for path-specific guidance. Claude Code loads them through their `paths` frontmatter. Codex does not interpret that frontmatter, so before reading, editing, reviewing, or otherwise working with a matching path, read and follow the listed rule. Do not duplicate the rule bodies here.
|
||||
|
||||
@@ -102,9 +32,5 @@ The files below are the shared source of truth for path-specific guidance. Claud
|
||||
- `.claude/rules/i18n.md`: sv/en conventions, "stays Swedish" surfaces
|
||||
- `.claude/rules/api-routes.md`: `withRouteContext` route pattern, endpoint map (`app/api/**`)
|
||||
- `.claude/rules/database.md`: migration rules, key tables/RPCs/triggers, pg-real (`supabase/migrations/**`)
|
||||
- `.claude/rules/mcp-server.md`: MCP tool authoring, staged-operation pattern
|
||||
- `.claude/rules/mcp-server.md`: MCP tool authoring, staged-operation pattern, OAuth 2.1 connector auth
|
||||
- `.claude/rules/bookkeeping.md`: BAS accounts, VAT treatments/rutor, `lib/core/` services
|
||||
|
||||
## Decision Log
|
||||
|
||||
When you make a non-obvious choice (picked approach A over B, declined a dependency, stopped because a rule here forbade something), append one line to `DECISIONS.md` (repo root): `[YYYY-MM-DD] <decision>: <why>`. Check that file before re-litigating a past decision.
|
||||
|
||||
+26
-9
@@ -30,9 +30,17 @@ The journal entry lifecycle is draft, then commit:
|
||||
Two invariants hold for every entry:
|
||||
|
||||
- Debits equal credits, and both sides are greater than zero.
|
||||
- Once committed, an entry is never edited or deleted. Mistakes are corrected
|
||||
with reversal entries (storno): `reverseEntry()` cancels a voucher and
|
||||
`correctEntry()` replaces it (`lib/core/bookkeeping/storno-service.ts`).
|
||||
- Once committed, an entry is never silently edited or deleted. BFL 5 kap.
|
||||
5 § allows two correction paths, and the code has exactly those two:
|
||||
- Storno: `reverseEntry()` cancels a voucher with a reversal entry and
|
||||
`correctEntry()` replaces it (`lib/core/bookkeeping/storno-service.ts`).
|
||||
Always allowed.
|
||||
- Inline rättelse (founder-approved 2026-07-23): the
|
||||
`correct_entry_metadata` and `correct_entry_lines_inline` RPCs
|
||||
strike-and-replace inside the same voucher, keeping the original
|
||||
readable and writing an immutable who/when record to
|
||||
`journal_entry_rattelse_log`. Only while the period is open and
|
||||
unlocked; past a lock, close, or declared state, storno is the only path.
|
||||
|
||||
If a gap still occurs in a voucher series (for example around imported
|
||||
history), it must be documented, and the explanation is stored
|
||||
@@ -42,9 +50,10 @@ history), it must be documented, and the explanation is stored
|
||||
|
||||
The rules above are not conventions; they are enforced by PostgreSQL triggers:
|
||||
|
||||
- Committed journal entries cannot be edited or deleted. The only change the
|
||||
triggers permit is the controlled status transition used by the storno flow
|
||||
(marking an entry as reversed).
|
||||
- Committed journal entries cannot be edited or deleted. The only changes the
|
||||
triggers permit are the controlled status transition used by the storno flow
|
||||
(marking an entry as reversed) and the audited inline-rättelse RPCs above,
|
||||
which are refused once the period is locked or closed.
|
||||
- Writes to closed or locked accounting periods are rejected, as are writes
|
||||
behind a company-wide lock date.
|
||||
- Documents linked to posted entries cannot be deleted; Swedish law requires
|
||||
@@ -98,12 +107,18 @@ documented Extension API to be licensed under any terms. See
|
||||
## Agent surface (MCP)
|
||||
|
||||
The bookkeeping engine is exposed as an MCP (Model Context Protocol) server
|
||||
with over 100 tools, so AI agents can operate the ledger: list and categorize
|
||||
with 150+ tools, so AI agents can operate the ledger: list and categorize
|
||||
transactions, draft vouchers, reconcile periods, generate reports and
|
||||
declarations.
|
||||
|
||||
- Authentication uses scoped API keys (stored as SHA-256 hashes, rate limited
|
||||
per key).
|
||||
per key). Claude and ChatGPT connectors instead authenticate with OAuth 2.1
|
||||
(PKCE; `app/api/mcp-oauth/{authorize,register,token}` plus the
|
||||
`.well-known` discovery documents), which mints a scoped API key behind the
|
||||
scenes. Authentication is lazy: a client can connect, list tools, and call a
|
||||
small set of public discovery tools before an account exists; the first
|
||||
tenant-touching call answers 401 and triggers the client's connect prompt
|
||||
(`extensions/general/mcp-server/public-tools.ts`).
|
||||
- Posting operations are staged: an agent proposes an operation, and a human
|
||||
approves it before anything is committed to the journal.
|
||||
|
||||
@@ -125,7 +140,9 @@ to core activity without core knowing about them.
|
||||
| `components/` | React components (shadcn/ui based) |
|
||||
| `extensions/` | Opt-in extension plugins |
|
||||
| `supabase/migrations/` | Database schema, RLS policies, enforcement triggers |
|
||||
| `packages/gnubok-mcp` | Published MCP bridge package |
|
||||
| `packages/accounted-mcp` | Published stdio MCP bridge for new installs (`accounted_*` tool namespace) |
|
||||
| `packages/gnubok-mcp` | Compatibility MCP bridge for existing installs (kept on purpose) |
|
||||
| `claude-plugin/` | Claude Code plugin: OAuth connector plus approval-gated workflow skills |
|
||||
| `messages/` | Swedish and English UI strings |
|
||||
| `tests/` | Shared test helpers and fixtures |
|
||||
| `docs/` | Self-hosting, Docker, extensions, white-label guides |
|
||||
|
||||
@@ -1253,6 +1253,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-08-26] No ratchet on direct requireAuth() calls in app/api: requireAuth() is the MFA (AAL2) guard withRouteContext itself calls, and .claude/rules/api-routes.md sanctions it for routes without a company context (onboarding, account, user prefs). The 20 remaining direct callers skip request ids and the canonical envelope, not MFA; migrating them is a consistency campaign, not a security fix, so it was not folded into the bypass PR.
|
||||
[2026-08-26] defer_invoice_booking (#967) now gates booking on every door, not just the dashboard: MCP send_invoice / mark_invoice_sent / create_supplier_invoice_from_inbox, v1 invoices send / mark-sent and supplier-invoices create, and the inbox convert route all checked accounting_method === 'accrual' and posted a verifikat at issue for deferred companies. All six now call booksInvoicesOnIssue() (lib/bookkeeping/booking-mode.ts), the same helper the dashboard routes use, so the setting has one meaning. No data repair attempted: vouchers already posted for deferred companies through these doors are legitimate entries and stay.
|
||||
[2026-08-20] The swedish-e-invoicing skill now names Upphandlingsmyndigheten as Sweden Peppol Authority across all eight files, not just the one that was flagged: the handover completed 1 July 2026 (regeringsbeslut Fi2025/01826) and the skill was written in future tense, so a partial fix would have left the atom internally contradictory and still pointed agents at peppol@digg.se. Four digg.se URLs were repointed to their verified 301 targets on upphandlingsmyndigheten.se; the fifth, DIGG Peppol testbadd, is a hard 404 with no redirect and no successor page at the new authority, so it was replaced with the SFTI Validex verification service (https://sfti.validex.net/) rather than left dead or guessed at. Historical attributions (Q4 2025 traffic statistics, the 0007:2021006883 Peppol-ID example) deliberately still say DIGG because they were accurate when published.
|
||||
[2026-08-26] AGENTS.md now defers to CLAUDE.md for every shared rule instead of duplicating it: the copy had drifted within weeks (no inline-rättelse path, cookie-first tenancy order, 100+ MCP tools). The four Codex-only constraints Emil added 2026-07-21 (erp-base staging-only migrations, prod writes and main pushes need his explicit approval, no local Docker) were kept in a labelled section rather than removed, because the erp-base Supabase project exists (ref pwxtzglxptnnvjrpixpg) and they describe his environment, not stale product facts; only the project-name spelling was corrected from erpbase.
|
||||
[2026-08-26] MCP tool counts in docs say "150+" (connect-claude, READMEs, rules, registry entry) instead of deriving the number from the tools array: lib/docs/content/connect-claude.ts is core code and core must never import from @/extensions/ (CI builds core with zero extensions), and an exact hand-written number (90+, 100+, 120) had drifted three times already. Regenerate by counting `name: 'gnubok_` in server.ts when the order of magnitude changes.
|
||||
[2026-08-26] API-key scope pickers (settings panel + OAuth consent) render from one SCOPE_GROUPS list in lib/auth/scope-catalog.ts, with MCP tool counts derived from TOOL_SCOPE_MAP at module load: the panel's hand-copied group list had drifted to 24 of 30 scopes (no articles, companies:write or reconciliation, so dashboard-minted keys could not call those tools) and every per-scope count was stale. The catalogue is a separate pure module rather than api-keys.ts itself because the panel is a client component and api-keys.ts imports crypto and the service-role client; a unit test enforces one-group-per-scope so the next scope cannot silently vanish from the pickers.
|
||||
[2026-08-26] Webhook event catalogue lives in lib/webhooks/public-events.ts (grouped, with docs prose) and the fan-out handler set, the v1 create enum (so the OpenAPI spec and skills/accounted-api), and the docs page all derive from it: the enum and the docs had drifted to 24 of the 28 events the handler delivered, so the four reconciliation.* events were rejected at subscribe time. No API_V1_VERSION bump: the changelog already lists them as additive.
|
||||
|
||||
@@ -17,7 +17,7 @@ Open-source Swedish accounting software for sole traders (enskild firma) and lim
|
||||
|
||||
**Compliant by construction.** Accounted implements double-entry bookkeeping under Swedish accounting law (Bokföringslagen). Voucher immutability, sequential voucher numbering, period locks, and 7-year document retention are enforced by database triggers, not by convention. Corrections are made the legal way, with reversal entries (storno), never by editing history. See [ARCHITECTURE.md](ARCHITECTURE.md) for how.
|
||||
|
||||
**Agent-native.** The full bookkeeping engine is exposed as 100+ MCP (Model Context Protocol) tools with scoped API keys, so an AI agent can do the books in Accounted: categorize transactions, draft vouchers, reconcile periods, and prepare declarations. Posting is staged for human approval, so the agent proposes and you decide.
|
||||
**Agent-native.** The full bookkeeping engine is exposed as 150+ MCP (Model Context Protocol) tools with scoped API keys or OAuth, so an AI agent can do the books in Accounted: categorize transactions, draft vouchers, reconcile periods, and prepare declarations. Posting is staged for human approval, so the agent proposes and you decide.
|
||||
|
||||
**Yours to run.** AGPL-3.0 licensed and fully self-hostable with Docker and Supabase. Use the hosted version at [app.gnubok.se](https://app.gnubok.se) or run your own.
|
||||
|
||||
@@ -30,9 +30,13 @@ Open-source Swedish accounting software for sole traders (enskild firma) and lim
|
||||
- **Tax reports** -- NE-bilaga, INK2, SRU export for Skatteverket
|
||||
- **Payroll** -- Salary runs, payslips, and AGI (arbetsgivardeklaration) employer declarations
|
||||
- **Supplier invoices** -- Registration, payment tracking, input VAT deduction
|
||||
- **Supplier payment files (betalfil)** -- Batch supplier payments into ISO 20022 pain.001 files for upload to the bank, rendered byte-identical on every download
|
||||
- **E-invoicing (Peppol)** -- Send and receive Peppol BIS Billing 3 e-invoices through Qvalia, a certified Swedish Access Point; received documents land in the supplier invoice inbox
|
||||
- **Skattekonto** -- Tax account transactions synced from Skatteverket or imported from statement files, linked to the booked 1630 movements for reconciliation
|
||||
- **Document archive** -- SHA-256 integrity, 7-year retention enforcement, full archive ZIP export
|
||||
- **SIE import/export** -- Standard Swedish accounting interchange format
|
||||
- **Agent access (MCP)** -- 100+ bookkeeping tools over the Model Context Protocol, with scoped API keys and staged approvals
|
||||
- **Agent access (MCP)** -- 150+ bookkeeping tools over the Model Context Protocol, with scoped API keys and staged approvals
|
||||
- **Claude connector and plugin** -- Connect Claude.ai or Claude Code over OAuth 2.1 and install approval-gated workflow skills (`/accounted:bookkeep`, `/accounted:vat`, `/accounted:year-end`, ...) from [claude-plugin/](claude-plugin/README.md)
|
||||
- **Extension system** -- Opt-in plugins for AI categorization, receipt OCR, email, calendar, and more
|
||||
|
||||
## Self-Hosting
|
||||
@@ -48,7 +52,7 @@ You need a Supabase project and must apply the database migrations before first
|
||||
|
||||
## Development Setup
|
||||
|
||||
Prerequisites: Node.js 20+, a Supabase project.
|
||||
Prerequisites: Node.js 20 or newer (CI runs Node 20; the Docker image ships Node 22), a Supabase project.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
@@ -65,7 +69,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow.
|
||||
- **Framework**: Next.js 16 (App Router), React 19, TypeScript (strict)
|
||||
- **Database**: Supabase (PostgreSQL + Row Level Security + email/password auth + TOTP MFA)
|
||||
- **Styling**: Tailwind CSS 4 + shadcn/ui
|
||||
- **Integrations**: Enable Banking (PSD2), Anthropic SDK, LangChain, OpenAI, Resend, JSZip
|
||||
- **Integrations**: Enable Banking (PSD2), Qvalia (Peppol), Skatteverket, Anthropic SDK on Amazon Bedrock (eu-north-1; direct Anthropic or any OpenAI-compatible endpoint for self-hosted via the Vercel AI SDK), Resend, JSZip
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
+14
-12
@@ -17,15 +17,15 @@ You do **not** need Node.js, npm, or anything else installed locally. The pre-bu
|
||||
mkdir Accounted && cd Accounted
|
||||
|
||||
# Compose file + env template
|
||||
curl -fsSLO https://raw.githubusercontent.com/gnubok/gnubok/main/docker-compose.yml
|
||||
curl -fsSLO https://raw.githubusercontent.com/gnubok/gnubok/main/.env.docker.example
|
||||
curl -fsSLO https://raw.githubusercontent.com/erp-mafia/accounted/main/docker-compose.yml
|
||||
curl -fsSLO https://raw.githubusercontent.com/erp-mafia/accounted/main/.env.docker.example
|
||||
|
||||
# Cron sidecar (Dockerfile + schedule)
|
||||
mkdir -p docker
|
||||
curl -fsSL -o docker/cron.Dockerfile \
|
||||
https://raw.githubusercontent.com/gnubok/gnubok/main/docker/cron.Dockerfile
|
||||
https://raw.githubusercontent.com/erp-mafia/accounted/main/docker/cron.Dockerfile
|
||||
curl -fsSL -o docker/crontab.self-hosted \
|
||||
https://raw.githubusercontent.com/gnubok/gnubok/main/docker/crontab.self-hosted
|
||||
https://raw.githubusercontent.com/erp-mafia/accounted/main/docker/crontab.self-hosted
|
||||
```
|
||||
|
||||
### 2. Configure your environment
|
||||
@@ -146,10 +146,10 @@ NEXT_PUBLIC_APP_URL=https://gnubok.example.com
|
||||
### 3. Download the overlay + Caddyfile
|
||||
|
||||
```bash
|
||||
curl -fsSLO https://raw.githubusercontent.com/gnubok/gnubok/main/docker-compose.caddy.yml
|
||||
curl -fsSLO https://raw.githubusercontent.com/erp-mafia/accounted/main/docker-compose.caddy.yml
|
||||
mkdir -p docker
|
||||
curl -fsSL -o docker/Caddyfile \
|
||||
https://raw.githubusercontent.com/gnubok/gnubok/main/docker/Caddyfile
|
||||
https://raw.githubusercontent.com/erp-mafia/accounted/main/docker/Caddyfile
|
||||
```
|
||||
|
||||
### 4. Start with the overlay
|
||||
@@ -216,19 +216,21 @@ No env vars needed: always available.
|
||||
|
||||
## Updating
|
||||
|
||||
The default `IMAGE_TAG=latest` follows `main` and updates on every `docker compose pull`. For production, **pin to a specific release** so updates are deliberate:
|
||||
The default `IMAGE_TAG=latest` follows `main` and updates on every `docker compose pull`. For production, **pin to a specific build** so updates are deliberate. Every merge to `main` publishes the image under two tags: `latest` and the bare 7-character commit SHA (for example `3e4b5dd`), so a pin looks like:
|
||||
|
||||
```env
|
||||
# .env
|
||||
IMAGE_TAG=1.2.3
|
||||
IMAGE_TAG=3e4b5dd
|
||||
```
|
||||
|
||||
Browse available tags at https://github.com/erp-mafia/gnubok/pkgs/container/gnubok. For maximum integrity, pin by digest:
|
||||
Browse available tags at https://github.com/erp-mafia/accounted/pkgs/container/gnubok (the image name keeps the historical `gnubok` package name on purpose). For maximum integrity, pin by digest; the digest is printed in the `docker-publish` workflow run and by `docker buildx imagetools inspect ghcr.io/erp-mafia/gnubok:<sha>`:
|
||||
|
||||
```env
|
||||
IMAGE_TAG=1.2.3@sha256:abcdef...
|
||||
IMAGE_TAG=3e4b5dd@sha256:abcdef...
|
||||
```
|
||||
|
||||
Semver tags (`1.2.3`, `1.2`, `1`) are published only when a `v*.*.*` git tag is cut. No such tag exists yet, so until the first tagged release the commit SHA is the only immutable pin.
|
||||
|
||||
Apply updates:
|
||||
|
||||
```bash
|
||||
@@ -246,8 +248,8 @@ If you prefer to build locally instead of pulling the pre-built image:
|
||||
|
||||
```bash
|
||||
# Clone the repo
|
||||
git clone https://github.com/gnubok/gnubok.git
|
||||
cd Accounted
|
||||
git clone https://github.com/erp-mafia/accounted.git
|
||||
cd accounted
|
||||
cp .env.docker.example .env
|
||||
# Fill in .env
|
||||
|
||||
|
||||
+58
-89
@@ -42,12 +42,14 @@ These are configured via `extensions.config.json` and only loaded when explicitl
|
||||
|
||||
Sector extensions are tied to a specific market sector. They're only relevant to businesses operating in that sector. A restaurant owner wants "Food Cost %" but an IT consultant does not.
|
||||
|
||||
Examples:
|
||||
Examples of what a sector extension could be (design intent, none of these exist):
|
||||
- **Restaurant:** Food Cost %, Earnings Per Alcohol Liter, POS Z-Report Import, Tip Tracking
|
||||
- **Construction:** ROT Calculator, Project Cost Tracking
|
||||
- **Hotel:** RevPAR, Occupancy Tracking
|
||||
- **IT/Consulting:** Billable Hours Ratio, Project Billing Metrics
|
||||
- **E-commerce:** Shopify Order Import, Multi-channel Revenue Analytics
|
||||
- **E-commerce:** Multi-channel Revenue Analytics
|
||||
|
||||
**Current state (2026-08-26):** no sector extension has been built. Every shipped extension lives under `extensions/general/`, and `SectorSlug` in `lib/extensions/types.ts` is currently just `'general'`. The sector concept, the marketplace routes (`app/(dashboard)/extensions/[sector]/...`) and the `sector/slug` extension-id format are in place for when the first one arrives. Shopify and WooCommerce order import shipped as general extensions, not e-commerce sector ones.
|
||||
|
||||
### The Unified Model
|
||||
|
||||
@@ -55,39 +57,30 @@ Both general and sector extensions live in the same system:
|
||||
|
||||
```
|
||||
extensions/
|
||||
general/ ← General extensions (any business)
|
||||
document-extraction/
|
||||
invoice-inbox/
|
||||
mcp-server/
|
||||
skatteverket/
|
||||
push-notifications/
|
||||
enable-banking/
|
||||
calendar/
|
||||
email/
|
||||
restaurant/ ← Restaurant sector extensions
|
||||
food-cost/
|
||||
earnings-per-liter/
|
||||
pos-import/
|
||||
tip-tracking/
|
||||
construction/ ← Construction sector extensions
|
||||
rot-calculator/
|
||||
project-cost/
|
||||
hotel/ ← Hotel sector extensions
|
||||
revpar/
|
||||
occupancy/
|
||||
tech/ ← IT/Consulting sector extensions
|
||||
billable-hours/
|
||||
project-billing/
|
||||
ecommerce/ ← E-commerce sector extensions
|
||||
shopify-import/
|
||||
multichannel-revenue/
|
||||
export/ ← Export & international trade extensions
|
||||
eu-sales-list/
|
||||
intrastat/
|
||||
vat-monitor/
|
||||
currency-receivables/
|
||||
general/ ← General extensions (any business); the only sector today
|
||||
arcim-migration/ ← Systemmigration: import from Fortnox, Visma, Bokio, Björn Lundén, Briox
|
||||
bolagsverket/ ← Digital årsredovisning filing to Bolagsverket (iXBRL)
|
||||
calendar/ ← Kalender: month/week/day views
|
||||
cloud-backup/ ← Molnsynkronisering: sync the säkerhetsbackup to the user's own cloud storage
|
||||
document-extraction/ ← AI-extrahering av underlag: reads receipts and invoices, fills supplier/amount/VAT/date
|
||||
email/ ← E-post (Resend): invoices and reminders by e-mail
|
||||
enable-banking/ ← Bankintegration (PSD2): automatic bank transaction sync
|
||||
invoice-inbox/ ← Dokumentinkorg: forward supplier invoices to a unique address
|
||||
mail/ ← Brevlådor: lets Kvittojakten (receipt hunt) search the user's mailboxes
|
||||
mcp-server/ ← MCP-server (API): bookkeeping via Claude, Cursor or any MCP client
|
||||
push-notifications/ ← Push-notiser: event notifications
|
||||
shopify/ ← Shopify: paid orders and refunds into the Ordersidan
|
||||
skatteverket/ ← Skatteverket: VAT declaration submission via BankID, skattekonto
|
||||
stripe/ ← Stripe-betalningar: payment links on invoices, automatic avprickning
|
||||
tic/ ← Bolagsuppgifter: company data from public registers via TIC
|
||||
whatsapp-inbox/ ← WhatsApp-inkorg: receipts as photo or PDF to Accounted's WhatsApp number
|
||||
woocommerce/ ← WooCommerce: paid orders and refunds into the transaction inbox
|
||||
_example-branding/ ← Whitelabel branding starter (disabled by default)
|
||||
example-logger/ ← Minimal example extension (index.ts only, no manifest)
|
||||
```
|
||||
|
||||
Which of these are compiled in is decided by `extensions.config.json`; today that is everything except `bolagsverket`, `push-notifications`, `_example-branding` and `example-logger`.
|
||||
|
||||
Each extension directory contains a `manifest.json` declaring metadata, entry point, workspace component path, required env vars, and npm dependencies.
|
||||
|
||||
In the marketplace:
|
||||
@@ -283,8 +276,8 @@ type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations'
|
||||
| Earnings Per Alcohol Liter | Restaurant | A + B (both) | Liters sold per day/week | Alcohol revenue from BAS 3001 | Calculates revenue/liter, trends over time |
|
||||
| Food Cost % | Restaurant | A (core) | None | Food purchases (4000-series), food revenue (3000-series) | Calculates food_cost/food_revenue %, trends |
|
||||
| Tip Tracking | Restaurant | B (manual) | Tip amounts per shift | Optionally reads staff cost accounts | Total tips, tips/employee, tip % of revenue |
|
||||
| POS Z-Report Import | Restaurant | B (manual) | Uploads Z-report CSV/Excel | None | Parses POS data, stores in extension, shows daily sales analytics |
|
||||
| Shopify Order Import | E-commerce | B (manual) | Uploads order export | None | Imports orders into extension, shows revenue by product, trends |
|
||||
| POS Z-Report Import (not built) | Restaurant | B (manual) | Uploads Z-report CSV/Excel | None | Parses POS data, stores in extension, shows daily sales analytics |
|
||||
| Shopify / WooCommerce (shipped, general) | General | B (manual) | Store connection | None | Pulls paid orders and refunds into `webshop_orders` for the order and transaction inboxes |
|
||||
| ROT Calculator | Construction | A + B (both) | Labor hours, material costs per job | Invoice data for customer billing | ROT deduction amounts (30% of labor, max 50k/year per customer) |
|
||||
| RevPAR | Hotel | A + B (both) | Room count and occupancy | Room revenue accounts | Revenue Per Available Room, occupancy rate |
|
||||
| Billable Hours Ratio | IT/Consulting | A + B (both) | Hours worked per project | Invoice data for billed amounts | Billable/total hours, effective hourly rate |
|
||||
@@ -297,7 +290,7 @@ type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations'
|
||||
|
||||
```
|
||||
extensions/ ← Extension source code (opt-in via config)
|
||||
general/ ← General extensions
|
||||
general/ ← General extensions (the only sector that exists)
|
||||
invoice-inbox/
|
||||
manifest.json ← Metadata, entry point, env vars, workspace path
|
||||
index.ts ← Extension definition + logic (exports Extension)
|
||||
@@ -313,11 +306,14 @@ extensions/ ← Extension source code (opt-in via conf
|
||||
push-notifications/
|
||||
manifest.json
|
||||
index.ts
|
||||
lib/
|
||||
api-routes.ts
|
||||
notification-scheduler.ts
|
||||
notification-sender.ts
|
||||
enable-banking/
|
||||
manifest.json
|
||||
index.ts
|
||||
lib/
|
||||
components/
|
||||
email/ ← Email service extension (registers Resend impl)
|
||||
manifest.json
|
||||
index.ts
|
||||
@@ -325,45 +321,22 @@ extensions/ ← Extension source code (opt-in via conf
|
||||
calendar/
|
||||
manifest.json
|
||||
index.ts
|
||||
restaurant/ ← Restaurant sector
|
||||
food-cost/
|
||||
manifest.json
|
||||
earnings-per-liter/
|
||||
manifest.json
|
||||
pos-import/
|
||||
manifest.json
|
||||
tip-tracking/
|
||||
manifest.json
|
||||
construction/ ← Construction sector
|
||||
rot-calculator/
|
||||
manifest.json
|
||||
project-cost/
|
||||
manifest.json
|
||||
hotel/ ← Hotel sector
|
||||
revpar/
|
||||
manifest.json
|
||||
occupancy/
|
||||
manifest.json
|
||||
tech/ ← IT/Consulting sector
|
||||
billable-hours/
|
||||
manifest.json
|
||||
project-billing/
|
||||
manifest.json
|
||||
ecommerce/ ← E-commerce sector
|
||||
shopify-import/
|
||||
manifest.json
|
||||
multichannel-revenue/
|
||||
manifest.json
|
||||
export/ ← Export & international trade sector
|
||||
eu-sales-list/
|
||||
components/
|
||||
mcp-server/ ← MCP server: server.ts, tools, prompts, resources, skills, widgets
|
||||
manifest.json
|
||||
index.ts
|
||||
intrastat/
|
||||
manifest.json
|
||||
vat-monitor/
|
||||
manifest.json
|
||||
currency-receivables/
|
||||
manifest.json
|
||||
server.ts
|
||||
arcim-migration/ ← Provider migration (Fortnox, Visma, Bokio, BL, Briox)
|
||||
bolagsverket/ ← Digital årsredovisning (iXBRL)
|
||||
cloud-backup/ ← Cloud sync of the säkerhetsbackup
|
||||
mail/ ← Mailbox connections for Kvittojakten
|
||||
shopify/ ← Shopify order import (api-routes.ts, components/, lib/)
|
||||
woocommerce/ ← WooCommerce order import (api-routes.ts, components/, lib/)
|
||||
stripe/ ← Stripe payment links and avprickning
|
||||
tic/ ← Company data lookup (TIC)
|
||||
whatsapp-inbox/ ← WhatsApp receipt intake
|
||||
_example-branding/ ← Whitelabel starter, disabled by default
|
||||
example-logger/ ← Minimal example, index.ts only
|
||||
|
||||
extensions.config.json ← Which extensions are enabled (empty = core-only)
|
||||
extensions.schema.json ← JSON Schema for extensions.config.json
|
||||
@@ -384,7 +357,7 @@ lib/
|
||||
email/
|
||||
service.ts ← EmailService interface + no-op default + getEmailService()
|
||||
reports/
|
||||
sru-export/ ← SRU file export (core, not an extension)
|
||||
sru-encoding.ts ← SRU file encoding (core, not an extension)
|
||||
ne-bilaga/ ← NE tax form attachment (core, not an extension)
|
||||
|
||||
scripts/
|
||||
@@ -403,22 +376,18 @@ components/
|
||||
DateRangeFilter.tsx
|
||||
EmptyExtensionState.tsx
|
||||
ExtensionLoadingSkeleton.tsx
|
||||
general/ ← General extension workspaces
|
||||
general/ ← General extension workspaces (the only ones that exist)
|
||||
ReceiptOcrWorkspace.tsx
|
||||
AiCategorizationWorkspace.tsx
|
||||
AiChatWorkspace.tsx
|
||||
restaurant/ ← Restaurant extension workspaces
|
||||
EarningsPerLiterWorkspace.tsx
|
||||
FoodCostWorkspace.tsx
|
||||
PosImportWorkspace.tsx
|
||||
construction/
|
||||
RotCalculatorWorkspace.tsx
|
||||
hotel/
|
||||
RevparWorkspace.tsx
|
||||
tech/
|
||||
BillableHoursWorkspace.tsx
|
||||
ecommerce/
|
||||
ShopifyImportWorkspace.tsx
|
||||
InvoiceInboxWorkspace.tsx
|
||||
EnableBankingWorkspace.tsx
|
||||
CalendarWorkspace.tsx
|
||||
CloudBackupWorkspace.tsx
|
||||
ArcimMigrationWorkspace.tsx
|
||||
PushNotificationsWorkspace.tsx
|
||||
TicWorkspace.tsx
|
||||
MailConnectionsPanel.tsx
|
||||
WhatsAppLinkPanel.tsx
|
||||
|
||||
app/(dashboard)/
|
||||
extensions/ ← Marketplace
|
||||
|
||||
@@ -5,7 +5,7 @@ Issue #546 requires two separable capabilities:
|
||||
1. Produce a correctly structured Peppol BIS Billing 3 invoice from Accounted data.
|
||||
2. Deliver and receive documents through the Peppol network.
|
||||
|
||||
The first slice implemented the invoice profile. The second slice adds an immutable staged-delivery and audit foundation. Neither slice claims network delivery.
|
||||
The first slice implemented the invoice profile. The second slice added an immutable staged-delivery and audit foundation. Neither of those two slices claimed network delivery; since 2026-08-21 both sending and receiving run over the Peppol network through Qvalia (see the Qvalia and Receiving (PR2) sections below). The sections up to "Access point: Qvalia" describe the foundation those slices left behind and the requirements the Qvalia work had to meet.
|
||||
|
||||
## Implemented scope
|
||||
|
||||
@@ -74,7 +74,7 @@ No invoice status should change merely because XML was generated or accepted by
|
||||
|
||||
### Receiving
|
||||
|
||||
Inbound invoices are a separate acceptance slice. It requires provider webhook authentication, raw XML retention, duplicate detection, supplier matching, safe attachment handling, and mapping into the supplier-invoice inbox without treating received content as trusted. Nothing in this foundation claims inbound support.
|
||||
Inbound invoices are a separate acceptance slice. It requires provider webhook authentication, raw XML retention, duplicate detection, supplier matching, safe attachment handling, and mapping into the supplier-invoice inbox without treating received content as trusted. The original foundation did not claim inbound support; that slice has since shipped (`peppol_registrations`, `peppol_inbound_documents`, the inbound cron and inbox delivery) and is described under "Receiving (PR2)" below.
|
||||
|
||||
### UI and API
|
||||
|
||||
|
||||
+7
-12
@@ -65,8 +65,8 @@ These are all available on Supabase hosted. `pg_cron` requires a paid plan: if y
|
||||
**Option A: Setup script (recommended):**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/erp-mafia/gnubok.git
|
||||
cd Accounted
|
||||
git clone https://github.com/erp-mafia/accounted.git
|
||||
cd accounted
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
@@ -75,8 +75,8 @@ The script checks prerequisites, prompts for your Supabase credentials, auto-gen
|
||||
**Option B: Manual:**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/erp-mafia/gnubok.git
|
||||
cd Accounted
|
||||
git clone https://github.com/erp-mafia/accounted.git
|
||||
cd accounted
|
||||
cp .env.docker.example .env
|
||||
```
|
||||
|
||||
@@ -313,14 +313,9 @@ VAPID_SUBJECT=mailto:you@example.com
|
||||
|
||||
Generate VAPID keys with `npx web-push generate-vapid-keys`. Push notifications require HTTPS.
|
||||
|
||||
### Error Tracking (Sentry)
|
||||
### Error Tracking
|
||||
|
||||
```bash
|
||||
SENTRY_DSN=https://...@sentry.io/...
|
||||
NEXT_PUBLIC_SENTRY_DSN=https://...@sentry.io/...
|
||||
```
|
||||
|
||||
Sentry is disabled if these are not set. No errors are thrown.
|
||||
There is no Sentry integration. `SENTRY_DSN` and `NEXT_PUBLIC_SENTRY_DSN` are not read by the app: setting them changes nothing. Error-level events go to the container logs (structured JSON on stdout/stderr); `lib/observability/sink.ts` is a provider-agnostic seam that stays a no-op until an adapter is registered with `registerObservabilitySink()`, so a self-hosted build carries no third-party error-tracking dependency. If you want alerting, ship the container logs to your log system and alert there. See [docs/security/logging-and-observability.md](security/logging-and-observability.md).
|
||||
|
||||
## Storage Buckets
|
||||
|
||||
@@ -341,7 +336,7 @@ If a new release includes database migrations, apply them before restarting:
|
||||
supabase db push
|
||||
```
|
||||
|
||||
Check the [release notes](https://github.com/erp-mafia/gnubok/releases) for migration instructions.
|
||||
Check the [release notes](https://github.com/erp-mafia/accounted/releases) for migration instructions.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Arkivplan
|
||||
|
||||
**Mall for användare av erp-base**
|
||||
**Mall för användare av Accounted**
|
||||
Upprättad i enlighet med BFNAR 2013:2 punkt 8.3
|
||||
|
||||
---
|
||||
@@ -29,11 +29,11 @@ Fyll i de markerade fälten. Radera denna instruktionssektion innan du arkiverar
|
||||
|
||||
| Fält | Uppgift |
|
||||
|---|---|
|
||||
| Programvara | erp-base ([DOMÄN]) |
|
||||
| Programvara | Accounted ([DOMÄN, för den molnbaserade tjänsten app.accounted.se]) |
|
||||
| Leverantör | [BOLAGSNAMN], org.nr [ORG-NR] |
|
||||
| Lagringsplats | Molnbaserad tjänst, data lagrat inom EU (Supabase/AWS) |
|
||||
| Åtkomst | Via webbläsare, inloggning med magic link (e-post) |
|
||||
| Kontoplan | BAS 2025/2026 (konfigurerad i erp-base) |
|
||||
| Lagringsplats | Molnbaserad tjänst, data lagrad inom EU (Supabase på AWS, region eu-north-1, Stockholm) |
|
||||
| Åtkomst | Via webbläsare. Inloggning med e-post och lösenord; tvåfaktorsautentisering (TOTP) krävs i den molnbaserade tjänsten. BankID kan kopplas som inloggningsmetod. |
|
||||
| Kontoplan | BAS 2026 (konfigurerad i Accounted) |
|
||||
|
||||
## 3. Förteckning över räkenskapsinformation
|
||||
|
||||
@@ -43,44 +43,48 @@ Tabellen nedan anger vilken räkenskapsinformation som finns, i vilken form den
|
||||
|
||||
| Räkenskapsinformation | Form | Lagringsplats | Arkiveringstid |
|
||||
|---|---|---|---|
|
||||
| Grundbokföring (registreringsordning) | Elektronisk | erp-base databas | 7 år efter räkenskapsårets utgång |
|
||||
| Huvudbokföring (systematisk ordning) | Elektronisk | erp-base databas | 7 år efter räkenskapsårets utgång |
|
||||
| Verifikationer (journalposter) | Elektronisk | erp-base databas | 7 år efter räkenskapsårets utgång |
|
||||
| Grundbokföring (registreringsordning) | Elektronisk | Accounted databas | 7 år efter räkenskapsårets utgång |
|
||||
| Huvudbokföring (systematisk ordning) | Elektronisk | Accounted databas | 7 år efter räkenskapsårets utgång |
|
||||
| Verifikationer (journalposter) | Elektronisk | Accounted databas | 7 år efter räkenskapsårets utgång |
|
||||
| Rättelselogg (rättelser i samma verifikat: ursprungsvärde, nytt värde, tidpunkt, utförare) | Elektronisk | Accounted databas (oföränderlig logg) | 7 år efter räkenskapsårets utgång |
|
||||
|
||||
### 3.2 Verifikationsunderlag
|
||||
|
||||
| Räkenskapsinformation | Form | Lagringsplats | Arkiveringstid | Anmärkning |
|
||||
|---|---|---|---|---|
|
||||
| Kundfakturor (utgående) | Elektronisk (PDF) | erp-base dokumentarkiv | 7 år | Genereras i erp-base |
|
||||
| Leverantörsfakturor (inkommande) | Elektronisk (PDF/bild) | erp-base dokumentarkiv | 7 år | Uppladdade/skannade |
|
||||
| Kvitton | Elektronisk (foto/PDF) | erp-base dokumentarkiv | 7 år | Fotograferade via appen |
|
||||
| Bankutdrag/kontoutdrag | Elektronisk | erp-base via PSD2-koppling | 7 år | Synkroniserade via Enable Banking |
|
||||
| Avtal och övriga underlag | [Elektronisk/Papper] | [erp-base / Fysisk pärm] | 7 år | [Ange var dessa förvaras] |
|
||||
| Kundfakturor (utgående) | Elektronisk (PDF) | Accounted dokumentarkiv | 7 år | Genereras i Accounted |
|
||||
| Leverantörsfakturor (inkommande) | Elektronisk (PDF/bild) | Accounted dokumentarkiv | 7 år | Uppladdade/skannade |
|
||||
| Kvitton | Elektronisk (foto/PDF) | Accounted dokumentarkiv | 7 år | Fotograferade via appen |
|
||||
| Bankutdrag/kontoutdrag | Elektronisk | Accounted via PSD2-koppling | 7 år | Synkroniserade via Enable Banking |
|
||||
| E-fakturor via Peppol (inkommande) | Elektronisk (UBL-XML, eventuell bifogad PDF) | Accounted dokumentarkiv | 7 år | Mottagna via Peppol-nätverket (accesspunkt Qvalia); originalfilen bevaras oförändrad |
|
||||
| Skattekontoutdrag (importerade filer) | Elektronisk (CSV/SKV-fil från Skatteverket) | Accounted databas (transaktionerna samt importlogg med filnamn och kontrollsumma) | 7 år | Importerade under Importera/Exportera, alternativt hämtade via Skatteverket-kopplingen |
|
||||
| Avtal och övriga underlag | [Elektronisk/Papper] | [Accounted / Fysisk pärm] | 7 år | [Ange var dessa förvaras] |
|
||||
|
||||
### 3.3 Årsbokslut och årsredovisning
|
||||
|
||||
| Räkenskapsinformation | Form | Lagringsplats | Arkiveringstid |
|
||||
|---|---|---|---|
|
||||
| Resultaträkning | Elektronisk | erp-base rapportmodul | 7 år |
|
||||
| Balansräkning | Elektronisk | erp-base rapportmodul | 7 år |
|
||||
| Årsredovisning (AB) / Årsbokslut (EF) | [Elektronisk/Papper] | [erp-base / Bolagsverket / Fysisk pärm] | 7 år (10 år rekommenderat) |
|
||||
| NE-bilaga (EF) | Elektronisk | erp-base rapportmodul | 7 år |
|
||||
| Resultaträkning | Elektronisk | Accounted rapportmodul | 7 år |
|
||||
| Balansräkning | Elektronisk | Accounted rapportmodul | 7 år |
|
||||
| Årsredovisning (AB) / Årsbokslut (EF) | [Elektronisk/Papper] | [Accounted / Bolagsverket / Fysisk pärm] | 7 år (10 år rekommenderat) |
|
||||
| NE-bilaga (EF) | Elektronisk | Accounted rapportmodul | 7 år |
|
||||
| SIE-filer (export) | Elektronisk | [Ange var exporterade filer sparas] | 7 år |
|
||||
| Säkerhetsbackup (ZIP-arkiv med SIE-filer, rapporter, underlag, register och behandlingshistorik) | Elektronisk | [Ange var exporterade arkiv sparas] | 7 år |
|
||||
|
||||
### 3.4 Skattedeklarationer och momsrapporter
|
||||
|
||||
| Räkenskapsinformation | Form | Lagringsplats | Arkiveringstid |
|
||||
|---|---|---|---|
|
||||
| Momsdeklarationer | Elektronisk | erp-base rapportmodul + Skatteverket | 7 år |
|
||||
| SRU-filer | Elektronisk | erp-base rapportmodul | 7 år |
|
||||
| Momsdeklarationer | Elektronisk | Accounted rapportmodul + Skatteverket | 7 år |
|
||||
| SRU-filer | Elektronisk | Accounted rapportmodul | 7 år |
|
||||
| Inkomstdeklaration | [Elektronisk/Papper] | [Skatteverket / Egen kopia] | 7 år |
|
||||
|
||||
### 3.5 Systemdokumentation
|
||||
|
||||
| Dokument | Form | Lagringsplats | Arkiveringstid |
|
||||
|---|---|---|---|
|
||||
| Systemdokumentation | Elektronisk | [erp-base / Egen lagring] | Samma som den räkenskapsinformation den avser |
|
||||
| Behandlingshistorik | Elektronisk | erp-base (automatiskt genererad) | Samma som den räkenskapsinformation den avser |
|
||||
| Systemdokumentation | Elektronisk | [Accounted / Egen lagring] | Samma som den räkenskapsinformation den avser |
|
||||
| Behandlingshistorik | Elektronisk | Accounted (automatiskt genererad) | Samma som den räkenskapsinformation den avser |
|
||||
| Denna arkivplan | [Elektronisk/Papper] | [Ange lagringsplats] | Samma som den räkenskapsinformation den avser |
|
||||
|
||||
## 4. Pappersoriginal
|
||||
@@ -97,9 +101,9 @@ Tabellen nedan anger vilken räkenskapsinformation som finns, i vilken form den
|
||||
|
||||
## 5. Säkerhetskopiering och redundans
|
||||
|
||||
5.1. erp-base sköter automatisk daglig säkerhetskopiering av databasen via Supabase-infrastrukturen.
|
||||
5.1. Accounted sköter automatisk daglig säkerhetskopiering av databasen via Supabase-infrastrukturen.
|
||||
|
||||
5.2. Kunden rekommenderas att regelbundet exportera SIE4-filer och spara dessa på en separat lagringsplats som kompletterande säkerhetskopia.
|
||||
5.2. Kunden rekommenderas att regelbundet ta ut säkerhetsbackupen (ZIP-arkiv under **Importera/Exportera > Exportera > Säkerhetsbackup**, innehåller SIE-filer, rapporter, underlag, register och behandlingshistorik) och spara den på en separat lagringsplats som kompletterande säkerhetskopia.
|
||||
|
||||
Kundens kompletterande säkerhetskopiering:
|
||||
- Frekvens: [t.ex. månadsvis, kvartalsvis]
|
||||
@@ -108,17 +112,17 @@ Kundens kompletterande säkerhetskopiering:
|
||||
|
||||
## 6. Åtkomst efter avslutad prenumeration
|
||||
|
||||
6.1. Vid uppsägning av erp-base-kontot har Kunden nittio (90) dagar att exportera all räkenskapsinformation i enlighet med Användarvillkoren avsnitt 8.
|
||||
6.1. Vid uppsägning av Accounted-kontot har Kunden nittio (90) dagar att exportera all räkenskapsinformation i enlighet med Användarvillkoren avsnitt 8.
|
||||
|
||||
6.2. Räkenskapsinformation som omfattas av sjuårig arkiveringsskyldighet bevaras i skrivskyddat läge av erp-base, alternativt tillhandahålls som fullständig dataexport.
|
||||
6.2. Räkenskapsinformation som omfattas av sjuårig arkiveringsskyldighet bevaras i skrivskyddat läge av Accounted, alternativt tillhandahålls som fullständig dataexport.
|
||||
|
||||
6.3. Det är Kundens ansvar att planera för dataportabilitet och säkerställa tillgång till räkenskapsinformation under hela arkiveringsperioden, oavsett om Tjänsten fortfarande används.
|
||||
|
||||
## 7. Geografisk lagring
|
||||
|
||||
7.1. All data i erp-base lagras inom EU/EES via Supabase (AWS-infrastruktur, region eu-central eller eu-west).
|
||||
7.1. All data i Accounted lagras inom EU/EES via Supabase (AWS-infrastruktur, region eu-north-1, Stockholm).
|
||||
|
||||
7.2. Viss behandling sker hos underbiträden i USA (se Personuppgiftsbiträdesavtalet, avsnitt 6.2) med stöd av EU-U.S. Data Privacy Framework eller standardavtalsklausuler.
|
||||
7.2. Maskinell behandling (kategorisering samt avläsning av underlag) sker inom EU via Amazon Bedrock (eu-north-1, Stockholm); datan lämnar inte EU. Underbiträden enligt integritetspolicyn: Supabase (EU, eu-north-1), Vercel (applikationshosting, globalt CDN med EU Data Residency), Enable Banking (EU), Amazon Web Services (AI-inferens, EU, eu-north-1), Resend (e-postleverans, USA) och PostHog (EU, Frankfurt). Det enda underbiträdet som behandlar uppgifter i USA är Resend, med stöd av standardavtalsklausuler (se Personuppgiftsbiträdesavtalet, avsnitt 6.2).
|
||||
|
||||
7.3. I enlighet med 7 kap. 3a § BFL får räkenskapsinformation i elektronisk form förvaras i annat EU-land under förutsättning att detta har anmälts till Skatteverket.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user