@tuturuuu/ui 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/package.json +4 -4
- package/src/components/ui/finance/invoices/components/subscription-attendance-summary.tsx +19 -5
- package/src/components/ui/finance/invoices/components/subscription-prepaid-controls.test.tsx +41 -0
- package/src/components/ui/finance/invoices/components/subscription-prepaid-controls.tsx +93 -0
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +82 -70
- package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +50 -25
- package/src/components/ui/finance/invoices/hooks.ts +11 -2
- package/src/components/ui/finance/invoices/internal-api.ts +5 -0
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +92 -31
- package/src/components/ui/finance/invoices/utils.test.ts +82 -0
- package/src/components/ui/finance/invoices/utils.ts +240 -7
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +7 -0
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +37 -0
- package/src/components/ui/tu-do/shared/board-client.tsx +3 -1
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +55 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.test.ts +97 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.ts +210 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +56 -8
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.test.tsx +63 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.tsx +218 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-restore-banner.tsx +83 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.test.tsx +120 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.tsx +180 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/utils.test.ts +100 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/utils.ts +109 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +381 -34
- package/src/components/ui/tu-do/templates/task-template-api.ts +142 -0
- package/src/components/ui/tu-do/templates/task-template-card.tsx +118 -0
- package/src/components/ui/tu-do/templates/task-template-client.tsx +258 -0
- package/src/components/ui/tu-do/templates/task-template-dialogs.test.tsx +167 -0
- package/src/components/ui/tu-do/templates/task-template-dialogs.tsx +376 -0
- package/src/components/ui/tu-do/templates/task-templates-hub.test.tsx +114 -0
- package/src/components/ui/tu-do/templates/task-templates-hub.tsx +50 -0
- package/src/components/ui/tu-do/templates/task-templates-page.tsx +6 -11
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from './components/invoice-products-permission-warning';
|
|
38
38
|
import { SubscriptionAttendanceSummary } from './components/subscription-attendance-summary';
|
|
39
39
|
import { SubscriptionGroupSelector } from './components/subscription-group-selector';
|
|
40
|
+
import { SubscriptionPrepaidControls } from './components/subscription-prepaid-controls';
|
|
40
41
|
import { CreatePromotionDialog } from './create-promotion-dialog';
|
|
41
42
|
import type { AvailablePromotion } from './hooks';
|
|
42
43
|
import {
|
|
@@ -63,15 +64,19 @@ import { formatInvoiceRecalculationDescription } from './invoice-visibility-form
|
|
|
63
64
|
import { ProductSelection } from './product-selection';
|
|
64
65
|
import type { SelectedProductItem } from './types';
|
|
65
66
|
import {
|
|
67
|
+
formatCoverageRangeLabel,
|
|
68
|
+
formatMonthLabel,
|
|
66
69
|
formatMonthValue,
|
|
67
70
|
getAvailableMonths,
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
getBillableAttendanceRecordsInRange,
|
|
72
|
+
getBillableSessionsForGroupsInRange,
|
|
73
|
+
getCoverageValidUntilMonthValue,
|
|
70
74
|
getGroupsDateRange,
|
|
71
75
|
getLinkedFinanceCategorySelection,
|
|
72
76
|
getMonthStartDate,
|
|
73
77
|
getSubscriptionAttendanceDisplayData,
|
|
74
|
-
|
|
78
|
+
isSubscriptionRangeFullyPaidForGroups,
|
|
79
|
+
PREPAID_MONTH_OPTION_HORIZON,
|
|
75
80
|
} from './utils';
|
|
76
81
|
|
|
77
82
|
interface Props {
|
|
@@ -131,6 +136,7 @@ export function SubscriptionInvoice({
|
|
|
131
136
|
defaultValue: formatMonthValue(new Date()),
|
|
132
137
|
shallow: true,
|
|
133
138
|
});
|
|
139
|
+
const [prepaidMonthCount, setPrepaidMonthCount] = useState(1);
|
|
134
140
|
|
|
135
141
|
const [customerSearch, setCustomerSearch] = useState('');
|
|
136
142
|
const [debouncedCustomerSearch] = useDebounce(customerSearch, 300);
|
|
@@ -279,7 +285,8 @@ export function SubscriptionInvoice({
|
|
|
279
285
|
selectedGroupIds,
|
|
280
286
|
[],
|
|
281
287
|
locale,
|
|
282
|
-
selectedMonth
|
|
288
|
+
selectedMonth,
|
|
289
|
+
PREPAID_MONTH_OPTION_HORIZON
|
|
283
290
|
),
|
|
284
291
|
[locale, selectedGroupIds, selectedMonth, userGroups]
|
|
285
292
|
);
|
|
@@ -324,7 +331,8 @@ export function SubscriptionInvoice({
|
|
|
324
331
|
wsId,
|
|
325
332
|
selectedUserId,
|
|
326
333
|
selectedGroupIds,
|
|
327
|
-
effectiveSelectedMonth
|
|
334
|
+
effectiveSelectedMonth,
|
|
335
|
+
prepaidMonthCount
|
|
328
336
|
);
|
|
329
337
|
|
|
330
338
|
const userAttendance = subscriptionInvoiceContext?.attendance ?? [];
|
|
@@ -335,35 +343,62 @@ export function SubscriptionInvoice({
|
|
|
335
343
|
? subscriptionInvoiceContextError
|
|
336
344
|
: null;
|
|
337
345
|
|
|
338
|
-
const
|
|
346
|
+
const coverageRangeLabel = useMemo(
|
|
347
|
+
() =>
|
|
348
|
+
formatCoverageRangeLabel({
|
|
349
|
+
locale,
|
|
350
|
+
prepaidMonthCount,
|
|
351
|
+
selectedMonth: effectiveSelectedMonth,
|
|
352
|
+
}),
|
|
353
|
+
[effectiveSelectedMonth, locale, prepaidMonthCount]
|
|
354
|
+
);
|
|
355
|
+
const coverageValidUntilLabel = useMemo(
|
|
356
|
+
() =>
|
|
357
|
+
formatMonthLabel(
|
|
358
|
+
getCoverageValidUntilMonthValue(
|
|
359
|
+
effectiveSelectedMonth,
|
|
360
|
+
prepaidMonthCount
|
|
361
|
+
),
|
|
362
|
+
locale
|
|
363
|
+
),
|
|
364
|
+
[effectiveSelectedMonth, locale, prepaidMonthCount]
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const isSelectedRangePaid = useMemo(() => {
|
|
339
368
|
if (selectedGroupIds.length === 0 || !effectiveSelectedMonth) return false;
|
|
340
369
|
|
|
341
370
|
const selectedMonthStart = getMonthStartDate(effectiveSelectedMonth);
|
|
342
371
|
|
|
343
|
-
// A
|
|
344
|
-
// If
|
|
372
|
+
// A range is paid only if all selected groups have coverage for every month.
|
|
373
|
+
// If any selected group has an unpaid month, invoice creation stays enabled.
|
|
345
374
|
if (Number.isNaN(selectedMonthStart.getTime())) return false;
|
|
346
375
|
|
|
347
|
-
return
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
376
|
+
return isSubscriptionRangeFullyPaidForGroups(
|
|
377
|
+
selectedGroupIds,
|
|
378
|
+
effectiveSelectedMonth,
|
|
379
|
+
prepaidMonthCount,
|
|
380
|
+
latestSubscriptionInvoices
|
|
353
381
|
);
|
|
354
|
-
}, [
|
|
382
|
+
}, [
|
|
383
|
+
effectiveSelectedMonth,
|
|
384
|
+
latestSubscriptionInvoices,
|
|
385
|
+
prepaidMonthCount,
|
|
386
|
+
selectedGroupIds,
|
|
387
|
+
]);
|
|
355
388
|
|
|
356
389
|
const billableAttendance = useMemo(
|
|
357
390
|
() =>
|
|
358
|
-
|
|
391
|
+
getBillableAttendanceRecordsInRange(
|
|
359
392
|
userAttendance,
|
|
360
393
|
selectedGroupIds,
|
|
361
394
|
effectiveSelectedMonth,
|
|
395
|
+
prepaidMonthCount,
|
|
362
396
|
latestSubscriptionInvoices
|
|
363
397
|
),
|
|
364
398
|
[
|
|
365
399
|
effectiveSelectedMonth,
|
|
366
400
|
latestSubscriptionInvoices,
|
|
401
|
+
prepaidMonthCount,
|
|
367
402
|
selectedGroupIds,
|
|
368
403
|
userAttendance,
|
|
369
404
|
]
|
|
@@ -371,15 +406,17 @@ export function SubscriptionInvoice({
|
|
|
371
406
|
|
|
372
407
|
const billableSessions = useMemo(
|
|
373
408
|
() =>
|
|
374
|
-
|
|
409
|
+
getBillableSessionsForGroupsInRange(
|
|
375
410
|
userGroups,
|
|
376
411
|
selectedGroupIds,
|
|
377
412
|
effectiveSelectedMonth,
|
|
413
|
+
prepaidMonthCount,
|
|
378
414
|
latestSubscriptionInvoices
|
|
379
415
|
),
|
|
380
416
|
[
|
|
381
417
|
effectiveSelectedMonth,
|
|
382
418
|
latestSubscriptionInvoices,
|
|
419
|
+
prepaidMonthCount,
|
|
383
420
|
selectedGroupIds,
|
|
384
421
|
userGroups,
|
|
385
422
|
]
|
|
@@ -387,22 +424,29 @@ export function SubscriptionInvoice({
|
|
|
387
424
|
|
|
388
425
|
const monthlyAttendance = useMemo(
|
|
389
426
|
() =>
|
|
390
|
-
|
|
427
|
+
getBillableAttendanceRecordsInRange(
|
|
391
428
|
userAttendance,
|
|
392
429
|
selectedGroupIds,
|
|
393
|
-
effectiveSelectedMonth
|
|
430
|
+
effectiveSelectedMonth,
|
|
431
|
+
prepaidMonthCount
|
|
394
432
|
),
|
|
395
|
-
[
|
|
433
|
+
[
|
|
434
|
+
effectiveSelectedMonth,
|
|
435
|
+
prepaidMonthCount,
|
|
436
|
+
selectedGroupIds,
|
|
437
|
+
userAttendance,
|
|
438
|
+
]
|
|
396
439
|
);
|
|
397
440
|
|
|
398
441
|
const monthlySessions = useMemo(
|
|
399
442
|
() =>
|
|
400
|
-
|
|
443
|
+
getBillableSessionsForGroupsInRange(
|
|
401
444
|
userGroups,
|
|
402
445
|
selectedGroupIds,
|
|
403
|
-
effectiveSelectedMonth
|
|
446
|
+
effectiveSelectedMonth,
|
|
447
|
+
prepaidMonthCount
|
|
404
448
|
),
|
|
405
|
-
[effectiveSelectedMonth, selectedGroupIds, userGroups]
|
|
449
|
+
[effectiveSelectedMonth, prepaidMonthCount, selectedGroupIds, userGroups]
|
|
406
450
|
);
|
|
407
451
|
|
|
408
452
|
const {
|
|
@@ -414,7 +458,7 @@ export function SubscriptionInvoice({
|
|
|
414
458
|
} = useMemo(
|
|
415
459
|
() =>
|
|
416
460
|
getSubscriptionAttendanceDisplayData({
|
|
417
|
-
isSelectedMonthPaid,
|
|
461
|
+
isSelectedMonthPaid: isSelectedRangePaid,
|
|
418
462
|
billableAttendance,
|
|
419
463
|
billableSessions,
|
|
420
464
|
monthlyAttendance,
|
|
@@ -423,7 +467,7 @@ export function SubscriptionInvoice({
|
|
|
423
467
|
[
|
|
424
468
|
billableAttendance,
|
|
425
469
|
billableSessions,
|
|
426
|
-
|
|
470
|
+
isSelectedRangePaid,
|
|
427
471
|
monthlyAttendance,
|
|
428
472
|
monthlySessions,
|
|
429
473
|
]
|
|
@@ -502,6 +546,7 @@ export function SubscriptionInvoice({
|
|
|
502
546
|
userAttendance: billableAttendance,
|
|
503
547
|
latestSubscriptionInvoices,
|
|
504
548
|
onSelectedProductsChange: setSubscriptionSelectedProducts,
|
|
549
|
+
prepaidMonthCount,
|
|
505
550
|
});
|
|
506
551
|
|
|
507
552
|
useSubscriptionInvoiceContent({
|
|
@@ -513,10 +558,11 @@ export function SubscriptionInvoice({
|
|
|
513
558
|
subscriptionSelectedProducts,
|
|
514
559
|
userAttendance: billableAttendance,
|
|
515
560
|
latestSubscriptionInvoices,
|
|
516
|
-
isSelectedMonthPaid,
|
|
561
|
+
isSelectedMonthPaid: isSelectedRangePaid,
|
|
517
562
|
locale,
|
|
518
563
|
onContentChange: setInvoiceContent,
|
|
519
564
|
onNotesChange: setInvoiceNotes,
|
|
565
|
+
prepaidMonthCount,
|
|
520
566
|
});
|
|
521
567
|
|
|
522
568
|
const subtotal = useInvoiceSubtotal(subscriptionSelectedProducts);
|
|
@@ -582,6 +628,7 @@ export function SubscriptionInvoice({
|
|
|
582
628
|
setSelectedCategoryId(defaultCategoryId || '');
|
|
583
629
|
setSelectedGroupIds(null);
|
|
584
630
|
setSelectedMonth(null);
|
|
631
|
+
setPrepaidMonthCount(1);
|
|
585
632
|
}
|
|
586
633
|
}, [
|
|
587
634
|
selectedUserId,
|
|
@@ -629,7 +676,8 @@ export function SubscriptionInvoice({
|
|
|
629
676
|
selectedGroupIds,
|
|
630
677
|
latestSubscriptionInvoices,
|
|
631
678
|
locale,
|
|
632
|
-
effectiveSelectedMonth
|
|
679
|
+
effectiveSelectedMonth,
|
|
680
|
+
PREPAID_MONTH_OPTION_HORIZON
|
|
633
681
|
),
|
|
634
682
|
[
|
|
635
683
|
effectiveSelectedMonth,
|
|
@@ -778,6 +826,7 @@ export function SubscriptionInvoice({
|
|
|
778
826
|
frontend_subtotal: subtotal,
|
|
779
827
|
frontend_discount_amount: discountAmount,
|
|
780
828
|
frontend_total: subscriptionRoundedTotal,
|
|
829
|
+
prepaid_month_count: prepaidMonthCount,
|
|
781
830
|
};
|
|
782
831
|
|
|
783
832
|
const result = await createSubscriptionInvoiceWithInternalApi(wsId, {
|
|
@@ -824,6 +873,7 @@ export function SubscriptionInvoice({
|
|
|
824
873
|
setSelectedWalletId(defaultWalletId || '');
|
|
825
874
|
setSelectedCategoryId(defaultCategoryId || '');
|
|
826
875
|
setSelectedGroupIds(null);
|
|
876
|
+
setPrepaidMonthCount(1);
|
|
827
877
|
setCustomerSearch('');
|
|
828
878
|
}
|
|
829
879
|
} catch (error) {
|
|
@@ -905,7 +955,16 @@ export function SubscriptionInvoice({
|
|
|
905
955
|
/>
|
|
906
956
|
)}
|
|
907
957
|
|
|
908
|
-
{selectedGroupIds.length > 0 &&
|
|
958
|
+
{selectedGroupIds.length > 0 && (
|
|
959
|
+
<SubscriptionPrepaidControls
|
|
960
|
+
coverageRangeLabel={coverageRangeLabel}
|
|
961
|
+
prepaidMonthCount={prepaidMonthCount}
|
|
962
|
+
validUntilLabel={coverageValidUntilLabel}
|
|
963
|
+
onPrepaidMonthCountChange={setPrepaidMonthCount}
|
|
964
|
+
/>
|
|
965
|
+
)}
|
|
966
|
+
|
|
967
|
+
{selectedGroupIds.length > 0 && !isSelectedRangePaid && !isBlocked && (
|
|
909
968
|
<>
|
|
910
969
|
<ProductSelection
|
|
911
970
|
products={products}
|
|
@@ -965,7 +1024,7 @@ export function SubscriptionInvoice({
|
|
|
965
1024
|
<SubscriptionAttendanceSummary
|
|
966
1025
|
selectedGroupIds={selectedGroupIds}
|
|
967
1026
|
selectedMonth={effectiveSelectedMonth}
|
|
968
|
-
isSelectedMonthPaid={
|
|
1027
|
+
isSelectedMonthPaid={isSelectedRangePaid}
|
|
969
1028
|
locale={locale}
|
|
970
1029
|
navigateMonth={navigateMonth}
|
|
971
1030
|
canNavigateMonth={canNavigateMonth}
|
|
@@ -979,11 +1038,13 @@ export function SubscriptionInvoice({
|
|
|
979
1038
|
attendanceStats={attendanceStats}
|
|
980
1039
|
totalSessions={totalSessions}
|
|
981
1040
|
attendanceRate={attendanceRate}
|
|
1041
|
+
coverageRangeLabel={coverageRangeLabel}
|
|
1042
|
+
prepaidMonthCount={prepaidMonthCount}
|
|
982
1043
|
/>
|
|
983
1044
|
)}
|
|
984
1045
|
|
|
985
1046
|
{subscriptionSelectedProducts.length > 0 &&
|
|
986
|
-
!
|
|
1047
|
+
!isSelectedRangePaid && (
|
|
987
1048
|
<Card>
|
|
988
1049
|
<CardHeader>
|
|
989
1050
|
<CardTitle>
|
|
@@ -1095,7 +1156,7 @@ export function SubscriptionInvoice({
|
|
|
1095
1156
|
!selectedWalletId ||
|
|
1096
1157
|
!selectedCategoryId ||
|
|
1097
1158
|
isCreating ||
|
|
1098
|
-
|
|
1159
|
+
isSelectedRangePaid
|
|
1099
1160
|
}
|
|
1100
1161
|
>
|
|
1101
1162
|
{isCreating ? (
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import {
|
|
3
|
+
formatCoverageRangeLabel,
|
|
4
|
+
getAvailableMonths,
|
|
3
5
|
getBillableAttendanceRecords,
|
|
6
|
+
getBillableQuantityMapForGroupsRange,
|
|
4
7
|
getBillableSessionsForGroups,
|
|
8
|
+
getCoverageMonths,
|
|
5
9
|
getLinkedFinanceCategorySelection,
|
|
6
10
|
getSubscriptionAttendanceDisplayData,
|
|
7
11
|
getSubscriptionCoverageInvoiceForGroup,
|
|
8
12
|
isSubscriptionMonthPaidForGroup,
|
|
13
|
+
isSubscriptionRangeFullyPaidForGroups,
|
|
9
14
|
type UserGroup,
|
|
10
15
|
} from './utils';
|
|
11
16
|
|
|
@@ -138,6 +143,21 @@ describe('subscription invoice attendance display data', () => {
|
|
|
138
143
|
});
|
|
139
144
|
|
|
140
145
|
describe('subscription invoice coverage', () => {
|
|
146
|
+
it('builds coverage months and labels multi-month ranges', () => {
|
|
147
|
+
expect(getCoverageMonths('2026-06', 3)).toEqual([
|
|
148
|
+
'2026-06',
|
|
149
|
+
'2026-07',
|
|
150
|
+
'2026-08',
|
|
151
|
+
]);
|
|
152
|
+
expect(
|
|
153
|
+
formatCoverageRangeLabel({
|
|
154
|
+
locale: 'en-US',
|
|
155
|
+
prepaidMonthCount: 3,
|
|
156
|
+
selectedMonth: '2026-06',
|
|
157
|
+
})
|
|
158
|
+
).toBe('June 2026 - August 2026');
|
|
159
|
+
});
|
|
160
|
+
|
|
141
161
|
it('treats valid_until as the exclusive first unpaid month', () => {
|
|
142
162
|
const latestInvoices = [{ group_id: groupId, valid_until: '2026-06-01' }];
|
|
143
163
|
|
|
@@ -183,6 +203,68 @@ describe('subscription invoice coverage', () => {
|
|
|
183
203
|
isSubscriptionMonthPaidForGroup(groupId, '2026-06', latestInvoices)
|
|
184
204
|
).toBe(true);
|
|
185
205
|
});
|
|
206
|
+
|
|
207
|
+
it('requires every coverage month to be paid before marking a range paid', () => {
|
|
208
|
+
expect(
|
|
209
|
+
isSubscriptionRangeFullyPaidForGroups([groupId], '2026-06', 3, [
|
|
210
|
+
{ group_id: groupId, valid_until: '2026-08-01' },
|
|
211
|
+
])
|
|
212
|
+
).toBe(false);
|
|
213
|
+
expect(
|
|
214
|
+
isSubscriptionRangeFullyPaidForGroups([groupId], '2026-06', 3, [
|
|
215
|
+
{ group_id: groupId, valid_until: '2026-09-01' },
|
|
216
|
+
])
|
|
217
|
+
).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('generates prepaid future month options capped by group ending dates', () => {
|
|
221
|
+
const groups = [
|
|
222
|
+
{
|
|
223
|
+
workspace_user_groups: {
|
|
224
|
+
id: groupId,
|
|
225
|
+
name: 'Math 7',
|
|
226
|
+
sessions: ['2026-06-05', '2026-07-05', '2026-08-05'],
|
|
227
|
+
starting_date: '2026-06-01',
|
|
228
|
+
ending_date: '2026-08-31',
|
|
229
|
+
} as NonNullable<UserGroup['workspace_user_groups']>,
|
|
230
|
+
},
|
|
231
|
+
] satisfies UserGroup[];
|
|
232
|
+
|
|
233
|
+
expect(
|
|
234
|
+
getAvailableMonths(groups, [groupId], [], 'en-US', '2026-06', 12).map(
|
|
235
|
+
(month) => month.value
|
|
236
|
+
)
|
|
237
|
+
).toEqual(['2026-06', '2026-07', '2026-08']);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('skips paid months and uses scheduled sessions for future prepaid quantities', () => {
|
|
241
|
+
const groups = [
|
|
242
|
+
{
|
|
243
|
+
workspace_user_groups: {
|
|
244
|
+
id: groupId,
|
|
245
|
+
name: 'Math 7',
|
|
246
|
+
sessions: ['2026-06-05', '2026-07-05', '2026-08-05'],
|
|
247
|
+
starting_date: '2026-06-01',
|
|
248
|
+
ending_date: null,
|
|
249
|
+
} as NonNullable<UserGroup['workspace_user_groups']>,
|
|
250
|
+
},
|
|
251
|
+
] satisfies UserGroup[];
|
|
252
|
+
|
|
253
|
+
expect(
|
|
254
|
+
getBillableQuantityMapForGroupsRange({
|
|
255
|
+
groupIds: [groupId],
|
|
256
|
+
latestInvoices: [{ group_id: groupId, valid_until: '2026-07-01' }],
|
|
257
|
+
now: new Date(2026, 5, 15),
|
|
258
|
+
prepaidMonthCount: 3,
|
|
259
|
+
selectedMonth: '2026-06',
|
|
260
|
+
useAttendanceBased: true,
|
|
261
|
+
userAttendance: [
|
|
262
|
+
{ group_id: groupId, date: '2026-06-05', status: 'PRESENT' },
|
|
263
|
+
],
|
|
264
|
+
userGroups: groups,
|
|
265
|
+
})
|
|
266
|
+
).toEqual({ [groupId]: 2 });
|
|
267
|
+
});
|
|
186
268
|
});
|
|
187
269
|
|
|
188
270
|
describe('linked finance category selection', () => {
|