Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
using Books.Api.EventFlow.Repositories;
|
|
|
|
|
using Ledger.Core.Models;
|
|
|
|
|
using Ledger.Core.Services;
|
|
|
|
|
|
|
|
|
|
namespace Books.Api.Reporting;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Service for generating VAT (moms) reports from ledger data.
|
|
|
|
|
/// Aggregates VAT account balances for SKAT compliance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class VatReportService(
|
|
|
|
|
IAccountRepository accountRepository,
|
|
|
|
|
ILedgerService ledgerService,
|
|
|
|
|
ILogger<VatReportService> logger) : IVatReportService
|
|
|
|
|
{
|
|
|
|
|
// Standard Danish VAT account numbers
|
2026-02-05 21:35:26 +01:00
|
|
|
// TODO: These should ideally come from company-level configuration,
|
|
|
|
|
// as different chart-of-accounts templates may use different numbers.
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
private const string InputVatAccountNumber = "5610"; // Købsmoms (indgående moms)
|
|
|
|
|
private const string OutputVatAccountNumber = "5611"; // Salgsmoms (udgående moms)
|
|
|
|
|
private const string EuAcquisitionVatAccountNumber = "5620"; // EU erhvervelsesmoms
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
|
|
|
|
|
public async Task<VatReportDto> GenerateReportAsync(
|
|
|
|
|
string companyId,
|
|
|
|
|
DateOnly periodStart,
|
|
|
|
|
DateOnly periodEnd,
|
|
|
|
|
CancellationToken ct = default)
|
|
|
|
|
{
|
|
|
|
|
// Validate period
|
|
|
|
|
if (periodEnd < periodStart)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Slutdato skal være efter startdato", nameof(periodEnd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var daysDiff = periodEnd.DayNumber - periodStart.DayNumber;
|
|
|
|
|
if (daysDiff > 366)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("Perioden må ikke overstige ét år (366 dage)", nameof(periodEnd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.LogInformation(
|
|
|
|
|
"Generating VAT report for company {CompanyId} from {PeriodStart} to {PeriodEnd}",
|
|
|
|
|
companyId, periodStart, periodEnd);
|
|
|
|
|
|
|
|
|
|
// Look up VAT accounts
|
|
|
|
|
var inputVatAccount = await accountRepository.GetByCompanyAndNumberAsync(
|
|
|
|
|
companyId, InputVatAccountNumber, ct);
|
|
|
|
|
var outputVatAccount = await accountRepository.GetByCompanyAndNumberAsync(
|
|
|
|
|
companyId, OutputVatAccountNumber, ct);
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
var euAcquisitionVatAccount = await accountRepository.GetByCompanyAndNumberAsync(
|
|
|
|
|
companyId, EuAcquisitionVatAccountNumber, ct);
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
|
|
|
|
|
var report = new VatReportDto
|
|
|
|
|
{
|
|
|
|
|
PeriodStart = periodStart,
|
|
|
|
|
PeriodEnd = periodEnd
|
|
|
|
|
};
|
|
|
|
|
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
// If no VAT accounts exist, return empty report
|
|
|
|
|
if (inputVatAccount == null && outputVatAccount == null && euAcquisitionVatAccount == null)
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
{
|
|
|
|
|
logger.LogWarning(
|
|
|
|
|
"No VAT accounts found for company {CompanyId}. Returning empty report.",
|
|
|
|
|
companyId);
|
|
|
|
|
return report;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect account IDs for ledger query
|
|
|
|
|
var accountIds = new List<Guid>();
|
|
|
|
|
|
|
|
|
|
if (inputVatAccount != null && TryParseAccountGuid(inputVatAccount.Id, out var inputGuid))
|
|
|
|
|
{
|
|
|
|
|
accountIds.Add(inputGuid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (outputVatAccount != null && TryParseAccountGuid(outputVatAccount.Id, out var outputGuid))
|
|
|
|
|
{
|
|
|
|
|
accountIds.Add(outputGuid);
|
|
|
|
|
}
|
|
|
|
|
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
if (euAcquisitionVatAccount != null && TryParseAccountGuid(euAcquisitionVatAccount.Id, out var euAcquisitionGuid))
|
|
|
|
|
{
|
|
|
|
|
accountIds.Add(euAcquisitionGuid);
|
|
|
|
|
}
|
|
|
|
|
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
if (accountIds.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
logger.LogWarning("No valid VAT account GUIDs found for company {CompanyId}", companyId);
|
|
|
|
|
return report;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query ledger for aggregated balances in the period
|
|
|
|
|
var query = new EntriesQuery
|
|
|
|
|
{
|
|
|
|
|
AccountIds = accountIds,
|
|
|
|
|
From = new DateTimeOffset(periodStart.ToDateTime(TimeOnly.MinValue)),
|
|
|
|
|
To = new DateTimeOffset(periodEnd.ToDateTime(TimeOnly.MaxValue)),
|
|
|
|
|
Aggregate = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var result = await ledgerService.QueryEntriesAsync(query, ct);
|
|
|
|
|
|
|
|
|
|
// Process aggregated balances
|
|
|
|
|
if (result.Aggregates != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var balance in result.Aggregates)
|
|
|
|
|
{
|
|
|
|
|
if (inputVatAccount != null &&
|
|
|
|
|
TryParseAccountGuid(inputVatAccount.Id, out var checkInputGuid) &&
|
|
|
|
|
balance.AccountId == checkInputGuid)
|
|
|
|
|
{
|
|
|
|
|
// Input VAT (5610): Debits are VAT receivable/deductions
|
|
|
|
|
// Box B = total input VAT deduction
|
|
|
|
|
report.BoxB = balance.TotalDebits;
|
|
|
|
|
report.TransactionCount += balance.EntryCount;
|
|
|
|
|
|
|
|
|
|
logger.LogDebug(
|
|
|
|
|
"Input VAT (5610): TotalDebits={Debits}, TotalCredits={Credits}, NetChange={Net}",
|
|
|
|
|
balance.TotalDebits, balance.TotalCredits, balance.NetChange);
|
|
|
|
|
}
|
|
|
|
|
else if (outputVatAccount != null &&
|
|
|
|
|
TryParseAccountGuid(outputVatAccount.Id, out var checkOutputGuid) &&
|
|
|
|
|
balance.AccountId == checkOutputGuid)
|
|
|
|
|
{
|
|
|
|
|
// Output VAT (5611): Credits are VAT payable to SKAT
|
|
|
|
|
// Box A = total output VAT (salgsmoms)
|
|
|
|
|
report.BoxA = balance.TotalCredits;
|
|
|
|
|
report.TransactionCount += balance.EntryCount;
|
|
|
|
|
|
|
|
|
|
logger.LogDebug(
|
|
|
|
|
"Output VAT (5611): TotalDebits={Debits}, TotalCredits={Credits}, NetChange={Net}",
|
|
|
|
|
balance.TotalDebits, balance.TotalCredits, balance.NetChange);
|
|
|
|
|
}
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
else if (euAcquisitionVatAccount != null &&
|
|
|
|
|
TryParseAccountGuid(euAcquisitionVatAccount.Id, out var checkEuGuid) &&
|
|
|
|
|
balance.AccountId == checkEuGuid)
|
|
|
|
|
{
|
|
|
|
|
// EU Acquisition VAT (5620): Debits are reverse charge VAT
|
|
|
|
|
// This covers both EU goods (IEUV) and EU/world services (IEUY/IVY)
|
|
|
|
|
// Box C + Box D = total EU acquisition VAT debits
|
|
|
|
|
// Without VAT code breakdown at ledger level, we report the total
|
|
|
|
|
// in Box C (EU goods). When VAT code-level data becomes available,
|
|
|
|
|
// split IEUV -> Box C and IEUY/IVY -> Box D.
|
|
|
|
|
report.BoxC = balance.TotalDebits;
|
|
|
|
|
report.TransactionCount += balance.EntryCount;
|
|
|
|
|
|
|
|
|
|
// Basis3 = base amount for EU acquisition (reverse charge VAT / 0.25)
|
|
|
|
|
if (balance.TotalDebits > 0)
|
|
|
|
|
{
|
|
|
|
|
report.Basis3 = Math.Round(balance.TotalDebits / 0.25m, 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.LogDebug(
|
|
|
|
|
"EU Acquisition VAT (5620): TotalDebits={Debits}, TotalCredits={Credits}, NetChange={Net}",
|
|
|
|
|
balance.TotalDebits, balance.TotalCredits, balance.NetChange);
|
|
|
|
|
}
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate summary totals
|
|
|
|
|
// Box A = Salgsmoms (domestic output VAT)
|
|
|
|
|
// Box B = Købsmoms (input VAT - deductible)
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
// Box C = EU-varekøb moms (reverse charge on EU goods/services)
|
|
|
|
|
// Box D = Ydelseskøb moms (reverse charge on services from abroad)
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
report.TotalOutputVat = report.BoxA + report.BoxC + report.BoxD;
|
|
|
|
|
report.TotalInputVat = report.BoxB;
|
|
|
|
|
report.NetVat = report.TotalOutputVat - report.TotalInputVat;
|
|
|
|
|
|
2026-02-05 21:35:26 +01:00
|
|
|
// Basis1 (Felt 1): Net domestic turnover with VAT
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
// Query revenue account totals (accounts 1000-1999) for actual turnover basis
|
|
|
|
|
// instead of back-calculating from VAT which is inaccurate with mixed rates
|
|
|
|
|
var revenueAccounts = await accountRepository.GetByCompanyIdAsync(companyId, ct);
|
|
|
|
|
var revenueAccountIds = new List<Guid>();
|
|
|
|
|
foreach (var acc in revenueAccounts)
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
{
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
if (int.TryParse(acc.AccountNumber, out var num) && num >= 1000 && num <= 1999 &&
|
|
|
|
|
TryParseAccountGuid(acc.Id, out var revGuid))
|
|
|
|
|
{
|
|
|
|
|
revenueAccountIds.Add(revGuid);
|
|
|
|
|
}
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
}
|
|
|
|
|
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
if (revenueAccountIds.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var revenueQuery = new EntriesQuery
|
|
|
|
|
{
|
|
|
|
|
AccountIds = revenueAccountIds,
|
|
|
|
|
From = new DateTimeOffset(periodStart.ToDateTime(TimeOnly.MinValue)),
|
|
|
|
|
To = new DateTimeOffset(periodEnd.ToDateTime(TimeOnly.MaxValue)),
|
|
|
|
|
Aggregate = true
|
|
|
|
|
};
|
|
|
|
|
var revenueResult = await ledgerService.QueryEntriesAsync(revenueQuery, ct);
|
|
|
|
|
if (revenueResult.Aggregates != null)
|
|
|
|
|
{
|
|
|
|
|
// Revenue accounts have credits for income; sum the credits
|
|
|
|
|
report.Basis1 = revenueResult.Aggregates.Sum(a => a.TotalCredits);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-05 21:35:26 +01:00
|
|
|
|
Audit v4: VAT calc, SAF-T compliance, security hardening, frontend quality
Backend (17 files):
- VAT: REP 25% deductibility (§42), EU reverse charge double-entry (IEUV/IEUY/IVY),
IVY rate 0%→25%, VatReport Box C/D populated, Basis1 from real revenue
- SAF-T: correct OECD namespace, closing balance net calc, zero-amount fallback,
credit note auto-numbering (§52)
- Security: BankingController CSRF state token + company auth check,
attachment canonical path traversal check, discount 0-100% validation,
deactivated product/customer update guard
- Quality: redact bank API logs, remove dead code (VatCalcService,
PaymentMatchingService), CompanyAggregate IEmit interfaces, fix URL encoding
Frontend (15 files):
- Fix double "kr." in AmountText and Dashboard Statistic components
- Fix UserSettings Switch defaultChecked desync with Form state
- Remove dual useCompany/useCompanyStore pattern (Dashboard, Moms, Bank)
- Correct SKAT VAT deadline calculation per period type
- Add half-yearly/yearly VAT period options
- Guard console.error with import.meta.env.DEV
- Use shared formatDate in BankConnectionsTab
- Remove dead NONE vatCode check, purge 7 legacy VAT codes from type union
- Migrate S25→U25, K25→I25 across all pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:38:52 +01:00
|
|
|
// Fallback: if no revenue data found, back-calculate from output VAT
|
|
|
|
|
if (report.Basis1 == 0 && report.BoxA > 0)
|
|
|
|
|
{
|
|
|
|
|
report.Basis1 = Math.Round(report.BoxA / 0.25m, 2);
|
|
|
|
|
}
|
2026-02-05 21:35:26 +01:00
|
|
|
|
Add all backend domain, commands, repositories, and tests
This commit includes all previously untracked backend files:
Domain:
- Accounts, Attachments, BankConnections, Customers
- FiscalYears, Invoices, JournalEntryDrafts
- Orders, Products, UserAccess
Commands & Handlers:
- Full CQRS command structure for all domains
Repositories:
- PostgreSQL repositories for all read models
- Bank transaction and ledger repositories
GraphQL:
- Input types, scalars, and types for all entities
- Mutations and queries
Infrastructure:
- Banking integration (Enable Banking client)
- File storage, Invoicing, Reporting, SAF-T export
- Database migrations (003-029)
Tests:
- Integration tests for GraphQL endpoints
- Domain tests
- Invoicing and reporting tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:19:42 +01:00
|
|
|
logger.LogInformation(
|
|
|
|
|
"VAT report generated for company {CompanyId}: OutputVAT={OutputVat}, InputVAT={InputVat}, NetVAT={NetVat}",
|
|
|
|
|
companyId, report.TotalOutputVat, report.TotalInputVat, report.NetVat);
|
|
|
|
|
|
|
|
|
|
return report;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool TryParseAccountGuid(string accountId, out Guid guid)
|
|
|
|
|
{
|
|
|
|
|
// Account IDs are in format "account-{guid}"
|
|
|
|
|
if (accountId.StartsWith("account-"))
|
|
|
|
|
{
|
|
|
|
|
return Guid.TryParse(accountId.Replace("account-", ""), out guid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guid = Guid.Empty;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|