diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..73c39054 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,56 @@ +name: Bug report +description: Report something that is broken or behaving incorrectly +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug. + + **Do not report security vulnerabilities here.** Use our [security policy](https://github.com/erp-mafia/accounted/security/policy) instead. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + placeholder: | + 1. Go to ... + 2. Click ... + 3. See error + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What you expected to happen instead. + validations: + required: true + - type: dropdown + id: deployment + attributes: + label: Deployment + options: + - Hosted (app.gnubok.se) + - Self-hosted (Docker) + - Local development + validations: + required: true + - type: input + id: environment + attributes: + label: Browser and OS + placeholder: "e.g. Chrome 138 on macOS 15" + - type: textarea + id: logs + attributes: + label: Relevant logs or screenshots + description: | + Paste any error messages or attach screenshots. + Remember to redact personal data, organization numbers, and financial details before posting. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..6277701d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Documentation + url: https://docs.gnubok.se + about: User guides and API reference. + - name: Security vulnerability + url: https://github.com/erp-mafia/accounted/security/policy + about: Report security issues privately, never in a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..a6de09f0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,30 @@ +name: Feature request +description: Suggest a new feature or improvement +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + description: Describe the problem or friction you are running into, not just the solution. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + description: What would you like to see happen? + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you have considered, or how you work around this today. + - type: textarea + id: context + attributes: + label: Additional context + description: | + Anything else that helps us understand the request. For accounting features, a reference + to the relevant Swedish rule or an example from another tool (e.g. Fortnox) is very helpful. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..5a8a6b67 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +# What and why + + + +## Checklist + +- [ ] Title follows [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`) +- [ ] `npm run lint`, `npm test`, and `npm run build` pass locally +- [ ] New or changed logic in `lib/` or `app/api/` has tests +- [ ] New or changed UI strings exist in both `messages/sv.json` and `messages/en.json` +- [ ] Migrations (if any) are new files in `supabase/migrations/`; no edits to already-shipped migrations +- [ ] Core builds with zero extensions enabled (no imports from `@/extensions/` in core code) +- [ ] Commits are signed off (`git commit -s`, see [DCO](../DCO)) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..99f97c5d --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,139 @@ +# Architecture + +Accounted is a multi-tenant double-entry bookkeeping system built for Swedish +accounting law. This document explains how the system is put together and why +some parts are deliberately rigid. For contribution workflow, see +[CONTRIBUTING.md](CONTRIBUTING.md). + +## Overview + +- **Framework**: Next.js (App Router) with React and TypeScript in strict mode. +- **Database**: Supabase (PostgreSQL with Row Level Security), which also + provides auth (email/password plus TOTP MFA). +- **Deployment**: Vercel-hosted is the primary target; a Docker self-hosted + setup is fully supported (see [docs/SELF-HOSTING.md](docs/SELF-HOSTING.md)). +- **UI**: Tailwind CSS with shadcn/ui components. User-facing product language + is Swedish and English (`messages/sv.json`, `messages/en.json`). + +## The bookkeeping engine + +All accounting writes flow through one engine: `lib/bookkeeping/engine.ts`. + +The journal entry lifecycle is draft, then commit: + +1. `createDraftEntry()` creates an uncommitted entry that can still change. +2. `commitEntry()` posts it. The voucher number is assigned atomically by the + `commit_journal_entry` database RPC, which keeps numbering sequential per + series. Swedish law requires an unbroken, explainable voucher sequence. +3. `createJournalEntry()` does both steps in one call. + +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`). + +If a gap still occurs in a voucher series (for example around imported +history), it must be documented, and the explanation is stored +(`voucher_gap_explanations`), following BFNAR 2013:2. + +## Legal enforcement lives in the database + +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). +- 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 + 7-year retention of accounting records. + +Application code never works around these triggers. If a code path hits one, +the code path is wrong, not the trigger. + +Two smaller invariants that show up everywhere in the codebase: + +- Monetary amounts are rounded with `Math.round(x * 100) / 100`. String-based + rounding such as `toFixed()` causes drift at the öre level and breaks entry + balance. +- Account numbers are strings (`'1930'`, never `1930`). They are identifiers, + not quantities. + +## Multi-tenancy and security + +Users belong to companies through `company_members`, and every business table +carries a `company_id`. Access control is layered: + +- **Row Level Security** in PostgreSQL restricts rows to companies the user + belongs to. +- **Explicit filtering**: queries still filter by `company_id` in code, as + defense in depth, because service-role code paths bypass RLS. +- **Route guards**: API routes wrap a shared route context helper that + resolves the authenticated user, the active company, and MFA enforcement in + one place. Routes never hand-roll their own auth. + +The active company is resolved server-side from the user's stored preference, +so the Next.js app and RLS always agree on which company is active. + +## Extension system + +Core is a complete accounting product on its own. Optional functionality +(AI categorization, receipt OCR, email, calendar, the MCP server, and more) +ships as extensions under `extensions/`, toggled by `extensions.config.json`. + +The boundary is strict and CI-enforced: + +- Core code never imports from `@/extensions/`. CI builds core with zero + extensions enabled, so a direct import breaks the build. +- Extensions integrate through the event bus and documented extension APIs, + and are wired via a generated static registry (`npm run setup:extensions`). + +Licensing follows the same boundary: the project is AGPL-3.0, with an +extension exception that allows third-party extensions using only the +documented Extension API to be licensed under any terms. See +[LICENSE](LICENSE) and [docs/EXTENSIONS.md](docs/EXTENSIONS.md). + +## 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 +transactions, draft vouchers, reconcile periods, generate reports and +declarations. + +- Authentication uses scoped API keys (stored as SHA-256 hashes, rate limited + per key). +- Posting operations are staged: an agent proposes an operation, and a human + approves it before anything is committed to the journal. + +## Events + +`lib/events/bus.ts` is a module-level singleton event bus. Domain events (for +example "invoice created" or "transaction imported") are how extensions react +to core activity without core knowing about them. + +## Repository map + +| Path | Contents | +|---|---| +| `app/` | Next.js App Router pages and API routes | +| `lib/bookkeeping/` | Engine, entry generators, account mapping, BAS chart data | +| `lib/core/` | Periods, year-end, storno, tax codes, audit, documents | +| `lib/reports/` | Balance sheet, income statement, VAT, SIE, tax reports | +| `lib/` (other) | Invoices, transactions, imports, salary, reconciliation, tax, providers | +| `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 | +| `messages/` | Swedish and English UI strings | +| `tests/` | Shared test helpers and fixtures | +| `docs/` | Self-hosting, Docker, extensions, white-label guides | + +## Testing + +- Unit and route tests run on Vitest with mocked Supabase clients + (`npm test`). +- Database behavior (triggers, RPCs, RLS) is tested against a real PostgreSQL + instance in `*.pg.test.ts` files (`npm run test:pg`), because mocking cannot + prove trigger semantics. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..ee0a3e13 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances + of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +**security@arcim.io**. All complaints will be reviewed and investigated +promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c35303b6..6c47cb56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,8 @@ # Contributing to Accounted -Thank you for your interest in contributing to gnubok. This guide covers the development workflow, coding standards, and submission process. +Thank you for your interest in contributing to Accounted. This guide covers the development workflow, coding standards, and submission process. + +Everyone interacting in the project is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md). ## Getting Started @@ -62,7 +64,7 @@ If you forget, you can amend: `git commit --amend -s`. ## Extension Development -See `CLAUDE.md` for the full extension architecture. Quick start: +See [ARCHITECTURE.md](ARCHITECTURE.md) and [docs/EXTENSIONS.md](docs/EXTENSIONS.md) for the full extension architecture. Quick start: ```bash npx tsx scripts/create-extension.ts --name my-ext --sector general --category operations --description "..." diff --git a/README.md b/README.md index b29ccd91..9f1fbbac 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,25 @@ # Accounted -Open-source Swedish accounting software for sole traders (enskild firma) and limited companies (aktiebolag). +Open-source Swedish accounting software for sole traders (enskild firma) and limited companies (aktiebolag). Double-entry bookkeeping that complies with Swedish accounting law, built to be operated by you or by your AI agent. [![License: AGPL-3.0-or-later](https://img.shields.io/badge/License-AGPL--3.0--or--later-blue.svg)](LICENSE) +[![Core Build](https://github.com/erp-mafia/accounted/actions/workflows/core-build.yml/badge.svg)](https://github.com/erp-mafia/accounted/actions/workflows/core-build.yml) +[![pg-real tests](https://github.com/erp-mafia/accounted/actions/workflows/test-pg-real.yml/badge.svg)](https://github.com/erp-mafia/accounted/actions/workflows/test-pg-real.yml) +[![Docker](https://github.com/erp-mafia/accounted/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/erp-mafia/accounted/actions/workflows/docker-publish.yml) -## What is Accounted? +[Website](https://accounted.se) · [Hosted app](https://app.gnubok.se) · [Documentation](https://docs.gnubok.se) -Accounted implements double-entry bookkeeping compliant with Swedish accounting law (Bokforingslagen). It supports the BAS 2026 chart of accounts, handles VAT declarations (momsdeklaration), SIE import/export, and enforces 7-year document retention. Built for sole traders and limited companies operating in Sweden. + + +## Why Accounted? + +**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. + +**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. ## Features @@ -15,21 +28,23 @@ Accounted implements double-entry bookkeeping compliant with Swedish accounting - **Bank reconciliation** -- PSD2 bank connection via Enable Banking, 4-pass automatic matching - **VAT declaration** -- SKV 4700 form mapping, per-rate breakdown, EU/export handling - **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 - **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 - **Extension system** -- Opt-in plugins for AI categorization, receipt OCR, email, calendar, and more ## Self-Hosting ```bash git clone https://github.com/erp-mafia/accounted.git -cd Accounted +cd accounted ./setup.sh # Prompts for Supabase credentials, generates .env docker compose up -d ``` -You need a Supabase project and must apply the database migrations before first use. See [SELF-HOSTING.md](docs/SELF-HOSTING.md) for the full step-by-step guide, including Supabase setup, auth configuration, optional features (AI, email, push notifications), and troubleshooting. +You need a Supabase project and must apply the database migrations before first use. See [docs/SELF-HOSTING.md](docs/SELF-HOSTING.md) for the full step-by-step guide, including Supabase setup, auth configuration, optional features (AI, email, push notifications), and troubleshooting. ## Development Setup @@ -43,6 +58,8 @@ npm run build # Production build npm run lint # ESLint ``` +See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow. + ## Tech Stack - **Framework**: Next.js 16 (App Router), React 19, TypeScript (strict) @@ -52,11 +69,20 @@ npm run lint # ESLint ## Documentation -- [SELF-HOSTING.md](docs/SELF-HOSTING.md) -- Full self-hosting guide (Docker, Supabase setup, migrations, optional features) -- [CLAUDE.md](CLAUDE.md) -- Architecture, bookkeeping engine, database conventions, extension system +- [User and API documentation](https://docs.gnubok.se) -- Guides for using the product and the public API +- [ARCHITECTURE.md](ARCHITECTURE.md) -- How the system is built: bookkeeping engine, legal enforcement, tenancy, extensions, agent surface +- [docs/SELF-HOSTING.md](docs/SELF-HOSTING.md) -- Full self-hosting guide (Docker, Supabase setup, migrations, optional features) +- [docs/EXTENSIONS.md](docs/EXTENSIONS.md) -- Extension development guide +- [docs/DOCKER.md](docs/DOCKER.md) -- Docker deployment reference - [CONTRIBUTING.md](CONTRIBUTING.md) -- Development workflow, code style, pull request process - [SECURITY.md](SECURITY.md) -- Vulnerability reporting policy +## Community + +- Found a bug or have an idea? [Open an issue](https://github.com/erp-mafia/accounted/issues/new/choose) +- Security vulnerabilities: see [SECURITY.md](SECURITY.md), never a public issue +- Everyone interacting in the project is expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md) + ## Contributing Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.