diff --git a/.claude/rules/database.md b/.claude/rules/database.md index a49c263b..3ec7d8c0 100644 --- a/.claude/rules/database.md +++ b/.claude/rules/database.md @@ -26,7 +26,7 @@ Use the `/supabase-migration` skill for new migrations. ## Key Tables (~60) - **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`, `account_balances`, `voucher_sequences`, `voucher_gap_explanations` +- **Bookkeeping**: `chart_of_accounts`, `fiscal_periods`, `journal_entries`, `journal_entry_lines`, `voucher_sequences`, `voucher_gap_explanations` - **Invoicing**: `customers`, `invoices`, `invoice_items`, `invoice_payments`, `invoice_inbox_items` - **Suppliers**: `suppliers`, `supplier_invoices`, `supplier_invoice_items` - **Banking**: `bank_connections`, `transactions`, `bank_file_imports`, `payment_match_log` diff --git a/.dockerignore b/.dockerignore index d2f06d2a..04557e18 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,6 +15,7 @@ tests/ .vscode/ *.md !README.md -!DOCKER.md +docs/*.md +!docs/DOCKER.md LICENSE docker-compose*.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index ef5c6d50..bb57a82f 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -95,7 +95,7 @@ jobs: # between publish and the blocking scan is the scan's own duration # (minutes), not a 24h cron window) plus a daily cron as a safety net. # Accepted residual risk: an image is live for that short scan window - # before the gate fires; see SELF-HOSTING.md / the risk register. + # before the gate fires; see docs/SELF-HOSTING.md / the risk register. continue-on-error: true uses: aquasecurity/trivy-action@v0.36.0 with: diff --git a/README.md b/README.md index 109c6c98..13348473 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ cd Accounted docker compose up -d ``` -You need a Supabase project and must apply the database migrations before first use. See [SELF-HOSTING.md](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 [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 @@ -52,7 +52,7 @@ npm run lint # ESLint ## Documentation -- [SELF-HOSTING.md](SELF-HOSTING.md) -- Full self-hosting guide (Docker, Supabase setup, migrations, optional features) +- [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 - [CONTRIBUTING.md](CONTRIBUTING.md) -- Development workflow, code style, pull request process - [SECURITY.md](SECURITY.md) -- Vulnerability reporting policy diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index e3a0e5c6..c67bbf0c 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -412,13 +412,12 @@ function LoginPageContent() { {tAuth('bankid_no_account_body')}
- +
) : ( diff --git a/app/(dashboard)/chat/[id]/page.tsx b/app/(dashboard)/chat/[id]/page.tsx index 1fa576c6..d65b8232 100644 --- a/app/(dashboard)/chat/[id]/page.tsx +++ b/app/(dashboard)/chat/[id]/page.tsx @@ -23,21 +23,25 @@ export default async function ChatConversationPage({ params }: PageProps) { const companyId = await getActiveCompanyId(supabase, user.id) if (!companyId) redirect('/onboarding') - const { data: conversation } = await supabase - .from('agent_conversations') - .select('id, intent_id, context_ref, title, pinned, archived, last_message_at') - .eq('id', id) - .eq('company_id', companyId) - .maybeSingle() + // Both queries key on the route id, so they run in parallel. The tenant + // check on the conversation row still gates rendering — when it fails, + // notFound() throws and the messages result is discarded unrendered. + const [{ data: conversation }, { data: messages }] = await Promise.all([ + supabase + .from('agent_conversations') + .select('id, intent_id, context_ref, title, pinned, archived, last_message_at') + .eq('id', id) + .eq('company_id', companyId) + .maybeSingle(), + supabase + .from('agent_messages') + .select('role, content, hidden, created_at') + .eq('conversation_id', id) + .order('created_at', { ascending: true }), + ]) if (!conversation) notFound() - const { data: messages } = await supabase - .from('agent_messages') - .select('role, content, hidden, created_at') - .eq('conversation_id', id) - .order('created_at', { ascending: true }) - return (Verifikat
+ {lines.map((line, i) => { + const debitAmt = typeof line.debit_amount === 'number' ? line.debit_amount : 0 + const creditAmt = typeof line.credit_amount === 'number' ? line.credit_amount : 0 + return ( +Anställd hittades inte
+ return{t('detail_not_found')}
} return ( @@ -167,149 +169,160 @@ export default function EmployeeDetailPage({ params }: { params: Promise<{ id: s- {employee.personnummer} · {EMPLOYMENT_LABELS[employee.employment_type]} + {employee.personnummer} · {t(EMPLOYMENT_LABEL_KEYS[employee.employment_type])}