@accounter/client 0.0.7-alpha-20250821153812-03cbf41f6d049dc4fec6c6e7c47a0f17144b7d6a → 0.0.7-alpha-20250821154917-56a2f7e8af3ffb2e1a9a98c07778af240de41d98
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/CHANGELOG.md +7 -1
- package/dist/assets/{index-C3Jzs8px.js → index-B06NFENr.js} +370 -330
- package/dist/assets/index-DBmbnNAy.css +1 -0
- package/dist/assets/{index.es-CP2tkYw-.js → index.es-DtQCKFFT.js} +6 -6
- package/dist/index.html +2 -2
- package/package.json +2 -1
- package/src/app.tsx +4 -0
- package/src/components/layout/sidelinks.tsx +15 -0
- package/src/components/screens/operations/annual-audit/annual-audit-filters.tsx +158 -0
- package/src/components/screens/operations/annual-audit/index.tsx +370 -0
- package/src/components/screens/operations/annual-audit/step-01-validate-charges/index.tsx +266 -0
- package/src/components/screens/operations/annual-audit/step-02-ledger-changes/index.tsx +121 -0
- package/src/components/screens/operations/annual-audit/step-03-opening-balance/index.tsx +171 -0
- package/src/components/screens/operations/annual-audit/step-base.tsx +149 -0
- package/src/components/screens/operations/annual-audit/step-simple.tsx +43 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/gql/gql.ts +18 -0
- package/src/gql/graphql.ts +61 -0
- package/src/hooks/use-get-admin-businesses.ts +57 -0
- package/dist/assets/index-BERPxH5K.css +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { AlertCircle, ChevronDown, ChevronRight, Eye } from 'lucide-react';
|
|
3
|
+
import { useQuery } from 'urql';
|
|
4
|
+
import {
|
|
5
|
+
AccountantApprovalStatusDocument,
|
|
6
|
+
AccountantStatus,
|
|
7
|
+
ChargeSortByField,
|
|
8
|
+
} from '../../../../../gql/graphql.js';
|
|
9
|
+
import type { TimelessDateString } from '../../../../../helpers/dates.js';
|
|
10
|
+
import { Badge } from '../../../../ui/badge.jsx';
|
|
11
|
+
import { Button } from '../../../../ui/button.jsx';
|
|
12
|
+
import { CardContent } from '../../../../ui/card.jsx';
|
|
13
|
+
import { Collapsible, CollapsibleContent } from '../../../../ui/collapsible.js';
|
|
14
|
+
import { getAllChargesHref } from '../../../charges/all-charges.jsx';
|
|
15
|
+
import {
|
|
16
|
+
BaseStepCard,
|
|
17
|
+
type BaseStepProps,
|
|
18
|
+
type StepAction,
|
|
19
|
+
type StepStatus,
|
|
20
|
+
} from '../step-base.jsx';
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
23
|
+
/* GraphQL */ `
|
|
24
|
+
query AccountantApprovalStatus($fromDate: TimelessDate!, $toDate: TimelessDate!) {
|
|
25
|
+
accountantApprovalStatus(from: $fromDate, to: $toDate) {
|
|
26
|
+
totalCharges
|
|
27
|
+
approvedCount
|
|
28
|
+
pendingCount
|
|
29
|
+
unapprovedCount
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
interface ChargeValidationData {
|
|
35
|
+
approvedPercentage: number;
|
|
36
|
+
pendingPercentage: number;
|
|
37
|
+
unapprovedPercentage: number;
|
|
38
|
+
totalCharges: number;
|
|
39
|
+
approvedCount: number;
|
|
40
|
+
pendingCount: number;
|
|
41
|
+
unapprovedCount: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface Step01Props extends BaseStepProps {
|
|
45
|
+
year: number;
|
|
46
|
+
adminBusinessId?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Step01ValidateCharges(props: Step01Props) {
|
|
50
|
+
const [status, setStatus] = useState<StepStatus>('loading');
|
|
51
|
+
const [chargeData, setChargeData] = useState<ChargeValidationData>({
|
|
52
|
+
approvedPercentage: 0,
|
|
53
|
+
pendingPercentage: 0,
|
|
54
|
+
unapprovedPercentage: 100,
|
|
55
|
+
totalCharges: 1,
|
|
56
|
+
approvedCount: 0,
|
|
57
|
+
pendingCount: 0,
|
|
58
|
+
unapprovedCount: 1,
|
|
59
|
+
});
|
|
60
|
+
const [isDetailsExpanded, setIsDetailsExpanded] = useState(false);
|
|
61
|
+
const [hasReportedCompletion, setHasReportedCompletion] = useState(false);
|
|
62
|
+
|
|
63
|
+
// Report status changes to parent
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (props.onStatusChange) {
|
|
66
|
+
props.onStatusChange(props.id, status);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Track if we've reported completion to avoid double counting
|
|
70
|
+
if (status === 'completed' && !hasReportedCompletion) {
|
|
71
|
+
setHasReportedCompletion(true);
|
|
72
|
+
} else if (status !== 'completed' && hasReportedCompletion) {
|
|
73
|
+
setHasReportedCompletion(false);
|
|
74
|
+
}
|
|
75
|
+
}, [status, props.onStatusChange, props.id, hasReportedCompletion]);
|
|
76
|
+
|
|
77
|
+
const [{ data, fetching }, fetchStatus] = useQuery({
|
|
78
|
+
query: AccountantApprovalStatusDocument,
|
|
79
|
+
variables: {
|
|
80
|
+
fromDate: `${props.year}-01-01` as TimelessDateString,
|
|
81
|
+
toDate: `${props.year}-12-31` as TimelessDateString,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!props.adminBusinessId) {
|
|
87
|
+
setStatus('blocked');
|
|
88
|
+
} else if (fetching) {
|
|
89
|
+
setStatus('loading');
|
|
90
|
+
}
|
|
91
|
+
}, [props.adminBusinessId, fetching]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (data?.accountantApprovalStatus) {
|
|
95
|
+
const { totalCharges, approvedCount, pendingCount, unapprovedCount } =
|
|
96
|
+
data.accountantApprovalStatus;
|
|
97
|
+
const accountantApprovalStatus: ChargeValidationData = {
|
|
98
|
+
approvedPercentage: (approvedCount / totalCharges) * 100 || 0,
|
|
99
|
+
pendingPercentage: (pendingCount / totalCharges) * 100 || 0,
|
|
100
|
+
unapprovedPercentage: (unapprovedCount / totalCharges) * 100 || 0,
|
|
101
|
+
totalCharges,
|
|
102
|
+
approvedCount,
|
|
103
|
+
pendingCount,
|
|
104
|
+
unapprovedCount,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
setChargeData(accountantApprovalStatus);
|
|
108
|
+
|
|
109
|
+
// Determine status based on data
|
|
110
|
+
if (
|
|
111
|
+
accountantApprovalStatus.pendingPercentage === 0 &&
|
|
112
|
+
accountantApprovalStatus.unapprovedPercentage === 0
|
|
113
|
+
) {
|
|
114
|
+
setStatus('completed');
|
|
115
|
+
} else if (
|
|
116
|
+
accountantApprovalStatus.pendingPercentage + accountantApprovalStatus.unapprovedPercentage <
|
|
117
|
+
30
|
|
118
|
+
) {
|
|
119
|
+
setStatus('in-progress');
|
|
120
|
+
} else {
|
|
121
|
+
setStatus('pending');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [data]);
|
|
125
|
+
|
|
126
|
+
const href = useMemo(() => {
|
|
127
|
+
return getAllChargesHref({
|
|
128
|
+
byOwners: props.adminBusinessId ? [props.adminBusinessId] : undefined,
|
|
129
|
+
fromAnyDate: `${props.year}-01-01` as TimelessDateString,
|
|
130
|
+
toAnyDate: `${props.year}-12-31` as TimelessDateString,
|
|
131
|
+
accountantStatus: [AccountantStatus.Pending, AccountantStatus.Unapproved],
|
|
132
|
+
sortBy: {
|
|
133
|
+
field: ChargeSortByField.Date,
|
|
134
|
+
asc: false,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}, [props.adminBusinessId, props.year]);
|
|
138
|
+
|
|
139
|
+
const actions: StepAction[] = [{ label: 'Review Charges', href }];
|
|
140
|
+
|
|
141
|
+
const refreshData = async () => {
|
|
142
|
+
await fetchStatus();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<BaseStepCard {...props} status={status} icon={<Eye className="h-4 w-4" />} actions={actions}>
|
|
147
|
+
<Collapsible open={isDetailsExpanded}>
|
|
148
|
+
<CardContent className="pt-0 border-t">
|
|
149
|
+
<Button
|
|
150
|
+
variant="ghost"
|
|
151
|
+
size="sm"
|
|
152
|
+
onClick={() => setIsDetailsExpanded(!isDetailsExpanded)}
|
|
153
|
+
className="w-full justify-between p-2 h-auto"
|
|
154
|
+
>
|
|
155
|
+
<div className="flex items-center gap-2">
|
|
156
|
+
<span className="text-sm font-medium">Charge Validation Details</span>
|
|
157
|
+
<Badge variant="outline" className="text-xs">
|
|
158
|
+
{chargeData.pendingCount + chargeData.unapprovedCount} need attention
|
|
159
|
+
</Badge>
|
|
160
|
+
</div>
|
|
161
|
+
{isDetailsExpanded ? (
|
|
162
|
+
<ChevronDown className="h-4 w-4" />
|
|
163
|
+
) : (
|
|
164
|
+
<ChevronRight className="h-4 w-4" />
|
|
165
|
+
)}
|
|
166
|
+
</Button>
|
|
167
|
+
</CardContent>
|
|
168
|
+
<CollapsibleContent>
|
|
169
|
+
<CardContent className="pt-0">
|
|
170
|
+
<div className="space-y-4">
|
|
171
|
+
<div className="space-y-3">
|
|
172
|
+
<div className="flex justify-between items-center text-sm">
|
|
173
|
+
<span className="font-medium">Charge Review Progress</span>
|
|
174
|
+
<div className="flex items-center gap-2">
|
|
175
|
+
<span className="text-muted-foreground">
|
|
176
|
+
{chargeData.totalCharges.toLocaleString()} total charges
|
|
177
|
+
</span>
|
|
178
|
+
<Button
|
|
179
|
+
variant="ghost"
|
|
180
|
+
size="sm"
|
|
181
|
+
onClick={refreshData}
|
|
182
|
+
disabled={status === 'loading'}
|
|
183
|
+
>
|
|
184
|
+
Refresh
|
|
185
|
+
</Button>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Combined Progress Bar */}
|
|
190
|
+
<div className="relative h-6 bg-gray-200 rounded-full overflow-hidden">
|
|
191
|
+
<div
|
|
192
|
+
className="absolute left-0 top-0 h-full bg-green-500 transition-all duration-300"
|
|
193
|
+
style={{ width: `${chargeData.approvedPercentage}%` }}
|
|
194
|
+
/>
|
|
195
|
+
<div
|
|
196
|
+
className="absolute top-0 h-full bg-orange-500 transition-all duration-300"
|
|
197
|
+
style={{
|
|
198
|
+
left: `${chargeData.approvedPercentage}%`,
|
|
199
|
+
width: `${chargeData.pendingPercentage}%`,
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
<div
|
|
203
|
+
className="absolute top-0 h-full bg-red-500 transition-all duration-300"
|
|
204
|
+
style={{
|
|
205
|
+
left: `${chargeData.approvedPercentage + chargeData.pendingPercentage}%`,
|
|
206
|
+
width: `${chargeData.unapprovedPercentage}%`,
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
{/* Legend */}
|
|
212
|
+
<div className="grid grid-cols-3 gap-4 text-sm">
|
|
213
|
+
<div className="flex items-center gap-2">
|
|
214
|
+
<div className="w-3 h-3 bg-green-500 rounded-full" />
|
|
215
|
+
<div>
|
|
216
|
+
<div className="font-medium text-green-700">
|
|
217
|
+
Approved ({chargeData.approvedPercentage}%)
|
|
218
|
+
</div>
|
|
219
|
+
<div className="text-xs text-muted-foreground">
|
|
220
|
+
{chargeData.approvedCount.toLocaleString()} charges
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div className="flex items-center gap-2">
|
|
226
|
+
<div className="w-3 h-3 bg-orange-500 rounded-full" />
|
|
227
|
+
<div>
|
|
228
|
+
<div className="font-medium text-orange-700">
|
|
229
|
+
Pending ({chargeData.pendingPercentage}%)
|
|
230
|
+
</div>
|
|
231
|
+
<div className="text-xs text-muted-foreground">
|
|
232
|
+
{chargeData.pendingCount.toLocaleString()} charges
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div className="flex items-center gap-2">
|
|
238
|
+
<div className="w-3 h-3 bg-red-500 rounded-full" />
|
|
239
|
+
<div>
|
|
240
|
+
<div className="font-medium text-red-700">
|
|
241
|
+
Unapproved ({chargeData.unapprovedPercentage}%)
|
|
242
|
+
</div>
|
|
243
|
+
<div className="text-xs text-muted-foreground">
|
|
244
|
+
{chargeData.unapprovedCount.toLocaleString()} charges
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{(chargeData.pendingPercentage > 0 || chargeData.unapprovedPercentage > 0) && (
|
|
252
|
+
<div className="flex items-center gap-2 p-3 bg-orange-50 rounded-lg border border-orange-200">
|
|
253
|
+
<AlertCircle className="h-4 w-4 text-orange-600" />
|
|
254
|
+
<span className="text-sm text-orange-800">
|
|
255
|
+
{chargeData.pendingCount + chargeData.unapprovedCount} charges need review
|
|
256
|
+
before proceeding
|
|
257
|
+
</span>
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
</CardContent>
|
|
262
|
+
</CollapsibleContent>
|
|
263
|
+
</Collapsible>
|
|
264
|
+
</BaseStepCard>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { AlertTriangle, Settings } from 'lucide-react';
|
|
3
|
+
import { useQuery } from 'urql';
|
|
4
|
+
import { ChargeSortByField, LedgerValidationStatusDocument } from '../../../../../gql/graphql.js';
|
|
5
|
+
import type { TimelessDateString } from '../../../../../helpers/index.js';
|
|
6
|
+
import { Badge } from '../../../../ui/badge.jsx';
|
|
7
|
+
import { CardContent } from '../../../../ui/card.jsx';
|
|
8
|
+
import { getAllChargesHref } from '../../../charges/all-charges.jsx';
|
|
9
|
+
import {
|
|
10
|
+
BaseStepCard,
|
|
11
|
+
type BaseStepProps,
|
|
12
|
+
type StepAction,
|
|
13
|
+
type StepStatus,
|
|
14
|
+
} from '../step-base.jsx';
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
17
|
+
/* GraphQL */ `
|
|
18
|
+
query LedgerValidationStatus($limit: Int, $filters: ChargeFilter) {
|
|
19
|
+
chargesWithLedgerChanges(limit: $limit, filters: $filters) {
|
|
20
|
+
charge {
|
|
21
|
+
id
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
interface Step02Props extends BaseStepProps {
|
|
28
|
+
year: number;
|
|
29
|
+
adminBusinessId?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Step02LedgerChanges(props: Step02Props) {
|
|
33
|
+
const [status, setStatus] = useState<StepStatus>('blocked');
|
|
34
|
+
const [pendingChanges, setPendingChanges] = useState<number>(Infinity);
|
|
35
|
+
|
|
36
|
+
const [{ data, fetching }, fetchStatus] = useQuery({
|
|
37
|
+
query: LedgerValidationStatusDocument,
|
|
38
|
+
variables: {
|
|
39
|
+
filters: {
|
|
40
|
+
byOwners: props.adminBusinessId ? [props.adminBusinessId] : [],
|
|
41
|
+
fromAnyDate: `${props.year}-01-01` as TimelessDateString,
|
|
42
|
+
toAnyDate: `${props.year}-12-31` as TimelessDateString,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
pause: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!data && !fetching && props.adminBusinessId) {
|
|
50
|
+
fetchStatus();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (fetching) setStatus('loading');
|
|
56
|
+
}, [fetching]);
|
|
57
|
+
|
|
58
|
+
// Report status changes to parent
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (props.onStatusChange) {
|
|
61
|
+
props.onStatusChange(props.id, status);
|
|
62
|
+
}
|
|
63
|
+
}, [status, props.onStatusChange, props.id]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (data?.chargesWithLedgerChanges) {
|
|
67
|
+
const pendingChanges = data.chargesWithLedgerChanges.filter(
|
|
68
|
+
charge => !!charge.charge?.id,
|
|
69
|
+
).length;
|
|
70
|
+
setPendingChanges(pendingChanges);
|
|
71
|
+
|
|
72
|
+
if (pendingChanges === 0) {
|
|
73
|
+
setStatus('completed');
|
|
74
|
+
} else {
|
|
75
|
+
setStatus('in-progress');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, [data]);
|
|
79
|
+
|
|
80
|
+
const href = useMemo(() => {
|
|
81
|
+
return getAllChargesHref({
|
|
82
|
+
byOwners: props.adminBusinessId ? [props.adminBusinessId] : undefined,
|
|
83
|
+
fromAnyDate: `${props.year}-01-01` as TimelessDateString,
|
|
84
|
+
toAnyDate: `${props.year}-12-31` as TimelessDateString,
|
|
85
|
+
sortBy: {
|
|
86
|
+
field: ChargeSortByField.Date,
|
|
87
|
+
asc: false,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}, [props.adminBusinessId, props.year]);
|
|
91
|
+
|
|
92
|
+
const actions: StepAction[] = [{ label: 'View Ledger Status', href }];
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<BaseStepCard
|
|
96
|
+
{...props}
|
|
97
|
+
status={status}
|
|
98
|
+
icon={<Settings className="h-4 w-4" />}
|
|
99
|
+
actions={actions}
|
|
100
|
+
>
|
|
101
|
+
{pendingChanges > 0 && (
|
|
102
|
+
<CardContent className="pt-0 border-t">
|
|
103
|
+
<div className="flex items-center gap-2 p-3 bg-red-50 rounded-lg border border-red-200">
|
|
104
|
+
<AlertTriangle className="h-4 w-4 text-red-600" />
|
|
105
|
+
<div className="flex-1">
|
|
106
|
+
<span className="text-sm text-red-800 font-medium">
|
|
107
|
+
{pendingChanges} pending ledger changes detected
|
|
108
|
+
</span>
|
|
109
|
+
{/* <div className="text-xs text-red-600 mt-1">
|
|
110
|
+
Last updated: {new Date(ledgerStatus.lastUpdate).toLocaleString()}
|
|
111
|
+
</div> */}
|
|
112
|
+
</div>
|
|
113
|
+
<Badge variant="destructive" className="text-xs">
|
|
114
|
+
Action Required
|
|
115
|
+
</Badge>
|
|
116
|
+
</div>
|
|
117
|
+
</CardContent>
|
|
118
|
+
)}
|
|
119
|
+
</BaseStepCard>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Calculator } from 'lucide-react';
|
|
5
|
+
import type { TimelessDateString } from '../../../../../helpers/dates.js';
|
|
6
|
+
import { BalanceChargeModal } from '../../../../common/modals/balance-charge-modal.jsx';
|
|
7
|
+
import { getContoReportHref } from '../../../../reports/conto/index.jsx';
|
|
8
|
+
import { getTrialBalanceReportHref } from '../../../../reports/trial-balance-report/index.jsx';
|
|
9
|
+
import { Collapsible, CollapsibleContent } from '../../../../ui/collapsible.js';
|
|
10
|
+
import { BaseStepCard, type BaseStepProps, type StepStatus } from '../step-base.jsx';
|
|
11
|
+
|
|
12
|
+
interface UserType {
|
|
13
|
+
type: 'new' | 'migrating' | 'continuing';
|
|
14
|
+
balanceStatus?: 'verified' | 'pending' | 'missing';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Step03Props extends BaseStepProps {
|
|
18
|
+
year: number;
|
|
19
|
+
adminBusinessId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Sub-step components
|
|
23
|
+
function SubStep3A({
|
|
24
|
+
level,
|
|
25
|
+
year,
|
|
26
|
+
adminBusinessId,
|
|
27
|
+
disabled,
|
|
28
|
+
}: {
|
|
29
|
+
level: number;
|
|
30
|
+
year: number;
|
|
31
|
+
adminBusinessId: string;
|
|
32
|
+
disabled: boolean;
|
|
33
|
+
}) {
|
|
34
|
+
const contoHref = getContoReportHref({
|
|
35
|
+
fromDate: `${year - 1}-01-01` as TimelessDateString,
|
|
36
|
+
toDate: `${year - 1}-12-31` as TimelessDateString,
|
|
37
|
+
ownerIds: [adminBusinessId],
|
|
38
|
+
});
|
|
39
|
+
const [balanceChargeModalOpen, setBalanceChargeModalOpen] = useState(false);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<BaseStepCard
|
|
44
|
+
id="3a"
|
|
45
|
+
title="Migrating Users"
|
|
46
|
+
description="Create balance charge and dynamic report"
|
|
47
|
+
status="pending"
|
|
48
|
+
level={level}
|
|
49
|
+
actions={[
|
|
50
|
+
{ label: 'Create Balance Charge', onClick: () => setBalanceChargeModalOpen(true) },
|
|
51
|
+
{ label: 'Generate Dynamic Report', href: contoHref },
|
|
52
|
+
// { label: 'Upload Conto 331 Report', href: '/upload/conto331' },
|
|
53
|
+
]}
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
/>
|
|
56
|
+
<BalanceChargeModal
|
|
57
|
+
open={balanceChargeModalOpen}
|
|
58
|
+
onOpenChange={setBalanceChargeModalOpen}
|
|
59
|
+
onClose={() => setBalanceChargeModalOpen(false)}
|
|
60
|
+
/>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function SubStep3B({
|
|
66
|
+
level,
|
|
67
|
+
year,
|
|
68
|
+
adminBusinessId,
|
|
69
|
+
disabled,
|
|
70
|
+
}: {
|
|
71
|
+
level: number;
|
|
72
|
+
year: number;
|
|
73
|
+
adminBusinessId: string;
|
|
74
|
+
disabled: boolean;
|
|
75
|
+
}) {
|
|
76
|
+
const href = getTrialBalanceReportHref({
|
|
77
|
+
toDate: `${year - 1}-12-31` as TimelessDateString,
|
|
78
|
+
ownerIds: [adminBusinessId],
|
|
79
|
+
});
|
|
80
|
+
return (
|
|
81
|
+
<BaseStepCard
|
|
82
|
+
id="3b"
|
|
83
|
+
title="Continuing Users"
|
|
84
|
+
description="Compare with previous year final trial balance"
|
|
85
|
+
status="pending"
|
|
86
|
+
level={level}
|
|
87
|
+
actions={[{ label: 'View Previous Year Ending Balance', href }]}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default function Step03OpeningBalance(props: Step03Props) {
|
|
94
|
+
const [status, setStatus] = useState<StepStatus>('loading');
|
|
95
|
+
const [userType, setUserType] = useState<UserType | null>(null);
|
|
96
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!props.adminBusinessId) {
|
|
100
|
+
setStatus('blocked');
|
|
101
|
+
}
|
|
102
|
+
}, [props.adminBusinessId]);
|
|
103
|
+
|
|
104
|
+
// Report status changes to parent
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (props.onStatusChange) {
|
|
107
|
+
props.onStatusChange(props.id, status);
|
|
108
|
+
}
|
|
109
|
+
}, [status, props.onStatusChange, props.id]);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
const fetchUserType = async () => {
|
|
113
|
+
try {
|
|
114
|
+
// Simulate API call to determine user type
|
|
115
|
+
await new Promise(resolve => setTimeout(resolve, 600));
|
|
116
|
+
|
|
117
|
+
const data: UserType = {
|
|
118
|
+
type: 'continuing',
|
|
119
|
+
balanceStatus: 'pending',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
setUserType(data);
|
|
123
|
+
|
|
124
|
+
if (data.type === 'new') {
|
|
125
|
+
setStatus('completed');
|
|
126
|
+
} else if (data.balanceStatus === 'verified') {
|
|
127
|
+
setStatus('completed');
|
|
128
|
+
} else {
|
|
129
|
+
setStatus('pending');
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Error fetching user type:', error);
|
|
133
|
+
setStatus('blocked');
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
fetchUserType();
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<>
|
|
142
|
+
<BaseStepCard
|
|
143
|
+
{...props}
|
|
144
|
+
status={status}
|
|
145
|
+
icon={<Calculator className="h-4 w-4" />}
|
|
146
|
+
hasSubsteps
|
|
147
|
+
isExpanded={isExpanded}
|
|
148
|
+
onToggleExpanded={() => setIsExpanded(!isExpanded)}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
{props.adminBusinessId && (
|
|
152
|
+
<Collapsible open={isExpanded}>
|
|
153
|
+
<CollapsibleContent className="space-y-2">
|
|
154
|
+
<SubStep3A
|
|
155
|
+
level={1}
|
|
156
|
+
year={props.year}
|
|
157
|
+
adminBusinessId={props.adminBusinessId}
|
|
158
|
+
disabled={userType?.type !== 'migrating'}
|
|
159
|
+
/>
|
|
160
|
+
<SubStep3B
|
|
161
|
+
level={1}
|
|
162
|
+
year={props.year}
|
|
163
|
+
adminBusinessId={props.adminBusinessId}
|
|
164
|
+
disabled={userType?.type !== 'continuing'}
|
|
165
|
+
/>
|
|
166
|
+
</CollapsibleContent>
|
|
167
|
+
</Collapsible>
|
|
168
|
+
)}
|
|
169
|
+
</>
|
|
170
|
+
);
|
|
171
|
+
}
|