@rubydogjp/openkk-client-usecases 26.1.12 → 26.2.1
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/LICENSE +21 -0
- package/dist/assist/assist-state-helpers.d.ts +27 -0
- package/dist/assist/assist-state-helpers.d.ts.map +1 -0
- package/dist/assist/assist-state-helpers.js +183 -0
- package/dist/assist/assist-state-helpers.js.map +1 -0
- package/dist/assist/assist-state.d.ts +4 -11
- package/dist/assist/assist-state.d.ts.map +1 -1
- package/dist/assist/assist-state.js +233 -303
- package/dist/assist/assist-state.js.map +1 -1
- package/dist/entries/account-options.d.ts +11 -0
- package/dist/entries/account-options.d.ts.map +1 -0
- package/dist/entries/account-options.js +41 -0
- package/dist/entries/account-options.js.map +1 -0
- package/dist/entries/entries-state.d.ts +9 -14
- package/dist/entries/entries-state.d.ts.map +1 -1
- package/dist/entries/entries-state.js +245 -159
- package/dist/entries/entries-state.js.map +1 -1
- package/dist/entries/entry-record-state.d.ts +6 -0
- package/dist/entries/entry-record-state.d.ts.map +1 -0
- package/dist/entries/entry-record-state.js +23 -0
- package/dist/entries/entry-record-state.js.map +1 -0
- package/dist/entries/import-mapping.d.ts +12 -29
- package/dist/entries/import-mapping.d.ts.map +1 -1
- package/dist/entries/import-mapping.js +23 -74
- package/dist/entries/import-mapping.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/shared/archive-data-policy.d.ts +6 -0
- package/dist/shared/archive-data-policy.d.ts.map +1 -0
- package/dist/shared/archive-data-policy.js +4 -0
- package/dist/shared/archive-data-policy.js.map +1 -0
- package/dist/shared/async-mutation-queue.d.ts +9 -0
- package/dist/shared/async-mutation-queue.d.ts.map +1 -0
- package/dist/shared/async-mutation-queue.js +21 -0
- package/dist/shared/async-mutation-queue.js.map +1 -0
- package/dist/shared/async-state-version.d.ts +13 -0
- package/dist/shared/async-state-version.d.ts.map +1 -0
- package/dist/shared/async-state-version.js +27 -0
- package/dist/shared/async-state-version.js.map +1 -0
- package/dist/shared/auth-operation-guard.d.ts +3 -0
- package/dist/shared/auth-operation-guard.d.ts.map +1 -0
- package/dist/shared/auth-operation-guard.js +13 -0
- package/dist/shared/auth-operation-guard.js.map +1 -0
- package/dist/shared/backend-api-context.d.ts.map +1 -1
- package/dist/shared/backend-api-context.js +1 -0
- package/dist/shared/backend-api-context.js.map +1 -1
- package/dist/shared/brand-config-context.js +1 -1
- package/dist/shared/brand-config-context.js.map +1 -1
- package/dist/shared/browser-storage.d.ts +9 -0
- package/dist/shared/browser-storage.d.ts.map +1 -0
- package/dist/shared/browser-storage.js +79 -0
- package/dist/shared/browser-storage.js.map +1 -0
- package/dist/shared/callouts-context.d.ts +1 -1
- package/dist/shared/callouts-context.d.ts.map +1 -1
- package/dist/shared/callouts-context.js +5 -2
- package/dist/shared/callouts-context.js.map +1 -1
- package/dist/shared/closing-state.d.ts +2 -1
- package/dist/shared/closing-state.d.ts.map +1 -1
- package/dist/shared/closing-state.js +21 -6
- package/dist/shared/closing-state.js.map +1 -1
- package/dist/shared/editing-policy.d.ts +3 -0
- package/dist/shared/editing-policy.d.ts.map +1 -0
- package/dist/shared/editing-policy.js +13 -0
- package/dist/shared/editing-policy.js.map +1 -0
- package/dist/shared/fiscal-period-list.d.ts +5 -0
- package/dist/shared/fiscal-period-list.d.ts.map +1 -0
- package/dist/shared/fiscal-period-list.js +50 -0
- package/dist/shared/fiscal-period-list.js.map +1 -0
- package/dist/shared/maintenance-state.d.ts.map +1 -1
- package/dist/shared/maintenance-state.js +15 -2
- package/dist/shared/maintenance-state.js.map +1 -1
- package/dist/shared/openkk-app-state.d.ts +14 -27
- package/dist/shared/openkk-app-state.d.ts.map +1 -1
- package/dist/shared/openkk-app-state.js +240 -171
- package/dist/shared/openkk-app-state.js.map +1 -1
- package/dist/shared/openkk-config-context.d.ts +1 -0
- package/dist/shared/openkk-config-context.d.ts.map +1 -1
- package/dist/shared/openkk-config-context.js +35 -2
- package/dist/shared/openkk-config-context.js.map +1 -1
- package/dist/shared/print-adapter-context.d.ts.map +1 -1
- package/dist/shared/print-adapter-context.js +1 -0
- package/dist/shared/print-adapter-context.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,64 +1,107 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useEffect, useMemo, useState, } from "react";
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
4
4
|
import { useOpenkkAppState } from "../shared/openkk-app-state.js";
|
|
5
5
|
import { useBackendApi } from "../shared/backend-api-context.js";
|
|
6
|
-
import { useOpenkkConfig } from "../shared/openkk-config-context.js";
|
|
7
|
-
import {
|
|
6
|
+
import { useOpenkkConfig, useOpenkkToday, } from "../shared/openkk-config-context.js";
|
|
7
|
+
import { assertEditingUnlocked } from "../shared/editing-policy.js";
|
|
8
|
+
import { isSelectedFiscalPeriodDataPurged } from "../shared/archive-data-policy.js";
|
|
9
|
+
import { KeyedAsyncStateVersion } from "../shared/async-state-version.js";
|
|
10
|
+
import { KeyedAsyncMutationQueue } from "../shared/async-mutation-queue.js";
|
|
11
|
+
import { buildOpeningJournalLines, fixedAssetDraftToPatch, listFixedAssetsForPeriod, mapFixedAsset, mapOpeningJournalToRecord, nextOpeningCarryoverId, fixedAssetDraftBusinessRate, resolveFixedAssetAccountId, upsertFixedAsset, } from "./assist-state-helpers.js";
|
|
12
|
+
import { capFixedAssetPreviewDate, draftBusinessRate, parseAmount, AppError, } from "@rubydogjp/openkk-client-domain";
|
|
8
13
|
const AssistContext = createContext(null);
|
|
9
14
|
export function OpenkkAssistProvider(props) {
|
|
10
15
|
const backendApi = useBackendApi();
|
|
11
16
|
const appState = useOpenkkAppState();
|
|
12
17
|
const config = useOpenkkConfig();
|
|
18
|
+
const today = useOpenkkToday();
|
|
13
19
|
const [fixedAssets, setFixedAssets] = useState([]);
|
|
14
20
|
const [bookAccountNameById, setBookAccountNameById] = useState({});
|
|
15
|
-
const [
|
|
21
|
+
const [bookAccounts, setBookAccounts] = useState([]);
|
|
16
22
|
const [bookAccountTypeById, setBookAccountTypeById] = useState({});
|
|
17
|
-
const [
|
|
23
|
+
const [taxCategories, setTaxCategories] = useState([]);
|
|
24
|
+
const [taxCategoryNameById, setTaxCategoryNameById] = useState({});
|
|
25
|
+
const [businessCategories, setBusinessCategories] = useState([]);
|
|
26
|
+
const [businessCategoryNameById, setBusinessCategoryNameById] = useState({});
|
|
27
|
+
const [masterLoadError, setMasterLoadError] = useState(null);
|
|
28
|
+
const [fixedAssetsLoadError, setFixedAssetsLoadError] = useState(null);
|
|
18
29
|
const [reloadNonce, setReloadNonce] = useState(0);
|
|
30
|
+
const sessionUserId = appState.session?.user.id ?? null;
|
|
31
|
+
const selectedFiscalPeriodId = useRef(appState.currentFiscalPeriodId);
|
|
32
|
+
const periodVersions = useRef(new KeyedAsyncStateVersion());
|
|
33
|
+
const assetMutationQueue = useRef(new KeyedAsyncMutationQueue());
|
|
34
|
+
selectedFiscalPeriodId.current = appState.currentFiscalPeriodId;
|
|
35
|
+
const currentFiscalPeriod = appState.fiscalPeriods.find((period) => period.id === appState.currentFiscalPeriodId);
|
|
36
|
+
const currentFiscalPeriodEndDate = currentFiscalPeriod?.endDate ?? null;
|
|
37
|
+
const currentFiscalPeriodDataPurged = isSelectedFiscalPeriodDataPurged(appState.fiscalPeriods, appState.currentFiscalPeriodId);
|
|
38
|
+
const fixedAssetPreviewAsOf = useMemo(() => capFixedAssetPreviewDate(today, currentFiscalPeriodEndDate), [today, currentFiscalPeriodEndDate]);
|
|
19
39
|
useEffect(() => {
|
|
40
|
+
if (sessionUserId == null) {
|
|
41
|
+
setMasterLoadError(null);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
20
44
|
let cancelled = false;
|
|
21
45
|
void (async () => {
|
|
22
46
|
try {
|
|
23
|
-
const accounts = await
|
|
47
|
+
const [accounts, taxCategories, businessCategories] = await Promise.all([
|
|
48
|
+
backendApi.masterData.getBookAccounts(),
|
|
49
|
+
backendApi.masterData.getTaxCategories(),
|
|
50
|
+
backendApi.masterData.getBusinessCategories(),
|
|
51
|
+
]);
|
|
24
52
|
if (cancelled)
|
|
25
53
|
return;
|
|
26
54
|
setBookAccountNameById(Object.fromEntries(accounts.map((account) => [account.id, account.name])));
|
|
27
|
-
|
|
55
|
+
setBookAccounts(accounts);
|
|
28
56
|
setBookAccountTypeById(Object.fromEntries(accounts.map((account) => [account.id, account.accountType])));
|
|
29
|
-
|
|
57
|
+
setTaxCategories(taxCategories);
|
|
58
|
+
setTaxCategoryNameById(Object.fromEntries(taxCategories.map((category) => [category.id, category.name])));
|
|
59
|
+
setBusinessCategories(businessCategories);
|
|
60
|
+
setBusinessCategoryNameById(Object.fromEntries(businessCategories.map((category) => [category.id, category.name])));
|
|
61
|
+
setMasterLoadError(null);
|
|
30
62
|
}
|
|
31
63
|
catch (error) {
|
|
32
64
|
if (cancelled)
|
|
33
65
|
return;
|
|
34
66
|
console.error("[openkk] assist master data load failed:", error);
|
|
35
|
-
|
|
67
|
+
setMasterLoadError(error);
|
|
36
68
|
}
|
|
37
69
|
})();
|
|
38
70
|
return () => {
|
|
39
71
|
cancelled = true;
|
|
40
72
|
};
|
|
41
|
-
}, [reloadNonce]);
|
|
73
|
+
}, [reloadNonce, sessionUserId]);
|
|
42
74
|
useEffect(() => {
|
|
43
75
|
const fiscalPeriodId = appState.currentFiscalPeriodId;
|
|
44
|
-
if (fiscalPeriodId == null ||
|
|
76
|
+
if (fiscalPeriodId == null ||
|
|
77
|
+
fiscalPeriodId.length === 0 ||
|
|
78
|
+
currentFiscalPeriodDataPurged) {
|
|
45
79
|
setFixedAssets([]);
|
|
80
|
+
setFixedAssetsLoadError(null);
|
|
46
81
|
return;
|
|
47
82
|
}
|
|
48
83
|
let cancelled = false;
|
|
84
|
+
const authOperationVersion = appState.captureAuthOperationVersion();
|
|
85
|
+
const readVersion = periodVersions.current.capture(fiscalPeriodId);
|
|
49
86
|
void (async () => {
|
|
50
87
|
try {
|
|
51
88
|
const remote = await backendApi.fixedAssets.getAll(fiscalPeriodId);
|
|
52
|
-
if (cancelled
|
|
89
|
+
if (cancelled ||
|
|
90
|
+
!appState.isAuthOperationCurrent(authOperationVersion) ||
|
|
91
|
+
!periodVersions.current.isCurrent(fiscalPeriodId, readVersion)) {
|
|
53
92
|
return;
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
}
|
|
94
|
+
setFixedAssets(remote.map((asset) => mapFixedAsset(asset, bookAccountNameById[asset.bookAccountId] ?? null, fixedAssetPreviewAsOf, currentFiscalPeriodEndDate)));
|
|
95
|
+
setFixedAssetsLoadError(null);
|
|
56
96
|
}
|
|
57
97
|
catch (error) {
|
|
58
|
-
if (cancelled
|
|
98
|
+
if (cancelled ||
|
|
99
|
+
!appState.isAuthOperationCurrent(authOperationVersion) ||
|
|
100
|
+
!periodVersions.current.isCurrent(fiscalPeriodId, readVersion)) {
|
|
59
101
|
return;
|
|
102
|
+
}
|
|
60
103
|
console.error("[openkk] fixed assets load failed:", error);
|
|
61
|
-
|
|
104
|
+
setFixedAssetsLoadError(error);
|
|
62
105
|
}
|
|
63
106
|
})();
|
|
64
107
|
return () => {
|
|
@@ -67,74 +110,102 @@ export function OpenkkAssistProvider(props) {
|
|
|
67
110
|
}, [
|
|
68
111
|
appState.currentFiscalPeriodId,
|
|
69
112
|
bookAccountNameById,
|
|
70
|
-
|
|
113
|
+
currentFiscalPeriodEndDate,
|
|
114
|
+
currentFiscalPeriodDataPurged,
|
|
115
|
+
fixedAssetPreviewAsOf,
|
|
71
116
|
reloadNonce,
|
|
72
117
|
]);
|
|
73
118
|
const value = useMemo(() => {
|
|
119
|
+
const loadError = masterLoadError ?? fixedAssetsLoadError;
|
|
74
120
|
return {
|
|
75
|
-
listFixedAssets() {
|
|
76
|
-
return fixedAssets;
|
|
121
|
+
listFixedAssets(fiscalPeriodId) {
|
|
122
|
+
return listFixedAssetsForPeriod(fixedAssets, fiscalPeriodId);
|
|
77
123
|
},
|
|
78
124
|
getFixedAsset(assetId) {
|
|
79
125
|
return fixedAssets.find((asset) => asset.id === assetId) ?? null;
|
|
80
126
|
},
|
|
81
127
|
async addFixedAsset(draft) {
|
|
128
|
+
assertEditingUnlocked(config.editingPolicy, "assist.addFixedAsset");
|
|
129
|
+
const authOperationVersion = appState.captureAuthOperationVersion();
|
|
82
130
|
const fiscalPeriodId = appState.currentFiscalPeriodId;
|
|
83
|
-
if (fiscalPeriodId == null
|
|
131
|
+
if (fiscalPeriodId == null)
|
|
84
132
|
return null;
|
|
85
|
-
|
|
86
|
-
const accountId =
|
|
87
|
-
if (accountId == null
|
|
133
|
+
periodVersions.current.invalidate(fiscalPeriodId);
|
|
134
|
+
const accountId = resolveFixedAssetAccountId(null, draft.account, bookAccounts);
|
|
135
|
+
if (accountId == null) {
|
|
88
136
|
throw new AppError({
|
|
89
137
|
messageForDeveloper: "assist.addFixedAsset: accountId missing",
|
|
90
138
|
messageForUser: "勘定科目が解決できないため保存できませんでした",
|
|
91
139
|
originalMessage: null,
|
|
92
140
|
statusCode: null,
|
|
141
|
+
code: null,
|
|
93
142
|
});
|
|
94
143
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
144
|
+
try {
|
|
145
|
+
const created = await backendApi.fixedAssets.create(fiscalPeriodId, {
|
|
146
|
+
name: draft.name,
|
|
147
|
+
acquisitionDate: draft.acquisitionDate,
|
|
148
|
+
acquisitionCost: parseAmount(draft.acquisitionCost),
|
|
149
|
+
usefulLife: Math.max(1, Math.round(draft.usefulLife) || 1),
|
|
150
|
+
depreciationMethod: "straight_line",
|
|
151
|
+
businessRate: fixedAssetDraftBusinessRate(draft),
|
|
152
|
+
bookAccountId: accountId,
|
|
153
|
+
});
|
|
154
|
+
appState.assertAuthOperationCurrent(authOperationVersion);
|
|
155
|
+
if (selectedFiscalPeriodId.current === fiscalPeriodId) {
|
|
156
|
+
const mapped = mapFixedAsset(created, bookAccountNameById[created.bookAccountId] ?? null, fixedAssetPreviewAsOf, currentFiscalPeriodEndDate ?? null);
|
|
157
|
+
setFixedAssets((current) => upsertFixedAsset(current, mapped));
|
|
158
|
+
}
|
|
159
|
+
return created.id;
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
periodVersions.current.invalidate(fiscalPeriodId);
|
|
163
|
+
}
|
|
109
164
|
},
|
|
110
165
|
async updateFixedAsset(assetId, draft) {
|
|
166
|
+
assertEditingUnlocked(config.editingPolicy, "assist.updateFixedAsset");
|
|
167
|
+
const authOperationVersion = appState.captureAuthOperationVersion();
|
|
111
168
|
const current = fixedAssets.find((asset) => asset.id === assetId) ?? null;
|
|
112
|
-
const fiscalPeriodId = current?.fiscalPeriodId ?? appState.currentFiscalPeriodId
|
|
113
|
-
if (fiscalPeriodId
|
|
169
|
+
const fiscalPeriodId = current?.fiscalPeriodId ?? appState.currentFiscalPeriodId;
|
|
170
|
+
if (fiscalPeriodId == null)
|
|
114
171
|
return false;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
172
|
+
const accountId = resolveFixedAssetAccountId(current == null
|
|
173
|
+
? null
|
|
174
|
+
: {
|
|
175
|
+
accountId: current.bookAccountId,
|
|
176
|
+
accountName: current.accountName,
|
|
177
|
+
}, draft.account, bookAccounts);
|
|
178
|
+
if (accountId == null) {
|
|
118
179
|
throw new AppError({
|
|
119
180
|
messageForDeveloper: "assist.updateFixedAsset: accountId missing",
|
|
120
181
|
messageForUser: "勘定科目が解決できないため保存できませんでした",
|
|
121
182
|
originalMessage: null,
|
|
122
183
|
statusCode: null,
|
|
184
|
+
code: null,
|
|
123
185
|
});
|
|
124
186
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
187
|
+
return await assetMutationQueue.current.run(assetId, async () => {
|
|
188
|
+
appState.assertAuthOperationCurrent(authOperationVersion);
|
|
189
|
+
periodVersions.current.invalidate(fiscalPeriodId);
|
|
190
|
+
try {
|
|
191
|
+
const patched = await backendApi.fixedAssets.patch(fiscalPeriodId, assetId, fixedAssetDraftToPatch(draft, accountId));
|
|
192
|
+
appState.assertAuthOperationCurrent(authOperationVersion);
|
|
193
|
+
if (selectedFiscalPeriodId.current === fiscalPeriodId) {
|
|
194
|
+
const mapped = mapFixedAsset(patched, bookAccountNameById[patched.bookAccountId] ?? null, fixedAssetPreviewAsOf, currentFiscalPeriodEndDate ?? null);
|
|
195
|
+
setFixedAssets((currentList) => upsertFixedAsset(currentList, mapped));
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
periodVersions.current.invalidate(fiscalPeriodId);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
132
203
|
},
|
|
133
204
|
listOpeningCarryovers(fiscalPeriodId) {
|
|
134
205
|
const period = appState.fiscalPeriods.find((p) => p.id === fiscalPeriodId);
|
|
135
|
-
const journals = period?.opening
|
|
206
|
+
const journals = period?.opening.journals ?? [];
|
|
136
207
|
return journals
|
|
137
|
-
.map((journal) => mapOpeningJournalToRecord(journal, fiscalPeriodId, bookAccountNameById, bookAccountTypeById))
|
|
208
|
+
.map((journal) => mapOpeningJournalToRecord(journal, fiscalPeriodId, bookAccountNameById, bookAccountTypeById, taxCategoryNameById, businessCategoryNameById))
|
|
138
209
|
.sort((left, right) => {
|
|
139
210
|
const byDate = left.date.localeCompare(right.date);
|
|
140
211
|
if (byDate !== 0)
|
|
@@ -144,164 +215,133 @@ export function OpenkkAssistProvider(props) {
|
|
|
144
215
|
},
|
|
145
216
|
getOpeningCarryover(carryoverId) {
|
|
146
217
|
const fiscalPeriodId = appState.currentFiscalPeriodId;
|
|
147
|
-
if (fiscalPeriodId == null
|
|
218
|
+
if (fiscalPeriodId == null)
|
|
148
219
|
return null;
|
|
149
220
|
const period = appState.fiscalPeriods.find((p) => p.id === fiscalPeriodId);
|
|
150
|
-
const journals = period?.opening
|
|
221
|
+
const journals = period?.opening.journals ?? [];
|
|
151
222
|
const journal = journals.find((item) => item.id === carryoverId);
|
|
152
223
|
if (journal == null)
|
|
153
224
|
return null;
|
|
154
|
-
return mapOpeningJournalToRecord(journal, fiscalPeriodId, bookAccountNameById, bookAccountTypeById);
|
|
225
|
+
return mapOpeningJournalToRecord(journal, fiscalPeriodId, bookAccountNameById, bookAccountTypeById, taxCategoryNameById, businessCategoryNameById);
|
|
155
226
|
},
|
|
156
|
-
async addOpeningCarryover(fiscalPeriodId) {
|
|
227
|
+
async addOpeningCarryover(fiscalPeriodId, draft) {
|
|
228
|
+
assertEditingUnlocked(config.editingPolicy, "assist.addOpeningCarryover");
|
|
157
229
|
const period = appState.fiscalPeriods.find((p) => p.id === fiscalPeriodId);
|
|
158
|
-
|
|
159
|
-
if (period == null || opening == null)
|
|
230
|
+
if (period == null)
|
|
160
231
|
return null;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
messageForUser: "勘定科目が取得できないため再振替仕訳を作成できませんでした",
|
|
171
|
-
originalMessage: null,
|
|
172
|
-
statusCode: null,
|
|
232
|
+
let nextId = null;
|
|
233
|
+
const updated = await appState.updateFiscalPeriod(fiscalPeriodId, (currentPeriod) => {
|
|
234
|
+
const currentOpening = currentPeriod.opening;
|
|
235
|
+
const generatedId = nextOpeningCarryoverId(fiscalPeriodId, currentOpening.journals);
|
|
236
|
+
nextId = generatedId;
|
|
237
|
+
const lines = buildOpeningJournalLines(generatedId, draft, {
|
|
238
|
+
accounts: bookAccounts,
|
|
239
|
+
taxCategories,
|
|
240
|
+
businessCategories,
|
|
173
241
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
id: `${nextId}-c`,
|
|
193
|
-
side: "credit",
|
|
194
|
-
bookAccountId: creditAccountId,
|
|
195
|
-
amount: 0,
|
|
196
|
-
partnerName: "",
|
|
197
|
-
taxCategoryId: "",
|
|
198
|
-
businessCategoryId: "",
|
|
242
|
+
if (lines == null) {
|
|
243
|
+
throw openingCarryoverAccountResolutionError("assist.addOpeningCarryover");
|
|
244
|
+
}
|
|
245
|
+
const newJournal = {
|
|
246
|
+
id: generatedId,
|
|
247
|
+
date: draft.date,
|
|
248
|
+
description: draft.description,
|
|
249
|
+
businessRate: draftBusinessRate(draft),
|
|
250
|
+
lines,
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
opening: {
|
|
254
|
+
balanceLines: currentOpening.balanceLines,
|
|
255
|
+
journals: [
|
|
256
|
+
...currentOpening.journals,
|
|
257
|
+
newJournal,
|
|
258
|
+
],
|
|
199
259
|
},
|
|
200
|
-
|
|
201
|
-
};
|
|
202
|
-
const updatedJournals = [
|
|
203
|
-
...(opening.openingJournals ?? []),
|
|
204
|
-
newJournal,
|
|
205
|
-
];
|
|
206
|
-
await appState.updateFiscalPeriod(fiscalPeriodId, {
|
|
207
|
-
opening: {
|
|
208
|
-
...opening,
|
|
209
|
-
openingJournals: updatedJournals,
|
|
210
|
-
},
|
|
260
|
+
};
|
|
211
261
|
});
|
|
212
|
-
return nextId;
|
|
262
|
+
return updated ? nextId : null;
|
|
213
263
|
},
|
|
214
264
|
async updateOpeningCarryover(carryoverId, draft) {
|
|
265
|
+
assertEditingUnlocked(config.editingPolicy, "assist.updateOpeningCarryover");
|
|
215
266
|
const fiscalPeriodId = appState.currentFiscalPeriodId;
|
|
216
|
-
if (fiscalPeriodId == null
|
|
267
|
+
if (fiscalPeriodId == null)
|
|
217
268
|
return false;
|
|
218
269
|
const period = appState.fiscalPeriods.find((p) => p.id === fiscalPeriodId);
|
|
219
|
-
|
|
220
|
-
if (period == null || opening == null)
|
|
270
|
+
if (period == null)
|
|
221
271
|
return false;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
272
|
+
return await appState.updateFiscalPeriod(fiscalPeriodId, (currentPeriod) => {
|
|
273
|
+
const currentOpening = currentPeriod.opening;
|
|
274
|
+
const journals = currentOpening.journals;
|
|
275
|
+
const target = journals.find((journal) => journal.id === carryoverId);
|
|
276
|
+
if (target == null)
|
|
277
|
+
return null;
|
|
278
|
+
const lines = buildOpeningJournalLines(target.id, draft, {
|
|
279
|
+
accounts: bookAccounts,
|
|
280
|
+
taxCategories,
|
|
281
|
+
businessCategories,
|
|
230
282
|
});
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return journal;
|
|
237
|
-
updated = true;
|
|
238
|
-
return {
|
|
239
|
-
...journal,
|
|
283
|
+
if (lines == null) {
|
|
284
|
+
throw openingCarryoverAccountResolutionError("assist.updateOpeningCarryover");
|
|
285
|
+
}
|
|
286
|
+
const nextJournal = {
|
|
287
|
+
...target,
|
|
240
288
|
date: draft.date,
|
|
241
289
|
description: draft.description,
|
|
242
|
-
businessRate:
|
|
243
|
-
lines
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
partnerName: draft.partner,
|
|
251
|
-
taxCategoryId: draft.taxCategory,
|
|
252
|
-
businessCategoryId: draft.businessCategory,
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
id: journal.lines.find((line) => line.side === "credit")?.id ??
|
|
256
|
-
`${journal.id}-c`,
|
|
257
|
-
side: "credit",
|
|
258
|
-
bookAccountId: creditAccountId,
|
|
259
|
-
amount: parseAmount(draft.creditAmount),
|
|
260
|
-
partnerName: draft.partner,
|
|
261
|
-
taxCategoryId: draft.taxCategory,
|
|
262
|
-
businessCategoryId: draft.businessCategory,
|
|
263
|
-
},
|
|
264
|
-
],
|
|
290
|
+
businessRate: draftBusinessRate(draft),
|
|
291
|
+
lines,
|
|
292
|
+
};
|
|
293
|
+
return {
|
|
294
|
+
opening: {
|
|
295
|
+
balanceLines: currentOpening.balanceLines,
|
|
296
|
+
journals: journals.map((journal) => journal.id === carryoverId ? nextJournal : journal),
|
|
297
|
+
},
|
|
265
298
|
};
|
|
266
299
|
});
|
|
267
|
-
if (!updated)
|
|
268
|
-
return false;
|
|
269
|
-
await appState.updateFiscalPeriod(fiscalPeriodId, {
|
|
270
|
-
opening: {
|
|
271
|
-
...opening,
|
|
272
|
-
openingJournals: nextJournals,
|
|
273
|
-
},
|
|
274
|
-
});
|
|
275
|
-
return true;
|
|
276
300
|
},
|
|
277
301
|
async deleteFixedAsset(assetId) {
|
|
302
|
+
assertEditingUnlocked(config.editingPolicy, "assist.deleteFixedAsset");
|
|
303
|
+
const authOperationVersion = appState.captureAuthOperationVersion();
|
|
278
304
|
const current = fixedAssets.find((asset) => asset.id === assetId) ?? null;
|
|
279
|
-
const fiscalPeriodId = current?.fiscalPeriodId ?? appState.currentFiscalPeriodId
|
|
280
|
-
if (fiscalPeriodId
|
|
305
|
+
const fiscalPeriodId = current?.fiscalPeriodId ?? appState.currentFiscalPeriodId;
|
|
306
|
+
if (fiscalPeriodId == null)
|
|
281
307
|
return false;
|
|
282
|
-
await
|
|
283
|
-
|
|
284
|
-
|
|
308
|
+
return await assetMutationQueue.current.run(assetId, async () => {
|
|
309
|
+
appState.assertAuthOperationCurrent(authOperationVersion);
|
|
310
|
+
periodVersions.current.invalidate(fiscalPeriodId);
|
|
311
|
+
try {
|
|
312
|
+
await backendApi.fixedAssets.remove(fiscalPeriodId, assetId);
|
|
313
|
+
appState.assertAuthOperationCurrent(authOperationVersion);
|
|
314
|
+
if (selectedFiscalPeriodId.current === fiscalPeriodId) {
|
|
315
|
+
setFixedAssets((currentList) => currentList.filter((asset) => asset.id !== assetId));
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
periodVersions.current.invalidate(fiscalPeriodId);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
285
323
|
},
|
|
286
324
|
async deleteOpeningCarryover(carryoverId) {
|
|
325
|
+
assertEditingUnlocked(config.editingPolicy, "assist.deleteOpeningCarryover");
|
|
287
326
|
const fiscalPeriodId = appState.currentFiscalPeriodId;
|
|
288
|
-
if (fiscalPeriodId == null
|
|
327
|
+
if (fiscalPeriodId == null)
|
|
289
328
|
return false;
|
|
290
329
|
const period = appState.fiscalPeriods.find((p) => p.id === fiscalPeriodId);
|
|
291
|
-
|
|
292
|
-
if (period == null || opening == null)
|
|
293
|
-
return false;
|
|
294
|
-
const journals = opening.openingJournals ?? [];
|
|
295
|
-
const nextJournals = journals.filter((journal) => journal.id !== carryoverId);
|
|
296
|
-
if (nextJournals.length === journals.length)
|
|
330
|
+
if (period == null)
|
|
297
331
|
return false;
|
|
298
|
-
await appState.updateFiscalPeriod(fiscalPeriodId, {
|
|
299
|
-
opening
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
332
|
+
return await appState.updateFiscalPeriod(fiscalPeriodId, (currentPeriod) => {
|
|
333
|
+
const currentOpening = currentPeriod.opening;
|
|
334
|
+
const journals = currentOpening.journals;
|
|
335
|
+
const nextJournals = journals.filter((journal) => journal.id !== carryoverId);
|
|
336
|
+
if (nextJournals.length === journals.length)
|
|
337
|
+
return null;
|
|
338
|
+
return {
|
|
339
|
+
opening: {
|
|
340
|
+
balanceLines: currentOpening.balanceLines,
|
|
341
|
+
journals: nextJournals,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
303
344
|
});
|
|
304
|
-
return true;
|
|
305
345
|
},
|
|
306
346
|
loadError,
|
|
307
347
|
reload() {
|
|
@@ -311,140 +351,29 @@ export function OpenkkAssistProvider(props) {
|
|
|
311
351
|
}, [
|
|
312
352
|
appState.currentFiscalPeriodId,
|
|
313
353
|
appState.fiscalPeriods,
|
|
314
|
-
|
|
354
|
+
bookAccounts,
|
|
315
355
|
bookAccountNameById,
|
|
316
356
|
bookAccountTypeById,
|
|
317
|
-
|
|
357
|
+
businessCategories,
|
|
358
|
+
businessCategoryNameById,
|
|
359
|
+
currentFiscalPeriodEndDate,
|
|
360
|
+
fixedAssetPreviewAsOf,
|
|
318
361
|
fixedAssets,
|
|
319
|
-
|
|
362
|
+
fixedAssetsLoadError,
|
|
363
|
+
masterLoadError,
|
|
364
|
+
taxCategories,
|
|
365
|
+
taxCategoryNameById,
|
|
320
366
|
]);
|
|
321
367
|
return (_jsx(AssistContext.Provider, { value: value, children: props.children }));
|
|
322
368
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const maxSuffix = journals.reduce((max, journal) => {
|
|
331
|
-
if (!journal.id.startsWith(prefix))
|
|
332
|
-
return max;
|
|
333
|
-
const suffix = Number(journal.id.slice(prefix.length));
|
|
334
|
-
return Number.isInteger(suffix) && suffix > max ? suffix : max;
|
|
335
|
-
}, 0);
|
|
336
|
-
return `${prefix}${maxSuffix + 1}`;
|
|
337
|
-
}
|
|
338
|
-
function mapOpeningJournalToRecord(journal, fiscalPeriodId, accountNameById, accountTypeById) {
|
|
339
|
-
const debit = journal.lines.find((line) => line.side === "debit");
|
|
340
|
-
const credit = journal.lines.find((line) => line.side === "credit");
|
|
341
|
-
return {
|
|
342
|
-
id: journal.id,
|
|
343
|
-
fiscalPeriodId,
|
|
344
|
-
date: journal.date,
|
|
345
|
-
description: journal.description,
|
|
346
|
-
debit: accountNameById[debit?.bookAccountId ?? ""] ?? debit?.bookAccountId ?? "",
|
|
347
|
-
debitType: accountTypeById[debit?.bookAccountId ?? ""] ?? "asset",
|
|
348
|
-
debitAmount: formatAmount(debit?.amount ?? 0),
|
|
349
|
-
credit: accountNameById[credit?.bookAccountId ?? ""] ??
|
|
350
|
-
credit?.bookAccountId ??
|
|
351
|
-
"",
|
|
352
|
-
creditType: accountTypeById[credit?.bookAccountId ?? ""] ?? "revenue",
|
|
353
|
-
creditAmount: formatAmount(credit?.amount ?? 0),
|
|
354
|
-
partner: debit?.partnerName ?? credit?.partnerName ?? "",
|
|
355
|
-
taxCategory: debit?.taxCategoryId ?? credit?.taxCategoryId ?? "対象外",
|
|
356
|
-
businessCategory: debit?.businessCategoryId ?? credit?.businessCategoryId ?? "対象外",
|
|
357
|
-
businessRate: String(Math.round((journal.businessRate ?? 1) * 100)),
|
|
358
|
-
debitBookAccountId: debit?.bookAccountId,
|
|
359
|
-
creditBookAccountId: credit?.bookAccountId,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
function resolveBookAccountIdByName(name, accountIdByName) {
|
|
363
|
-
const id = accountIdByName[name];
|
|
364
|
-
return id == null || id.length === 0 ? null : id;
|
|
365
|
-
}
|
|
366
|
-
function formatAmount(value) {
|
|
367
|
-
return new Intl.NumberFormat("ja-JP").format(Math.abs(value));
|
|
368
|
-
}
|
|
369
|
-
function mapFixedAssetToPreview(asset, accountName, today) {
|
|
370
|
-
// 売却・廃棄・除却済みは処分日(無ければ今日)で償却を打ち切る。
|
|
371
|
-
const isClosed = asset.status !== "active";
|
|
372
|
-
const asOf = isClosed && asset.disposalDate
|
|
373
|
-
? (parseIsoLocalDate(asset.disposalDate) ?? today)
|
|
374
|
-
: today;
|
|
375
|
-
const depreciation = computeStraightLineDepreciation({
|
|
376
|
-
acquisitionDate: asset.acquisitionDate,
|
|
377
|
-
acquisitionCost: asset.acquisitionCost,
|
|
378
|
-
usefulLife: asset.usefulLife,
|
|
379
|
-
asOf,
|
|
369
|
+
function openingCarryoverAccountResolutionError(operation) {
|
|
370
|
+
return new AppError({
|
|
371
|
+
messageForDeveloper: `${operation}: account resolution failed`,
|
|
372
|
+
messageForUser: "勘定科目が解決できないため保存できませんでした",
|
|
373
|
+
originalMessage: null,
|
|
374
|
+
statusCode: null,
|
|
375
|
+
code: null,
|
|
380
376
|
});
|
|
381
|
-
return {
|
|
382
|
-
id: asset.id,
|
|
383
|
-
fiscalPeriodId: asset.fiscalPeriodId,
|
|
384
|
-
name: asset.name,
|
|
385
|
-
account: accountName ?? asset.bookAccountId,
|
|
386
|
-
accountId: asset.bookAccountId,
|
|
387
|
-
period: depreciation.periodLabel,
|
|
388
|
-
remaining: depreciation.remainingLabel,
|
|
389
|
-
progress: depreciation.progress,
|
|
390
|
-
current: formatYen(depreciation.currentBookValue),
|
|
391
|
-
purchase: formatYen(asset.acquisitionCost),
|
|
392
|
-
status: mapFixedAssetStatusLabel(asset.status),
|
|
393
|
-
depreciationAmount: formatYen(depreciation.annualDepreciation),
|
|
394
|
-
acquisitionDate: asset.acquisitionDate,
|
|
395
|
-
acquisitionCost: asset.acquisitionCost,
|
|
396
|
-
usefulLife: asset.usefulLife,
|
|
397
|
-
businessRate: asset.businessRate,
|
|
398
|
-
disposalDate: asset.disposalDate || undefined,
|
|
399
|
-
disposalPrice: asset.disposalPrice
|
|
400
|
-
? formatYen(asset.disposalPrice)
|
|
401
|
-
: undefined,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
function formatYen(value) {
|
|
405
|
-
return new Intl.NumberFormat("ja-JP").format(value);
|
|
406
|
-
}
|
|
407
|
-
function mapFixedAssetStatusLabel(status) {
|
|
408
|
-
if (status === "active")
|
|
409
|
-
return "償却中";
|
|
410
|
-
if (status === "sold")
|
|
411
|
-
return "売却済";
|
|
412
|
-
if (status === "disposed")
|
|
413
|
-
return "廃棄済";
|
|
414
|
-
if (status === "retired")
|
|
415
|
-
return "完了";
|
|
416
|
-
return status;
|
|
417
|
-
}
|
|
418
|
-
function mapFixedAssetStatusApi(statusLabel) {
|
|
419
|
-
if (statusLabel === "償却中")
|
|
420
|
-
return "active";
|
|
421
|
-
if (statusLabel === "売却済")
|
|
422
|
-
return "sold";
|
|
423
|
-
if (statusLabel === "廃棄済")
|
|
424
|
-
return "disposed";
|
|
425
|
-
if (statusLabel === "完了")
|
|
426
|
-
return "retired";
|
|
427
|
-
return "active";
|
|
428
|
-
}
|
|
429
|
-
export function fixedAssetDraftToPatch(draft, bookAccountId) {
|
|
430
|
-
return {
|
|
431
|
-
name: draft.name,
|
|
432
|
-
acquisitionDate: draft.acquisitionDate,
|
|
433
|
-
acquisitionCost: parseAmount(draft.acquisitionCost),
|
|
434
|
-
usefulLife: Math.max(1, Math.round(draft.usefulLife) || 1),
|
|
435
|
-
businessRate: Math.max(0, Math.min(100, draft.businessRatePercent)) / 100,
|
|
436
|
-
status: mapFixedAssetStatusApi(draft.status),
|
|
437
|
-
disposalDate: isFixedAssetClosedStatus(draft.status)
|
|
438
|
-
? (draft.disposalDate ?? "")
|
|
439
|
-
: "",
|
|
440
|
-
disposalPrice: draft.status === "売却済" ? parseAmount(draft.disposalPrice ?? "0") : 0,
|
|
441
|
-
bookAccountId,
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
function isFixedAssetClosedStatus(statusLabel) {
|
|
445
|
-
return (statusLabel === "売却済" ||
|
|
446
|
-
statusLabel === "廃棄済" ||
|
|
447
|
-
statusLabel === "完了");
|
|
448
377
|
}
|
|
449
378
|
export function useOpenkkAssist() {
|
|
450
379
|
const value = useContext(AssistContext);
|
|
@@ -454,6 +383,7 @@ export function useOpenkkAssist() {
|
|
|
454
383
|
messageForUser: "補助データを読み込めませんでした",
|
|
455
384
|
originalMessage: null,
|
|
456
385
|
statusCode: null,
|
|
386
|
+
code: null,
|
|
457
387
|
});
|
|
458
388
|
}
|
|
459
389
|
return value;
|