@rubydogjp/openkk-client-domain 26.1.12 → 26.2.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 (145) hide show
  1. package/LICENSE +21 -0
  2. package/dist/archive/fiscal-period-archive-zip.d.ts +12 -0
  3. package/dist/archive/fiscal-period-archive-zip.d.ts.map +1 -0
  4. package/dist/archive/fiscal-period-archive-zip.js +299 -0
  5. package/dist/archive/fiscal-period-archive-zip.js.map +1 -0
  6. package/dist/archive/fiscal-period-archive.d.ts +3 -2
  7. package/dist/archive/fiscal-period-archive.d.ts.map +1 -1
  8. package/dist/archive/fiscal-period-archive.js +8 -180
  9. package/dist/archive/fiscal-period-archive.js.map +1 -1
  10. package/dist/assist/fixed-asset-data.d.ts +20 -20
  11. package/dist/assist/fixed-asset-data.d.ts.map +1 -1
  12. package/dist/assist/fixed-asset-depreciation.d.ts.map +1 -1
  13. package/dist/assist/fixed-asset-depreciation.js +2 -7
  14. package/dist/assist/fixed-asset-depreciation.js.map +1 -1
  15. package/dist/assist/fixed-asset-draft.d.ts +17 -0
  16. package/dist/assist/fixed-asset-draft.d.ts.map +1 -0
  17. package/dist/assist/fixed-asset-draft.js +105 -0
  18. package/dist/assist/fixed-asset-draft.js.map +1 -0
  19. package/dist/assist/opening-carryover.d.ts +10 -14
  20. package/dist/assist/opening-carryover.d.ts.map +1 -1
  21. package/dist/entries/book-account.d.ts +13 -0
  22. package/dist/entries/book-account.d.ts.map +1 -0
  23. package/dist/entries/book-account.js +11 -0
  24. package/dist/entries/book-account.js.map +1 -0
  25. package/dist/entries/category-resolution.d.ts +5 -0
  26. package/dist/entries/category-resolution.d.ts.map +1 -0
  27. package/dist/entries/category-resolution.js +11 -0
  28. package/dist/entries/category-resolution.js.map +1 -0
  29. package/dist/entries/default-master-data.d.ts.map +1 -1
  30. package/dist/entries/default-master-data.js +1 -4
  31. package/dist/entries/default-master-data.js.map +1 -1
  32. package/dist/entries/entries-types.d.ts +18 -10
  33. package/dist/entries/entries-types.d.ts.map +1 -1
  34. package/dist/entries/entry-limits.d.ts +9 -0
  35. package/dist/entries/entry-limits.d.ts.map +1 -0
  36. package/dist/entries/entry-limits.js +41 -0
  37. package/dist/entries/entry-limits.js.map +1 -0
  38. package/dist/entries/entry-record.d.ts +26 -26
  39. package/dist/entries/entry-record.d.ts.map +1 -1
  40. package/dist/entries/entry-record.js +96 -71
  41. package/dist/entries/entry-record.js.map +1 -1
  42. package/dist/entries/generated-master-data.d.ts +2 -2
  43. package/dist/entries/generated-master-data.d.ts.map +1 -1
  44. package/dist/entries/import-export.d.ts +3 -1
  45. package/dist/entries/import-export.d.ts.map +1 -1
  46. package/dist/entries/import-export.js +365 -123
  47. package/dist/entries/import-export.js.map +1 -1
  48. package/dist/entries/quick-guide-data.d.ts +20 -18
  49. package/dist/entries/quick-guide-data.d.ts.map +1 -1
  50. package/dist/entries/quick-guide-data.js +191 -21
  51. package/dist/entries/quick-guide-data.js.map +1 -1
  52. package/dist/entries/virtual-entries.d.ts +21 -23
  53. package/dist/entries/virtual-entries.d.ts.map +1 -1
  54. package/dist/entries/virtual-entries.js +144 -246
  55. package/dist/entries/virtual-entries.js.map +1 -1
  56. package/dist/index.d.ts +6 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +6 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/print/entry-metadata.d.ts +3 -0
  61. package/dist/print/entry-metadata.d.ts.map +1 -0
  62. package/dist/print/entry-metadata.js +14 -0
  63. package/dist/print/entry-metadata.js.map +1 -0
  64. package/dist/print/examples/general-ledger-example.d.ts +1 -1
  65. package/dist/print/examples/general-ledger-example.d.ts.map +1 -1
  66. package/dist/print/examples/general-ledger-example.js +4 -4
  67. package/dist/print/examples/general-ledger-example.js.map +1 -1
  68. package/dist/print/examples/journal-example.d.ts.map +1 -1
  69. package/dist/print/examples/journal-example.js +158 -12
  70. package/dist/print/examples/journal-example.js.map +1 -1
  71. package/dist/print/financial-statements-print.d.ts +1 -1
  72. package/dist/print/financial-statements-print.d.ts.map +1 -1
  73. package/dist/print/financial-statements-print.js +39 -39
  74. package/dist/print/financial-statements-print.js.map +1 -1
  75. package/dist/print/fs-data.d.ts +2 -6
  76. package/dist/print/fs-data.d.ts.map +1 -1
  77. package/dist/print/fs-data.js +106 -59
  78. package/dist/print/fs-data.js.map +1 -1
  79. package/dist/print/general-ledger-print.d.ts.map +1 -1
  80. package/dist/print/general-ledger-print.js +153 -61
  81. package/dist/print/general-ledger-print.js.map +1 -1
  82. package/dist/print/journal-print.d.ts.map +1 -1
  83. package/dist/print/journal-print.js +63 -22
  84. package/dist/print/journal-print.js.map +1 -1
  85. package/dist/shared/app-error.d.ts +6 -8
  86. package/dist/shared/app-error.d.ts.map +1 -1
  87. package/dist/shared/app-error.js +28 -73
  88. package/dist/shared/app-error.js.map +1 -1
  89. package/dist/shared/bootstrap-data.d.ts +0 -10
  90. package/dist/shared/bootstrap-data.d.ts.map +1 -1
  91. package/dist/shared/bootstrap-data.js +1 -144
  92. package/dist/shared/bootstrap-data.js.map +1 -1
  93. package/dist/shared/brand-config.d.ts +3 -3
  94. package/dist/shared/brand-config.d.ts.map +1 -1
  95. package/dist/shared/models.d.ts +28 -33
  96. package/dist/shared/models.d.ts.map +1 -1
  97. package/dist/shared/openkk-config.d.ts +22 -22
  98. package/dist/shared/openkk-config.d.ts.map +1 -1
  99. package/dist/shared/openkk-config.js +18 -5
  100. package/dist/shared/openkk-config.js.map +1 -1
  101. package/dist/shared/parse-utils.d.ts +6 -1
  102. package/dist/shared/parse-utils.d.ts.map +1 -1
  103. package/dist/shared/parse-utils.js +13 -1
  104. package/dist/shared/parse-utils.js.map +1 -1
  105. package/dist/shared/period-lock.d.ts +1 -3
  106. package/dist/shared/period-lock.d.ts.map +1 -1
  107. package/dist/shared/period-lock.js +3 -13
  108. package/dist/shared/period-lock.js.map +1 -1
  109. package/dist/shared/text-limits.d.ts +3 -0
  110. package/dist/shared/text-limits.d.ts.map +1 -0
  111. package/dist/shared/text-limits.js +7 -0
  112. package/dist/shared/text-limits.js.map +1 -0
  113. package/dist/shared/user.d.ts +2 -3
  114. package/dist/shared/user.d.ts.map +1 -1
  115. package/dist/shared/user.js +1 -1
  116. package/dist/shared/user.js.map +1 -1
  117. package/dist/steps/next-fiscal-period.d.ts +3 -26
  118. package/dist/steps/next-fiscal-period.d.ts.map +1 -1
  119. package/dist/steps/next-fiscal-period.js +37 -83
  120. package/dist/steps/next-fiscal-period.js.map +1 -1
  121. package/dist/steps/period-check.d.ts +9 -0
  122. package/dist/steps/period-check.d.ts.map +1 -1
  123. package/dist/steps/period-check.js +6 -0
  124. package/dist/steps/period-check.js.map +1 -1
  125. package/dist/steps/step-derivation.d.ts +1 -1
  126. package/dist/steps/step-derivation.d.ts.map +1 -1
  127. package/dist/steps/step-derivation.js +1 -1
  128. package/dist/steps/step-derivation.js.map +1 -1
  129. package/dist/steps/step-trend.d.ts +2 -4
  130. package/dist/steps/step-trend.d.ts.map +1 -1
  131. package/dist/steps/step-trend.js +11 -7
  132. package/dist/steps/step-trend.js.map +1 -1
  133. package/dist/steps/summary.d.ts +2 -16
  134. package/dist/steps/summary.d.ts.map +1 -1
  135. package/dist/steps/summary.js +4 -43
  136. package/dist/steps/summary.js.map +1 -1
  137. package/dist/steps/year-month.d.ts +2 -3
  138. package/dist/steps/year-month.d.ts.map +1 -1
  139. package/dist/steps/year-month.js +4 -1
  140. package/dist/steps/year-month.js.map +1 -1
  141. package/package.json +2 -2
  142. package/dist/entries/example-entries.d.ts +0 -7
  143. package/dist/entries/example-entries.d.ts.map +0 -1
  144. package/dist/entries/example-entries.js +0 -97
  145. package/dist/entries/example-entries.js.map +0 -1
@@ -1,31 +1,29 @@
1
+ import { entryToVisualPairs, resolveEntryPairMetadata, } from "./entry-record.js";
1
2
  import { AppError } from "../shared/app-error.js";
2
- import { parseIsoLocalDate, weekdayJa } from "../shared/parse-utils.js";
3
+ import { formatBusinessRatePercent, parseBusinessRate, parseIsoLocalDate, weekdayJa, } from "../shared/parse-utils.js";
4
+ import { assertEntryLineCount, assertEntryImportItemCount, assertEntryImportLineCount, assertEntryImportSize, } from "./entry-limits.js";
5
+ export { assertEntryImportSize, MAX_ENTRY_LINES, MAX_ENTRY_IMPORT_ITEMS, MAX_ENTRY_IMPORT_LINES, MAX_ENTRY_IMPORT_SIZE, } from "./entry-limits.js";
3
6
  export const OPENKK_JOURNAL_SCHEMA = "openkk-journal-v1";
4
7
  export function exportEntriesAsJson(entries) {
5
8
  return JSON.stringify({
6
9
  schema: OPENKK_JOURNAL_SCHEMA,
7
- entries: entries.map((entry) => ({
8
- localId: entry.localId ?? entry.id,
9
- date: entry.date,
10
- weekday: entry.weekday,
11
- debit: entry.debit,
12
- debitType: entry.debitType,
13
- debitAmount: entry.debitAmount,
14
- credit: entry.credit,
15
- creditType: entry.creditType,
16
- creditAmount: entry.creditAmount,
17
- description: entry.description,
18
- partner: entry.partner,
19
- businessRate: entry.businessRate,
20
- taxCategory: entry.taxCategory,
21
- businessCategory: entry.businessCategory,
22
- ...(entry.lines != null && entry.lines.length > 0
23
- ? { lines: entry.lines.map((line) => ({ ...line })) }
24
- : {}),
25
- })),
10
+ entries: entries.map((entry) => {
11
+ const fields = journalFields(entry);
12
+ return {
13
+ localId: entry.localId ?? entry.id,
14
+ date: entry.date,
15
+ weekday: entry.weekday,
16
+ ...fields,
17
+ description: entry.description,
18
+ businessRate: formatBusinessRatePercent(entry.businessRate),
19
+ businessRateRatio: entry.businessRate,
20
+ lines: entry.lines.map((line) => ({ ...line })),
21
+ };
22
+ }),
26
23
  }, null, 2);
27
24
  }
28
25
  export function importEntriesFromJson(input) {
26
+ assertEntryImportSize(input.text.length);
29
27
  const parsed = parseEntriesJson(input.text);
30
28
  if (parsed.schema != null &&
31
29
  parsed.schema !== "" &&
@@ -35,34 +33,42 @@ export function importEntriesFromJson(input) {
35
33
  if (!Array.isArray(parsed.entries)) {
36
34
  throw importFileFormatError("entries array not found");
37
35
  }
36
+ assertEntryImportItemCount(parsed.entries.length);
38
37
  const seenLocalIds = new Set();
38
+ let importLineCount = 0;
39
39
  return parsed.entries.map((raw, index) => {
40
- const entry = raw;
41
40
  const rowNo = index + 1;
41
+ if (!isRecord(raw)) {
42
+ throw importFileRowError(`row ${rowNo}: entry must be an object`, rowNo);
43
+ }
44
+ const entry = raw;
42
45
  const localId = validateRequiredLocalId(typeof entry.localId === "string" ? entry.localId : "", rowNo, seenLocalIds);
43
- return normalizeEntry({
46
+ const normalized = normalizeEntry({
44
47
  fiscalPeriodId: input.fiscalPeriodId,
45
48
  id: `entry-${input.fiscalPeriodId}-json-${index + 1}`,
46
49
  rowNo,
47
50
  localId,
48
- date: entry.date,
49
- weekday: entry.weekday,
50
- debit: entry.debit,
51
- debitType: entry.debitType,
52
- debitAmount: entry.debitAmount,
53
- credit: entry.credit,
54
- creditType: entry.creditType,
55
- creditAmount: entry.creditAmount,
56
- description: entry.description,
57
- partner: entry.partner,
58
- businessRate: entry.businessRate,
59
- taxCategory: entry.taxCategory,
60
- businessCategory: entry.businessCategory,
61
- lines: Array.isArray(entry.lines) ? entry.lines : undefined,
51
+ date: stringValue(entry.date),
52
+ debit: stringValue(entry.debit),
53
+ debitType: stringValue(entry.debitType),
54
+ debitAmount: stringValue(entry.debitAmount),
55
+ credit: stringValue(entry.credit),
56
+ creditType: stringValue(entry.creditType),
57
+ creditAmount: stringValue(entry.creditAmount),
58
+ description: stringValue(entry.description),
59
+ partner: stringValue(entry.partner),
60
+ businessRate: stringValue(entry.businessRate),
61
+ businessRateRatio: entry.businessRateRatio ?? null,
62
+ taxCategory: stringValue(entry.taxCategory),
63
+ businessCategory: stringValue(entry.businessCategory),
64
+ lines: validateJsonLines(entry.lines, rowNo),
62
65
  });
66
+ importLineCount += normalized.lines.length;
67
+ assertEntryImportLineCount(importLineCount);
68
+ return normalized;
63
69
  });
64
70
  }
65
- const csvHeaders = [
71
+ const journalCsvDataHeaders = [
66
72
  "localId",
67
73
  "date",
68
74
  "weekday",
@@ -75,31 +81,67 @@ const csvHeaders = [
75
81
  "description",
76
82
  "partner",
77
83
  "businessRate",
84
+ "businessRateRatio",
78
85
  "taxCategory",
79
86
  "businessCategory",
80
87
  "lines",
81
88
  ];
89
+ const csvHeaders = [
90
+ ...journalCsvDataHeaders,
91
+ "openkkSchema",
92
+ "openkkEscapedFields",
93
+ ];
94
+ const OPENKK_JOURNAL_CSV_SCHEMA = "openkk-journal-csv-v1";
95
+ function journalFields(entry) {
96
+ const pair = entryToVisualPairs(entry)[0] ?? {
97
+ debit: null,
98
+ credit: null,
99
+ };
100
+ return {
101
+ debit: pair.debit?.accountName ?? "",
102
+ debitType: pair.debit?.accountType ?? "asset",
103
+ debitAmount: pair.debit?.amount ?? "",
104
+ credit: pair.credit?.accountName ?? "",
105
+ creditType: pair.credit?.accountType ?? "asset",
106
+ creditAmount: pair.credit?.amount ?? "",
107
+ ...resolveEntryPairMetadata(pair),
108
+ };
109
+ }
82
110
  export function exportEntriesAsCsv(entries) {
83
111
  const lines = [csvHeaders.join(",")];
84
112
  for (const entry of entries) {
85
- lines.push([
113
+ const fields = journalFields(entry);
114
+ const rawValues = [
86
115
  entry.localId ?? entry.id,
87
116
  entry.date,
88
117
  entry.weekday,
89
- entry.debit,
90
- entry.debitType,
91
- entry.debitAmount,
92
- entry.credit,
93
- entry.creditType,
94
- entry.creditAmount,
118
+ fields.debit,
119
+ fields.debitType,
120
+ fields.debitAmount,
121
+ fields.credit,
122
+ fields.creditType,
123
+ fields.creditAmount,
95
124
  entry.description,
96
- entry.partner,
97
- entry.businessRate,
98
- entry.taxCategory,
99
- entry.businessCategory,
100
- entry.lines != null && entry.lines.length > 0
101
- ? JSON.stringify(entry.lines)
102
- : "",
125
+ fields.partner,
126
+ formatBusinessRatePercent(entry.businessRate),
127
+ String(entry.businessRate),
128
+ fields.taxCategory,
129
+ fields.businessCategory,
130
+ JSON.stringify(entry.lines),
131
+ ];
132
+ const escapedFields = [];
133
+ const safeValues = rawValues.map((value, index) => {
134
+ if (!needsSpreadsheetFormulaProtection(value))
135
+ return value;
136
+ const fieldName = journalCsvDataHeaders[index];
137
+ if (fieldName != null)
138
+ escapedFields.push(fieldName);
139
+ return `'${value}`;
140
+ });
141
+ lines.push([
142
+ ...safeValues,
143
+ OPENKK_JOURNAL_CSV_SCHEMA,
144
+ JSON.stringify(escapedFields),
103
145
  ]
104
146
  .map(escapeCsvField)
105
147
  .join(","));
@@ -107,44 +149,69 @@ export function exportEntriesAsCsv(entries) {
107
149
  return lines.join("\n");
108
150
  }
109
151
  export function importEntriesFromCsv(input) {
152
+ assertEntryImportSize(input.text.length);
110
153
  const rows = parseCsv(stripBom(input.text));
111
- if (rows.length < 2) {
112
- return [];
113
- }
114
154
  const header = rows[0] ?? [];
115
155
  const indexMap = Object.fromEntries(csvHeaders.map((name) => [name, header.indexOf(name)]));
116
- if (indexMap.date < 0 || indexMap.debit < 0 || indexMap.credit < 0) {
117
- throw importFileFormatError("CSV header missing required columns: date, debit, credit");
156
+ if (indexMap.localId < 0 ||
157
+ indexMap.date < 0 ||
158
+ indexMap.debit < 0 ||
159
+ indexMap.credit < 0) {
160
+ throw importFileFormatError("CSV header missing required columns: localId, date, debit, credit");
118
161
  }
162
+ if (rows.length < 2)
163
+ return [];
164
+ assertEntryImportItemCount(rows.length - 1);
119
165
  const seenLocalIds = new Set();
166
+ let importLineCount = 0;
120
167
  return rows.slice(1).map((cells, index) => {
121
168
  const rowNo = index + 2;
122
- const localId = validateRequiredLocalId(readCsvCell(cells, indexMap.localId), rowNo, seenLocalIds);
123
- return normalizeEntry({
169
+ const escapedFields = parseCsvEscapedFields(cells, indexMap, rowNo);
170
+ const read = (field) => readJournalCsvCell(cells, indexMap[field], escapedFields.has(field));
171
+ const localId = validateRequiredLocalId(read("localId"), rowNo, seenLocalIds);
172
+ const normalized = normalizeEntry({
124
173
  fiscalPeriodId: input.fiscalPeriodId,
125
174
  id: `entry-${input.fiscalPeriodId}-csv-${index + 1}`,
126
175
  rowNo,
127
176
  localId,
128
- date: readCsvCell(cells, indexMap.date),
129
- weekday: readCsvCell(cells, indexMap.weekday),
130
- debit: readCsvCell(cells, indexMap.debit),
131
- debitType: readCsvCell(cells, indexMap.debitType),
132
- debitAmount: readCsvCell(cells, indexMap.debitAmount),
133
- credit: readCsvCell(cells, indexMap.credit),
134
- creditType: readCsvCell(cells, indexMap.creditType),
135
- creditAmount: readCsvCell(cells, indexMap.creditAmount),
136
- description: readCsvCell(cells, indexMap.description),
137
- partner: readCsvCell(cells, indexMap.partner),
138
- businessRate: readCsvCell(cells, indexMap.businessRate),
139
- taxCategory: readCsvCell(cells, indexMap.taxCategory),
140
- businessCategory: readCsvCell(cells, indexMap.businessCategory),
141
- lines: parseCsvLinesCell(readCsvCell(cells, indexMap.lines), rowNo),
177
+ date: read("date"),
178
+ debit: read("debit"),
179
+ debitType: read("debitType"),
180
+ debitAmount: read("debitAmount"),
181
+ credit: read("credit"),
182
+ creditType: read("creditType"),
183
+ creditAmount: read("creditAmount"),
184
+ description: read("description"),
185
+ partner: read("partner"),
186
+ businessRate: read("businessRate"),
187
+ businessRateRatio: read("businessRateRatio"),
188
+ taxCategory: read("taxCategory"),
189
+ businessCategory: read("businessCategory"),
190
+ lines: parseCsvLinesCell(read("lines"), rowNo),
142
191
  });
192
+ importLineCount += normalized.lines.length;
193
+ assertEntryImportLineCount(importLineCount);
194
+ return normalized;
143
195
  });
144
196
  }
197
+ export function decodeJournalImportBytes(bytes) {
198
+ assertEntryImportSize(bytes.byteLength);
199
+ try {
200
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
201
+ }
202
+ catch (error) {
203
+ throw new AppError({
204
+ messageForDeveloper: "entries import is not valid UTF-8",
205
+ messageForUser: "取込ファイルの文字コードを確認できませんでした。UTF-8形式で保存してからもう一度取り込んでください。",
206
+ originalMessage: error instanceof Error ? error.message : String(error),
207
+ statusCode: null,
208
+ code: null,
209
+ });
210
+ }
211
+ }
145
212
  function parseCsvLinesCell(raw, rowNo) {
146
213
  if (raw.trim() === "")
147
- return undefined;
214
+ return [];
148
215
  let parsed;
149
216
  try {
150
217
  parsed = JSON.parse(raw);
@@ -161,42 +228,100 @@ function normalizeEntry(input) {
161
228
  if (parseIsoLocalDate(input.date) == null) {
162
229
  throw importFileRowError(`row ${input.rowNo}: invalid date (${input.date})`, input.rowNo);
163
230
  }
164
- return {
231
+ const partner = input.partner.trim();
232
+ const taxCategory = input.taxCategory.trim() || "対象外";
233
+ const businessCategory = input.businessCategory.trim() || "対象外";
234
+ const importedLines = normalizeLines(input.lines, input.rowNo);
235
+ const lines = importedLines.length > 0
236
+ ? importedLines
237
+ : [
238
+ pairInputToLine("debit", input),
239
+ pairInputToLine("credit", input),
240
+ ].map((line) => ({
241
+ ...line,
242
+ partnerName: partner === "" ? null : partner,
243
+ taxCategoryName: taxCategory,
244
+ businessCategoryName: businessCategory,
245
+ }));
246
+ const result = {
165
247
  id: input.id,
166
248
  localId: input.localId,
167
249
  fiscalPeriodId: input.fiscalPeriodId,
168
250
  date: input.date,
169
- weekday: input.weekday && input.weekday.length > 0
170
- ? input.weekday
171
- : weekdayFromDate(input.date),
172
- debit: input.debit || "未設定",
173
- debitType: normalizeType(input.debitType),
174
- debitAmount: normalizeAmount(input.debitAmount),
175
- credit: input.credit || "未設定",
176
- creditType: normalizeType(input.creditType),
177
- creditAmount: normalizeAmount(input.creditAmount),
251
+ weekday: weekdayFromDate(input.date),
178
252
  description: input.description || "",
179
- partner: input.partner || "",
180
- businessRate: input.businessRate || "100",
181
- taxCategory: input.taxCategory || "対象外",
182
- businessCategory: input.businessCategory || "対象外",
183
- lines: normalizeLines(input.lines),
253
+ businessRate: importedBusinessRate(input),
254
+ lines,
184
255
  };
256
+ assertNormalizedEntry(result, input.rowNo);
257
+ return result;
185
258
  }
186
- function normalizeLines(lines) {
187
- if (lines == null || lines.length === 0)
188
- return undefined;
189
- return lines.map((line) => ({
190
- side: line.side === "credit" ? "credit" : "debit",
191
- accountName: line.accountName || "未設定",
192
- accountType: normalizeType(line.accountType),
193
- amount: normalizeAmount(line.amount),
194
- ...(line.bookAccountId != null && line.bookAccountId.length > 0
195
- ? { bookAccountId: line.bookAccountId }
196
- : {}),
197
- }));
259
+ function pairInputToLine(side, input) {
260
+ const debit = side === "debit";
261
+ return {
262
+ id: null,
263
+ side,
264
+ accountName: (debit ? input.debit : input.credit).trim(),
265
+ accountType: normalizeType(debit ? input.debitType : input.creditType, input.rowNo, `${side} type`),
266
+ amount: normalizeAmount(debit ? input.debitAmount : input.creditAmount, input.rowNo, `${side} amount`),
267
+ bookAccountId: null,
268
+ partnerName: null,
269
+ taxCategoryId: null,
270
+ taxCategoryName: null,
271
+ businessCategoryId: null,
272
+ businessCategoryName: null,
273
+ };
198
274
  }
199
- function normalizeType(value) {
275
+ function importedBusinessRate(input) {
276
+ const ratio = input.businessRateRatio;
277
+ if (ratio == null || ratio === "") {
278
+ return parseBusinessRate(input.businessRate);
279
+ }
280
+ const rate = typeof ratio === "number"
281
+ ? ratio
282
+ : typeof ratio === "string"
283
+ ? Number(ratio.trim())
284
+ : Number.NaN;
285
+ if (!Number.isFinite(rate) || rate < 0 || rate > 1) {
286
+ throw importFileRowError(`row ${input.rowNo}: invalid businessRateRatio`, input.rowNo);
287
+ }
288
+ return rate;
289
+ }
290
+ function normalizeLines(lines, rowNo) {
291
+ assertEntryLineCount(lines.length);
292
+ return lines.map((line, index) => {
293
+ if (!isRecord(line)) {
294
+ throw importFileRowError(`row ${rowNo}: line ${index + 1} must be an object`, rowNo);
295
+ }
296
+ if (line.side !== "debit" && line.side !== "credit") {
297
+ throw importFileRowError(`row ${rowNo}: line ${index + 1} has invalid side`, rowNo);
298
+ }
299
+ const accountName = stringValue(line.accountName).trim();
300
+ if (accountName === "") {
301
+ throw importFileRowError(`row ${rowNo}: line ${index + 1} account is required`, rowNo);
302
+ }
303
+ const bookAccountId = stringValue(line.bookAccountId).trim();
304
+ const partnerName = stringValue(line.partnerName).trim();
305
+ const taxCategoryId = stringValue(line.taxCategoryId).trim();
306
+ const taxCategoryName = stringValue(line.taxCategoryName).trim();
307
+ const businessCategoryId = stringValue(line.businessCategoryId).trim();
308
+ const businessCategoryName = stringValue(line.businessCategoryName).trim();
309
+ return {
310
+ side: line.side,
311
+ accountName,
312
+ accountType: normalizeType(stringValue(line.accountType), rowNo, `line ${index + 1} account type`),
313
+ amount: normalizeAmount(stringValue(line.amount), rowNo, `line ${index + 1} amount`),
314
+ id: null,
315
+ bookAccountId: bookAccountId === "" ? null : bookAccountId,
316
+ partnerName: partnerName === "" ? null : partnerName,
317
+ taxCategoryId: taxCategoryId === "" ? null : taxCategoryId,
318
+ taxCategoryName: taxCategoryName === "" ? null : taxCategoryName,
319
+ businessCategoryId: businessCategoryId === "" ? null : businessCategoryId,
320
+ businessCategoryName: businessCategoryName === "" ? null : businessCategoryName,
321
+ };
322
+ });
323
+ }
324
+ function normalizeType(value, rowNo, label) {
200
325
  if (value === "asset" ||
201
326
  value === "liability" ||
202
327
  value === "equity" ||
@@ -205,38 +330,83 @@ function normalizeType(value) {
205
330
  value === "expense") {
206
331
  return value;
207
332
  }
208
- return "asset";
333
+ if (value.trim() === "")
334
+ return "asset";
335
+ throw importFileRowError(`row ${rowNo}: invalid ${label} (${value})`, rowNo);
209
336
  }
210
- function normalizeAmount(value) {
337
+ function normalizeAmount(value, rowNo, label) {
211
338
  const n = Number(String(value).replaceAll(",", "").trim());
212
- if (!Number.isFinite(n))
213
- return "0";
339
+ if (!Number.isSafeInteger(n) || n < 0) {
340
+ throw importFileRowError(`row ${rowNo}: invalid ${label} (${value})`, rowNo);
341
+ }
214
342
  return n.toLocaleString("ja-JP");
215
343
  }
216
344
  function weekdayFromDate(dateText) {
217
345
  return weekdayJa(dateText) || "月";
218
346
  }
219
347
  function escapeCsvField(value) {
220
- if (value.includes(",") || value.includes('"') || value.includes("\n")) {
348
+ if (value.includes(",") ||
349
+ value.includes('"') ||
350
+ value.includes("\n") ||
351
+ value.includes("\r")) {
221
352
  return `"${value.replaceAll('"', '""')}"`;
222
353
  }
223
354
  return value;
224
355
  }
356
+ function needsSpreadsheetFormulaProtection(value) {
357
+ return /^[=+\-@\t\r\n]/.test(value);
358
+ }
359
+ function parseCsvEscapedFields(cells, indexMap, rowNo) {
360
+ if (readCsvCell(cells, indexMap.openkkSchema) !== OPENKK_JOURNAL_CSV_SCHEMA) {
361
+ return new Set();
362
+ }
363
+ const raw = readCsvCell(cells, indexMap.openkkEscapedFields);
364
+ let parsed;
365
+ try {
366
+ parsed = JSON.parse(raw);
367
+ }
368
+ catch {
369
+ throw importFileRowError(`row ${rowNo}: invalid escaped fields metadata`, rowNo);
370
+ }
371
+ if (!Array.isArray(parsed) ||
372
+ parsed.some((field) => !isJournalCsvDataHeader(field))) {
373
+ throw importFileRowError(`row ${rowNo}: invalid escaped fields metadata`, rowNo);
374
+ }
375
+ return new Set(parsed);
376
+ }
377
+ function isJournalCsvDataHeader(value) {
378
+ return (typeof value === "string" &&
379
+ journalCsvDataHeaders.includes(value));
380
+ }
381
+ function readJournalCsvCell(cells, index, formulaProtected) {
382
+ const value = readCsvCell(cells, index);
383
+ return formulaProtected && value.startsWith("'") ? value.slice(1) : value;
384
+ }
225
385
  function parseCsv(text) {
226
386
  const rows = [];
227
387
  let row = [];
228
388
  let field = "";
229
- let inQuotes = false;
389
+ let state = "start";
390
+ const finishField = () => {
391
+ row.push(field);
392
+ field = "";
393
+ state = "start";
394
+ };
395
+ const finishRow = () => {
396
+ finishField();
397
+ rows.push(row);
398
+ row = [];
399
+ };
230
400
  for (let i = 0; i < text.length; i += 1) {
231
401
  const ch = text[i];
232
- if (inQuotes) {
402
+ if (state === "quoted") {
233
403
  if (ch === '"') {
234
404
  if (text[i + 1] === '"') {
235
405
  field += '"';
236
406
  i += 1;
237
407
  }
238
408
  else {
239
- inQuotes = false;
409
+ state = "after_quote";
240
410
  }
241
411
  }
242
412
  else {
@@ -244,30 +414,95 @@ function parseCsv(text) {
244
414
  }
245
415
  continue;
246
416
  }
417
+ const isLineBreak = ch === "\n" || ch === "\r";
418
+ if (state === "after_quote") {
419
+ if (ch === ",") {
420
+ finishField();
421
+ continue;
422
+ }
423
+ if (isLineBreak) {
424
+ finishRow();
425
+ if (ch === "\r" && text[i + 1] === "\n")
426
+ i += 1;
427
+ continue;
428
+ }
429
+ throw importFileFormatError("CSV has an unexpected character after a closing quote");
430
+ }
247
431
  if (ch === '"') {
248
- inQuotes = true;
432
+ if (state !== "start") {
433
+ throw importFileFormatError("CSV has an unexpected quote in an unquoted field");
434
+ }
435
+ state = "quoted";
249
436
  continue;
250
437
  }
251
438
  if (ch === ",") {
252
- row.push(field);
253
- field = "";
439
+ finishField();
254
440
  continue;
255
441
  }
256
- if (ch === "\n") {
257
- row.push(field);
258
- rows.push(row);
259
- row = [];
260
- field = "";
442
+ if (isLineBreak) {
443
+ finishRow();
444
+ if (ch === "\r" && text[i + 1] === "\n")
445
+ i += 1;
261
446
  continue;
262
447
  }
263
- if (ch !== "\r") {
264
- field += ch;
265
- }
448
+ field += ch;
449
+ state = "unquoted";
450
+ }
451
+ if (state === "quoted") {
452
+ throw importFileFormatError("CSV has an unterminated quoted field");
266
453
  }
267
- row.push(field);
268
- rows.push(row);
454
+ finishRow();
269
455
  return rows.filter((r) => r.some((v) => v.trim().length > 0));
270
456
  }
457
+ function validateJsonLines(value, rowNo) {
458
+ if (value == null)
459
+ return [];
460
+ if (!Array.isArray(value)) {
461
+ throw importFileRowError(`row ${rowNo}: lines must be an array`, rowNo);
462
+ }
463
+ return value;
464
+ }
465
+ function assertNormalizedEntry(entry, rowNo) {
466
+ if (entry.description.trim() === "") {
467
+ throw importFileRowError(`row ${rowNo}: description is required`, rowNo);
468
+ }
469
+ for (const line of entry.lines) {
470
+ if (line.accountName.trim() === "") {
471
+ throw importFileRowError(`row ${rowNo}: account is required`, rowNo);
472
+ }
473
+ }
474
+ let debitTotal = 0;
475
+ let creditTotal = 0;
476
+ for (const line of entry.lines) {
477
+ const amount = numericAmount(line.amount);
478
+ if (line.side === "debit")
479
+ debitTotal += amount;
480
+ else
481
+ creditTotal += amount;
482
+ if (!Number.isSafeInteger(debitTotal) ||
483
+ !Number.isSafeInteger(creditTotal)) {
484
+ throw importFileRowError(`row ${rowNo}: entry totals exceed the safe integer range`, rowNo);
485
+ }
486
+ }
487
+ if (debitTotal <= 0 || creditTotal <= 0 || debitTotal !== creditTotal) {
488
+ throw importFileRowError(`row ${rowNo}: debit and credit totals must be positive and equal`, rowNo);
489
+ }
490
+ const rate = entry.businessRate;
491
+ if (!Number.isFinite(rate) || rate < 0 || rate > 1) {
492
+ throw importFileRowError(`row ${rowNo}: invalid business rate`, rowNo);
493
+ }
494
+ }
495
+ function numericAmount(value) {
496
+ return Number(value.replaceAll(",", ""));
497
+ }
498
+ function isRecord(value) {
499
+ return typeof value === "object" && value != null && !Array.isArray(value);
500
+ }
501
+ function stringValue(value) {
502
+ return typeof value === "string" || typeof value === "number"
503
+ ? String(value)
504
+ : "";
505
+ }
271
506
  function readCsvCell(cells, index) {
272
507
  if (index < 0)
273
508
  return "";
@@ -289,7 +524,11 @@ function stripBom(text) {
289
524
  }
290
525
  function parseEntriesJson(text) {
291
526
  try {
292
- return JSON.parse(stripBom(text));
527
+ const parsed = JSON.parse(stripBom(text));
528
+ if (!isRecord(parsed)) {
529
+ throw new Error("journal JSON root must be an object");
530
+ }
531
+ return parsed;
293
532
  }
294
533
  catch (error) {
295
534
  throw new AppError({
@@ -297,6 +536,7 @@ function parseEntriesJson(text) {
297
536
  messageForUser: "JSONファイルの内容を確認できませんでした。ファイルの形式を確認してください。",
298
537
  originalMessage: error instanceof Error ? error.message : String(error),
299
538
  statusCode: null,
539
+ code: null,
300
540
  });
301
541
  }
302
542
  }
@@ -306,6 +546,7 @@ function importFileFormatError(messageForDeveloper) {
306
546
  messageForUser: "取込ファイルの形式を確認できませんでした。オープン会計で書き出したCSVまたはJSONを選択してください。",
307
547
  originalMessage: null,
308
548
  statusCode: null,
549
+ code: null,
309
550
  });
310
551
  }
311
552
  function importFileRowError(messageForDeveloper, rowNo) {
@@ -314,6 +555,7 @@ function importFileRowError(messageForDeveloper, rowNo) {
314
555
  messageForUser: `${rowNo}行目の取引データを確認できませんでした。ファイルの内容を修正してからもう一度取り込んでください。`,
315
556
  originalMessage: null,
316
557
  statusCode: null,
558
+ code: null,
317
559
  });
318
560
  }
319
561
  //# sourceMappingURL=import-export.js.map