1
1
import { Struct } from "$/domain/entities/generic/Struct" ;
2
+ import { Config } from "$/domain/entities/Config" ;
3
+ import { addToDate , toISODateWithoutTimezone , getDiff , stringToTime } from "$/utils/date" ;
4
+ import _ , { Collection } from "$/domain/entities/generic/Collection" ;
5
+ import { Maybe } from "$/utils/ts-utils" ;
2
6
3
- export type PeriodDetailsAttrs = { year : number ; startDate : string ; endDate : string } ;
4
- type DatePeriodAttrs = { startDate : string ; endDate : string ; periods : PeriodDetailsAttrs [ ] } ;
7
+ const DEFAULT_FUTURE_PERIODS = 1 ;
8
+
9
+ export type YearlyPeriodDetailsAttrs = {
10
+ year : number ;
11
+ startDate : string ;
12
+ endDate : string ;
13
+ } ;
14
+
15
+ type DatePeriodAttrs = { startDate : string ; endDate : string ; periods : YearlyPeriodDetailsAttrs [ ] } ;
5
16
6
17
export class DatePeriod extends Struct < DatePeriodAttrs > ( ) {
7
18
get years ( ) {
@@ -24,10 +35,87 @@ export class DatePeriod extends Struct<DatePeriodAttrs>() {
24
35
} ) ) ;
25
36
}
26
37
38
+ get dataInputPeriods ( ) : MonthlyPeriodDetails [ ] {
39
+ const { periods, startDate : startDateStr , endDate : endDateStr } = this ;
40
+ if ( ! periods . length ) {
41
+ return [ ] ;
42
+ }
43
+
44
+ const allStartTimes = this . extractPeriodDateAsTimes ( "startDate" ) ;
45
+ const allEndTimes = this . extractPeriodDateAsTimes ( "endDate" ) ;
46
+
47
+ if ( ! allStartTimes . length || ! allEndTimes . length ) {
48
+ return [ ] ;
49
+ }
50
+
51
+ const startDate = new Date ( startDateStr ) ;
52
+ const endDate = new Date ( endDateStr ) ;
53
+
54
+ const openingDate = new Date ( Math . min ( ...allStartTimes , startDate . getTime ( ) ) ) ;
55
+ const closingDate = new Date ( Math . max ( ...allEndTimes , endDate . getTime ( ) ) ) ;
56
+
57
+ const startYear = startDate . getFullYear ( ) ;
58
+ const startMonth = startDate . getMonth ( ) ;
59
+
60
+ const totalMonths = Math . round ( getDiff ( startDate , endDate , "month" ) ) ;
61
+
62
+ return Collection . range ( 0 , totalMonths )
63
+ . map ( monthOffset => {
64
+ const targetYear = startYear + Math . floor ( ( startMonth + monthOffset ) / 12 ) ;
65
+ const targetMonth = ( ( startMonth + monthOffset ) % 12 ) + 1 ;
66
+
67
+ return MonthlyPeriodDetails . create ( {
68
+ year : targetYear ,
69
+ month : targetMonth ,
70
+ startDate : toISODateWithoutTimezone ( openingDate ) ,
71
+ endDate : toISODateWithoutTimezone ( closingDate ) ,
72
+ } ) ;
73
+ } )
74
+ . value ( ) ;
75
+ }
76
+
77
+ private extractPeriodDateAsTimes < K extends "startDate" | "endDate" > ( field : K ) {
78
+ return _ ( this . periods )
79
+ . compactMap ( p => stringToTime ( p [ field ] ) )
80
+ . value ( ) ;
81
+ }
82
+
83
+ generatePeriods ( config : DataPeriodConfig ) : DatePeriod [ "periods" ] {
84
+ const { startDate, endDate, periods, years } = this ;
85
+
86
+ const month = config . periodEndDateMonth ;
87
+ const day = config . periodEndDateDay ;
88
+ const units = config . periodLastYearUnits ;
89
+ const unitValue = config . periodLastYearEndDate ;
90
+ const lastYear = years [ years . length - 1 ] ;
91
+
92
+ return years . map ( year => {
93
+ const currentPeriod = periods . find ( period => period . year === year ) ;
94
+
95
+ const defaultEndDate = new Date ( year + 1 , month - 1 , day , 0 , 0 , 0 ) . toISOString ( ) ;
96
+
97
+ const lastYearEndDate =
98
+ units && unitValue ? addToDate ( endDate ?? "" , units , unitValue ) : endDate ;
99
+
100
+ const endM = year === lastYear ? lastYearEndDate : defaultEndDate ;
101
+
102
+ return {
103
+ year,
104
+ startDate : currentPeriod ?. startDate ?? startDate ?? "" ,
105
+ endDate : currentPeriod ?. endDate ?? endM ?? "" ,
106
+ } ;
107
+ } ) ;
108
+ }
109
+
110
+ initializePeriods ( config : DataPeriodConfig ) : DatePeriod {
111
+ const periods = this . generatePeriods ( config ) ;
112
+ return this . updatedPeriods ( periods ) ;
113
+ }
114
+
27
115
private buildShortFormat ( date : string ) {
28
116
if ( ! date ) return "" ;
29
117
30
- const datePart = new Date ( date ) . toISOString ( ) . split ( "T" ) [ 0 ] ;
118
+ const datePart = toISODateWithoutTimezone ( new Date ( date ) ) . split ( "T" ) [ 0 ] ;
31
119
if ( ! datePart ) return "" ;
32
120
return datePart . replace ( / - / g, "" ) ;
33
121
}
@@ -46,7 +134,30 @@ export class DatePeriod extends Struct<DatePeriodAttrs>() {
46
134
return this . _update ( { [ fieldName ] : value } ) ;
47
135
}
48
136
49
- updatedPeriods ( periods : PeriodDetailsAttrs [ ] ) : DatePeriod {
137
+ updatedPeriods ( periods : YearlyPeriodDetailsAttrs [ ] ) : DatePeriod {
50
138
return this . _update ( { periods } ) ;
51
139
}
140
+
141
+ static getFuturePeriods ( endDateStr : Maybe < string > ) : number {
142
+ if ( ! endDateStr ) return DEFAULT_FUTURE_PERIODS ;
143
+
144
+ const end = new Date ( endDateStr ) ;
145
+ const now = new Date ( ) ;
146
+
147
+ const monthsDiff = Math . ceil ( getDiff ( now , end , "month" ) ) ;
148
+
149
+ return Math . max ( monthsDiff + 1 , DEFAULT_FUTURE_PERIODS ) ;
150
+ }
52
151
}
152
+
153
+ export type MonthlyPeriodDetailsAttrs = YearlyPeriodDetailsAttrs & { month : number } ;
154
+ export class MonthlyPeriodDetails extends Struct < MonthlyPeriodDetailsAttrs > ( ) {
155
+ get period ( ) {
156
+ return `${ this . year } ${ String ( this . month ) . padStart ( 2 , "0" ) } ` ;
157
+ }
158
+ }
159
+
160
+ type DataPeriodConfig = Pick <
161
+ Config ,
162
+ "periodEndDateMonth" | "periodEndDateDay" | "periodLastYearUnits" | "periodLastYearEndDate"
163
+ > ;
0 commit comments