av6-core 1.7.15 → 1.7.17

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/dist/index.d.mts CHANGED
@@ -6,6 +6,7 @@ import { Readable } from 'stream';
6
6
  import winston from 'winston';
7
7
  import * as PrismaNamespace from '@prisma/client';
8
8
  import { PrismaClient, Prisma } from '@prisma/client';
9
+ import EventEmitter from 'events';
9
10
 
10
11
  declare enum ErrorMessageType {
11
12
  INVALID_ID = "Invalid id: %1 Numeric value expected.",
@@ -527,6 +528,42 @@ declare enum ApprovalStatus {
527
528
  REJECTED = 3,
528
529
  CANCELLED = 4
529
530
  }
531
+ declare enum FlowType {
532
+ STATUS = 0,
533
+ CO_PAY = 1,
534
+ INDIVIDUAL_DISCOUNT = 2,
535
+ OVERALL_DISCOUNT = 3,
536
+ SPECIAL_DISCOUNT = 4,
537
+ REFUND = 5
538
+ }
539
+ type StepType = "MIN_MAX" | "NORMAL";
540
+ type ApprovalStep = {
541
+ id?: number;
542
+ flowId: number;
543
+ level: number;
544
+ minAmount?: Decimal | DecimalJsLike | number | string | null;
545
+ maxAmount?: Decimal | DecimalJsLike | number | string | null;
546
+ stepType?: StepType;
547
+ config?: InputJsonValue;
548
+ childConfig?: InputJsonValue;
549
+ isActive?: boolean;
550
+ createdBy?: number | null;
551
+ updatedBy?: number | null;
552
+ createdAt?: Date | string;
553
+ updatedAt?: Date | string;
554
+ };
555
+ type ApproverMapping = {
556
+ id?: number;
557
+ staffId?: number | null;
558
+ stepId: number;
559
+ ccId: number;
560
+ roleId?: number | null;
561
+ isActive?: boolean;
562
+ createdBy?: number | null;
563
+ updatedBy?: number | null;
564
+ createdAt?: Date | string;
565
+ updatedAt?: Date | string;
566
+ };
530
567
  type ApprovalInstance = {
531
568
  id?: number;
532
569
  flowId: number;
@@ -544,6 +581,28 @@ type ApprovalInstance = {
544
581
  createdAt?: Date | string;
545
582
  updatedAt?: Date | string;
546
583
  };
584
+ type ApprovalFlow = {
585
+ name: string;
586
+ id: number;
587
+ subjectType: string;
588
+ service: string;
589
+ isActive: boolean;
590
+ createdBy: number | null;
591
+ updatedBy: number | null;
592
+ createdAt: Date;
593
+ updatedAt: Date;
594
+ flowType: FlowType;
595
+ };
596
+ type ApprovalAction = {
597
+ id: number;
598
+ isActive: boolean;
599
+ level: number;
600
+ instanceId: number;
601
+ actedBy: number;
602
+ statusAfter: ApprovalStatus;
603
+ comment: string | null;
604
+ actedAt: Date;
605
+ };
547
606
  type LevelReadyEvt = {
548
607
  instanceId: number;
549
608
  subjectType: string;
@@ -566,6 +625,195 @@ declare global {
566
625
  "approval:REJECTED": (i: ApprovalInstance) => void;
567
626
  }
568
627
  }
628
+ interface CreateApprovalFlow {
629
+ subjectType: string;
630
+ name: string;
631
+ }
632
+ interface UpdateApprovalFlow extends CreateApprovalFlow {
633
+ id: number;
634
+ }
635
+ interface IApprovalStep {
636
+ id?: number;
637
+ flowId: number;
638
+ subjectType: string;
639
+ }
640
+ type FlowWithStepsResponse = ApprovalFlow & {
641
+ steps: ApprovalStep[];
642
+ };
643
+ interface FlowWithSelectedStepResponse extends ApprovalFlow {
644
+ step: ApprovalStep | null;
645
+ }
646
+ interface ActInput {
647
+ instanceId: number;
648
+ approverId: number;
649
+ action: "APPROVE" | "REJECT";
650
+ ccId: number;
651
+ comment?: string;
652
+ }
653
+ interface RawFlowWithSelectedStepResponse {
654
+ flowId: number;
655
+ stepId: number;
656
+ level: number;
657
+ minAmount: number;
658
+ maxAmount: number;
659
+ stepType: string;
660
+ ccId: number;
661
+ service: string;
662
+ }
663
+ interface StartFlowReq {
664
+ service: string;
665
+ subjectType: string;
666
+ subjectId: number;
667
+ netTotal: number;
668
+ ccId: number;
669
+ refNo: string;
670
+ level?: number;
671
+ extra?: Record<string, string | number | boolean | null>;
672
+ }
673
+ interface NotificationEvent {
674
+ instanceId: number;
675
+ subjectType: string;
676
+ service: string;
677
+ subjectId: number;
678
+ level: number;
679
+ ccId: number;
680
+ approvers: ApproverMapping[];
681
+ }
682
+ interface CommonApproveReq {
683
+ service: string;
684
+ subjectType: string;
685
+ id: number;
686
+ comment?: string;
687
+ ccId: number;
688
+ approverId: number;
689
+ approveType: "APPROVE" | "REJECT";
690
+ }
691
+ interface CommonGetApprovalActionReq {
692
+ service: string;
693
+ subjectType: string;
694
+ id: number;
695
+ }
696
+ interface GetPendingApprovalReq {
697
+ service: string;
698
+ ccId: number;
699
+ staffId: number;
700
+ }
701
+ interface IParentConfigJSON {
702
+ tableName: string;
703
+ approvedAt?: string;
704
+ approvedBy?: string;
705
+ approvalNote?: string;
706
+ status?: string;
707
+ refundAmount?: string;
708
+ discountType?: string;
709
+ discountValue?: string;
710
+ discountAmount?: string;
711
+ totalCopaymentAmount?: string;
712
+ isApproved?: string;
713
+ paymentStatus?: string;
714
+ }
715
+ interface IParentConfigData {
716
+ approvedAt?: string | null;
717
+ approvedBy?: string | null;
718
+ approvalNote?: string | null;
719
+ status?: string | null;
720
+ refundAmount?: number | null;
721
+ discountType?: string | null;
722
+ discountValue?: number | null;
723
+ discountAmount?: number | null;
724
+ totalCopaymentAmount?: number | null;
725
+ isApproved?: string | null;
726
+ paymentStatus?: string | null;
727
+ }
728
+ interface IChildConfigJSON {
729
+ tableName: string;
730
+ discountType?: string;
731
+ discountValue?: string;
732
+ discountAmount?: string;
733
+ copayValue?: string;
734
+ copayType?: string;
735
+ copayAmount?: string;
736
+ coPayModificationStatus?: string;
737
+ }
738
+ interface IChildConfigData {
739
+ discountType?: string | null;
740
+ discountValue?: number | null;
741
+ discountAmount?: number | null;
742
+ copayValue?: number | null;
743
+ copayType?: string | null;
744
+ copayAmount?: number | null;
745
+ coPayModificationStatus?: string | null;
746
+ }
747
+ interface ICommonApprovalUpdate {
748
+ id: number;
749
+ flowType: FlowType;
750
+ status?: string | null;
751
+ approvedAt?: string | null;
752
+ approvedBy?: string | null;
753
+ approvalNote?: string | null;
754
+ refundAmount?: number | null;
755
+ }
756
+ interface EventInstance {
757
+ subjectType: string;
758
+ service: string;
759
+ instanceId: number;
760
+ flowType: FlowType;
761
+ subjectId: number;
762
+ approverId: number;
763
+ step: ApprovalStep;
764
+ comment?: string;
765
+ }
766
+ interface ApprovalInstanceByUser {
767
+ id: number;
768
+ flowId: number;
769
+ subjectType: string;
770
+ service: string;
771
+ subjectId: number;
772
+ refNo: number;
773
+ currentStepId: number;
774
+ netTotal: number;
775
+ status: ApprovalStatus;
776
+ createdBy: string;
777
+ createdAt: Date;
778
+ level: number;
779
+ flowType: FlowType;
780
+ flowName: string;
781
+ extra?: string | null;
782
+ }
783
+ interface ApprovalActionDto extends Omit<ApprovalAction, "actedBy"> {
784
+ actedByDetails: EmployeeCache | null;
785
+ }
786
+ interface GetMyApprovalFlow extends Omit<CommonFilterWithDate, "sortBy"> {
787
+ staffId: number;
788
+ ccId: number;
789
+ service: string;
790
+ status?: ApprovalStatus[];
791
+ flowType?: FlowType;
792
+ }
793
+ interface ApprovalDeps {
794
+ helpers: Helpers;
795
+ logger: winston.Logger;
796
+ requestStorage: AsyncLocalStorage<Store>;
797
+ prisma: PrismaClient;
798
+ eventBus: EventEmitter;
799
+ }
800
+ type PrismaTransactionClient = Omit<PrismaClient, "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends">;
801
+
802
+ declare class ApprovalService {
803
+ private deps;
804
+ private approvalRepo;
805
+ constructor(deps: ApprovalDeps);
806
+ startFlow(tx: PrismaClient | PrismaTransactionClient, { service, subjectType, subjectId, netTotal, ccId, refNo, level, extra }: StartFlowReq): Promise<void>;
807
+ lastLevel(steps: ApprovalStep[]): Promise<number>;
808
+ /** Approver clicks “Approve” or “Reject”. */
809
+ act({ instanceId, approverId, action, ccId, comment }: ActInput): Promise<any>;
810
+ private emitEvents;
811
+ private assertPermission;
812
+ }
813
+
814
+ declare const approvalRepository: (helpers: Helpers) => {
815
+ findMatchingFlow(tx: PrismaClient | Prisma.TransactionClient, type: string, service: string, ccId: number, netTotal: number, level?: number): Promise<RawFlowWithSelectedStepResponse | null>;
816
+ };
569
817
 
570
818
  declare function customOmit<T extends object, K extends keyof T>(obj: T, keys: K[]): {
571
819
  rest: Omit<T, K>;
@@ -760,4 +1008,4 @@ declare class AuditProxy<Module extends string = "OPD" | "PROCEDURE" | "GENERAL_
760
1008
  createAuditedService<T extends object>(serviceName: string, service: T): T;
761
1009
  }
762
1010
 
763
- export { type AuditContext, type AuditContextProvider, AuditCore, type AuditLogPayload, AuditLogger, AuditProxy, type BulkAtomicResult, type BulkConfig, type BulkConflictConfig, type BulkOnConflict, type CacheAdapter, type CalculationRes, type ColValue, type CommonCreateRequestRepository, type CommonExcelRequest, type CommonFilterRequest, type CommonFilterWithDate, type CommonServiceResponse, type CommonUpdateRequestRepository, type Config, type Context, type CreateUINConfigRequest, type CrudContext, type CrudDelegate, type DataType, type DeepMerge, type DeleteParams, type DeleteRequestRepository, type Deps, type DropdownRequest, type DropdownRequestService, type DtoFromMapping, type DtoNullOnMissing, type DynamicCreateInput, type DynamicCrudConfig, type DynamicShortCode, type DynamicUpdateInput, type EmitPayload, type EmployeeCache, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FieldConfig, type FieldRules, type FieldType, type FixedMap, type FixedSearchRequest, type FixedSearchRequestService, type Helpers, type ImportExcel, type ImportExcelRequestService, type LockUnlockParams, type LockUnlockRequestRepository, type LogicNode, type Mapper, type MergeAll, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type Op, type PaginatedResponse, type PathToSelectWithSelect, type PathValue, type PathsToSelectWithSelect, type Presence, type Recipient, type RelationConfig, type RelationStrategy, type RelationWriteConfig, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type SingleValidationMapping, type SourcePath, type Store, type ToggleActive, type TxClient, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UnionToIntersection, type UniqueConfig, type UpdateConfigByCodeInput, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, commonService, convertArrayPatternToEachBlocksGeneric, customOmit, findDifferences, formatDatesDeep, fromTimestampToSqlDatetime, getDynamicValue, getNestedValue, getNestedValueV2, getPattern, interpolate, objectTo2DArray, renderEmailTemplate, renderTemplate, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
1011
+ export { type ActInput, type ApprovalAction, type ApprovalActionDto, type ApprovalDeps, type ApprovalFlow, type ApprovalInstance, type ApprovalInstanceByUser, ApprovalService, ApprovalStatus, type ApprovalStep, type ApproverMapping, type AuditContext, type AuditContextProvider, AuditCore, type AuditLogPayload, AuditLogger, AuditProxy, type BulkAtomicResult, type BulkConfig, type BulkConflictConfig, type BulkOnConflict, type CacheAdapter, type CalculationRes, type ColValue, type CommonApproveReq, type CommonCreateRequestRepository, type CommonExcelRequest, type CommonFilterRequest, type CommonFilterWithDate, type CommonGetApprovalActionReq, type CommonServiceResponse, type CommonUpdateRequestRepository, type Config, type Context, type CreateApprovalFlow, type CreateUINConfigRequest, type CrudContext, type CrudDelegate, type DataType, type DeepMerge, type DeleteParams, type DeleteRequestRepository, type Deps, type DropdownRequest, type DropdownRequestService, type DtoFromMapping, type DtoNullOnMissing, type DynamicCreateInput, type DynamicCrudConfig, type DynamicShortCode, type DynamicUpdateInput, type EmitPayload, type EmployeeCache, type EventInstance, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FieldConfig, type FieldRules, type FieldType, type FixedMap, type FixedSearchRequest, type FixedSearchRequestService, FlowType, type FlowWithSelectedStepResponse, type FlowWithStepsResponse, type GetMyApprovalFlow, type GetPendingApprovalReq, type Helpers, type IApprovalStep, type IChildConfigData, type IChildConfigJSON, type ICommonApprovalUpdate, type IParentConfigData, type IParentConfigJSON, type ImportExcel, type ImportExcelRequestService, type LevelDoneEvt, type LevelReadyEvt, type LockUnlockParams, type LockUnlockRequestRepository, type LogicNode, type Mapper, type MergeAll, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type NotificationEvent, type Op, type PaginatedResponse, type PathToSelectWithSelect, type PathValue, type PathsToSelectWithSelect, type Presence, type PrismaTransactionClient, type RawFlowWithSelectedStepResponse, type Recipient, type RelationConfig, type RelationStrategy, type RelationWriteConfig, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type SingleValidationMapping, type SourcePath, type StartFlowReq, type StepType, type Store, type ToggleActive, type TxClient, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UnionToIntersection, type UniqueConfig, type UpdateApprovalFlow, type UpdateConfigByCodeInput, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, approvalRepository, commonService, convertArrayPatternToEachBlocksGeneric, customOmit, findDifferences, formatDatesDeep, fromTimestampToSqlDatetime, getDynamicValue, getNestedValue, getNestedValueV2, getPattern, interpolate, objectTo2DArray, renderEmailTemplate, renderTemplate, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { Readable } from 'stream';
6
6
  import winston from 'winston';
7
7
  import * as PrismaNamespace from '@prisma/client';
8
8
  import { PrismaClient, Prisma } from '@prisma/client';
9
+ import EventEmitter from 'events';
9
10
 
10
11
  declare enum ErrorMessageType {
11
12
  INVALID_ID = "Invalid id: %1 Numeric value expected.",
@@ -527,6 +528,42 @@ declare enum ApprovalStatus {
527
528
  REJECTED = 3,
528
529
  CANCELLED = 4
529
530
  }
531
+ declare enum FlowType {
532
+ STATUS = 0,
533
+ CO_PAY = 1,
534
+ INDIVIDUAL_DISCOUNT = 2,
535
+ OVERALL_DISCOUNT = 3,
536
+ SPECIAL_DISCOUNT = 4,
537
+ REFUND = 5
538
+ }
539
+ type StepType = "MIN_MAX" | "NORMAL";
540
+ type ApprovalStep = {
541
+ id?: number;
542
+ flowId: number;
543
+ level: number;
544
+ minAmount?: Decimal | DecimalJsLike | number | string | null;
545
+ maxAmount?: Decimal | DecimalJsLike | number | string | null;
546
+ stepType?: StepType;
547
+ config?: InputJsonValue;
548
+ childConfig?: InputJsonValue;
549
+ isActive?: boolean;
550
+ createdBy?: number | null;
551
+ updatedBy?: number | null;
552
+ createdAt?: Date | string;
553
+ updatedAt?: Date | string;
554
+ };
555
+ type ApproverMapping = {
556
+ id?: number;
557
+ staffId?: number | null;
558
+ stepId: number;
559
+ ccId: number;
560
+ roleId?: number | null;
561
+ isActive?: boolean;
562
+ createdBy?: number | null;
563
+ updatedBy?: number | null;
564
+ createdAt?: Date | string;
565
+ updatedAt?: Date | string;
566
+ };
530
567
  type ApprovalInstance = {
531
568
  id?: number;
532
569
  flowId: number;
@@ -544,6 +581,28 @@ type ApprovalInstance = {
544
581
  createdAt?: Date | string;
545
582
  updatedAt?: Date | string;
546
583
  };
584
+ type ApprovalFlow = {
585
+ name: string;
586
+ id: number;
587
+ subjectType: string;
588
+ service: string;
589
+ isActive: boolean;
590
+ createdBy: number | null;
591
+ updatedBy: number | null;
592
+ createdAt: Date;
593
+ updatedAt: Date;
594
+ flowType: FlowType;
595
+ };
596
+ type ApprovalAction = {
597
+ id: number;
598
+ isActive: boolean;
599
+ level: number;
600
+ instanceId: number;
601
+ actedBy: number;
602
+ statusAfter: ApprovalStatus;
603
+ comment: string | null;
604
+ actedAt: Date;
605
+ };
547
606
  type LevelReadyEvt = {
548
607
  instanceId: number;
549
608
  subjectType: string;
@@ -566,6 +625,195 @@ declare global {
566
625
  "approval:REJECTED": (i: ApprovalInstance) => void;
567
626
  }
568
627
  }
628
+ interface CreateApprovalFlow {
629
+ subjectType: string;
630
+ name: string;
631
+ }
632
+ interface UpdateApprovalFlow extends CreateApprovalFlow {
633
+ id: number;
634
+ }
635
+ interface IApprovalStep {
636
+ id?: number;
637
+ flowId: number;
638
+ subjectType: string;
639
+ }
640
+ type FlowWithStepsResponse = ApprovalFlow & {
641
+ steps: ApprovalStep[];
642
+ };
643
+ interface FlowWithSelectedStepResponse extends ApprovalFlow {
644
+ step: ApprovalStep | null;
645
+ }
646
+ interface ActInput {
647
+ instanceId: number;
648
+ approverId: number;
649
+ action: "APPROVE" | "REJECT";
650
+ ccId: number;
651
+ comment?: string;
652
+ }
653
+ interface RawFlowWithSelectedStepResponse {
654
+ flowId: number;
655
+ stepId: number;
656
+ level: number;
657
+ minAmount: number;
658
+ maxAmount: number;
659
+ stepType: string;
660
+ ccId: number;
661
+ service: string;
662
+ }
663
+ interface StartFlowReq {
664
+ service: string;
665
+ subjectType: string;
666
+ subjectId: number;
667
+ netTotal: number;
668
+ ccId: number;
669
+ refNo: string;
670
+ level?: number;
671
+ extra?: Record<string, string | number | boolean | null>;
672
+ }
673
+ interface NotificationEvent {
674
+ instanceId: number;
675
+ subjectType: string;
676
+ service: string;
677
+ subjectId: number;
678
+ level: number;
679
+ ccId: number;
680
+ approvers: ApproverMapping[];
681
+ }
682
+ interface CommonApproveReq {
683
+ service: string;
684
+ subjectType: string;
685
+ id: number;
686
+ comment?: string;
687
+ ccId: number;
688
+ approverId: number;
689
+ approveType: "APPROVE" | "REJECT";
690
+ }
691
+ interface CommonGetApprovalActionReq {
692
+ service: string;
693
+ subjectType: string;
694
+ id: number;
695
+ }
696
+ interface GetPendingApprovalReq {
697
+ service: string;
698
+ ccId: number;
699
+ staffId: number;
700
+ }
701
+ interface IParentConfigJSON {
702
+ tableName: string;
703
+ approvedAt?: string;
704
+ approvedBy?: string;
705
+ approvalNote?: string;
706
+ status?: string;
707
+ refundAmount?: string;
708
+ discountType?: string;
709
+ discountValue?: string;
710
+ discountAmount?: string;
711
+ totalCopaymentAmount?: string;
712
+ isApproved?: string;
713
+ paymentStatus?: string;
714
+ }
715
+ interface IParentConfigData {
716
+ approvedAt?: string | null;
717
+ approvedBy?: string | null;
718
+ approvalNote?: string | null;
719
+ status?: string | null;
720
+ refundAmount?: number | null;
721
+ discountType?: string | null;
722
+ discountValue?: number | null;
723
+ discountAmount?: number | null;
724
+ totalCopaymentAmount?: number | null;
725
+ isApproved?: string | null;
726
+ paymentStatus?: string | null;
727
+ }
728
+ interface IChildConfigJSON {
729
+ tableName: string;
730
+ discountType?: string;
731
+ discountValue?: string;
732
+ discountAmount?: string;
733
+ copayValue?: string;
734
+ copayType?: string;
735
+ copayAmount?: string;
736
+ coPayModificationStatus?: string;
737
+ }
738
+ interface IChildConfigData {
739
+ discountType?: string | null;
740
+ discountValue?: number | null;
741
+ discountAmount?: number | null;
742
+ copayValue?: number | null;
743
+ copayType?: string | null;
744
+ copayAmount?: number | null;
745
+ coPayModificationStatus?: string | null;
746
+ }
747
+ interface ICommonApprovalUpdate {
748
+ id: number;
749
+ flowType: FlowType;
750
+ status?: string | null;
751
+ approvedAt?: string | null;
752
+ approvedBy?: string | null;
753
+ approvalNote?: string | null;
754
+ refundAmount?: number | null;
755
+ }
756
+ interface EventInstance {
757
+ subjectType: string;
758
+ service: string;
759
+ instanceId: number;
760
+ flowType: FlowType;
761
+ subjectId: number;
762
+ approverId: number;
763
+ step: ApprovalStep;
764
+ comment?: string;
765
+ }
766
+ interface ApprovalInstanceByUser {
767
+ id: number;
768
+ flowId: number;
769
+ subjectType: string;
770
+ service: string;
771
+ subjectId: number;
772
+ refNo: number;
773
+ currentStepId: number;
774
+ netTotal: number;
775
+ status: ApprovalStatus;
776
+ createdBy: string;
777
+ createdAt: Date;
778
+ level: number;
779
+ flowType: FlowType;
780
+ flowName: string;
781
+ extra?: string | null;
782
+ }
783
+ interface ApprovalActionDto extends Omit<ApprovalAction, "actedBy"> {
784
+ actedByDetails: EmployeeCache | null;
785
+ }
786
+ interface GetMyApprovalFlow extends Omit<CommonFilterWithDate, "sortBy"> {
787
+ staffId: number;
788
+ ccId: number;
789
+ service: string;
790
+ status?: ApprovalStatus[];
791
+ flowType?: FlowType;
792
+ }
793
+ interface ApprovalDeps {
794
+ helpers: Helpers;
795
+ logger: winston.Logger;
796
+ requestStorage: AsyncLocalStorage<Store>;
797
+ prisma: PrismaClient;
798
+ eventBus: EventEmitter;
799
+ }
800
+ type PrismaTransactionClient = Omit<PrismaClient, "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends">;
801
+
802
+ declare class ApprovalService {
803
+ private deps;
804
+ private approvalRepo;
805
+ constructor(deps: ApprovalDeps);
806
+ startFlow(tx: PrismaClient | PrismaTransactionClient, { service, subjectType, subjectId, netTotal, ccId, refNo, level, extra }: StartFlowReq): Promise<void>;
807
+ lastLevel(steps: ApprovalStep[]): Promise<number>;
808
+ /** Approver clicks “Approve” or “Reject”. */
809
+ act({ instanceId, approverId, action, ccId, comment }: ActInput): Promise<any>;
810
+ private emitEvents;
811
+ private assertPermission;
812
+ }
813
+
814
+ declare const approvalRepository: (helpers: Helpers) => {
815
+ findMatchingFlow(tx: PrismaClient | Prisma.TransactionClient, type: string, service: string, ccId: number, netTotal: number, level?: number): Promise<RawFlowWithSelectedStepResponse | null>;
816
+ };
569
817
 
570
818
  declare function customOmit<T extends object, K extends keyof T>(obj: T, keys: K[]): {
571
819
  rest: Omit<T, K>;
@@ -760,4 +1008,4 @@ declare class AuditProxy<Module extends string = "OPD" | "PROCEDURE" | "GENERAL_
760
1008
  createAuditedService<T extends object>(serviceName: string, service: T): T;
761
1009
  }
762
1010
 
763
- export { type AuditContext, type AuditContextProvider, AuditCore, type AuditLogPayload, AuditLogger, AuditProxy, type BulkAtomicResult, type BulkConfig, type BulkConflictConfig, type BulkOnConflict, type CacheAdapter, type CalculationRes, type ColValue, type CommonCreateRequestRepository, type CommonExcelRequest, type CommonFilterRequest, type CommonFilterWithDate, type CommonServiceResponse, type CommonUpdateRequestRepository, type Config, type Context, type CreateUINConfigRequest, type CrudContext, type CrudDelegate, type DataType, type DeepMerge, type DeleteParams, type DeleteRequestRepository, type Deps, type DropdownRequest, type DropdownRequestService, type DtoFromMapping, type DtoNullOnMissing, type DynamicCreateInput, type DynamicCrudConfig, type DynamicShortCode, type DynamicUpdateInput, type EmitPayload, type EmployeeCache, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FieldConfig, type FieldRules, type FieldType, type FixedMap, type FixedSearchRequest, type FixedSearchRequestService, type Helpers, type ImportExcel, type ImportExcelRequestService, type LockUnlockParams, type LockUnlockRequestRepository, type LogicNode, type Mapper, type MergeAll, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type Op, type PaginatedResponse, type PathToSelectWithSelect, type PathValue, type PathsToSelectWithSelect, type Presence, type Recipient, type RelationConfig, type RelationStrategy, type RelationWriteConfig, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type SingleValidationMapping, type SourcePath, type Store, type ToggleActive, type TxClient, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UnionToIntersection, type UniqueConfig, type UpdateConfigByCodeInput, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, commonService, convertArrayPatternToEachBlocksGeneric, customOmit, findDifferences, formatDatesDeep, fromTimestampToSqlDatetime, getDynamicValue, getNestedValue, getNestedValueV2, getPattern, interpolate, objectTo2DArray, renderEmailTemplate, renderTemplate, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
1011
+ export { type ActInput, type ApprovalAction, type ApprovalActionDto, type ApprovalDeps, type ApprovalFlow, type ApprovalInstance, type ApprovalInstanceByUser, ApprovalService, ApprovalStatus, type ApprovalStep, type ApproverMapping, type AuditContext, type AuditContextProvider, AuditCore, type AuditLogPayload, AuditLogger, AuditProxy, type BulkAtomicResult, type BulkConfig, type BulkConflictConfig, type BulkOnConflict, type CacheAdapter, type CalculationRes, type ColValue, type CommonApproveReq, type CommonCreateRequestRepository, type CommonExcelRequest, type CommonFilterRequest, type CommonFilterWithDate, type CommonGetApprovalActionReq, type CommonServiceResponse, type CommonUpdateRequestRepository, type Config, type Context, type CreateApprovalFlow, type CreateUINConfigRequest, type CrudContext, type CrudDelegate, type DataType, type DeepMerge, type DeleteParams, type DeleteRequestRepository, type Deps, type DropdownRequest, type DropdownRequestService, type DtoFromMapping, type DtoNullOnMissing, type DynamicCreateInput, type DynamicCrudConfig, type DynamicShortCode, type DynamicUpdateInput, type EmitPayload, type EmployeeCache, type EventInstance, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FieldConfig, type FieldRules, type FieldType, type FixedMap, type FixedSearchRequest, type FixedSearchRequestService, FlowType, type FlowWithSelectedStepResponse, type FlowWithStepsResponse, type GetMyApprovalFlow, type GetPendingApprovalReq, type Helpers, type IApprovalStep, type IChildConfigData, type IChildConfigJSON, type ICommonApprovalUpdate, type IParentConfigData, type IParentConfigJSON, type ImportExcel, type ImportExcelRequestService, type LevelDoneEvt, type LevelReadyEvt, type LockUnlockParams, type LockUnlockRequestRepository, type LogicNode, type Mapper, type MergeAll, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type NotificationEvent, type Op, type PaginatedResponse, type PathToSelectWithSelect, type PathValue, type PathsToSelectWithSelect, type Presence, type PrismaTransactionClient, type RawFlowWithSelectedStepResponse, type Recipient, type RelationConfig, type RelationStrategy, type RelationWriteConfig, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type SingleValidationMapping, type SourcePath, type StartFlowReq, type StepType, type Store, type ToggleActive, type TxClient, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UnionToIntersection, type UniqueConfig, type UpdateApprovalFlow, type UpdateConfigByCodeInput, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, approvalRepository, commonService, convertArrayPatternToEachBlocksGeneric, customOmit, findDifferences, formatDatesDeep, fromTimestampToSqlDatetime, getDynamicValue, getNestedValue, getNestedValueV2, getPattern, interpolate, objectTo2DArray, renderEmailTemplate, renderTemplate, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
package/dist/index.js CHANGED
@@ -30,10 +30,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ApprovalService: () => ApprovalService,
34
+ ApprovalStatus: () => ApprovalStatus,
33
35
  AuditCore: () => AuditCore,
34
36
  AuditLogger: () => AuditLogger,
35
37
  AuditProxy: () => AuditProxy,
38
+ FlowType: () => FlowType,
36
39
  NotificationEmitter: () => NotificationEmitter,
40
+ approvalRepository: () => approvalRepository,
37
41
  commonService: () => commonService,
38
42
  convertArrayPatternToEachBlocksGeneric: () => convertArrayPatternToEachBlocksGeneric,
39
43
  customOmit: () => customOmit,
@@ -2300,6 +2304,314 @@ var commonService = (serviceDeps) => {
2300
2304
  };
2301
2305
  };
2302
2306
 
2307
+ // src/repository/approval.repository.ts
2308
+ var approvalRepository = (helpers) => {
2309
+ return {
2310
+ async findMatchingFlow(tx, type, service, ccId, netTotal, level = 1) {
2311
+ const result = await tx.$queryRaw(`
2312
+ SELECT af.id AS flowId,
2313
+ s.id AS stepId,
2314
+ s.level,
2315
+ s.min_amount AS minAmount,
2316
+ s.max_amount AS maxAmount,
2317
+ s.step_type AS stepType,
2318
+ af.service
2319
+ FROM core_approval_flow AS af
2320
+ JOIN core_approval_step AS s ON s.flow_id = af.id AND s.level = ${level}
2321
+ WHERE af.subject_type = ${type}
2322
+ AND af.service = ${service}
2323
+ AND af.is_active = TRUE
2324
+ AND s.is_active = TRUE
2325
+ AND ( (s.step_type = 'MIN_MAX'
2326
+ AND s.min_amount <= ${netTotal}
2327
+ AND s.max_amount >= ${netTotal})
2328
+ OR (s.step_type = 'NORMAL') )
2329
+ LIMIT 1; -- we expect exactly one matching step
2330
+ `);
2331
+ if (result.length === 0) {
2332
+ throw new helpers.ErrorHandler(400, "No matching flow found.");
2333
+ }
2334
+ return result[0];
2335
+ }
2336
+ };
2337
+ };
2338
+
2339
+ // src/types/approval.type.ts
2340
+ var ApprovalStatus = /* @__PURE__ */ ((ApprovalStatus2) => {
2341
+ ApprovalStatus2[ApprovalStatus2["PENDING"] = 0] = "PENDING";
2342
+ ApprovalStatus2[ApprovalStatus2["PARTIALLY_APPROVED"] = 1] = "PARTIALLY_APPROVED";
2343
+ ApprovalStatus2[ApprovalStatus2["APPROVED"] = 2] = "APPROVED";
2344
+ ApprovalStatus2[ApprovalStatus2["REJECTED"] = 3] = "REJECTED";
2345
+ ApprovalStatus2[ApprovalStatus2["CANCELLED"] = 4] = "CANCELLED";
2346
+ return ApprovalStatus2;
2347
+ })(ApprovalStatus || {});
2348
+ var FlowType = /* @__PURE__ */ ((FlowType3) => {
2349
+ FlowType3[FlowType3["STATUS"] = 0] = "STATUS";
2350
+ FlowType3[FlowType3["CO_PAY"] = 1] = "CO_PAY";
2351
+ FlowType3[FlowType3["INDIVIDUAL_DISCOUNT"] = 2] = "INDIVIDUAL_DISCOUNT";
2352
+ FlowType3[FlowType3["OVERALL_DISCOUNT"] = 3] = "OVERALL_DISCOUNT";
2353
+ FlowType3[FlowType3["SPECIAL_DISCOUNT"] = 4] = "SPECIAL_DISCOUNT";
2354
+ FlowType3[FlowType3["REFUND"] = 5] = "REFUND";
2355
+ return FlowType3;
2356
+ })(FlowType || {});
2357
+
2358
+ // src/services/approval.service.ts
2359
+ var ApprovalService = class {
2360
+ constructor(deps) {
2361
+ this.deps = deps;
2362
+ this.approvalRepo = approvalRepository(deps.helpers);
2363
+ }
2364
+ deps;
2365
+ approvalRepo;
2366
+ async startFlow(tx, { service, subjectType, subjectId, netTotal, ccId, refNo, level = 1, extra }) {
2367
+ const store = this.deps.requestStorage.getStore();
2368
+ const currentUser = store?.user?.id;
2369
+ const flow = await this.approvalRepo.findMatchingFlow(tx, subjectType, service, ccId, netTotal, level);
2370
+ if (!flow) throw new Error("No approval flow configured");
2371
+ await tx.approvalInstance.updateMany({
2372
+ where: {
2373
+ service: flow.service,
2374
+ subjectType,
2375
+ subjectId
2376
+ },
2377
+ data: {
2378
+ isActive: false,
2379
+ updatedBy: currentUser
2380
+ }
2381
+ });
2382
+ const inst = await tx.approvalInstance.create({
2383
+ data: {
2384
+ flowId: flow.flowId,
2385
+ service: flow.service,
2386
+ subjectType,
2387
+ subjectId,
2388
+ currentStep: flow.stepId,
2389
+ netTotal,
2390
+ refNo,
2391
+ extra,
2392
+ createdBy: currentUser
2393
+ }
2394
+ });
2395
+ const approvers = await tx.approverMapping.findMany({
2396
+ where: { stepId: flow.stepId, ccId, isActive: true }
2397
+ });
2398
+ this.deps.eventBus.emit("approval:LEVEL_READY", {
2399
+ instanceId: inst.id,
2400
+ subjectType: inst.subjectType,
2401
+ service: inst.service,
2402
+ subjectId: inst.subjectId,
2403
+ level: 1,
2404
+ approvers,
2405
+ ccId
2406
+ });
2407
+ }
2408
+ async lastLevel(steps) {
2409
+ if (steps.length === 0) throw new Error("No steps defined in the approval flow");
2410
+ return Math.max(...steps.map((s) => s.level));
2411
+ }
2412
+ /** Approver clicks “Approve” or “Reject”. */
2413
+ async act({ instanceId, approverId, action, ccId, comment }) {
2414
+ return this.deps.prisma.$transaction(async (tx) => {
2415
+ const inst = await tx.approvalInstance.findUnique({
2416
+ where: { id: instanceId },
2417
+ include: { flow: { where: { isActive: true }, include: { steps: { where: { isActive: true } } } } }
2418
+ });
2419
+ if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
2420
+ if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
2421
+ const step = inst.flow.steps.find((s) => s.id === inst.currentStep);
2422
+ if (!step) throw new this.deps.helpers.ErrorHandler(400, "Current step not found in the flow");
2423
+ await this.assertPermission(step, approverId, instanceId, ccId, tx);
2424
+ inst.flow.steps = inst.flow.steps.filter((s) => {
2425
+ return s.stepType === "NORMAL" || s.stepType === "MIN_MAX" && Number(inst.netTotal) >= Number(s.minAmount) && Number(inst.netTotal) <= Number(s.maxAmount);
2426
+ });
2427
+ const lastLevel = await this.lastLevel(inst.flow.steps);
2428
+ const newStatus = action === "REJECT" ? 3 /* REJECTED */ : step.level === lastLevel ? 2 /* APPROVED */ : 1 /* PARTIALLY_APPROVED */;
2429
+ await tx.approvalAction.create({
2430
+ data: {
2431
+ instanceId,
2432
+ level: step.level,
2433
+ actedBy: approverId,
2434
+ comment,
2435
+ statusAfter: newStatus
2436
+ }
2437
+ });
2438
+ let newFlow = null;
2439
+ if (newStatus === 1 /* PARTIALLY_APPROVED */) {
2440
+ newFlow = await this.approvalRepo.findMatchingFlow(
2441
+ tx,
2442
+ inst.flow.subjectType,
2443
+ inst.flow.service,
2444
+ ccId,
2445
+ Number(inst.netTotal || 0),
2446
+ step.level + 1
2447
+ );
2448
+ if (!newFlow) {
2449
+ throw new this.deps.helpers.ErrorHandler(400, "No next step found for the approval flow");
2450
+ }
2451
+ }
2452
+ const updated = await tx.approvalInstance.update({
2453
+ where: { id: instanceId },
2454
+ data: {
2455
+ currentStep: newStatus === 1 /* PARTIALLY_APPROVED */ ? newFlow?.stepId : step.id,
2456
+ status: newStatus
2457
+ }
2458
+ });
2459
+ setImmediate(() => this.emitEvents(updated, inst.flow?.flowType, approverId, step, comment));
2460
+ this.deps.eventBus.emit("approval:LEVEL_DONE", {
2461
+ instanceId: inst.id,
2462
+ level: step.level,
2463
+ actedBy: approverId,
2464
+ action,
2465
+ comment
2466
+ });
2467
+ if (newStatus === 1 /* PARTIALLY_APPROVED */) {
2468
+ const nextLevel = step.level + 1;
2469
+ const approvers = await tx.approverMapping.findMany({
2470
+ where: { stepId: updated.currentStep, ccId, isActive: true }
2471
+ });
2472
+ this.deps.eventBus.emit("approval:LEVEL_READY", {
2473
+ instanceId: inst.id,
2474
+ subjectType: inst.subjectType,
2475
+ service: inst.service,
2476
+ subjectId: inst.subjectId,
2477
+ level: nextLevel,
2478
+ approvers,
2479
+ ccId
2480
+ });
2481
+ }
2482
+ return updated;
2483
+ });
2484
+ }
2485
+ /* ------------ private helpers ------------ */
2486
+ // private async advance(id: number, ccId: number, stepId: number, tx: PrismaClient | PrismaTransactionClient = this.deps.prisma) {
2487
+ // console.log(`Advancing approval instance ${id}`);
2488
+ // // // Fetch the approval instance by its id, including associated flow steps
2489
+ // const inst = await tx.approvalInstance.findUniqueOrThrow({
2490
+ // where: { id },
2491
+ // include: { flow: { include: { steps: true } } },
2492
+ // });
2493
+ // if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
2494
+ // if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
2495
+ // // Get the current step for the instance
2496
+ // let nextLevel: number;
2497
+ // if (inst.currentLevel === 0) {
2498
+ // nextLevel = 1;
2499
+ // } else {
2500
+ // const currentStep = inst.flow.steps.find((s) => s.level === inst.currentLevel);
2501
+ // if (!currentStep) throw new Error(`Invalid level ${inst.currentLevel} for the instance`);
2502
+ // // If this step is done (e.g., approved or rejected), move on to the next level
2503
+ // nextLevel = inst.currentLevel + 1;
2504
+ // }
2505
+ // // Check if there is a next level defined
2506
+ // const nextStep = inst.flow.steps.find((s) => s.level === nextLevel);
2507
+ // if (nextStep) {
2508
+ // // Update the instance to move to the next level
2509
+ // await tx.approvalInstance.update({
2510
+ // where: { id },
2511
+ // data: {
2512
+ // currentLevel: nextLevel,
2513
+ // status: "PENDING", // Reset to "PENDING" as we are progressing the approval to the next level
2514
+ // },
2515
+ // });
2516
+ // const approvers = await tx.approverMapping.findMany({
2517
+ // where: { stepId: nextStep.id, ccId, isActive: true },
2518
+ // });
2519
+ // // Emit event for the next level approvers
2520
+ // this.deps.eventBus.emit("approval:LEVEL_READY", {
2521
+ // instanceId: inst.id,
2522
+ // subjectType: inst.subjectType,
2523
+ // subjectId: inst.subjectId,
2524
+ // level: nextLevel,
2525
+ // approvers: approvers,
2526
+ // ccId,
2527
+ // });
2528
+ // } else {
2529
+ // // If no next step, mark the instance as fully approved (completed)
2530
+ // await tx.approvalInstance.update({
2531
+ // where: { id },
2532
+ // data: { status: "APPROVED" }, // or REJECTED if the final level is not approved
2533
+ // });
2534
+ // // Emit event for final approval
2535
+ // this.deps.eventBus.emit("approval:APPROVED", {
2536
+ // instanceId: inst.id,
2537
+ // subjectType: inst.subjectType,
2538
+ // subjectId: inst.subjectId,
2539
+ // });
2540
+ // }
2541
+ // }
2542
+ emitEvents(instance, flowType, approverId, step, comment) {
2543
+ this.deps.eventBus.emit(`approval:${instance.status}`, {
2544
+ instanceId: instance.id,
2545
+ flowType,
2546
+ subjectId: instance.subjectId,
2547
+ approverId,
2548
+ step,
2549
+ comment,
2550
+ subjectType: instance.subjectType,
2551
+ service: instance.service
2552
+ });
2553
+ }
2554
+ async assertPermission(step, approverId, instanceId, ccId, tx) {
2555
+ const result = await tx.$queryRaw(`
2556
+ SELECT COUNT(*) AS count
2557
+ FROM (
2558
+ SELECT am.staff_id AS staff_id
2559
+ FROM core_approver_mapping am
2560
+ WHERE am.step_id = ${step.id}
2561
+ AND am.is_active = TRUE
2562
+ AND am.cc_id = ${ccId}
2563
+ AND am.staff_id = ${approverId}
2564
+
2565
+ UNION
2566
+
2567
+ SELECT scc.staff_id AS staff_id
2568
+ FROM core_approver_mapping am
2569
+ LEFT JOIN staff_roles sr
2570
+ ON sr.role_id = am.role_id
2571
+ LEFT JOIN staff_collection_center scc
2572
+ ON scc.collection_center_id = am.cc_id
2573
+ AND scc.staff_id = sr.staff_id
2574
+ WHERE am.step_id = ${step.id}
2575
+ AND am.is_active = TRUE
2576
+ AND am.cc_id = ${ccId}
2577
+ AND scc.staff_id = ${approverId}
2578
+ ) AS staff_union;
2579
+ `);
2580
+ if (Number(result[0].count) === 0) {
2581
+ throw new this.deps.helpers.ErrorHandler(403, "You are not allowed to act on this approval step");
2582
+ }
2583
+ const existingActsQuery = `
2584
+ SELECT COUNT(*) AS count
2585
+ FROM core_approval_action a
2586
+ JOIN core_approval_instance ai ON ai.id = a.instance_id
2587
+ WHERE ai.id = ?
2588
+ AND a.level = ?
2589
+ AND a.acted_by = ?
2590
+ AND a.is_active = true
2591
+ AND ai.is_active = true
2592
+ `;
2593
+ const actionsResult = await tx.$queryRawUnsafe(existingActsQuery, instanceId, step.level, approverId);
2594
+ if (Number(actionsResult[0].count) > 0) {
2595
+ throw new this.deps.helpers.ErrorHandler(409, "You have already submitted a decision for this level");
2596
+ }
2597
+ const prevApproversQuery = `
2598
+ SELECT COUNT(*) AS count
2599
+ FROM core_approval_action a
2600
+ JOIN core_approval_instance ai ON ai.id = a.instance_id
2601
+ WHERE ai.id = ?
2602
+ AND a.level < ?
2603
+ AND a.is_active = true
2604
+ AND ai.is_active = true
2605
+ AND a.acted_by IS NOT NULL
2606
+ `;
2607
+ const prevActionsResult = await tx.$queryRawUnsafe(prevApproversQuery, instanceId, step.level);
2608
+ if (Number(prevActionsResult[0].count) !== step.level - 1) {
2609
+ throw new this.deps.helpers.ErrorHandler(403, "You must wait for previous approvers to act first");
2610
+ }
2611
+ return step;
2612
+ }
2613
+ };
2614
+
2303
2615
  // src/utils/audit.utils.ts
2304
2616
  function isValidDate(value) {
2305
2617
  if (value instanceof Date) {
@@ -4294,10 +4606,14 @@ var AuditProxy = class {
4294
4606
  };
4295
4607
  // Annotate the CommonJS export names for ESM import in node:
4296
4608
  0 && (module.exports = {
4609
+ ApprovalService,
4610
+ ApprovalStatus,
4297
4611
  AuditCore,
4298
4612
  AuditLogger,
4299
4613
  AuditProxy,
4614
+ FlowType,
4300
4615
  NotificationEmitter,
4616
+ approvalRepository,
4301
4617
  commonService,
4302
4618
  convertArrayPatternToEachBlocksGeneric,
4303
4619
  customOmit,
package/dist/index.mjs CHANGED
@@ -2244,6 +2244,314 @@ var commonService = (serviceDeps) => {
2244
2244
  };
2245
2245
  };
2246
2246
 
2247
+ // src/repository/approval.repository.ts
2248
+ var approvalRepository = (helpers) => {
2249
+ return {
2250
+ async findMatchingFlow(tx, type, service, ccId, netTotal, level = 1) {
2251
+ const result = await tx.$queryRaw(`
2252
+ SELECT af.id AS flowId,
2253
+ s.id AS stepId,
2254
+ s.level,
2255
+ s.min_amount AS minAmount,
2256
+ s.max_amount AS maxAmount,
2257
+ s.step_type AS stepType,
2258
+ af.service
2259
+ FROM core_approval_flow AS af
2260
+ JOIN core_approval_step AS s ON s.flow_id = af.id AND s.level = ${level}
2261
+ WHERE af.subject_type = ${type}
2262
+ AND af.service = ${service}
2263
+ AND af.is_active = TRUE
2264
+ AND s.is_active = TRUE
2265
+ AND ( (s.step_type = 'MIN_MAX'
2266
+ AND s.min_amount <= ${netTotal}
2267
+ AND s.max_amount >= ${netTotal})
2268
+ OR (s.step_type = 'NORMAL') )
2269
+ LIMIT 1; -- we expect exactly one matching step
2270
+ `);
2271
+ if (result.length === 0) {
2272
+ throw new helpers.ErrorHandler(400, "No matching flow found.");
2273
+ }
2274
+ return result[0];
2275
+ }
2276
+ };
2277
+ };
2278
+
2279
+ // src/types/approval.type.ts
2280
+ var ApprovalStatus = /* @__PURE__ */ ((ApprovalStatus2) => {
2281
+ ApprovalStatus2[ApprovalStatus2["PENDING"] = 0] = "PENDING";
2282
+ ApprovalStatus2[ApprovalStatus2["PARTIALLY_APPROVED"] = 1] = "PARTIALLY_APPROVED";
2283
+ ApprovalStatus2[ApprovalStatus2["APPROVED"] = 2] = "APPROVED";
2284
+ ApprovalStatus2[ApprovalStatus2["REJECTED"] = 3] = "REJECTED";
2285
+ ApprovalStatus2[ApprovalStatus2["CANCELLED"] = 4] = "CANCELLED";
2286
+ return ApprovalStatus2;
2287
+ })(ApprovalStatus || {});
2288
+ var FlowType = /* @__PURE__ */ ((FlowType3) => {
2289
+ FlowType3[FlowType3["STATUS"] = 0] = "STATUS";
2290
+ FlowType3[FlowType3["CO_PAY"] = 1] = "CO_PAY";
2291
+ FlowType3[FlowType3["INDIVIDUAL_DISCOUNT"] = 2] = "INDIVIDUAL_DISCOUNT";
2292
+ FlowType3[FlowType3["OVERALL_DISCOUNT"] = 3] = "OVERALL_DISCOUNT";
2293
+ FlowType3[FlowType3["SPECIAL_DISCOUNT"] = 4] = "SPECIAL_DISCOUNT";
2294
+ FlowType3[FlowType3["REFUND"] = 5] = "REFUND";
2295
+ return FlowType3;
2296
+ })(FlowType || {});
2297
+
2298
+ // src/services/approval.service.ts
2299
+ var ApprovalService = class {
2300
+ constructor(deps) {
2301
+ this.deps = deps;
2302
+ this.approvalRepo = approvalRepository(deps.helpers);
2303
+ }
2304
+ deps;
2305
+ approvalRepo;
2306
+ async startFlow(tx, { service, subjectType, subjectId, netTotal, ccId, refNo, level = 1, extra }) {
2307
+ const store = this.deps.requestStorage.getStore();
2308
+ const currentUser = store?.user?.id;
2309
+ const flow = await this.approvalRepo.findMatchingFlow(tx, subjectType, service, ccId, netTotal, level);
2310
+ if (!flow) throw new Error("No approval flow configured");
2311
+ await tx.approvalInstance.updateMany({
2312
+ where: {
2313
+ service: flow.service,
2314
+ subjectType,
2315
+ subjectId
2316
+ },
2317
+ data: {
2318
+ isActive: false,
2319
+ updatedBy: currentUser
2320
+ }
2321
+ });
2322
+ const inst = await tx.approvalInstance.create({
2323
+ data: {
2324
+ flowId: flow.flowId,
2325
+ service: flow.service,
2326
+ subjectType,
2327
+ subjectId,
2328
+ currentStep: flow.stepId,
2329
+ netTotal,
2330
+ refNo,
2331
+ extra,
2332
+ createdBy: currentUser
2333
+ }
2334
+ });
2335
+ const approvers = await tx.approverMapping.findMany({
2336
+ where: { stepId: flow.stepId, ccId, isActive: true }
2337
+ });
2338
+ this.deps.eventBus.emit("approval:LEVEL_READY", {
2339
+ instanceId: inst.id,
2340
+ subjectType: inst.subjectType,
2341
+ service: inst.service,
2342
+ subjectId: inst.subjectId,
2343
+ level: 1,
2344
+ approvers,
2345
+ ccId
2346
+ });
2347
+ }
2348
+ async lastLevel(steps) {
2349
+ if (steps.length === 0) throw new Error("No steps defined in the approval flow");
2350
+ return Math.max(...steps.map((s) => s.level));
2351
+ }
2352
+ /** Approver clicks “Approve” or “Reject”. */
2353
+ async act({ instanceId, approverId, action, ccId, comment }) {
2354
+ return this.deps.prisma.$transaction(async (tx) => {
2355
+ const inst = await tx.approvalInstance.findUnique({
2356
+ where: { id: instanceId },
2357
+ include: { flow: { where: { isActive: true }, include: { steps: { where: { isActive: true } } } } }
2358
+ });
2359
+ if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
2360
+ if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
2361
+ const step = inst.flow.steps.find((s) => s.id === inst.currentStep);
2362
+ if (!step) throw new this.deps.helpers.ErrorHandler(400, "Current step not found in the flow");
2363
+ await this.assertPermission(step, approverId, instanceId, ccId, tx);
2364
+ inst.flow.steps = inst.flow.steps.filter((s) => {
2365
+ return s.stepType === "NORMAL" || s.stepType === "MIN_MAX" && Number(inst.netTotal) >= Number(s.minAmount) && Number(inst.netTotal) <= Number(s.maxAmount);
2366
+ });
2367
+ const lastLevel = await this.lastLevel(inst.flow.steps);
2368
+ const newStatus = action === "REJECT" ? 3 /* REJECTED */ : step.level === lastLevel ? 2 /* APPROVED */ : 1 /* PARTIALLY_APPROVED */;
2369
+ await tx.approvalAction.create({
2370
+ data: {
2371
+ instanceId,
2372
+ level: step.level,
2373
+ actedBy: approverId,
2374
+ comment,
2375
+ statusAfter: newStatus
2376
+ }
2377
+ });
2378
+ let newFlow = null;
2379
+ if (newStatus === 1 /* PARTIALLY_APPROVED */) {
2380
+ newFlow = await this.approvalRepo.findMatchingFlow(
2381
+ tx,
2382
+ inst.flow.subjectType,
2383
+ inst.flow.service,
2384
+ ccId,
2385
+ Number(inst.netTotal || 0),
2386
+ step.level + 1
2387
+ );
2388
+ if (!newFlow) {
2389
+ throw new this.deps.helpers.ErrorHandler(400, "No next step found for the approval flow");
2390
+ }
2391
+ }
2392
+ const updated = await tx.approvalInstance.update({
2393
+ where: { id: instanceId },
2394
+ data: {
2395
+ currentStep: newStatus === 1 /* PARTIALLY_APPROVED */ ? newFlow?.stepId : step.id,
2396
+ status: newStatus
2397
+ }
2398
+ });
2399
+ setImmediate(() => this.emitEvents(updated, inst.flow?.flowType, approverId, step, comment));
2400
+ this.deps.eventBus.emit("approval:LEVEL_DONE", {
2401
+ instanceId: inst.id,
2402
+ level: step.level,
2403
+ actedBy: approverId,
2404
+ action,
2405
+ comment
2406
+ });
2407
+ if (newStatus === 1 /* PARTIALLY_APPROVED */) {
2408
+ const nextLevel = step.level + 1;
2409
+ const approvers = await tx.approverMapping.findMany({
2410
+ where: { stepId: updated.currentStep, ccId, isActive: true }
2411
+ });
2412
+ this.deps.eventBus.emit("approval:LEVEL_READY", {
2413
+ instanceId: inst.id,
2414
+ subjectType: inst.subjectType,
2415
+ service: inst.service,
2416
+ subjectId: inst.subjectId,
2417
+ level: nextLevel,
2418
+ approvers,
2419
+ ccId
2420
+ });
2421
+ }
2422
+ return updated;
2423
+ });
2424
+ }
2425
+ /* ------------ private helpers ------------ */
2426
+ // private async advance(id: number, ccId: number, stepId: number, tx: PrismaClient | PrismaTransactionClient = this.deps.prisma) {
2427
+ // console.log(`Advancing approval instance ${id}`);
2428
+ // // // Fetch the approval instance by its id, including associated flow steps
2429
+ // const inst = await tx.approvalInstance.findUniqueOrThrow({
2430
+ // where: { id },
2431
+ // include: { flow: { include: { steps: true } } },
2432
+ // });
2433
+ // if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
2434
+ // if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
2435
+ // // Get the current step for the instance
2436
+ // let nextLevel: number;
2437
+ // if (inst.currentLevel === 0) {
2438
+ // nextLevel = 1;
2439
+ // } else {
2440
+ // const currentStep = inst.flow.steps.find((s) => s.level === inst.currentLevel);
2441
+ // if (!currentStep) throw new Error(`Invalid level ${inst.currentLevel} for the instance`);
2442
+ // // If this step is done (e.g., approved or rejected), move on to the next level
2443
+ // nextLevel = inst.currentLevel + 1;
2444
+ // }
2445
+ // // Check if there is a next level defined
2446
+ // const nextStep = inst.flow.steps.find((s) => s.level === nextLevel);
2447
+ // if (nextStep) {
2448
+ // // Update the instance to move to the next level
2449
+ // await tx.approvalInstance.update({
2450
+ // where: { id },
2451
+ // data: {
2452
+ // currentLevel: nextLevel,
2453
+ // status: "PENDING", // Reset to "PENDING" as we are progressing the approval to the next level
2454
+ // },
2455
+ // });
2456
+ // const approvers = await tx.approverMapping.findMany({
2457
+ // where: { stepId: nextStep.id, ccId, isActive: true },
2458
+ // });
2459
+ // // Emit event for the next level approvers
2460
+ // this.deps.eventBus.emit("approval:LEVEL_READY", {
2461
+ // instanceId: inst.id,
2462
+ // subjectType: inst.subjectType,
2463
+ // subjectId: inst.subjectId,
2464
+ // level: nextLevel,
2465
+ // approvers: approvers,
2466
+ // ccId,
2467
+ // });
2468
+ // } else {
2469
+ // // If no next step, mark the instance as fully approved (completed)
2470
+ // await tx.approvalInstance.update({
2471
+ // where: { id },
2472
+ // data: { status: "APPROVED" }, // or REJECTED if the final level is not approved
2473
+ // });
2474
+ // // Emit event for final approval
2475
+ // this.deps.eventBus.emit("approval:APPROVED", {
2476
+ // instanceId: inst.id,
2477
+ // subjectType: inst.subjectType,
2478
+ // subjectId: inst.subjectId,
2479
+ // });
2480
+ // }
2481
+ // }
2482
+ emitEvents(instance, flowType, approverId, step, comment) {
2483
+ this.deps.eventBus.emit(`approval:${instance.status}`, {
2484
+ instanceId: instance.id,
2485
+ flowType,
2486
+ subjectId: instance.subjectId,
2487
+ approverId,
2488
+ step,
2489
+ comment,
2490
+ subjectType: instance.subjectType,
2491
+ service: instance.service
2492
+ });
2493
+ }
2494
+ async assertPermission(step, approverId, instanceId, ccId, tx) {
2495
+ const result = await tx.$queryRaw(`
2496
+ SELECT COUNT(*) AS count
2497
+ FROM (
2498
+ SELECT am.staff_id AS staff_id
2499
+ FROM core_approver_mapping am
2500
+ WHERE am.step_id = ${step.id}
2501
+ AND am.is_active = TRUE
2502
+ AND am.cc_id = ${ccId}
2503
+ AND am.staff_id = ${approverId}
2504
+
2505
+ UNION
2506
+
2507
+ SELECT scc.staff_id AS staff_id
2508
+ FROM core_approver_mapping am
2509
+ LEFT JOIN staff_roles sr
2510
+ ON sr.role_id = am.role_id
2511
+ LEFT JOIN staff_collection_center scc
2512
+ ON scc.collection_center_id = am.cc_id
2513
+ AND scc.staff_id = sr.staff_id
2514
+ WHERE am.step_id = ${step.id}
2515
+ AND am.is_active = TRUE
2516
+ AND am.cc_id = ${ccId}
2517
+ AND scc.staff_id = ${approverId}
2518
+ ) AS staff_union;
2519
+ `);
2520
+ if (Number(result[0].count) === 0) {
2521
+ throw new this.deps.helpers.ErrorHandler(403, "You are not allowed to act on this approval step");
2522
+ }
2523
+ const existingActsQuery = `
2524
+ SELECT COUNT(*) AS count
2525
+ FROM core_approval_action a
2526
+ JOIN core_approval_instance ai ON ai.id = a.instance_id
2527
+ WHERE ai.id = ?
2528
+ AND a.level = ?
2529
+ AND a.acted_by = ?
2530
+ AND a.is_active = true
2531
+ AND ai.is_active = true
2532
+ `;
2533
+ const actionsResult = await tx.$queryRawUnsafe(existingActsQuery, instanceId, step.level, approverId);
2534
+ if (Number(actionsResult[0].count) > 0) {
2535
+ throw new this.deps.helpers.ErrorHandler(409, "You have already submitted a decision for this level");
2536
+ }
2537
+ const prevApproversQuery = `
2538
+ SELECT COUNT(*) AS count
2539
+ FROM core_approval_action a
2540
+ JOIN core_approval_instance ai ON ai.id = a.instance_id
2541
+ WHERE ai.id = ?
2542
+ AND a.level < ?
2543
+ AND a.is_active = true
2544
+ AND ai.is_active = true
2545
+ AND a.acted_by IS NOT NULL
2546
+ `;
2547
+ const prevActionsResult = await tx.$queryRawUnsafe(prevApproversQuery, instanceId, step.level);
2548
+ if (Number(prevActionsResult[0].count) !== step.level - 1) {
2549
+ throw new this.deps.helpers.ErrorHandler(403, "You must wait for previous approvers to act first");
2550
+ }
2551
+ return step;
2552
+ }
2553
+ };
2554
+
2247
2555
  // src/utils/audit.utils.ts
2248
2556
  function isValidDate(value) {
2249
2557
  if (value instanceof Date) {
@@ -4237,10 +4545,14 @@ var AuditProxy = class {
4237
4545
  }
4238
4546
  };
4239
4547
  export {
4548
+ ApprovalService,
4549
+ ApprovalStatus,
4240
4550
  AuditCore,
4241
4551
  AuditLogger,
4242
4552
  AuditProxy,
4553
+ FlowType,
4243
4554
  NotificationEmitter,
4555
+ approvalRepository,
4244
4556
  commonService,
4245
4557
  convertArrayPatternToEachBlocksGeneric,
4246
4558
  customOmit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "av6-core",
3
- "version": "1.7.15",
3
+ "version": "1.7.17",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",