@moatless/bookkeeping 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accounting/index.d.ts +9 -0
- package/dist/accounting/index.js +14 -0
- package/dist/accounting/line-generator.d.ts +34 -0
- package/dist/accounting/line-generator.js +136 -0
- package/dist/accounting/tax-codes.d.ts +32 -0
- package/dist/accounting/tax-codes.js +279 -0
- package/dist/accounting/traktamente-rates.d.ts +48 -0
- package/dist/accounting/traktamente-rates.js +325 -0
- package/dist/accounting/types.d.ts +69 -0
- package/dist/accounting/types.js +5 -0
- package/dist/accounting/validation.d.ts +41 -0
- package/dist/accounting/validation.js +118 -0
- package/dist/auth/fortnox-login.d.ts +15 -0
- package/dist/auth/fortnox-login.js +170 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/prompts.d.ts +6 -0
- package/dist/auth/prompts.js +56 -0
- package/dist/auth/token-store.d.ts +19 -0
- package/dist/auth/token-store.js +54 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +21 -0
- package/dist/progress/index.d.ts +1 -0
- package/dist/progress/index.js +1 -0
- package/dist/progress/sync-progress.d.ts +31 -0
- package/dist/progress/sync-progress.js +65 -0
- package/dist/services/bokio-journal.d.ts +29 -0
- package/dist/services/bokio-journal.js +175 -0
- package/dist/services/document-download.d.ts +46 -0
- package/dist/services/document-download.js +105 -0
- package/dist/services/fortnox-inbox.d.ts +18 -0
- package/dist/services/fortnox-inbox.js +150 -0
- package/dist/services/fortnox-journal.d.ts +22 -0
- package/dist/services/fortnox-journal.js +166 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.js +6 -0
- package/dist/services/journal-sync.d.ts +23 -0
- package/dist/services/journal-sync.js +124 -0
- package/dist/services/journal.service.d.ts +45 -0
- package/dist/services/journal.service.js +204 -0
- package/dist/storage/filesystem.d.ts +49 -0
- package/dist/storage/filesystem.js +122 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/interface.d.ts +48 -0
- package/dist/storage/interface.js +5 -0
- package/dist/sync-types.d.ts +61 -0
- package/dist/sync-types.js +1 -0
- package/dist/transformers/bokio.d.ts +10 -0
- package/dist/transformers/bokio.js +56 -0
- package/dist/transformers/fortnox.d.ts +6 -0
- package/dist/transformers/fortnox.js +39 -0
- package/dist/transformers/index.d.ts +3 -0
- package/dist/transformers/index.js +2 -0
- package/dist/types/discarded-item.d.ts +29 -0
- package/dist/types/discarded-item.js +1 -0
- package/dist/types/document.d.ts +63 -0
- package/dist/types/document.js +9 -0
- package/dist/types/exported-document.d.ts +61 -0
- package/dist/types/exported-document.js +9 -0
- package/dist/types/exported-fiscal-year.d.ts +10 -0
- package/dist/types/exported-fiscal-year.js +1 -0
- package/dist/types/exported-inbox-document.d.ts +14 -0
- package/dist/types/exported-inbox-document.js +10 -0
- package/dist/types/fiscal-year.d.ts +10 -0
- package/dist/types/fiscal-year.js +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +10 -0
- package/dist/types/journal-entry.d.ts +79 -0
- package/dist/types/journal-entry.js +12 -0
- package/dist/types/ledger-account.d.ts +5 -0
- package/dist/types/ledger-account.js +1 -0
- package/dist/utils/file-namer.d.ts +48 -0
- package/dist/utils/file-namer.js +80 -0
- package/dist/utils/git.d.ts +9 -0
- package/dist/utils/git.js +41 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/paths.d.ts +17 -0
- package/dist/utils/paths.js +24 -0
- package/dist/utils/retry.d.ts +17 -0
- package/dist/utils/retry.js +48 -0
- package/dist/utils/templates.d.ts +12 -0
- package/dist/utils/templates.js +222 -0
- package/dist/utils/yaml.d.ts +12 -0
- package/dist/utils/yaml.js +47 -0
- package/dist/yaml/entry-helpers.d.ts +57 -0
- package/dist/yaml/entry-helpers.js +125 -0
- package/dist/yaml/index.d.ts +2 -0
- package/dist/yaml/index.js +2 -0
- package/dist/yaml/yaml-serializer.d.ts +21 -0
- package/dist/yaml/yaml-serializer.js +60 -0
- package/package.json +37 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accounting module - Tax codes, line generation, and validation
|
|
3
|
+
* Shared between server API and gitops CLI
|
|
4
|
+
*/
|
|
5
|
+
export * from "./types";
|
|
6
|
+
export * from "./tax-codes";
|
|
7
|
+
export * from "./line-generator";
|
|
8
|
+
export * from "./validation";
|
|
9
|
+
export * from "./traktamente-rates";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accounting module - Tax codes, line generation, and validation
|
|
3
|
+
* Shared between server API and gitops CLI
|
|
4
|
+
*/
|
|
5
|
+
// Types
|
|
6
|
+
export * from "./types";
|
|
7
|
+
// Tax codes
|
|
8
|
+
export * from "./tax-codes";
|
|
9
|
+
// Line generation
|
|
10
|
+
export * from "./line-generator";
|
|
11
|
+
// Validation
|
|
12
|
+
export * from "./validation";
|
|
13
|
+
// Traktamente (Swedish per diem)
|
|
14
|
+
export * from "./traktamente-rates";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry line generation from tax codes
|
|
3
|
+
* Shared between server API and gitops CLI
|
|
4
|
+
*/
|
|
5
|
+
import type { GenerateLinesInput, GeneratedLinesResult, JournalLineInput } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Round number to 2 decimal places
|
|
8
|
+
* @param value Number to round
|
|
9
|
+
* @returns Rounded number
|
|
10
|
+
*/
|
|
11
|
+
export declare function round2(value: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* Create balanced journal lines from a tax code
|
|
14
|
+
*
|
|
15
|
+
* This function generates the appropriate debit/credit lines based on the tax code:
|
|
16
|
+
* - Calculates VAT amounts
|
|
17
|
+
* - Creates appropriate accounting lines (base, VAT, balancing)
|
|
18
|
+
* - Handles reverse charge scenarios
|
|
19
|
+
* - Validates balance (total debits === total credits)
|
|
20
|
+
*
|
|
21
|
+
* @param input Line generation input parameters
|
|
22
|
+
* @returns Generated lines and totals
|
|
23
|
+
* @throws Error if validation fails or entry is unbalanced
|
|
24
|
+
*/
|
|
25
|
+
export declare function createJournalLinesFromTaxCode(input: GenerateLinesInput): GeneratedLinesResult;
|
|
26
|
+
/**
|
|
27
|
+
* Calculate total debits and credits from a set of lines
|
|
28
|
+
* @param lines Journal lines to sum
|
|
29
|
+
* @returns Total debit and credit amounts
|
|
30
|
+
*/
|
|
31
|
+
export declare function calculateLineTotals(lines: JournalLineInput[]): {
|
|
32
|
+
totalDebit: number;
|
|
33
|
+
totalCredit: number;
|
|
34
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal entry line generation from tax codes
|
|
3
|
+
* Shared between server API and gitops CLI
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Round number to 2 decimal places
|
|
7
|
+
* @param value Number to round
|
|
8
|
+
* @returns Rounded number
|
|
9
|
+
*/
|
|
10
|
+
export function round2(value) {
|
|
11
|
+
return Math.round(value * 100) / 100;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create balanced journal lines from a tax code
|
|
15
|
+
*
|
|
16
|
+
* This function generates the appropriate debit/credit lines based on the tax code:
|
|
17
|
+
* - Calculates VAT amounts
|
|
18
|
+
* - Creates appropriate accounting lines (base, VAT, balancing)
|
|
19
|
+
* - Handles reverse charge scenarios
|
|
20
|
+
* - Validates balance (total debits === total credits)
|
|
21
|
+
*
|
|
22
|
+
* @param input Line generation input parameters
|
|
23
|
+
* @returns Generated lines and totals
|
|
24
|
+
* @throws Error if validation fails or entry is unbalanced
|
|
25
|
+
*/
|
|
26
|
+
export function createJournalLinesFromTaxCode(input) {
|
|
27
|
+
const { taxCode, baseAmount, baseAccountCode, balancingAccountCode, memo, startingLineNumber = 1, } = input;
|
|
28
|
+
// Validation
|
|
29
|
+
if (!baseAccountCode?.trim()) {
|
|
30
|
+
throw new Error("baseAccountCode is required");
|
|
31
|
+
}
|
|
32
|
+
if (!balancingAccountCode?.trim()) {
|
|
33
|
+
throw new Error("balancingAccountCode is required");
|
|
34
|
+
}
|
|
35
|
+
if (baseAmount <= 0) {
|
|
36
|
+
throw new Error("baseAmount must be > 0");
|
|
37
|
+
}
|
|
38
|
+
// Calculate amounts
|
|
39
|
+
const normalizedBase = round2(baseAmount);
|
|
40
|
+
const rate = taxCode.ratePercent / 100;
|
|
41
|
+
const vatAmount = round2(normalizedBase * rate);
|
|
42
|
+
const grossAmount = round2(normalizedBase + vatAmount);
|
|
43
|
+
const lines = [];
|
|
44
|
+
let lineNo = startingLineNumber;
|
|
45
|
+
const posting = taxCode.posting;
|
|
46
|
+
// Helper to add a line with proper normalization
|
|
47
|
+
const pushLine = (ledger_account_code, debit, credit) => {
|
|
48
|
+
const normalizedDebit = debit && debit > 0 ? round2(debit) : undefined;
|
|
49
|
+
const normalizedCredit = credit && credit > 0 ? round2(credit) : undefined;
|
|
50
|
+
if (!normalizedDebit && !normalizedCredit)
|
|
51
|
+
return;
|
|
52
|
+
lines.push({
|
|
53
|
+
line_number: lineNo++,
|
|
54
|
+
ledger_account_code,
|
|
55
|
+
debit: normalizedDebit,
|
|
56
|
+
credit: normalizedCredit,
|
|
57
|
+
memo,
|
|
58
|
+
tax_code: taxCode.code,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
// Generate lines based on direction and reverse charge
|
|
62
|
+
if (taxCode.direction === "PURCHASE") {
|
|
63
|
+
if (taxCode.isReverseCharge) {
|
|
64
|
+
// Reverse charge purchase (e.g. EU/non-EU services)
|
|
65
|
+
// Base expense (debit) and supplier liability (credit) - no VAT on these
|
|
66
|
+
pushLine(baseAccountCode, normalizedBase, 0);
|
|
67
|
+
pushLine(balancingAccountCode, 0, normalizedBase);
|
|
68
|
+
// Reverse charge VAT accounts (if VAT rate > 0)
|
|
69
|
+
if (posting.reverseChargeOutputAccountCode && vatAmount > 0) {
|
|
70
|
+
pushLine(posting.reverseChargeOutputAccountCode, 0, vatAmount);
|
|
71
|
+
}
|
|
72
|
+
if (posting.reverseChargeInputAccountCode && vatAmount > 0) {
|
|
73
|
+
pushLine(posting.reverseChargeInputAccountCode, vatAmount, 0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Normal domestic purchase
|
|
78
|
+
if (taxCode.ratePercent > 0 && posting.inputVatAccountCode) {
|
|
79
|
+
// Purchase with VAT
|
|
80
|
+
pushLine(baseAccountCode, normalizedBase, 0); // Expense
|
|
81
|
+
pushLine(posting.inputVatAccountCode, vatAmount, 0); // Input VAT
|
|
82
|
+
pushLine(balancingAccountCode, 0, grossAmount); // Supplier liability
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Purchase without VAT (exempt, 0%)
|
|
86
|
+
pushLine(baseAccountCode, normalizedBase, 0);
|
|
87
|
+
pushLine(balancingAccountCode, 0, normalizedBase);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (taxCode.direction === "SALE") {
|
|
92
|
+
if (taxCode.isReverseCharge) {
|
|
93
|
+
// Reverse charge sale (e.g. domestic construction)
|
|
94
|
+
pushLine(balancingAccountCode, normalizedBase, 0); // AR/Bank
|
|
95
|
+
pushLine(baseAccountCode, 0, normalizedBase); // Revenue
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Normal sale
|
|
99
|
+
if (taxCode.ratePercent > 0 && posting.outputVatAccountCode) {
|
|
100
|
+
// Sale with VAT
|
|
101
|
+
pushLine(balancingAccountCode, grossAmount, 0); // AR/Bank
|
|
102
|
+
pushLine(baseAccountCode, 0, normalizedBase); // Revenue
|
|
103
|
+
pushLine(posting.outputVatAccountCode, 0, vatAmount); // Output VAT
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Sale without VAT (exempt, 0%, EU B2B)
|
|
107
|
+
pushLine(balancingAccountCode, normalizedBase, 0);
|
|
108
|
+
pushLine(baseAccountCode, 0, normalizedBase);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Validate balance
|
|
113
|
+
const totalDebit = round2(lines.reduce((sum, l) => sum + (l.debit ?? 0), 0));
|
|
114
|
+
const totalCredit = round2(lines.reduce((sum, l) => sum + (l.credit ?? 0), 0));
|
|
115
|
+
if (totalDebit !== totalCredit) {
|
|
116
|
+
throw new Error(`Journal not balanced: debit=${totalDebit}, credit=${totalCredit}`);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
lines,
|
|
120
|
+
totals: {
|
|
121
|
+
net: normalizedBase,
|
|
122
|
+
vat: vatAmount,
|
|
123
|
+
gross: grossAmount,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Calculate total debits and credits from a set of lines
|
|
129
|
+
* @param lines Journal lines to sum
|
|
130
|
+
* @returns Total debit and credit amounts
|
|
131
|
+
*/
|
|
132
|
+
export function calculateLineTotals(lines) {
|
|
133
|
+
const totalDebit = round2(lines.reduce((sum, l) => sum + (l.debit ?? 0), 0));
|
|
134
|
+
const totalCredit = round2(lines.reduce((sum, l) => sum + (l.credit ?? 0), 0));
|
|
135
|
+
return { totalDebit, totalCredit };
|
|
136
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swedish tax code definitions and resolution logic
|
|
3
|
+
* Shared between server API and gitops CLI
|
|
4
|
+
*/
|
|
5
|
+
import type { TaxCode } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Swedish VAT tax codes with posting account configurations
|
|
8
|
+
*/
|
|
9
|
+
export declare const SE_TAX_CODES: TaxCode[];
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a tax code by its code string
|
|
12
|
+
* @param code Tax code string (e.g. "SE_VAT_25_PURCHASE_DOMESTIC")
|
|
13
|
+
* @returns Tax code configuration or undefined if not found
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveTaxCode(code: string): TaxCode | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Get all available tax codes
|
|
18
|
+
* @returns Array of all tax codes
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAllTaxCodes(): TaxCode[];
|
|
21
|
+
/**
|
|
22
|
+
* Get tax codes filtered by direction (PURCHASE or SALE)
|
|
23
|
+
* @param direction Tax code direction
|
|
24
|
+
* @returns Filtered tax codes
|
|
25
|
+
*/
|
|
26
|
+
export declare function getTaxCodesByDirection(direction: "PURCHASE" | "SALE"): TaxCode[];
|
|
27
|
+
/**
|
|
28
|
+
* Get tax codes filtered by territory
|
|
29
|
+
* @param territory Tax code territory
|
|
30
|
+
* @returns Filtered tax codes
|
|
31
|
+
*/
|
|
32
|
+
export declare function getTaxCodesByTerritory(territory: "DOMESTIC" | "EU" | "OUTSIDE_EU"): TaxCode[];
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swedish tax code definitions and resolution logic
|
|
3
|
+
* Shared between server API and gitops CLI
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Swedish VAT tax codes with posting account configurations
|
|
7
|
+
*/
|
|
8
|
+
export const SE_TAX_CODES = [
|
|
9
|
+
// --- DOMESTIC PURCHASES (inköp i Sverige, ingående moms) ---
|
|
10
|
+
{
|
|
11
|
+
code: "SE_VAT_25_PURCHASE_DOMESTIC",
|
|
12
|
+
description: "Inköp i Sverige, moms 25 %",
|
|
13
|
+
kind: "VAT",
|
|
14
|
+
direction: "PURCHASE",
|
|
15
|
+
territory: "DOMESTIC",
|
|
16
|
+
ratePercent: 25,
|
|
17
|
+
isReverseCharge: false,
|
|
18
|
+
isZeroRated: false,
|
|
19
|
+
isExempt: false,
|
|
20
|
+
posting: {
|
|
21
|
+
inputVatAccountCode: "2641", // Ingående moms 25 %
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
code: "SE_VAT_12_PURCHASE_DOMESTIC",
|
|
26
|
+
description: "Inköp i Sverige, moms 12 %",
|
|
27
|
+
kind: "VAT",
|
|
28
|
+
direction: "PURCHASE",
|
|
29
|
+
territory: "DOMESTIC",
|
|
30
|
+
ratePercent: 12,
|
|
31
|
+
isReverseCharge: false,
|
|
32
|
+
isZeroRated: false,
|
|
33
|
+
isExempt: false,
|
|
34
|
+
posting: {
|
|
35
|
+
inputVatAccountCode: "2641", // many use same 2641 for all rates; you can split if you want
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
code: "SE_VAT_6_PURCHASE_DOMESTIC",
|
|
40
|
+
description: "Inköp i Sverige, moms 6 %",
|
|
41
|
+
kind: "VAT",
|
|
42
|
+
direction: "PURCHASE",
|
|
43
|
+
territory: "DOMESTIC",
|
|
44
|
+
ratePercent: 6,
|
|
45
|
+
isReverseCharge: false,
|
|
46
|
+
isZeroRated: false,
|
|
47
|
+
isExempt: false,
|
|
48
|
+
posting: {
|
|
49
|
+
inputVatAccountCode: "2641",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
code: "SE_VAT_0_PURCHASE_DOMESTIC",
|
|
54
|
+
description: "Inköp i Sverige, 0 % moms (momspliktigt men 0 %)",
|
|
55
|
+
kind: "VAT",
|
|
56
|
+
direction: "PURCHASE",
|
|
57
|
+
territory: "DOMESTIC",
|
|
58
|
+
ratePercent: 0,
|
|
59
|
+
isReverseCharge: false,
|
|
60
|
+
isZeroRated: true,
|
|
61
|
+
isExempt: false,
|
|
62
|
+
posting: {
|
|
63
|
+
// no VAT accounts
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
code: "SE_VAT_EXEMPT_PURCHASE",
|
|
68
|
+
description: "Momsfria inköp (utanför momsens tillämpningsområde)",
|
|
69
|
+
kind: "VAT",
|
|
70
|
+
direction: "PURCHASE",
|
|
71
|
+
territory: "DOMESTIC",
|
|
72
|
+
ratePercent: 0,
|
|
73
|
+
isReverseCharge: false,
|
|
74
|
+
isZeroRated: false,
|
|
75
|
+
isExempt: true,
|
|
76
|
+
posting: {
|
|
77
|
+
// no VAT accounts
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
// --- DOMESTIC SALES (försäljning i Sverige, utgående moms) ---
|
|
81
|
+
{
|
|
82
|
+
code: "SE_VAT_25_SALE_DOMESTIC",
|
|
83
|
+
description: "Försäljning i Sverige, moms 25 %",
|
|
84
|
+
kind: "VAT",
|
|
85
|
+
direction: "SALE",
|
|
86
|
+
territory: "DOMESTIC",
|
|
87
|
+
ratePercent: 25,
|
|
88
|
+
isReverseCharge: false,
|
|
89
|
+
isZeroRated: false,
|
|
90
|
+
isExempt: false,
|
|
91
|
+
posting: {
|
|
92
|
+
outputVatAccountCode: "2611", // Utgående moms 25 %
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
code: "SE_VAT_12_SALE_DOMESTIC",
|
|
97
|
+
description: "Försäljning i Sverige, moms 12 %",
|
|
98
|
+
kind: "VAT",
|
|
99
|
+
direction: "SALE",
|
|
100
|
+
territory: "DOMESTIC",
|
|
101
|
+
ratePercent: 12,
|
|
102
|
+
isReverseCharge: false,
|
|
103
|
+
isZeroRated: false,
|
|
104
|
+
isExempt: false,
|
|
105
|
+
posting: {
|
|
106
|
+
outputVatAccountCode: "2611", // or a separate 261X if you want
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
code: "SE_VAT_6_SALE_DOMESTIC",
|
|
111
|
+
description: "Försäljning i Sverige, moms 6 %",
|
|
112
|
+
kind: "VAT",
|
|
113
|
+
direction: "SALE",
|
|
114
|
+
territory: "DOMESTIC",
|
|
115
|
+
ratePercent: 6,
|
|
116
|
+
isReverseCharge: false,
|
|
117
|
+
isZeroRated: false,
|
|
118
|
+
isExempt: false,
|
|
119
|
+
posting: {
|
|
120
|
+
outputVatAccountCode: "2611",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
code: "SE_VAT_0_SALE_DOMESTIC",
|
|
125
|
+
description: "Momsfri försäljning i Sverige (undantagen/0 %)",
|
|
126
|
+
kind: "VAT",
|
|
127
|
+
direction: "SALE",
|
|
128
|
+
territory: "DOMESTIC",
|
|
129
|
+
ratePercent: 0,
|
|
130
|
+
isReverseCharge: false,
|
|
131
|
+
isZeroRated: false, // treat as exempt
|
|
132
|
+
isExempt: true,
|
|
133
|
+
posting: {
|
|
134
|
+
// no VAT accounts
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
// --- EU PURCHASES (inköp från annat EU-land, omvänd moms) ---
|
|
138
|
+
{
|
|
139
|
+
code: "SE_VAT_25_PURCHASE_EU_GOODS_RC",
|
|
140
|
+
description: "Inköp av varor från annat EU-land, 25 %, omvänd skattskyldighet",
|
|
141
|
+
kind: "VAT",
|
|
142
|
+
direction: "PURCHASE",
|
|
143
|
+
territory: "EU",
|
|
144
|
+
ratePercent: 25,
|
|
145
|
+
isReverseCharge: true,
|
|
146
|
+
isZeroRated: false,
|
|
147
|
+
isExempt: false,
|
|
148
|
+
posting: {
|
|
149
|
+
reverseChargeOutputAccountCode: "2614", // Beräknad utg moms EU varor
|
|
150
|
+
reverseChargeInputAccountCode: "2645", // Beräknad ing moms EU varor
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
code: "SE_VAT_25_PURCHASE_EU_SERVICES_RC",
|
|
155
|
+
description: "Inköp av tjänster från annat EU-land, 25 %, omvänd skattskyldighet",
|
|
156
|
+
kind: "VAT",
|
|
157
|
+
direction: "PURCHASE",
|
|
158
|
+
territory: "EU",
|
|
159
|
+
ratePercent: 25,
|
|
160
|
+
isReverseCharge: true,
|
|
161
|
+
isZeroRated: false,
|
|
162
|
+
isExempt: false,
|
|
163
|
+
posting: {
|
|
164
|
+
reverseChargeOutputAccountCode: "2614", // or 2615 depending on your kontoplan
|
|
165
|
+
reverseChargeInputAccountCode: "2645",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
// --- NON-EU PURCHASES (inköp från land utanför EU, omvänd moms) ---
|
|
169
|
+
{
|
|
170
|
+
code: "SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC",
|
|
171
|
+
description: "Inköp av tjänster från land utanför EU, 25 %, omvänd skattskyldighet",
|
|
172
|
+
kind: "VAT",
|
|
173
|
+
direction: "PURCHASE",
|
|
174
|
+
territory: "OUTSIDE_EU",
|
|
175
|
+
ratePercent: 25,
|
|
176
|
+
isReverseCharge: true,
|
|
177
|
+
isZeroRated: false,
|
|
178
|
+
isExempt: false,
|
|
179
|
+
posting: {
|
|
180
|
+
reverseChargeOutputAccountCode: "2614", // Beräknad utg moms tjänsteförvärv utlandet
|
|
181
|
+
reverseChargeInputAccountCode: "2645", // Beräknad ing moms förvärv utlandet
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
// --- EU SALES (försäljning till EU, 0 %) ---
|
|
185
|
+
{
|
|
186
|
+
code: "SE_VAT_0_SALE_EU_GOODS_B2B",
|
|
187
|
+
description: "Varuförsäljning till momsregistrerad kund i annat EU-land (0 %)",
|
|
188
|
+
kind: "VAT",
|
|
189
|
+
direction: "SALE",
|
|
190
|
+
territory: "EU",
|
|
191
|
+
ratePercent: 0,
|
|
192
|
+
isReverseCharge: false,
|
|
193
|
+
isZeroRated: true,
|
|
194
|
+
isExempt: false,
|
|
195
|
+
posting: {
|
|
196
|
+
// 0 % – no VAT accounts, but you track base in VAT report via this code
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
code: "SE_VAT_0_SALE_EU_SERVICES_B2B",
|
|
201
|
+
description: "Tjänsteförsäljning till momsregistrerad kund i annat EU-land (0 %)",
|
|
202
|
+
kind: "VAT",
|
|
203
|
+
direction: "SALE",
|
|
204
|
+
territory: "EU",
|
|
205
|
+
ratePercent: 0,
|
|
206
|
+
isReverseCharge: false,
|
|
207
|
+
isZeroRated: true,
|
|
208
|
+
isExempt: false,
|
|
209
|
+
posting: {
|
|
210
|
+
// 0 % – no VAT accounts
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
// --- DOMESTIC REVERSE CHARGE (byggtjänster m.m.) ---
|
|
214
|
+
{
|
|
215
|
+
code: "SE_VAT_RC_SALE_DOMESTIC_CONSTRUCTION",
|
|
216
|
+
description: "Försäljning av byggtjänster med omvänd skattskyldighet (ingen moms på fakturan)",
|
|
217
|
+
kind: "VAT",
|
|
218
|
+
direction: "SALE",
|
|
219
|
+
territory: "DOMESTIC",
|
|
220
|
+
ratePercent: 0,
|
|
221
|
+
isReverseCharge: true,
|
|
222
|
+
isZeroRated: false,
|
|
223
|
+
isExempt: false,
|
|
224
|
+
posting: {
|
|
225
|
+
// Seller does not post VAT on the invoice; VAT is handled by buyer.
|
|
226
|
+
// So no 26xx lines here.
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
code: "SE_VAT_RC_PURCHASE_DOMESTIC_CONSTRUCTION",
|
|
231
|
+
description: "Inköp av byggtjänster med omvänd skattskyldighet (du beräknar svensk moms)",
|
|
232
|
+
kind: "VAT",
|
|
233
|
+
direction: "PURCHASE",
|
|
234
|
+
territory: "DOMESTIC",
|
|
235
|
+
ratePercent: 25,
|
|
236
|
+
isReverseCharge: true,
|
|
237
|
+
isZeroRated: false,
|
|
238
|
+
isExempt: false,
|
|
239
|
+
posting: {
|
|
240
|
+
reverseChargeOutputAccountCode: "2617", // Utg moms omvänd, bygg
|
|
241
|
+
reverseChargeInputAccountCode: "2647", // Ing moms omvänd, bygg
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
/**
|
|
246
|
+
* Tax code lookup map for fast resolution
|
|
247
|
+
*/
|
|
248
|
+
const TAX_CODE_MAP = new Map(SE_TAX_CODES.map((code) => [code.code, code]));
|
|
249
|
+
/**
|
|
250
|
+
* Resolve a tax code by its code string
|
|
251
|
+
* @param code Tax code string (e.g. "SE_VAT_25_PURCHASE_DOMESTIC")
|
|
252
|
+
* @returns Tax code configuration or undefined if not found
|
|
253
|
+
*/
|
|
254
|
+
export function resolveTaxCode(code) {
|
|
255
|
+
return TAX_CODE_MAP.get(code);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get all available tax codes
|
|
259
|
+
* @returns Array of all tax codes
|
|
260
|
+
*/
|
|
261
|
+
export function getAllTaxCodes() {
|
|
262
|
+
return SE_TAX_CODES;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get tax codes filtered by direction (PURCHASE or SALE)
|
|
266
|
+
* @param direction Tax code direction
|
|
267
|
+
* @returns Filtered tax codes
|
|
268
|
+
*/
|
|
269
|
+
export function getTaxCodesByDirection(direction) {
|
|
270
|
+
return SE_TAX_CODES.filter((code) => code.direction === direction);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get tax codes filtered by territory
|
|
274
|
+
* @param territory Tax code territory
|
|
275
|
+
* @returns Filtered tax codes
|
|
276
|
+
*/
|
|
277
|
+
export function getTaxCodesByTerritory(territory) {
|
|
278
|
+
return SE_TAX_CODES.filter((code) => code.territory === territory);
|
|
279
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swedish traktamente (per diem) rates from Skatteverket
|
|
3
|
+
*
|
|
4
|
+
* Rates are updated annually and published by Skatteverket.
|
|
5
|
+
* Source: https://skatteverket.se/utlandstraktamente
|
|
6
|
+
*/
|
|
7
|
+
export interface TraktamenteRate {
|
|
8
|
+
countryCode: string;
|
|
9
|
+
year: number;
|
|
10
|
+
fullDay: number;
|
|
11
|
+
halfDay: number;
|
|
12
|
+
night?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get traktamente rate for a specific country and year
|
|
16
|
+
*/
|
|
17
|
+
export declare function getTraktamenteRate(countryCode: string, year: number): TraktamenteRate | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Get all available rates for a year
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAllRatesForYear(year: number): TraktamenteRate[];
|
|
22
|
+
/**
|
|
23
|
+
* Get available years
|
|
24
|
+
*/
|
|
25
|
+
export declare function getAvailableYears(): number[];
|
|
26
|
+
/**
|
|
27
|
+
* Check if a country code is supported
|
|
28
|
+
*/
|
|
29
|
+
export declare function isSupportedCountry(countryCode: string, year: number): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate traktamente for a trip
|
|
32
|
+
*
|
|
33
|
+
* Rules (Skatteverket):
|
|
34
|
+
* - Departure day: before 12:00 = full day, 12:00 or later = half day
|
|
35
|
+
* - Return day: after 19:00 = full day, 19:00 or earlier = half day
|
|
36
|
+
* - Days in between = full days
|
|
37
|
+
*/
|
|
38
|
+
export interface TraktamenteCalculation {
|
|
39
|
+
rate: TraktamenteRate;
|
|
40
|
+
departureDate: string;
|
|
41
|
+
departureTime: string;
|
|
42
|
+
returnDate: string;
|
|
43
|
+
returnTime: string;
|
|
44
|
+
fullDays: number;
|
|
45
|
+
halfDays: number;
|
|
46
|
+
totalAmount: number;
|
|
47
|
+
}
|
|
48
|
+
export declare function calculateTraktamente(countryCode: string, year: number, departureDate: string, departureTime: string, returnDate: string, returnTime: string): TraktamenteCalculation | undefined;
|