@robosystems/client 0.2.47 → 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.
@@ -432,7 +432,14 @@ class LedgerClient {
432
432
  };
433
433
  }
434
434
  /**
435
- * Create a draft closing entry from a schedule's facts for a period.
435
+ * Idempotently create (or refresh) a draft closing entry from a schedule's
436
+ * facts for a period. The `outcome` field describes what actually happened:
437
+ *
438
+ * - `created` — new draft
439
+ * - `unchanged` — existing draft still matches the schedule; no-op
440
+ * - `regenerated` — existing draft was stale; replaced
441
+ * - `removed` — schedule no longer covers this period; stale draft deleted
442
+ * - `skipped` — no existing draft and no in-scope fact; nothing to do
436
443
  */
437
444
  async createClosingEntry(graphId, structureId, postingDate, periodStart, periodEnd, memo) {
438
445
  const body = {
@@ -449,15 +456,282 @@ class LedgerClient {
449
456
  throw new Error(`Create closing entry failed: ${JSON.stringify(response.error)}`);
450
457
  }
451
458
  const data = response.data;
459
+ return toClosingEntry(data);
460
+ }
461
+ // ── Closing Book ─────────────────────────────────────────────────────
462
+ /**
463
+ * Get all closing book structure categories for the sidebar.
464
+ * Returns statements, account rollups, schedules, trial balance,
465
+ * and period close grouped into categories.
466
+ */
467
+ async getClosingBookStructures(graphId) {
468
+ const response = await (0, sdk_gen_1.getClosingBookStructures)({
469
+ path: { graph_id: graphId },
470
+ });
471
+ if (response.error) {
472
+ throw new Error(`Get closing book structures failed: ${JSON.stringify(response.error)}`);
473
+ }
474
+ return response.data;
475
+ }
476
+ /**
477
+ * Get account rollups — CoA accounts grouped by reporting element with balances.
478
+ * Shows how company-specific accounts roll up to standardized reporting lines.
479
+ */
480
+ async getAccountRollups(graphId, options) {
481
+ const response = await (0, sdk_gen_1.getAccountRollups)({
482
+ path: { graph_id: graphId },
483
+ query: {
484
+ mapping_id: options?.mappingId,
485
+ start_date: options?.startDate,
486
+ end_date: options?.endDate,
487
+ },
488
+ });
489
+ if (response.error) {
490
+ throw new Error(`Get account rollups failed: ${JSON.stringify(response.error)}`);
491
+ }
492
+ return response.data;
493
+ }
494
+ // ── Fiscal Calendar ─────────────────────────────────────────────────
495
+ /**
496
+ * Initialize the fiscal calendar for a graph. Creates FiscalPeriod rows
497
+ * for the data window, sets `closed_through` / `close_target`, and emits
498
+ * an `initialized` audit event. Fails with 409 if already initialized.
499
+ */
500
+ async initializeLedger(graphId, options) {
501
+ const body = {
502
+ closed_through: options?.closedThrough ?? null,
503
+ fiscal_year_start_month: options?.fiscalYearStartMonth,
504
+ earliest_data_period: options?.earliestDataPeriod ?? null,
505
+ auto_seed_schedules: options?.autoSeedSchedules,
506
+ note: options?.note ?? null,
507
+ };
508
+ const response = await (0, sdk_gen_1.initializeLedger)({
509
+ path: { graph_id: graphId },
510
+ body,
511
+ });
512
+ if (response.error) {
513
+ throw new Error(`Initialize ledger failed: ${JSON.stringify(response.error)}`);
514
+ }
515
+ const data = response.data;
452
516
  return {
453
- entryId: data.entry_id,
454
- status: data.status,
455
- postingDate: data.posting_date,
456
- memo: data.memo,
457
- debitElementId: data.debit_element_id,
458
- creditElementId: data.credit_element_id,
459
- amount: data.amount,
517
+ fiscalCalendar: toFiscalCalendarState(data.fiscal_calendar),
518
+ periodsCreated: data.periods_created ?? 0,
519
+ warnings: data.warnings ?? [],
460
520
  };
461
521
  }
522
+ /**
523
+ * Get the current fiscal calendar state — pointers, gap, closeable status.
524
+ */
525
+ async getFiscalCalendar(graphId) {
526
+ const response = await (0, sdk_gen_1.getFiscalCalendar)({
527
+ path: { graph_id: graphId },
528
+ });
529
+ if (response.error) {
530
+ throw new Error(`Get fiscal calendar failed: ${JSON.stringify(response.error)}`);
531
+ }
532
+ return toFiscalCalendarState(response.data);
533
+ }
534
+ /**
535
+ * Set the close target for a graph. Validates that the target is not in
536
+ * the future and not before `closed_through`.
537
+ */
538
+ async setCloseTarget(graphId, period, note) {
539
+ const body = {
540
+ period,
541
+ note: note ?? null,
542
+ };
543
+ const response = await (0, sdk_gen_1.setCloseTarget)({
544
+ path: { graph_id: graphId },
545
+ body,
546
+ });
547
+ if (response.error) {
548
+ throw new Error(`Set close target failed: ${JSON.stringify(response.error)}`);
549
+ }
550
+ return toFiscalCalendarState(response.data);
551
+ }
552
+ /**
553
+ * Close a fiscal period — the final commit action.
554
+ *
555
+ * Validates closeable gates, transitions all draft entries in the period
556
+ * to `posted`, marks the FiscalPeriod closed, and advances `closed_through`
557
+ * (auto-advancing `close_target` when reached).
558
+ */
559
+ async closePeriod(graphId, period, options) {
560
+ const body = {
561
+ note: options?.note ?? null,
562
+ allow_stale_sync: options?.allowStaleSync,
563
+ };
564
+ const response = await (0, sdk_gen_1.closeFiscalPeriod)({
565
+ path: { graph_id: graphId, period },
566
+ body,
567
+ });
568
+ if (response.error) {
569
+ throw new Error(`Close period failed: ${JSON.stringify(response.error)}`);
570
+ }
571
+ const data = response.data;
572
+ return {
573
+ period: data.period,
574
+ entriesPosted: data.entries_posted ?? 0,
575
+ targetAutoAdvanced: data.target_auto_advanced ?? false,
576
+ fiscalCalendar: toFiscalCalendarState(data.fiscal_calendar),
577
+ };
578
+ }
579
+ /**
580
+ * Reopen a closed fiscal period. Requires a non-empty `reason` for the
581
+ * audit log. Posted entries stay posted; the period transitions to
582
+ * `closing` so the user can post adjustments and re-close.
583
+ */
584
+ async reopenPeriod(graphId, period, reason, note) {
585
+ const body = {
586
+ reason,
587
+ note: note ?? null,
588
+ };
589
+ const response = await (0, sdk_gen_1.reopenFiscalPeriod)({
590
+ path: { graph_id: graphId, period },
591
+ body,
592
+ });
593
+ if (response.error) {
594
+ throw new Error(`Reopen period failed: ${JSON.stringify(response.error)}`);
595
+ }
596
+ return toFiscalCalendarState(response.data);
597
+ }
598
+ /**
599
+ * List all draft entries in a fiscal period for review before close.
600
+ * Fully expanded with line items, element metadata, and per-entry balance.
601
+ *
602
+ * Pure read — call repeatedly without side effects.
603
+ */
604
+ async listPeriodDrafts(graphId, period) {
605
+ const response = await (0, sdk_gen_1.listPeriodDrafts)({
606
+ path: { graph_id: graphId, period },
607
+ });
608
+ if (response.error) {
609
+ throw new Error(`List period drafts failed: ${JSON.stringify(response.error)}`);
610
+ }
611
+ const data = response.data;
612
+ return {
613
+ period: data.period,
614
+ periodStart: data.period_start,
615
+ periodEnd: data.period_end,
616
+ draftCount: data.draft_count,
617
+ totalDebit: data.total_debit,
618
+ totalCredit: data.total_credit,
619
+ allBalanced: data.all_balanced,
620
+ drafts: (data.drafts ?? []).map((d) => ({
621
+ entryId: d.entry_id,
622
+ postingDate: d.posting_date,
623
+ type: d.type,
624
+ memo: d.memo ?? null,
625
+ provenance: d.provenance ?? null,
626
+ sourceStructureId: d.source_structure_id ?? null,
627
+ sourceStructureName: d.source_structure_name ?? null,
628
+ lineItems: d.line_items.map((li) => ({
629
+ lineItemId: li.line_item_id,
630
+ elementId: li.element_id,
631
+ elementCode: li.element_code ?? null,
632
+ elementName: li.element_name,
633
+ debitAmount: li.debit_amount,
634
+ creditAmount: li.credit_amount,
635
+ description: li.description ?? null,
636
+ })),
637
+ totalDebit: d.total_debit,
638
+ totalCredit: d.total_credit,
639
+ balanced: d.balanced,
640
+ })),
641
+ };
642
+ }
643
+ // ── Schedule mutations ──────────────────────────────────────────────
644
+ /**
645
+ * Truncate a schedule — end it early by deleting facts with
646
+ * `period_start > new_end_date`, along with any stale draft entries they
647
+ * produced. Historical posted facts are preserved.
648
+ *
649
+ * `new_end_date` must be a month-end date (service enforces this).
650
+ */
651
+ async truncateSchedule(graphId, structureId, options) {
652
+ const body = {
653
+ new_end_date: options.newEndDate,
654
+ reason: options.reason,
655
+ };
656
+ const response = await (0, sdk_gen_1.truncateSchedule)({
657
+ path: { graph_id: graphId, structure_id: structureId },
658
+ body,
659
+ });
660
+ if (response.error) {
661
+ throw new Error(`Truncate schedule failed: ${JSON.stringify(response.error)}`);
662
+ }
663
+ const data = response.data;
664
+ return {
665
+ structureId: data.structure_id,
666
+ newEndDate: data.new_end_date,
667
+ factsDeleted: data.facts_deleted,
668
+ reason: data.reason,
669
+ };
670
+ }
671
+ /**
672
+ * Create a manual draft closing entry with arbitrary balanced line items.
673
+ * Not tied to a schedule — used for disposals, adjustments, and other
674
+ * one-off closing events.
675
+ *
676
+ * Line items must sum to balanced debits/credits. Rejects entries
677
+ * targeting an already-closed period.
678
+ */
679
+ async createManualClosingEntry(graphId, options) {
680
+ const body = {
681
+ posting_date: options.postingDate,
682
+ memo: options.memo,
683
+ entry_type: options.entryType,
684
+ line_items: options.lineItems.map((li) => ({
685
+ element_id: li.elementId,
686
+ debit_amount: li.debitAmount ?? 0,
687
+ credit_amount: li.creditAmount ?? 0,
688
+ description: li.description ?? null,
689
+ })),
690
+ };
691
+ const response = await (0, sdk_gen_1.createManualClosingEntry)({
692
+ path: { graph_id: graphId },
693
+ body,
694
+ });
695
+ if (response.error) {
696
+ throw new Error(`Create manual closing entry failed: ${JSON.stringify(response.error)}`);
697
+ }
698
+ return toClosingEntry(response.data);
699
+ }
462
700
  }
463
701
  exports.LedgerClient = LedgerClient;
702
+ // ── Internal helpers ──────────────────────────────────────────────────
703
+ function toClosingEntry(data) {
704
+ return {
705
+ outcome: data.outcome,
706
+ entryId: data.entry_id ?? null,
707
+ status: data.status ?? null,
708
+ postingDate: data.posting_date ?? null,
709
+ memo: data.memo ?? null,
710
+ debitElementId: data.debit_element_id ?? null,
711
+ creditElementId: data.credit_element_id ?? null,
712
+ amount: data.amount ?? null,
713
+ reason: data.reason ?? null,
714
+ };
715
+ }
716
+ function toFiscalCalendarState(data) {
717
+ return {
718
+ graphId: data.graph_id,
719
+ fiscalYearStartMonth: data.fiscal_year_start_month,
720
+ closedThrough: data.closed_through ?? null,
721
+ closeTarget: data.close_target ?? null,
722
+ gapPeriods: data.gap_periods ?? 0,
723
+ catchUpSequence: data.catch_up_sequence ?? [],
724
+ closeableNow: data.closeable_now ?? false,
725
+ blockers: data.blockers ?? [],
726
+ lastCloseAt: data.last_close_at ?? null,
727
+ initializedAt: data.initialized_at ?? null,
728
+ lastSyncAt: data.last_sync_at ?? null,
729
+ periods: (data.periods ?? []).map((p) => ({
730
+ name: p.name,
731
+ startDate: p.start_date,
732
+ endDate: p.end_date,
733
+ status: p.status,
734
+ closedAt: p.closed_at ?? null,
735
+ })),
736
+ };
737
+ }