@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.
Files changed (93) hide show
  1. package/dist/accounting/index.d.ts +9 -0
  2. package/dist/accounting/index.js +14 -0
  3. package/dist/accounting/line-generator.d.ts +34 -0
  4. package/dist/accounting/line-generator.js +136 -0
  5. package/dist/accounting/tax-codes.d.ts +32 -0
  6. package/dist/accounting/tax-codes.js +279 -0
  7. package/dist/accounting/traktamente-rates.d.ts +48 -0
  8. package/dist/accounting/traktamente-rates.js +325 -0
  9. package/dist/accounting/types.d.ts +69 -0
  10. package/dist/accounting/types.js +5 -0
  11. package/dist/accounting/validation.d.ts +41 -0
  12. package/dist/accounting/validation.js +118 -0
  13. package/dist/auth/fortnox-login.d.ts +15 -0
  14. package/dist/auth/fortnox-login.js +170 -0
  15. package/dist/auth/index.d.ts +3 -0
  16. package/dist/auth/index.js +3 -0
  17. package/dist/auth/prompts.d.ts +6 -0
  18. package/dist/auth/prompts.js +56 -0
  19. package/dist/auth/token-store.d.ts +19 -0
  20. package/dist/auth/token-store.js +54 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +21 -0
  23. package/dist/progress/index.d.ts +1 -0
  24. package/dist/progress/index.js +1 -0
  25. package/dist/progress/sync-progress.d.ts +31 -0
  26. package/dist/progress/sync-progress.js +65 -0
  27. package/dist/services/bokio-journal.d.ts +29 -0
  28. package/dist/services/bokio-journal.js +175 -0
  29. package/dist/services/document-download.d.ts +46 -0
  30. package/dist/services/document-download.js +105 -0
  31. package/dist/services/fortnox-inbox.d.ts +18 -0
  32. package/dist/services/fortnox-inbox.js +150 -0
  33. package/dist/services/fortnox-journal.d.ts +22 -0
  34. package/dist/services/fortnox-journal.js +166 -0
  35. package/dist/services/index.d.ts +6 -0
  36. package/dist/services/index.js +6 -0
  37. package/dist/services/journal-sync.d.ts +23 -0
  38. package/dist/services/journal-sync.js +124 -0
  39. package/dist/services/journal.service.d.ts +45 -0
  40. package/dist/services/journal.service.js +204 -0
  41. package/dist/storage/filesystem.d.ts +49 -0
  42. package/dist/storage/filesystem.js +122 -0
  43. package/dist/storage/index.d.ts +2 -0
  44. package/dist/storage/index.js +1 -0
  45. package/dist/storage/interface.d.ts +48 -0
  46. package/dist/storage/interface.js +5 -0
  47. package/dist/sync-types.d.ts +61 -0
  48. package/dist/sync-types.js +1 -0
  49. package/dist/transformers/bokio.d.ts +10 -0
  50. package/dist/transformers/bokio.js +56 -0
  51. package/dist/transformers/fortnox.d.ts +6 -0
  52. package/dist/transformers/fortnox.js +39 -0
  53. package/dist/transformers/index.d.ts +3 -0
  54. package/dist/transformers/index.js +2 -0
  55. package/dist/types/discarded-item.d.ts +29 -0
  56. package/dist/types/discarded-item.js +1 -0
  57. package/dist/types/document.d.ts +63 -0
  58. package/dist/types/document.js +9 -0
  59. package/dist/types/exported-document.d.ts +61 -0
  60. package/dist/types/exported-document.js +9 -0
  61. package/dist/types/exported-fiscal-year.d.ts +10 -0
  62. package/dist/types/exported-fiscal-year.js +1 -0
  63. package/dist/types/exported-inbox-document.d.ts +14 -0
  64. package/dist/types/exported-inbox-document.js +10 -0
  65. package/dist/types/fiscal-year.d.ts +10 -0
  66. package/dist/types/fiscal-year.js +1 -0
  67. package/dist/types/index.d.ts +10 -0
  68. package/dist/types/index.js +10 -0
  69. package/dist/types/journal-entry.d.ts +79 -0
  70. package/dist/types/journal-entry.js +12 -0
  71. package/dist/types/ledger-account.d.ts +5 -0
  72. package/dist/types/ledger-account.js +1 -0
  73. package/dist/utils/file-namer.d.ts +48 -0
  74. package/dist/utils/file-namer.js +80 -0
  75. package/dist/utils/git.d.ts +9 -0
  76. package/dist/utils/git.js +41 -0
  77. package/dist/utils/index.d.ts +6 -0
  78. package/dist/utils/index.js +6 -0
  79. package/dist/utils/paths.d.ts +17 -0
  80. package/dist/utils/paths.js +24 -0
  81. package/dist/utils/retry.d.ts +17 -0
  82. package/dist/utils/retry.js +48 -0
  83. package/dist/utils/templates.d.ts +12 -0
  84. package/dist/utils/templates.js +222 -0
  85. package/dist/utils/yaml.d.ts +12 -0
  86. package/dist/utils/yaml.js +47 -0
  87. package/dist/yaml/entry-helpers.d.ts +57 -0
  88. package/dist/yaml/entry-helpers.js +125 -0
  89. package/dist/yaml/index.d.ts +2 -0
  90. package/dist/yaml/index.js +2 -0
  91. package/dist/yaml/yaml-serializer.d.ts +21 -0
  92. package/dist/yaml/yaml-serializer.js +60 -0
  93. package/package.json +37 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Get the raw AGENTS.md template with placeholders
3
+ */
4
+ export declare function getAgentsTemplate(): string;
5
+ export interface RenderAgentsTemplateOptions {
6
+ companyName: string;
7
+ provider: "Bokio" | "Fortnox";
8
+ }
9
+ /**
10
+ * Render the AGENTS.md template with variable substitution
11
+ */
12
+ export declare function renderAgentsTemplate(options: RenderAgentsTemplateOptions): string;
@@ -0,0 +1,222 @@
1
+ // Template inlined as string to ensure it's bundled properly
2
+ const AGENTS_TEMPLATE = `# AGENTS.md
3
+
4
+ This is an accounting data repository for {{COMPANY_NAME}} (Swedish company), storing financial records, journal entries, and documents. It integrates with {{PROVIDER}} (Swedish accounting software) for document management.
5
+
6
+ ## Repository Structure
7
+
8
+ \`\`\`
9
+ /
10
+ ├── accounts.yaml # Swedish chart of accounts (account codes 1000-9999)
11
+ ├── journal-entries/ # Main accounting data organized by fiscal year
12
+ │ └── FY-YYYY/ # Fiscal year folders (FY-2014 through FY-2026)
13
+ │ ├── _fiscal-year.yaml
14
+ │ └── [SERIES]-[NUM]-[DATE]-[DESC]/
15
+ │ ├── entry.yaml
16
+ │ └── *.pdf # Supporting documents
17
+
18
+ ├── inbox/ # Raw incoming documents (no entry yet)
19
+ │ └── [DATE]-[NAME]/
20
+ │ ├── documents.yaml
21
+ │ └── *.pdf
22
+ └── drafts/ # Documents with entries, ready to post
23
+ └── [DATE]-[NAME]/ # e.g., "2025-12-24-anthropic"
24
+ ├── documents.yaml
25
+ ├── entry.yaml
26
+ └── *.pdf
27
+ \`\`\`
28
+
29
+ ## CLI Commands
30
+
31
+ Use the \`npx ledgit-cli\` CLI to interact with this repository:
32
+
33
+ \`\`\`bash
34
+ # Sync data from provider
35
+ npx ledgit-cli sync-journal # Sync journal entries from accounting provider
36
+ npx ledgit-cli sync-inbox # Download inbox files from accounting provider
37
+ npx ledgit-cli company-info # Display company information
38
+
39
+ # Create journal entries
40
+ npx ledgit-cli create-entry --path <inbox-dir-or-file> \\
41
+ --tax-code <code> --base-account <acc> --balancing-account <acc> \\
42
+ --series <A-K> --entry-number <num>
43
+
44
+ # For raw files (not from inbox), also provide:
45
+ # --description "Vendor name" --document-date 2025-01-15 --amount 1234.50
46
+
47
+ # Manage inbox
48
+ npx ledgit-cli discard <inbox-directory> # Remove item, mark for deletion from provider
49
+ npx ledgit-cli parse-pdf -f <file.pdf> # Extract text from PDF (for analysis)
50
+
51
+ # Currency conversion
52
+ npx ledgit-cli convert-currency --amount <num> --currency <code> [--date YYYY-MM-DD]
53
+ # Converts foreign currency to SEK using Riksbank exchange rates
54
+ # Caches rates locally for efficiency
55
+
56
+ # Generate journal entry lines (alternative to create-entry for adding lines)
57
+ npx ledgit-cli generate-lines --inbox-dir <path> \\
58
+ --tax-code <code> --base-amount <num> --base-account <acc> \\
59
+ --balancing-account <acc> [--memo "description"] [--currency USD]
60
+
61
+ # List available tax codes
62
+ npx ledgit-cli list-tax-codes
63
+
64
+ # Update instructions
65
+ npx ledgit-cli update # Update AGENTS.md to latest version
66
+ \`\`\`
67
+
68
+ **Currency Conversion:** Both \`create-entry\` and \`generate-lines\` automatically convert to SEK when \`--currency\` is different from SEK, using Riksbank exchange rates for the entry date.
69
+
70
+ ## Workflow Overview
71
+
72
+ \`\`\`
73
+ inbox/ → (create-entry) → drafts/ → (user confirms) → journal-entries/ → (sync-journal) → Provider
74
+ \`\`\`
75
+
76
+ ## Bookkeeping New Entries
77
+
78
+ **Important:** Always ask the user for clarification if anything is unclear about the transaction before creating an entry.
79
+
80
+ To create a journal entry from an inbox document:
81
+
82
+ 1. **Read the document** - Use \`npx ledgit-cli parse-pdf\` for PDFs to understand the content
83
+ 2. **Update documents.yaml** - After parsing, populate missing metadata in \`documents.yaml\`:
84
+ - \`documentDate\`: Invoice/receipt date (YYYY-MM-DD)
85
+ - \`description\`: Vendor or document name
86
+ - \`totalAmount\`: With \`amount\` (string) and \`currency\` (e.g., EUR, SEK)
87
+ 3. **Look for similar entries** - Search \`journal-entries/\` and \`drafts/\` for entries from the same vendor or similar transaction types to learn which accounts and tax codes were used previously
88
+ 4. **Check accounts** - Review \`accounts.yaml\` for appropriate expense/revenue accounts
89
+ 5. **Determine tax code** - Based on vendor location and transaction type (see Tax Codes below). Explain your reasoning and describe the tax code meaning to the user
90
+ 6. **Ask for confirmation** - Before creating, confirm with the user: vendor, amount, accounts (with names), and tax code (with description)
91
+ 7. **Create entry** - Run \`npx ledgit-cli create-entry\` with the appropriate parameters
92
+ 8. **Files moved to drafts** - The inbox item is automatically moved to \`drafts/\` with \`entry.yaml\`
93
+
94
+ To discard unwanted inbox items:
95
+ \`\`\`bash
96
+ npx ledgit-cli discard inbox/2025-01-15-spam-document
97
+ \`\`\`
98
+ This removes the directory and marks the item for deletion from the provider on next sync.
99
+
100
+ ## Posting Entries
101
+
102
+ **Important:** Moving entries to \`journal-entries/\` posts them to the real ledger. Always confirm with the user before posting.
103
+
104
+ When the user confirms draft entries are ready to post:
105
+
106
+ 1. **Determine entry number** - Check existing entries in \`journal-entries/FY-YYYY/\` for the series to find the next available number
107
+ 2. **Move to journal-entries** - Rename the draft directory to the proper format:
108
+ \`\`\`
109
+ drafts/2025-01-15-anthropic/
110
+ → journal-entries/FY-2025/A-042-2025-01-15-anthropic/
111
+ \`\`\`
112
+ Format: \`{SERIES}-{NUM}-{DATE}-{DESC}/\`
113
+ 3. **Update entry.yaml** - Ensure \`entryNumber\` matches the directory and \`status\` is set appropriately
114
+ 4. **Sync to provider** - Run \`npx ledgit-cli sync-journal\` to post the entries to {{PROVIDER}}
115
+
116
+ ## Tax Codes
117
+
118
+ ### Common Tax Code Patterns
119
+
120
+ | Scenario | Tax Code | Accounts |
121
+ |----------|----------|----------|
122
+ | Domestic purchase 25% | \`SE_VAT_25_PURCHASE_DOMESTIC\` | Expense + 2641 + 2440 |
123
+ | Domestic purchase 12% | \`SE_VAT_12_PURCHASE_DOMESTIC\` | Expense + 2641 + 2440 |
124
+ | Non-EU services (US SaaS) | \`SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC\` | 5422 + 2645 + 2614 + 2820 |
125
+ | EU services | \`SE_VAT_25_PURCHASE_EU_SERVICES_RC\` | Expense + 2645 + 2614 + 2440 |
126
+ | Exempt purchase | \`SE_VAT_EXEMPT_PURCHASE\` | Expense + balancing |
127
+
128
+ **Notes:**
129
+ - \`RC\` = Reverse Charge (buyer reports VAT instead of seller)
130
+ - Use \`--list-tax-codes\` to see all available codes with descriptions
131
+ - Account 2641 = Incoming VAT (domestic), 2645 = Incoming VAT (reverse charge)
132
+ - Account 2614 = Outgoing VAT (reverse charge)
133
+
134
+ ## Entities
135
+
136
+ ### Journal Entry (entry.yaml)
137
+
138
+ \`\`\`yaml
139
+ series: A # A=Admin, B=Customer invoices, C=Customer payments,
140
+ # D=Supplier invoices, E=Supplier payments, K=Salary
141
+ entryNumber: 1
142
+ entryDate: 2024-01-03
143
+ description: Transaction description
144
+ status: POSTED # or DRAFT
145
+ currency: SEK
146
+ lines:
147
+ - account: "1930" # Account code from accounts.yaml
148
+ debit: 1000.00 # or credit:
149
+ memo: Account description
150
+ \`\`\`
151
+
152
+ ### Document Metadata (documents.yaml)
153
+
154
+ One entry can have multiple supporting documents (e.g., invoice + receipt, contract + amendment).
155
+
156
+ \`\`\`yaml
157
+ - fileName: invoice.pdf
158
+ mimeType: application/pdf
159
+ sourceIntegration: {{PROVIDER_LOWER}}
160
+ sourceId: UUID-1
161
+ description: Vendor invoice
162
+ documentDate: 2024-01-15
163
+ totalAmount:
164
+ amount: "1250.00"
165
+ currency: SEK
166
+ - fileName: receipt.pdf
167
+ mimeType: application/pdf
168
+ sourceIntegration: {{PROVIDER_LOWER}}
169
+ sourceId: UUID-2
170
+ description: Payment receipt
171
+ documentDate: 2024-01-16
172
+ \`\`\`
173
+
174
+ ### Fiscal Year (_fiscal-year.yaml)
175
+
176
+ \`\`\`yaml
177
+ name: FY 2024
178
+ startDate: 2024-01-01
179
+ endDate: 2024-12-31
180
+ status: OPEN
181
+ externalId: "12" # Provider reference
182
+ \`\`\`
183
+
184
+ ## Account Codes
185
+
186
+ Swedish BAS account codes are organized by ranges:
187
+
188
+ - **1xxx** - Assets (Tillgångar)
189
+ - **2xxx** - Liabilities & Equity (Skulder och eget kapital)
190
+ - **3xxx** - Revenue (Intäkter)
191
+ - **4xxx** - Cost of goods sold (Inköp)
192
+ - **5xxx-6xxx** - Operating expenses (Kostnader)
193
+ - **7xxx** - Personnel costs (Personalkostnader)
194
+ - **8xxx** - Financial items (Finansiella poster)
195
+ - **9xxx** - Year-end allocations (Bokslutsdispositioner)
196
+
197
+ Common accounts:
198
+ - \`1930\` - Business bank account
199
+ - \`2440\` - Supplier payables
200
+ - \`2610\` - Outgoing VAT 25%
201
+ - \`2640\` - Incoming VAT
202
+ - \`2820\` - Short-term liabilities (credit card, etc.)
203
+ - \`4000\` - Cost of goods sold
204
+ - \`5420\` - Software and IT services
205
+ - \`6570\` - Bank charges
206
+ `;
207
+ /**
208
+ * Get the raw AGENTS.md template with placeholders
209
+ */
210
+ export function getAgentsTemplate() {
211
+ return AGENTS_TEMPLATE;
212
+ }
213
+ /**
214
+ * Render the AGENTS.md template with variable substitution
215
+ */
216
+ export function renderAgentsTemplate(options) {
217
+ const template = getAgentsTemplate();
218
+ return template
219
+ .replace(/\{\{COMPANY_NAME\}\}/g, options.companyName)
220
+ .replace(/\{\{PROVIDER\}\}/g, options.provider)
221
+ .replace(/\{\{PROVIDER_LOWER\}\}/g, options.provider.toLowerCase());
222
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Parse YAML string to object
3
+ */
4
+ export declare function parseYaml<T = unknown>(yaml: string): T;
5
+ /**
6
+ * Convert data to YAML format with special handling for:
7
+ * - Monetary values (Json type: {amount, currency})
8
+ * - Enums (direct string values)
9
+ * - Nulls (omitted for cleaner output)
10
+ * - Arrays and nested objects
11
+ */
12
+ export declare function toYaml(data: unknown): string;
@@ -0,0 +1,47 @@
1
+ import YAML from "yaml";
2
+ /**
3
+ * Parse YAML string to object
4
+ */
5
+ export function parseYaml(yaml) {
6
+ return YAML.parse(yaml);
7
+ }
8
+ /**
9
+ * Convert data to YAML format with special handling for:
10
+ * - Monetary values (Json type: {amount, currency})
11
+ * - Enums (direct string values)
12
+ * - Nulls (omitted for cleaner output)
13
+ * - Arrays and nested objects
14
+ */
15
+ export function toYaml(data) {
16
+ // Remove null values for cleaner output
17
+ const cleanData = removeNulls(data);
18
+ return YAML.stringify(cleanData, {
19
+ indent: 2,
20
+ lineWidth: 120,
21
+ minContentWidth: 0,
22
+ doubleQuotedAsJSON: true,
23
+ });
24
+ }
25
+ /**
26
+ * Recursively remove null values from objects and arrays
27
+ * This produces cleaner YAML output by omitting null fields
28
+ */
29
+ function removeNulls(obj) {
30
+ if (obj === null || obj === undefined) {
31
+ return undefined;
32
+ }
33
+ if (Array.isArray(obj)) {
34
+ return obj.map(removeNulls).filter((item) => item !== undefined);
35
+ }
36
+ if (typeof obj === "object" && obj !== null) {
37
+ const result = {};
38
+ for (const [key, value] of Object.entries(obj)) {
39
+ const cleanValue = removeNulls(value);
40
+ if (cleanValue !== undefined) {
41
+ result[key] = cleanValue;
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+ return obj;
47
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * YAML helper functions for reading, updating, and validating journal entry files
3
+ * Used by CLI tools
4
+ */
5
+ import type { JournalEntry, Document } from "../types";
6
+ import type { JournalLineInput } from "../accounting";
7
+ /**
8
+ * Read and parse entry.yaml file
9
+ */
10
+ export declare function readEntryYaml(filePath: string): Promise<JournalEntry>;
11
+ interface DocumentMetadata {
12
+ fileName: string;
13
+ mimeType?: string;
14
+ sourceIntegration?: string;
15
+ sourceId?: string;
16
+ }
17
+ /**
18
+ * Read and parse documents.yaml file (list format)
19
+ */
20
+ export declare function readDocumentsYaml(filePath: string): Promise<DocumentMetadata[]>;
21
+ /**
22
+ * @deprecated Use readDocumentsYaml instead
23
+ * Read and parse document.yaml file (legacy single object format)
24
+ */
25
+ export declare function readDocumentYaml(filePath: string): Promise<Document>;
26
+ /**
27
+ * Write entry to entry.yaml file
28
+ */
29
+ export declare function writeEntryYaml(filePath: string, entry: JournalEntry): Promise<void>;
30
+ /**
31
+ * Update entry with new lines (appending and renumbering)
32
+ */
33
+ export declare function appendLinesToEntry(entryPath: string, newLines: JournalLineInput[], totals: {
34
+ totalDebit: number;
35
+ totalCredit: number;
36
+ }): Promise<void>;
37
+ /**
38
+ * Read and parse accounts.yaml file
39
+ */
40
+ export declare function readAccountsYaml(filePath: string): Promise<{
41
+ code: string;
42
+ name: string;
43
+ }[]>;
44
+ /**
45
+ * Validate that account code exists in accounts list
46
+ */
47
+ export declare function validateAccountExists(accountCode: string, accounts: Array<{
48
+ code: string;
49
+ }>): boolean;
50
+ /**
51
+ * Get account name by code
52
+ */
53
+ export declare function getAccountName(accountCode: string, accounts: Array<{
54
+ code: string;
55
+ name: string;
56
+ }>): string | undefined;
57
+ export {};
@@ -0,0 +1,125 @@
1
+ /**
2
+ * YAML helper functions for reading, updating, and validating journal entry files
3
+ * Used by CLI tools
4
+ */
5
+ import * as fs from "node:fs/promises";
6
+ import { parseYaml, toYaml } from "./yaml-serializer";
7
+ /**
8
+ * Read and parse entry.yaml file
9
+ */
10
+ export async function readEntryYaml(filePath) {
11
+ try {
12
+ const content = await fs.readFile(filePath, "utf-8");
13
+ return parseYaml(content);
14
+ }
15
+ catch (err) {
16
+ throw new Error(`Failed to read entry.yaml at ${filePath}: ${err}`);
17
+ }
18
+ }
19
+ /**
20
+ * Read and parse documents.yaml file (list format)
21
+ */
22
+ export async function readDocumentsYaml(filePath) {
23
+ try {
24
+ const content = await fs.readFile(filePath, "utf-8");
25
+ return parseYaml(content) || [];
26
+ }
27
+ catch (err) {
28
+ throw new Error(`Failed to read documents.yaml at ${filePath}: ${err}`);
29
+ }
30
+ }
31
+ /**
32
+ * @deprecated Use readDocumentsYaml instead
33
+ * Read and parse document.yaml file (legacy single object format)
34
+ */
35
+ export async function readDocumentYaml(filePath) {
36
+ try {
37
+ const content = await fs.readFile(filePath, "utf-8");
38
+ return parseYaml(content);
39
+ }
40
+ catch (err) {
41
+ throw new Error(`Failed to read document.yaml at ${filePath}: ${err}`);
42
+ }
43
+ }
44
+ /**
45
+ * Write entry to entry.yaml file
46
+ */
47
+ export async function writeEntryYaml(filePath, entry) {
48
+ try {
49
+ const yaml = toYaml(entry);
50
+ await fs.writeFile(filePath, yaml, "utf-8");
51
+ }
52
+ catch (err) {
53
+ throw new Error(`Failed to write entry.yaml at ${filePath}: ${err}`);
54
+ }
55
+ }
56
+ /**
57
+ * Update entry with new lines (appending and renumbering)
58
+ */
59
+ export async function appendLinesToEntry(entryPath, newLines, totals) {
60
+ // Read existing entry
61
+ const entry = await readEntryYaml(entryPath);
62
+ // Combine existing and new lines
63
+ const allLines = [...(entry.lines || [])];
64
+ // Append new lines with proper formatting
65
+ for (const newLine of newLines) {
66
+ allLines.push({
67
+ lineNumber: newLine.line_number,
68
+ account: newLine.ledger_account_code,
69
+ debit: {
70
+ amount: newLine.debit || 0,
71
+ currency: "SEK",
72
+ },
73
+ credit: {
74
+ amount: newLine.credit || 0,
75
+ currency: "SEK",
76
+ },
77
+ ...(newLine.memo && { memo: newLine.memo }),
78
+ ...(newLine.tax_code && { taxCode: newLine.tax_code }),
79
+ ...(newLine.cost_center_code && {
80
+ costCenter: newLine.cost_center_code,
81
+ }),
82
+ });
83
+ }
84
+ // Renumber all lines sequentially
85
+ allLines.forEach((line, index) => {
86
+ line.lineNumber = index + 1;
87
+ });
88
+ // Update entry
89
+ entry.lines = allLines;
90
+ entry.totalDebit = {
91
+ amount: totals.totalDebit,
92
+ currency: "SEK",
93
+ };
94
+ entry.totalCredit = {
95
+ amount: totals.totalCredit,
96
+ currency: "SEK",
97
+ };
98
+ // Write back
99
+ await writeEntryYaml(entryPath, entry);
100
+ }
101
+ /**
102
+ * Read and parse accounts.yaml file
103
+ */
104
+ export async function readAccountsYaml(filePath) {
105
+ try {
106
+ const content = await fs.readFile(filePath, "utf-8");
107
+ const data = parseYaml(content);
108
+ return data.accounts || [];
109
+ }
110
+ catch (err) {
111
+ throw new Error(`Failed to read accounts.yaml at ${filePath}: ${err}`);
112
+ }
113
+ }
114
+ /**
115
+ * Validate that account code exists in accounts list
116
+ */
117
+ export function validateAccountExists(accountCode, accounts) {
118
+ return accounts.some((account) => account.code === accountCode);
119
+ }
120
+ /**
121
+ * Get account name by code
122
+ */
123
+ export function getAccountName(accountCode, accounts) {
124
+ return accounts.find((account) => account.code === accountCode)?.name;
125
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./yaml-serializer";
2
+ export * from "./entry-helpers";
@@ -0,0 +1,2 @@
1
+ export * from "./yaml-serializer";
2
+ export * from "./entry-helpers";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Parse YAML string to object
3
+ */
4
+ export declare function parseYaml<T = unknown>(yaml: string): T;
5
+ /**
6
+ * Convert data to YAML format with special handling for:
7
+ * - Monetary values (Json type: {amount, currency})
8
+ * - Enums (direct string values)
9
+ * - Nulls (omitted for cleaner output)
10
+ * - Arrays and nested objects
11
+ */
12
+ export declare function toYaml(data: unknown): string;
13
+ /**
14
+ * Convert a single object to YAML
15
+ */
16
+ export declare function objectToYaml(obj: Record<string, unknown>): string;
17
+ /**
18
+ * Convert an array of objects to YAML
19
+ * The output will be a YAML array
20
+ */
21
+ export declare function arrayToYaml(arr: unknown[]): string;
@@ -0,0 +1,60 @@
1
+ import YAML from "yaml";
2
+ /**
3
+ * Parse YAML string to object
4
+ */
5
+ export function parseYaml(yaml) {
6
+ return YAML.parse(yaml);
7
+ }
8
+ /**
9
+ * Convert data to YAML format with special handling for:
10
+ * - Monetary values (Json type: {amount, currency})
11
+ * - Enums (direct string values)
12
+ * - Nulls (omitted for cleaner output)
13
+ * - Arrays and nested objects
14
+ */
15
+ export function toYaml(data) {
16
+ // Remove null values for cleaner output
17
+ const cleanData = removeNulls(data);
18
+ return YAML.stringify(cleanData, {
19
+ indent: 2,
20
+ lineWidth: 120,
21
+ minContentWidth: 0,
22
+ doubleQuotedAsJSON: true,
23
+ });
24
+ }
25
+ /**
26
+ * Recursively remove null values from objects and arrays
27
+ * This produces cleaner YAML output by omitting null fields
28
+ */
29
+ function removeNulls(obj) {
30
+ if (obj === null || obj === undefined) {
31
+ return undefined;
32
+ }
33
+ if (Array.isArray(obj)) {
34
+ return obj.map(removeNulls).filter((item) => item !== undefined);
35
+ }
36
+ if (typeof obj === "object" && obj !== null) {
37
+ const result = {};
38
+ for (const [key, value] of Object.entries(obj)) {
39
+ const cleanValue = removeNulls(value);
40
+ if (cleanValue !== undefined) {
41
+ result[key] = cleanValue;
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+ return obj;
47
+ }
48
+ /**
49
+ * Convert a single object to YAML
50
+ */
51
+ export function objectToYaml(obj) {
52
+ return toYaml(obj);
53
+ }
54
+ /**
55
+ * Convert an array of objects to YAML
56
+ * The output will be a YAML array
57
+ */
58
+ export function arrayToYaml(arr) {
59
+ return toYaml(arr);
60
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@moatless/bookkeeping",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/aorwall/ledgit.git",
10
+ "directory": "packages/bookkeeping"
11
+ },
12
+ "main": "dist/index.js",
13
+ "files": ["dist"],
14
+ "types": "dist/index.d.ts",
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "type-check": "tsc --noEmit",
19
+ "test": "bun test",
20
+ "test:coverage": "bun test --coverage"
21
+ },
22
+ "dependencies": {
23
+ "@moatless/api-client": "workspace:*",
24
+ "@moatless/fortnox-client": "workspace:*",
25
+ "@moatless/bokio-client": "workspace:*",
26
+ "cli-progress": "^3.12.0",
27
+ "open": "^10.1.0",
28
+ "simple-git": "^3.22.0",
29
+ "yaml": "^2.7.1"
30
+ },
31
+ "devDependencies": {
32
+ "@types/cli-progress": "^3.11.6",
33
+ "@types/node": "^25.0.3",
34
+ "bun-types": "latest",
35
+ "typescript": "^5.8.3"
36
+ }
37
+ }