@robosystems/client 0.2.48 → 0.2.49

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.
@@ -10,13 +10,16 @@
10
10
 
11
11
  import {
12
12
  autoMapElements,
13
+ closeFiscalPeriod,
13
14
  createClosingEntry,
15
+ createManualClosingEntry,
14
16
  createMappingAssociation,
15
17
  createSchedule,
16
18
  createStructure,
17
19
  deleteMappingAssociation,
18
20
  getAccountRollups,
19
21
  getClosingBookStructures,
22
+ getFiscalCalendar,
20
23
  getLedgerAccountTree,
21
24
  getLedgerEntity,
22
25
  getLedgerSummary,
@@ -28,21 +31,32 @@ import {
28
31
  getPeriodCloseStatus,
29
32
  getReportingTaxonomy,
30
33
  getScheduleFacts,
34
+ initializeLedger,
31
35
  listElements,
32
36
  listLedgerAccounts,
33
37
  listLedgerTransactions,
34
38
  listMappings,
39
+ listPeriodDrafts,
35
40
  listSchedules,
36
41
  listStructures,
42
+ reopenFiscalPeriod,
43
+ setCloseTarget,
44
+ truncateSchedule,
37
45
  } from '../sdk/sdk.gen'
38
46
  import type {
39
47
  AccountListResponse,
40
48
  AccountRollupsResponse,
41
49
  AccountTreeResponse,
50
+ ClosePeriodRequest,
51
+ ClosePeriodResponse,
42
52
  ClosingBookStructuresResponse,
43
53
  ClosingEntryResponse,
44
54
  CreateClosingEntryRequest,
55
+ CreateManualClosingEntryRequest,
45
56
  CreateScheduleRequest,
57
+ FiscalCalendarResponse,
58
+ InitializeLedgerRequest,
59
+ InitializeLedgerResponse,
46
60
  LedgerSummaryResponse,
47
61
  LedgerTransactionDetailResponse,
48
62
  LedgerTransactionListResponse,
@@ -50,12 +64,17 @@ import type {
50
64
  MappingDetailResponse,
51
65
  PeriodCloseItemResponse,
52
66
  PeriodCloseStatusResponse,
67
+ PeriodDraftsResponse,
68
+ ReopenPeriodRequest,
53
69
  ScheduleCreatedResponse,
54
70
  ScheduleFactResponse,
55
71
  ScheduleFactsResponse,
56
72
  ScheduleListResponse,
57
73
  ScheduleSummaryResponse,
74
+ SetCloseTargetRequest,
58
75
  TrialBalanceResponse,
76
+ TruncateScheduleRequest,
77
+ TruncateScheduleResponse,
59
78
  } from '../sdk/types.gen'
60
79
 
61
80
  // ── Friendly types ──────────────────────────────────────────────────────
@@ -137,14 +156,145 @@ export interface PeriodCloseStatus {
137
156
  totalPosted: number
138
157
  }
139
158
 
159
+ /**
160
+ * Outcome of an idempotent `createClosingEntry` call.
161
+ *
162
+ * - `created` — no prior draft, new draft created
163
+ * - `unchanged` — prior draft matches current schedule fact, no-op
164
+ * - `regenerated` — prior draft was stale, replaced with a fresh one
165
+ * - `removed` — prior draft existed but schedule no longer covers this period
166
+ * - `skipped` — no prior draft and no in-scope fact; nothing to do
167
+ */
168
+ export type ClosingEntryOutcome = 'created' | 'unchanged' | 'regenerated' | 'removed' | 'skipped'
169
+
170
+ /**
171
+ * Result of an idempotent closing-entry call. `entry_id`, `amount`, and
172
+ * related fields are null for `removed` and `skipped` outcomes.
173
+ */
140
174
  export interface ClosingEntry {
141
- entryId: string
175
+ outcome: ClosingEntryOutcome
176
+ entryId: string | null
177
+ status: string | null
178
+ postingDate: string | null
179
+ memo: string | null
180
+ debitElementId: string | null
181
+ creditElementId: string | null
182
+ amount: number | null
183
+ reason: string | null
184
+ }
185
+
186
+ export type LedgerEntryType = 'standard' | 'adjusting' | 'closing' | 'reversing'
187
+
188
+ // ── Fiscal calendar types ──────────────────────────────────────────────
189
+
190
+ export interface FiscalPeriodSummary {
191
+ name: string
192
+ startDate: string
193
+ endDate: string
142
194
  status: string
195
+ closedAt: string | null
196
+ }
197
+
198
+ export interface FiscalCalendarState {
199
+ graphId: string
200
+ fiscalYearStartMonth: number
201
+ closedThrough: string | null
202
+ closeTarget: string | null
203
+ gapPeriods: number
204
+ catchUpSequence: string[]
205
+ closeableNow: boolean
206
+ blockers: string[]
207
+ lastCloseAt: string | null
208
+ initializedAt: string | null
209
+ lastSyncAt: string | null
210
+ periods: FiscalPeriodSummary[]
211
+ }
212
+
213
+ export interface InitializeLedgerOptions {
214
+ closedThrough?: string | null
215
+ fiscalYearStartMonth?: number
216
+ earliestDataPeriod?: string | null
217
+ autoSeedSchedules?: boolean
218
+ note?: string | null
219
+ }
220
+
221
+ export interface InitializeLedgerResult {
222
+ fiscalCalendar: FiscalCalendarState
223
+ periodsCreated: number
224
+ warnings: string[]
225
+ }
226
+
227
+ export interface ClosePeriodOptions {
228
+ note?: string | null
229
+ allowStaleSync?: boolean
230
+ }
231
+
232
+ export interface ClosePeriodResult {
233
+ period: string
234
+ entriesPosted: number
235
+ targetAutoAdvanced: boolean
236
+ fiscalCalendar: FiscalCalendarState
237
+ }
238
+
239
+ export interface DraftLineItemView {
240
+ lineItemId: string
241
+ elementId: string
242
+ elementCode: string | null
243
+ elementName: string
244
+ debitAmount: number
245
+ creditAmount: number
246
+ description: string | null
247
+ }
248
+
249
+ export interface DraftEntryView {
250
+ entryId: string
251
+ postingDate: string
252
+ type: string
253
+ memo: string | null
254
+ provenance: string | null
255
+ sourceStructureId: string | null
256
+ sourceStructureName: string | null
257
+ lineItems: DraftLineItemView[]
258
+ totalDebit: number
259
+ totalCredit: number
260
+ balanced: boolean
261
+ }
262
+
263
+ export interface PeriodDraftsView {
264
+ period: string
265
+ periodStart: string
266
+ periodEnd: string
267
+ draftCount: number
268
+ totalDebit: number
269
+ totalCredit: number
270
+ allBalanced: boolean
271
+ drafts: DraftEntryView[]
272
+ }
273
+
274
+ export interface ManualClosingLineItem {
275
+ elementId: string
276
+ debitAmount?: number
277
+ creditAmount?: number
278
+ description?: string | null
279
+ }
280
+
281
+ export interface CreateManualClosingEntryOptions {
143
282
  postingDate: string
144
283
  memo: string
145
- debitElementId: string
146
- creditElementId: string
147
- amount: number
284
+ lineItems: ManualClosingLineItem[]
285
+ entryType?: LedgerEntryType
286
+ }
287
+
288
+ export interface TruncateScheduleOptions {
289
+ newEndDate: string
290
+ reason: string
291
+ }
292
+
293
+ export interface TruncateScheduleResult {
294
+ structureId: string
295
+ newEndDate: string
296
+ factsDeleted: number
297
+ reason: string
148
298
  }
149
299
 
150
300
  export interface CreateScheduleOptions {
@@ -156,7 +306,7 @@ export interface CreateScheduleOptions {
156
306
  entryTemplate: {
157
307
  debitElementId: string
158
308
  creditElementId: string
159
- entryType?: string
309
+ entryType?: LedgerEntryType
160
310
  memoTemplate?: string
161
311
  }
162
312
  taxonomyId?: string
@@ -717,7 +867,14 @@ export class LedgerClient {
717
867
  }
718
868
 
719
869
  /**
720
- * Create a draft closing entry from a schedule's facts for a period.
870
+ * Idempotently create (or refresh) a draft closing entry from a schedule's
871
+ * facts for a period. The `outcome` field describes what actually happened:
872
+ *
873
+ * - `created` — new draft
874
+ * - `unchanged` — existing draft still matches the schedule; no-op
875
+ * - `regenerated` — existing draft was stale; replaced
876
+ * - `removed` — schedule no longer covers this period; stale draft deleted
877
+ * - `skipped` — no existing draft and no in-scope fact; nothing to do
721
878
  */
722
879
  async createClosingEntry(
723
880
  graphId: string,
@@ -744,15 +901,7 @@ export class LedgerClient {
744
901
  }
745
902
 
746
903
  const data = response.data as ClosingEntryResponse
747
- return {
748
- entryId: data.entry_id,
749
- status: data.status,
750
- postingDate: data.posting_date,
751
- memo: data.memo,
752
- debitElementId: data.debit_element_id,
753
- creditElementId: data.credit_element_id,
754
- amount: data.amount,
755
- }
904
+ return toClosingEntry(data)
756
905
  }
757
906
 
758
907
  // ── Closing Book ─────────────────────────────────────────────────────
@@ -797,4 +946,304 @@ export class LedgerClient {
797
946
 
798
947
  return response.data as AccountRollupsResponse
799
948
  }
949
+
950
+ // ── Fiscal Calendar ─────────────────────────────────────────────────
951
+
952
+ /**
953
+ * Initialize the fiscal calendar for a graph. Creates FiscalPeriod rows
954
+ * for the data window, sets `closed_through` / `close_target`, and emits
955
+ * an `initialized` audit event. Fails with 409 if already initialized.
956
+ */
957
+ async initializeLedger(
958
+ graphId: string,
959
+ options?: InitializeLedgerOptions
960
+ ): Promise<InitializeLedgerResult> {
961
+ const body: InitializeLedgerRequest = {
962
+ closed_through: options?.closedThrough ?? null,
963
+ fiscal_year_start_month: options?.fiscalYearStartMonth,
964
+ earliest_data_period: options?.earliestDataPeriod ?? null,
965
+ auto_seed_schedules: options?.autoSeedSchedules,
966
+ note: options?.note ?? null,
967
+ }
968
+
969
+ const response = await initializeLedger({
970
+ path: { graph_id: graphId },
971
+ body,
972
+ })
973
+
974
+ if (response.error) {
975
+ throw new Error(`Initialize ledger failed: ${JSON.stringify(response.error)}`)
976
+ }
977
+
978
+ const data = response.data as InitializeLedgerResponse
979
+ return {
980
+ fiscalCalendar: toFiscalCalendarState(data.fiscal_calendar),
981
+ periodsCreated: data.periods_created ?? 0,
982
+ warnings: data.warnings ?? [],
983
+ }
984
+ }
985
+
986
+ /**
987
+ * Get the current fiscal calendar state — pointers, gap, closeable status.
988
+ */
989
+ async getFiscalCalendar(graphId: string): Promise<FiscalCalendarState> {
990
+ const response = await getFiscalCalendar({
991
+ path: { graph_id: graphId },
992
+ })
993
+
994
+ if (response.error) {
995
+ throw new Error(`Get fiscal calendar failed: ${JSON.stringify(response.error)}`)
996
+ }
997
+
998
+ return toFiscalCalendarState(response.data as FiscalCalendarResponse)
999
+ }
1000
+
1001
+ /**
1002
+ * Set the close target for a graph. Validates that the target is not in
1003
+ * the future and not before `closed_through`.
1004
+ */
1005
+ async setCloseTarget(
1006
+ graphId: string,
1007
+ period: string,
1008
+ note?: string | null
1009
+ ): Promise<FiscalCalendarState> {
1010
+ const body: SetCloseTargetRequest = {
1011
+ period,
1012
+ note: note ?? null,
1013
+ }
1014
+
1015
+ const response = await setCloseTarget({
1016
+ path: { graph_id: graphId },
1017
+ body,
1018
+ })
1019
+
1020
+ if (response.error) {
1021
+ throw new Error(`Set close target failed: ${JSON.stringify(response.error)}`)
1022
+ }
1023
+
1024
+ return toFiscalCalendarState(response.data as FiscalCalendarResponse)
1025
+ }
1026
+
1027
+ /**
1028
+ * Close a fiscal period — the final commit action.
1029
+ *
1030
+ * Validates closeable gates, transitions all draft entries in the period
1031
+ * to `posted`, marks the FiscalPeriod closed, and advances `closed_through`
1032
+ * (auto-advancing `close_target` when reached).
1033
+ */
1034
+ async closePeriod(
1035
+ graphId: string,
1036
+ period: string,
1037
+ options?: ClosePeriodOptions
1038
+ ): Promise<ClosePeriodResult> {
1039
+ const body: ClosePeriodRequest = {
1040
+ note: options?.note ?? null,
1041
+ allow_stale_sync: options?.allowStaleSync,
1042
+ }
1043
+
1044
+ const response = await closeFiscalPeriod({
1045
+ path: { graph_id: graphId, period },
1046
+ body,
1047
+ })
1048
+
1049
+ if (response.error) {
1050
+ throw new Error(`Close period failed: ${JSON.stringify(response.error)}`)
1051
+ }
1052
+
1053
+ const data = response.data as ClosePeriodResponse
1054
+ return {
1055
+ period: data.period,
1056
+ entriesPosted: data.entries_posted ?? 0,
1057
+ targetAutoAdvanced: data.target_auto_advanced ?? false,
1058
+ fiscalCalendar: toFiscalCalendarState(data.fiscal_calendar),
1059
+ }
1060
+ }
1061
+
1062
+ /**
1063
+ * Reopen a closed fiscal period. Requires a non-empty `reason` for the
1064
+ * audit log. Posted entries stay posted; the period transitions to
1065
+ * `closing` so the user can post adjustments and re-close.
1066
+ */
1067
+ async reopenPeriod(
1068
+ graphId: string,
1069
+ period: string,
1070
+ reason: string,
1071
+ note?: string | null
1072
+ ): Promise<FiscalCalendarState> {
1073
+ const body: ReopenPeriodRequest = {
1074
+ reason,
1075
+ note: note ?? null,
1076
+ }
1077
+
1078
+ const response = await reopenFiscalPeriod({
1079
+ path: { graph_id: graphId, period },
1080
+ body,
1081
+ })
1082
+
1083
+ if (response.error) {
1084
+ throw new Error(`Reopen period failed: ${JSON.stringify(response.error)}`)
1085
+ }
1086
+
1087
+ return toFiscalCalendarState(response.data as FiscalCalendarResponse)
1088
+ }
1089
+
1090
+ /**
1091
+ * List all draft entries in a fiscal period for review before close.
1092
+ * Fully expanded with line items, element metadata, and per-entry balance.
1093
+ *
1094
+ * Pure read — call repeatedly without side effects.
1095
+ */
1096
+ async listPeriodDrafts(graphId: string, period: string): Promise<PeriodDraftsView> {
1097
+ const response = await listPeriodDrafts({
1098
+ path: { graph_id: graphId, period },
1099
+ })
1100
+
1101
+ if (response.error) {
1102
+ throw new Error(`List period drafts failed: ${JSON.stringify(response.error)}`)
1103
+ }
1104
+
1105
+ const data = response.data as PeriodDraftsResponse
1106
+ return {
1107
+ period: data.period,
1108
+ periodStart: data.period_start,
1109
+ periodEnd: data.period_end,
1110
+ draftCount: data.draft_count,
1111
+ totalDebit: data.total_debit,
1112
+ totalCredit: data.total_credit,
1113
+ allBalanced: data.all_balanced,
1114
+ drafts: (data.drafts ?? []).map((d) => ({
1115
+ entryId: d.entry_id,
1116
+ postingDate: d.posting_date,
1117
+ type: d.type,
1118
+ memo: d.memo ?? null,
1119
+ provenance: d.provenance ?? null,
1120
+ sourceStructureId: d.source_structure_id ?? null,
1121
+ sourceStructureName: d.source_structure_name ?? null,
1122
+ lineItems: d.line_items.map((li) => ({
1123
+ lineItemId: li.line_item_id,
1124
+ elementId: li.element_id,
1125
+ elementCode: li.element_code ?? null,
1126
+ elementName: li.element_name,
1127
+ debitAmount: li.debit_amount,
1128
+ creditAmount: li.credit_amount,
1129
+ description: li.description ?? null,
1130
+ })),
1131
+ totalDebit: d.total_debit,
1132
+ totalCredit: d.total_credit,
1133
+ balanced: d.balanced,
1134
+ })),
1135
+ }
1136
+ }
1137
+
1138
+ // ── Schedule mutations ──────────────────────────────────────────────
1139
+
1140
+ /**
1141
+ * Truncate a schedule — end it early by deleting facts with
1142
+ * `period_start > new_end_date`, along with any stale draft entries they
1143
+ * produced. Historical posted facts are preserved.
1144
+ *
1145
+ * `new_end_date` must be a month-end date (service enforces this).
1146
+ */
1147
+ async truncateSchedule(
1148
+ graphId: string,
1149
+ structureId: string,
1150
+ options: TruncateScheduleOptions
1151
+ ): Promise<TruncateScheduleResult> {
1152
+ const body: TruncateScheduleRequest = {
1153
+ new_end_date: options.newEndDate,
1154
+ reason: options.reason,
1155
+ }
1156
+
1157
+ const response = await truncateSchedule({
1158
+ path: { graph_id: graphId, structure_id: structureId },
1159
+ body,
1160
+ })
1161
+
1162
+ if (response.error) {
1163
+ throw new Error(`Truncate schedule failed: ${JSON.stringify(response.error)}`)
1164
+ }
1165
+
1166
+ const data = response.data as TruncateScheduleResponse
1167
+ return {
1168
+ structureId: data.structure_id,
1169
+ newEndDate: data.new_end_date,
1170
+ factsDeleted: data.facts_deleted,
1171
+ reason: data.reason,
1172
+ }
1173
+ }
1174
+
1175
+ /**
1176
+ * Create a manual draft closing entry with arbitrary balanced line items.
1177
+ * Not tied to a schedule — used for disposals, adjustments, and other
1178
+ * one-off closing events.
1179
+ *
1180
+ * Line items must sum to balanced debits/credits. Rejects entries
1181
+ * targeting an already-closed period.
1182
+ */
1183
+ async createManualClosingEntry(
1184
+ graphId: string,
1185
+ options: CreateManualClosingEntryOptions
1186
+ ): Promise<ClosingEntry> {
1187
+ const body: CreateManualClosingEntryRequest = {
1188
+ posting_date: options.postingDate,
1189
+ memo: options.memo,
1190
+ entry_type: options.entryType,
1191
+ line_items: options.lineItems.map((li) => ({
1192
+ element_id: li.elementId,
1193
+ debit_amount: li.debitAmount ?? 0,
1194
+ credit_amount: li.creditAmount ?? 0,
1195
+ description: li.description ?? null,
1196
+ })),
1197
+ }
1198
+
1199
+ const response = await createManualClosingEntry({
1200
+ path: { graph_id: graphId },
1201
+ body,
1202
+ })
1203
+
1204
+ if (response.error) {
1205
+ throw new Error(`Create manual closing entry failed: ${JSON.stringify(response.error)}`)
1206
+ }
1207
+
1208
+ return toClosingEntry(response.data as ClosingEntryResponse)
1209
+ }
1210
+ }
1211
+
1212
+ // ── Internal helpers ──────────────────────────────────────────────────
1213
+
1214
+ function toClosingEntry(data: ClosingEntryResponse): ClosingEntry {
1215
+ return {
1216
+ outcome: data.outcome as ClosingEntryOutcome,
1217
+ entryId: data.entry_id ?? null,
1218
+ status: data.status ?? null,
1219
+ postingDate: data.posting_date ?? null,
1220
+ memo: data.memo ?? null,
1221
+ debitElementId: data.debit_element_id ?? null,
1222
+ creditElementId: data.credit_element_id ?? null,
1223
+ amount: data.amount ?? null,
1224
+ reason: data.reason ?? null,
1225
+ }
1226
+ }
1227
+
1228
+ function toFiscalCalendarState(data: FiscalCalendarResponse): FiscalCalendarState {
1229
+ return {
1230
+ graphId: data.graph_id,
1231
+ fiscalYearStartMonth: data.fiscal_year_start_month,
1232
+ closedThrough: data.closed_through ?? null,
1233
+ closeTarget: data.close_target ?? null,
1234
+ gapPeriods: data.gap_periods ?? 0,
1235
+ catchUpSequence: data.catch_up_sequence ?? [],
1236
+ closeableNow: data.closeable_now ?? false,
1237
+ blockers: data.blockers ?? [],
1238
+ lastCloseAt: data.last_close_at ?? null,
1239
+ initializedAt: data.initialized_at ?? null,
1240
+ lastSyncAt: data.last_sync_at ?? null,
1241
+ periods: (data.periods ?? []).map((p) => ({
1242
+ name: p.name,
1243
+ startDate: p.start_date,
1244
+ endDate: p.end_date,
1245
+ status: p.status,
1246
+ closedAt: p.closed_at ?? null,
1247
+ })),
1248
+ }
800
1249
  }