av6-core 1.7.14 → 1.7.16
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 +103 -2
- package/dist/index.d.ts +103 -2
- package/dist/index.js +291 -0
- package/dist/index.mjs +290 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JsonValue } from '@prisma/client/runtime/library';
|
|
1
|
+
import { JsonValue, Decimal, DecimalJsLike, InputJsonValue } from '@prisma/client/runtime/library';
|
|
2
2
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
3
|
import { AxiosResponse } from 'axios';
|
|
4
4
|
import ExcelJs from 'exceljs';
|
|
@@ -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.",
|
|
@@ -520,6 +521,106 @@ interface CommonServiceResponse {
|
|
|
520
521
|
|
|
521
522
|
declare const commonService: (serviceDeps: Deps) => CommonServiceResponse;
|
|
522
523
|
|
|
524
|
+
declare enum ApprovalStatus {
|
|
525
|
+
PENDING = 0,
|
|
526
|
+
PARTIALLY_APPROVED = 1,
|
|
527
|
+
APPROVED = 2,
|
|
528
|
+
REJECTED = 3,
|
|
529
|
+
CANCELLED = 4
|
|
530
|
+
}
|
|
531
|
+
type StepType = "MIN_MAX" | "NORMAL";
|
|
532
|
+
type ApprovalStep = {
|
|
533
|
+
id?: number;
|
|
534
|
+
flowId: number;
|
|
535
|
+
level: number;
|
|
536
|
+
minAmount?: Decimal | DecimalJsLike | number | string | null;
|
|
537
|
+
maxAmount?: Decimal | DecimalJsLike | number | string | null;
|
|
538
|
+
stepType?: StepType;
|
|
539
|
+
config?: InputJsonValue;
|
|
540
|
+
childConfig?: InputJsonValue;
|
|
541
|
+
isActive?: boolean;
|
|
542
|
+
createdBy?: number | null;
|
|
543
|
+
updatedBy?: number | null;
|
|
544
|
+
createdAt?: Date | string;
|
|
545
|
+
updatedAt?: Date | string;
|
|
546
|
+
};
|
|
547
|
+
type ApprovalInstance = {
|
|
548
|
+
id?: number;
|
|
549
|
+
flowId: number;
|
|
550
|
+
subjectType: string;
|
|
551
|
+
service: string;
|
|
552
|
+
subjectId: number;
|
|
553
|
+
refNo?: string | null;
|
|
554
|
+
currentStep: number;
|
|
555
|
+
netTotal?: Decimal | DecimalJsLike | number | string | null;
|
|
556
|
+
status?: ApprovalStatus;
|
|
557
|
+
extra?: JsonValue | InputJsonValue;
|
|
558
|
+
isActive?: boolean;
|
|
559
|
+
createdBy?: number | null;
|
|
560
|
+
updatedBy?: number | null;
|
|
561
|
+
createdAt?: Date | string;
|
|
562
|
+
updatedAt?: Date | string;
|
|
563
|
+
};
|
|
564
|
+
type LevelReadyEvt = {
|
|
565
|
+
instanceId: number;
|
|
566
|
+
subjectType: string;
|
|
567
|
+
subjectId: number;
|
|
568
|
+
level: number;
|
|
569
|
+
approverIds: number[];
|
|
570
|
+
};
|
|
571
|
+
type LevelDoneEvt = {
|
|
572
|
+
instanceId: number;
|
|
573
|
+
level: number;
|
|
574
|
+
actedBy: number;
|
|
575
|
+
action: "APPROVE" | "REJECT";
|
|
576
|
+
comment?: string;
|
|
577
|
+
};
|
|
578
|
+
declare global {
|
|
579
|
+
interface ApprovalEvents {
|
|
580
|
+
"approval:LEVEL_READY": (e: LevelReadyEvt) => void;
|
|
581
|
+
"approval:LEVEL_DONE": (e: LevelDoneEvt) => void;
|
|
582
|
+
"approval:APPROVED": (i: ApprovalInstance) => void;
|
|
583
|
+
"approval:REJECTED": (i: ApprovalInstance) => void;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
interface ActInput {
|
|
587
|
+
instanceId: number;
|
|
588
|
+
approverId: number;
|
|
589
|
+
action: "APPROVE" | "REJECT";
|
|
590
|
+
ccId: number;
|
|
591
|
+
comment?: string;
|
|
592
|
+
}
|
|
593
|
+
interface StartFlowReq {
|
|
594
|
+
service: string;
|
|
595
|
+
subjectType: string;
|
|
596
|
+
subjectId: number;
|
|
597
|
+
netTotal: number;
|
|
598
|
+
ccId: number;
|
|
599
|
+
refNo: string;
|
|
600
|
+
level?: number;
|
|
601
|
+
extra?: Record<string, string | number | boolean | null>;
|
|
602
|
+
}
|
|
603
|
+
interface ApprovalDeps {
|
|
604
|
+
helpers: Helpers;
|
|
605
|
+
logger: winston.Logger;
|
|
606
|
+
requestStorage: AsyncLocalStorage<Store>;
|
|
607
|
+
prisma: PrismaClient;
|
|
608
|
+
eventBus: EventEmitter;
|
|
609
|
+
}
|
|
610
|
+
type PrismaTransactionClient = Omit<PrismaClient, "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends">;
|
|
611
|
+
|
|
612
|
+
declare class ApprovalService {
|
|
613
|
+
private deps;
|
|
614
|
+
private approvalRepo;
|
|
615
|
+
constructor(deps: ApprovalDeps);
|
|
616
|
+
startFlow(tx: PrismaClient | PrismaTransactionClient, { service, subjectType, subjectId, netTotal, ccId, refNo, level, extra }: StartFlowReq): Promise<void>;
|
|
617
|
+
lastLevel(steps: ApprovalStep[]): Promise<number>;
|
|
618
|
+
/** Approver clicks “Approve” or “Reject”. */
|
|
619
|
+
act({ instanceId, approverId, action, ccId, comment }: ActInput): Promise<any>;
|
|
620
|
+
private emitEvents;
|
|
621
|
+
private assertPermission;
|
|
622
|
+
}
|
|
623
|
+
|
|
523
624
|
declare function customOmit<T extends object, K extends keyof T>(obj: T, keys: K[]): {
|
|
524
625
|
rest: Omit<T, K>;
|
|
525
626
|
omitted: Pick<T, K>;
|
|
@@ -713,4 +814,4 @@ declare class AuditProxy<Module extends string = "OPD" | "PROCEDURE" | "GENERAL_
|
|
|
713
814
|
createAuditedService<T extends object>(serviceName: string, service: T): T;
|
|
714
815
|
}
|
|
715
816
|
|
|
716
|
-
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 };
|
|
817
|
+
export { ApprovalService, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { JsonValue } from '@prisma/client/runtime/library';
|
|
1
|
+
import { JsonValue, Decimal, DecimalJsLike, InputJsonValue } from '@prisma/client/runtime/library';
|
|
2
2
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
3
|
import { AxiosResponse } from 'axios';
|
|
4
4
|
import ExcelJs from 'exceljs';
|
|
@@ -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.",
|
|
@@ -520,6 +521,106 @@ interface CommonServiceResponse {
|
|
|
520
521
|
|
|
521
522
|
declare const commonService: (serviceDeps: Deps) => CommonServiceResponse;
|
|
522
523
|
|
|
524
|
+
declare enum ApprovalStatus {
|
|
525
|
+
PENDING = 0,
|
|
526
|
+
PARTIALLY_APPROVED = 1,
|
|
527
|
+
APPROVED = 2,
|
|
528
|
+
REJECTED = 3,
|
|
529
|
+
CANCELLED = 4
|
|
530
|
+
}
|
|
531
|
+
type StepType = "MIN_MAX" | "NORMAL";
|
|
532
|
+
type ApprovalStep = {
|
|
533
|
+
id?: number;
|
|
534
|
+
flowId: number;
|
|
535
|
+
level: number;
|
|
536
|
+
minAmount?: Decimal | DecimalJsLike | number | string | null;
|
|
537
|
+
maxAmount?: Decimal | DecimalJsLike | number | string | null;
|
|
538
|
+
stepType?: StepType;
|
|
539
|
+
config?: InputJsonValue;
|
|
540
|
+
childConfig?: InputJsonValue;
|
|
541
|
+
isActive?: boolean;
|
|
542
|
+
createdBy?: number | null;
|
|
543
|
+
updatedBy?: number | null;
|
|
544
|
+
createdAt?: Date | string;
|
|
545
|
+
updatedAt?: Date | string;
|
|
546
|
+
};
|
|
547
|
+
type ApprovalInstance = {
|
|
548
|
+
id?: number;
|
|
549
|
+
flowId: number;
|
|
550
|
+
subjectType: string;
|
|
551
|
+
service: string;
|
|
552
|
+
subjectId: number;
|
|
553
|
+
refNo?: string | null;
|
|
554
|
+
currentStep: number;
|
|
555
|
+
netTotal?: Decimal | DecimalJsLike | number | string | null;
|
|
556
|
+
status?: ApprovalStatus;
|
|
557
|
+
extra?: JsonValue | InputJsonValue;
|
|
558
|
+
isActive?: boolean;
|
|
559
|
+
createdBy?: number | null;
|
|
560
|
+
updatedBy?: number | null;
|
|
561
|
+
createdAt?: Date | string;
|
|
562
|
+
updatedAt?: Date | string;
|
|
563
|
+
};
|
|
564
|
+
type LevelReadyEvt = {
|
|
565
|
+
instanceId: number;
|
|
566
|
+
subjectType: string;
|
|
567
|
+
subjectId: number;
|
|
568
|
+
level: number;
|
|
569
|
+
approverIds: number[];
|
|
570
|
+
};
|
|
571
|
+
type LevelDoneEvt = {
|
|
572
|
+
instanceId: number;
|
|
573
|
+
level: number;
|
|
574
|
+
actedBy: number;
|
|
575
|
+
action: "APPROVE" | "REJECT";
|
|
576
|
+
comment?: string;
|
|
577
|
+
};
|
|
578
|
+
declare global {
|
|
579
|
+
interface ApprovalEvents {
|
|
580
|
+
"approval:LEVEL_READY": (e: LevelReadyEvt) => void;
|
|
581
|
+
"approval:LEVEL_DONE": (e: LevelDoneEvt) => void;
|
|
582
|
+
"approval:APPROVED": (i: ApprovalInstance) => void;
|
|
583
|
+
"approval:REJECTED": (i: ApprovalInstance) => void;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
interface ActInput {
|
|
587
|
+
instanceId: number;
|
|
588
|
+
approverId: number;
|
|
589
|
+
action: "APPROVE" | "REJECT";
|
|
590
|
+
ccId: number;
|
|
591
|
+
comment?: string;
|
|
592
|
+
}
|
|
593
|
+
interface StartFlowReq {
|
|
594
|
+
service: string;
|
|
595
|
+
subjectType: string;
|
|
596
|
+
subjectId: number;
|
|
597
|
+
netTotal: number;
|
|
598
|
+
ccId: number;
|
|
599
|
+
refNo: string;
|
|
600
|
+
level?: number;
|
|
601
|
+
extra?: Record<string, string | number | boolean | null>;
|
|
602
|
+
}
|
|
603
|
+
interface ApprovalDeps {
|
|
604
|
+
helpers: Helpers;
|
|
605
|
+
logger: winston.Logger;
|
|
606
|
+
requestStorage: AsyncLocalStorage<Store>;
|
|
607
|
+
prisma: PrismaClient;
|
|
608
|
+
eventBus: EventEmitter;
|
|
609
|
+
}
|
|
610
|
+
type PrismaTransactionClient = Omit<PrismaClient, "$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends">;
|
|
611
|
+
|
|
612
|
+
declare class ApprovalService {
|
|
613
|
+
private deps;
|
|
614
|
+
private approvalRepo;
|
|
615
|
+
constructor(deps: ApprovalDeps);
|
|
616
|
+
startFlow(tx: PrismaClient | PrismaTransactionClient, { service, subjectType, subjectId, netTotal, ccId, refNo, level, extra }: StartFlowReq): Promise<void>;
|
|
617
|
+
lastLevel(steps: ApprovalStep[]): Promise<number>;
|
|
618
|
+
/** Approver clicks “Approve” or “Reject”. */
|
|
619
|
+
act({ instanceId, approverId, action, ccId, comment }: ActInput): Promise<any>;
|
|
620
|
+
private emitEvents;
|
|
621
|
+
private assertPermission;
|
|
622
|
+
}
|
|
623
|
+
|
|
523
624
|
declare function customOmit<T extends object, K extends keyof T>(obj: T, keys: K[]): {
|
|
524
625
|
rest: Omit<T, K>;
|
|
525
626
|
omitted: Pick<T, K>;
|
|
@@ -713,4 +814,4 @@ declare class AuditProxy<Module extends string = "OPD" | "PROCEDURE" | "GENERAL_
|
|
|
713
814
|
createAuditedService<T extends object>(serviceName: string, service: T): T;
|
|
714
815
|
}
|
|
715
816
|
|
|
716
|
-
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 };
|
|
817
|
+
export { ApprovalService, 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 };
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ 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,
|
|
33
34
|
AuditCore: () => AuditCore,
|
|
34
35
|
AuditLogger: () => AuditLogger,
|
|
35
36
|
AuditProxy: () => AuditProxy,
|
|
@@ -2300,6 +2301,295 @@ var commonService = (serviceDeps) => {
|
|
|
2300
2301
|
};
|
|
2301
2302
|
};
|
|
2302
2303
|
|
|
2304
|
+
// src/repository/approval.repository.ts
|
|
2305
|
+
var approvalRepository = (helpers) => {
|
|
2306
|
+
return {
|
|
2307
|
+
async findMatchingFlow(tx, type, service, ccId, netTotal, level = 1) {
|
|
2308
|
+
const result = await tx.$queryRaw(`
|
|
2309
|
+
SELECT af.id AS flowId,
|
|
2310
|
+
s.id AS stepId,
|
|
2311
|
+
s.level,
|
|
2312
|
+
s.min_amount AS minAmount,
|
|
2313
|
+
s.max_amount AS maxAmount,
|
|
2314
|
+
s.step_type AS stepType,
|
|
2315
|
+
af.service
|
|
2316
|
+
FROM core_approval_flow AS af
|
|
2317
|
+
JOIN core_approval_step AS s ON s.flow_id = af.id AND s.level = ${level}
|
|
2318
|
+
WHERE af.subject_type = ${type}
|
|
2319
|
+
AND af.service = ${service}
|
|
2320
|
+
AND af.is_active = TRUE
|
|
2321
|
+
AND s.is_active = TRUE
|
|
2322
|
+
AND ( (s.step_type = 'MIN_MAX'
|
|
2323
|
+
AND s.min_amount <= ${netTotal}
|
|
2324
|
+
AND s.max_amount >= ${netTotal})
|
|
2325
|
+
OR (s.step_type = 'NORMAL') )
|
|
2326
|
+
LIMIT 1; -- we expect exactly one matching step
|
|
2327
|
+
`);
|
|
2328
|
+
if (result.length === 0) {
|
|
2329
|
+
throw new helpers.ErrorHandler(400, "No matching flow found.");
|
|
2330
|
+
}
|
|
2331
|
+
return result[0];
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
// src/services/approval.service.ts
|
|
2337
|
+
var ApprovalService = class {
|
|
2338
|
+
constructor(deps) {
|
|
2339
|
+
this.deps = deps;
|
|
2340
|
+
this.approvalRepo = approvalRepository(deps.helpers);
|
|
2341
|
+
}
|
|
2342
|
+
deps;
|
|
2343
|
+
approvalRepo;
|
|
2344
|
+
async startFlow(tx, { service, subjectType, subjectId, netTotal, ccId, refNo, level = 1, extra }) {
|
|
2345
|
+
const store = this.deps.requestStorage.getStore();
|
|
2346
|
+
const currentUser = store?.user?.id;
|
|
2347
|
+
const flow = await this.approvalRepo.findMatchingFlow(tx, subjectType, service, ccId, netTotal, level);
|
|
2348
|
+
if (!flow) throw new Error("No approval flow configured");
|
|
2349
|
+
await tx.approvalInstance.updateMany({
|
|
2350
|
+
where: {
|
|
2351
|
+
service: flow.service,
|
|
2352
|
+
subjectType,
|
|
2353
|
+
subjectId
|
|
2354
|
+
},
|
|
2355
|
+
data: {
|
|
2356
|
+
isActive: false,
|
|
2357
|
+
updatedBy: currentUser
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
const inst = await tx.approvalInstance.create({
|
|
2361
|
+
data: {
|
|
2362
|
+
flowId: flow.flowId,
|
|
2363
|
+
service: flow.service,
|
|
2364
|
+
subjectType,
|
|
2365
|
+
subjectId,
|
|
2366
|
+
currentStep: flow.stepId,
|
|
2367
|
+
netTotal,
|
|
2368
|
+
refNo,
|
|
2369
|
+
extra,
|
|
2370
|
+
createdBy: currentUser
|
|
2371
|
+
}
|
|
2372
|
+
});
|
|
2373
|
+
const approvers = await tx.approverMapping.findMany({
|
|
2374
|
+
where: { stepId: flow.stepId, ccId, isActive: true }
|
|
2375
|
+
});
|
|
2376
|
+
this.deps.eventBus.emit("approval:LEVEL_READY", {
|
|
2377
|
+
instanceId: inst.id,
|
|
2378
|
+
subjectType: inst.subjectType,
|
|
2379
|
+
service: inst.service,
|
|
2380
|
+
subjectId: inst.subjectId,
|
|
2381
|
+
level: 1,
|
|
2382
|
+
approvers,
|
|
2383
|
+
ccId
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
async lastLevel(steps) {
|
|
2387
|
+
if (steps.length === 0) throw new Error("No steps defined in the approval flow");
|
|
2388
|
+
return Math.max(...steps.map((s) => s.level));
|
|
2389
|
+
}
|
|
2390
|
+
/** Approver clicks “Approve” or “Reject”. */
|
|
2391
|
+
async act({ instanceId, approverId, action, ccId, comment }) {
|
|
2392
|
+
return this.deps.prisma.$transaction(async (tx) => {
|
|
2393
|
+
const inst = await tx.approvalInstance.findUnique({
|
|
2394
|
+
where: { id: instanceId },
|
|
2395
|
+
include: { flow: { where: { isActive: true }, include: { steps: { where: { isActive: true } } } } }
|
|
2396
|
+
});
|
|
2397
|
+
if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
|
|
2398
|
+
if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
|
|
2399
|
+
const step = inst.flow.steps.find((s) => s.id === inst.currentStep);
|
|
2400
|
+
if (!step) throw new this.deps.helpers.ErrorHandler(400, "Current step not found in the flow");
|
|
2401
|
+
await this.assertPermission(step, approverId, instanceId, ccId, tx);
|
|
2402
|
+
inst.flow.steps = inst.flow.steps.filter((s) => {
|
|
2403
|
+
return s.stepType === "NORMAL" || s.stepType === "MIN_MAX" && Number(inst.netTotal) >= Number(s.minAmount) && Number(inst.netTotal) <= Number(s.maxAmount);
|
|
2404
|
+
});
|
|
2405
|
+
const lastLevel = await this.lastLevel(inst.flow.steps);
|
|
2406
|
+
const newStatus = action === "REJECT" ? 3 /* REJECTED */ : step.level === lastLevel ? 2 /* APPROVED */ : 1 /* PARTIALLY_APPROVED */;
|
|
2407
|
+
await tx.approvalAction.create({
|
|
2408
|
+
data: {
|
|
2409
|
+
instanceId,
|
|
2410
|
+
level: step.level,
|
|
2411
|
+
actedBy: approverId,
|
|
2412
|
+
comment,
|
|
2413
|
+
statusAfter: newStatus
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
let newFlow = null;
|
|
2417
|
+
if (newStatus === 1 /* PARTIALLY_APPROVED */) {
|
|
2418
|
+
newFlow = await this.approvalRepo.findMatchingFlow(
|
|
2419
|
+
tx,
|
|
2420
|
+
inst.flow.subjectType,
|
|
2421
|
+
inst.flow.service,
|
|
2422
|
+
ccId,
|
|
2423
|
+
Number(inst.netTotal || 0),
|
|
2424
|
+
step.level + 1
|
|
2425
|
+
);
|
|
2426
|
+
if (!newFlow) {
|
|
2427
|
+
throw new this.deps.helpers.ErrorHandler(400, "No next step found for the approval flow");
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const updated = await tx.approvalInstance.update({
|
|
2431
|
+
where: { id: instanceId },
|
|
2432
|
+
data: {
|
|
2433
|
+
currentStep: newStatus === 1 /* PARTIALLY_APPROVED */ ? newFlow?.stepId : step.id,
|
|
2434
|
+
status: newStatus
|
|
2435
|
+
}
|
|
2436
|
+
});
|
|
2437
|
+
setImmediate(() => this.emitEvents(updated, inst.flow?.flowType, approverId, step, comment));
|
|
2438
|
+
this.deps.eventBus.emit("approval:LEVEL_DONE", {
|
|
2439
|
+
instanceId: inst.id,
|
|
2440
|
+
level: step.level,
|
|
2441
|
+
actedBy: approverId,
|
|
2442
|
+
action,
|
|
2443
|
+
comment
|
|
2444
|
+
});
|
|
2445
|
+
if (newStatus === 1 /* PARTIALLY_APPROVED */) {
|
|
2446
|
+
const nextLevel = step.level + 1;
|
|
2447
|
+
const approvers = await tx.approverMapping.findMany({
|
|
2448
|
+
where: { stepId: updated.currentStep, ccId, isActive: true }
|
|
2449
|
+
});
|
|
2450
|
+
this.deps.eventBus.emit("approval:LEVEL_READY", {
|
|
2451
|
+
instanceId: inst.id,
|
|
2452
|
+
subjectType: inst.subjectType,
|
|
2453
|
+
service: inst.service,
|
|
2454
|
+
subjectId: inst.subjectId,
|
|
2455
|
+
level: nextLevel,
|
|
2456
|
+
approvers,
|
|
2457
|
+
ccId
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
return updated;
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
/* ------------ private helpers ------------ */
|
|
2464
|
+
// private async advance(id: number, ccId: number, stepId: number, tx: PrismaClient | PrismaTransactionClient = this.deps.prisma) {
|
|
2465
|
+
// console.log(`Advancing approval instance ${id}`);
|
|
2466
|
+
// // // Fetch the approval instance by its id, including associated flow steps
|
|
2467
|
+
// const inst = await tx.approvalInstance.findUniqueOrThrow({
|
|
2468
|
+
// where: { id },
|
|
2469
|
+
// include: { flow: { include: { steps: true } } },
|
|
2470
|
+
// });
|
|
2471
|
+
// if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
|
|
2472
|
+
// if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
|
|
2473
|
+
// // Get the current step for the instance
|
|
2474
|
+
// let nextLevel: number;
|
|
2475
|
+
// if (inst.currentLevel === 0) {
|
|
2476
|
+
// nextLevel = 1;
|
|
2477
|
+
// } else {
|
|
2478
|
+
// const currentStep = inst.flow.steps.find((s) => s.level === inst.currentLevel);
|
|
2479
|
+
// if (!currentStep) throw new Error(`Invalid level ${inst.currentLevel} for the instance`);
|
|
2480
|
+
// // If this step is done (e.g., approved or rejected), move on to the next level
|
|
2481
|
+
// nextLevel = inst.currentLevel + 1;
|
|
2482
|
+
// }
|
|
2483
|
+
// // Check if there is a next level defined
|
|
2484
|
+
// const nextStep = inst.flow.steps.find((s) => s.level === nextLevel);
|
|
2485
|
+
// if (nextStep) {
|
|
2486
|
+
// // Update the instance to move to the next level
|
|
2487
|
+
// await tx.approvalInstance.update({
|
|
2488
|
+
// where: { id },
|
|
2489
|
+
// data: {
|
|
2490
|
+
// currentLevel: nextLevel,
|
|
2491
|
+
// status: "PENDING", // Reset to "PENDING" as we are progressing the approval to the next level
|
|
2492
|
+
// },
|
|
2493
|
+
// });
|
|
2494
|
+
// const approvers = await tx.approverMapping.findMany({
|
|
2495
|
+
// where: { stepId: nextStep.id, ccId, isActive: true },
|
|
2496
|
+
// });
|
|
2497
|
+
// // Emit event for the next level approvers
|
|
2498
|
+
// this.deps.eventBus.emit("approval:LEVEL_READY", {
|
|
2499
|
+
// instanceId: inst.id,
|
|
2500
|
+
// subjectType: inst.subjectType,
|
|
2501
|
+
// subjectId: inst.subjectId,
|
|
2502
|
+
// level: nextLevel,
|
|
2503
|
+
// approvers: approvers,
|
|
2504
|
+
// ccId,
|
|
2505
|
+
// });
|
|
2506
|
+
// } else {
|
|
2507
|
+
// // If no next step, mark the instance as fully approved (completed)
|
|
2508
|
+
// await tx.approvalInstance.update({
|
|
2509
|
+
// where: { id },
|
|
2510
|
+
// data: { status: "APPROVED" }, // or REJECTED if the final level is not approved
|
|
2511
|
+
// });
|
|
2512
|
+
// // Emit event for final approval
|
|
2513
|
+
// this.deps.eventBus.emit("approval:APPROVED", {
|
|
2514
|
+
// instanceId: inst.id,
|
|
2515
|
+
// subjectType: inst.subjectType,
|
|
2516
|
+
// subjectId: inst.subjectId,
|
|
2517
|
+
// });
|
|
2518
|
+
// }
|
|
2519
|
+
// }
|
|
2520
|
+
emitEvents(instance, flowType, approverId, step, comment) {
|
|
2521
|
+
this.deps.eventBus.emit(`approval:${instance.status}`, {
|
|
2522
|
+
instanceId: instance.id,
|
|
2523
|
+
flowType,
|
|
2524
|
+
subjectId: instance.subjectId,
|
|
2525
|
+
approverId,
|
|
2526
|
+
step,
|
|
2527
|
+
comment,
|
|
2528
|
+
subjectType: instance.subjectType,
|
|
2529
|
+
service: instance.service
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
async assertPermission(step, approverId, instanceId, ccId, tx) {
|
|
2533
|
+
const result = await tx.$queryRaw(`
|
|
2534
|
+
SELECT COUNT(*) AS count
|
|
2535
|
+
FROM (
|
|
2536
|
+
SELECT am.staff_id AS staff_id
|
|
2537
|
+
FROM core_approver_mapping am
|
|
2538
|
+
WHERE am.step_id = ${step.id}
|
|
2539
|
+
AND am.is_active = TRUE
|
|
2540
|
+
AND am.cc_id = ${ccId}
|
|
2541
|
+
AND am.staff_id = ${approverId}
|
|
2542
|
+
|
|
2543
|
+
UNION
|
|
2544
|
+
|
|
2545
|
+
SELECT scc.staff_id AS staff_id
|
|
2546
|
+
FROM core_approver_mapping am
|
|
2547
|
+
LEFT JOIN staff_roles sr
|
|
2548
|
+
ON sr.role_id = am.role_id
|
|
2549
|
+
LEFT JOIN staff_collection_center scc
|
|
2550
|
+
ON scc.collection_center_id = am.cc_id
|
|
2551
|
+
AND scc.staff_id = sr.staff_id
|
|
2552
|
+
WHERE am.step_id = ${step.id}
|
|
2553
|
+
AND am.is_active = TRUE
|
|
2554
|
+
AND am.cc_id = ${ccId}
|
|
2555
|
+
AND scc.staff_id = ${approverId}
|
|
2556
|
+
) AS staff_union;
|
|
2557
|
+
`);
|
|
2558
|
+
if (Number(result[0].count) === 0) {
|
|
2559
|
+
throw new this.deps.helpers.ErrorHandler(403, "You are not allowed to act on this approval step");
|
|
2560
|
+
}
|
|
2561
|
+
const existingActsQuery = `
|
|
2562
|
+
SELECT COUNT(*) AS count
|
|
2563
|
+
FROM core_approval_action a
|
|
2564
|
+
JOIN core_approval_instance ai ON ai.id = a.instance_id
|
|
2565
|
+
WHERE ai.id = ?
|
|
2566
|
+
AND a.level = ?
|
|
2567
|
+
AND a.acted_by = ?
|
|
2568
|
+
AND a.is_active = true
|
|
2569
|
+
AND ai.is_active = true
|
|
2570
|
+
`;
|
|
2571
|
+
const actionsResult = await tx.$queryRawUnsafe(existingActsQuery, instanceId, step.level, approverId);
|
|
2572
|
+
if (Number(actionsResult[0].count) > 0) {
|
|
2573
|
+
throw new this.deps.helpers.ErrorHandler(409, "You have already submitted a decision for this level");
|
|
2574
|
+
}
|
|
2575
|
+
const prevApproversQuery = `
|
|
2576
|
+
SELECT COUNT(*) AS count
|
|
2577
|
+
FROM core_approval_action a
|
|
2578
|
+
JOIN core_approval_instance ai ON ai.id = a.instance_id
|
|
2579
|
+
WHERE ai.id = ?
|
|
2580
|
+
AND a.level < ?
|
|
2581
|
+
AND a.is_active = true
|
|
2582
|
+
AND ai.is_active = true
|
|
2583
|
+
AND a.acted_by IS NOT NULL
|
|
2584
|
+
`;
|
|
2585
|
+
const prevActionsResult = await tx.$queryRawUnsafe(prevApproversQuery, instanceId, step.level);
|
|
2586
|
+
if (Number(prevActionsResult[0].count) !== step.level - 1) {
|
|
2587
|
+
throw new this.deps.helpers.ErrorHandler(403, "You must wait for previous approvers to act first");
|
|
2588
|
+
}
|
|
2589
|
+
return step;
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
|
|
2303
2593
|
// src/utils/audit.utils.ts
|
|
2304
2594
|
function isValidDate(value) {
|
|
2305
2595
|
if (value instanceof Date) {
|
|
@@ -4294,6 +4584,7 @@ var AuditProxy = class {
|
|
|
4294
4584
|
};
|
|
4295
4585
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4296
4586
|
0 && (module.exports = {
|
|
4587
|
+
ApprovalService,
|
|
4297
4588
|
AuditCore,
|
|
4298
4589
|
AuditLogger,
|
|
4299
4590
|
AuditProxy,
|
package/dist/index.mjs
CHANGED
|
@@ -2244,6 +2244,295 @@ 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/services/approval.service.ts
|
|
2280
|
+
var ApprovalService = class {
|
|
2281
|
+
constructor(deps) {
|
|
2282
|
+
this.deps = deps;
|
|
2283
|
+
this.approvalRepo = approvalRepository(deps.helpers);
|
|
2284
|
+
}
|
|
2285
|
+
deps;
|
|
2286
|
+
approvalRepo;
|
|
2287
|
+
async startFlow(tx, { service, subjectType, subjectId, netTotal, ccId, refNo, level = 1, extra }) {
|
|
2288
|
+
const store = this.deps.requestStorage.getStore();
|
|
2289
|
+
const currentUser = store?.user?.id;
|
|
2290
|
+
const flow = await this.approvalRepo.findMatchingFlow(tx, subjectType, service, ccId, netTotal, level);
|
|
2291
|
+
if (!flow) throw new Error("No approval flow configured");
|
|
2292
|
+
await tx.approvalInstance.updateMany({
|
|
2293
|
+
where: {
|
|
2294
|
+
service: flow.service,
|
|
2295
|
+
subjectType,
|
|
2296
|
+
subjectId
|
|
2297
|
+
},
|
|
2298
|
+
data: {
|
|
2299
|
+
isActive: false,
|
|
2300
|
+
updatedBy: currentUser
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
const inst = await tx.approvalInstance.create({
|
|
2304
|
+
data: {
|
|
2305
|
+
flowId: flow.flowId,
|
|
2306
|
+
service: flow.service,
|
|
2307
|
+
subjectType,
|
|
2308
|
+
subjectId,
|
|
2309
|
+
currentStep: flow.stepId,
|
|
2310
|
+
netTotal,
|
|
2311
|
+
refNo,
|
|
2312
|
+
extra,
|
|
2313
|
+
createdBy: currentUser
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
const approvers = await tx.approverMapping.findMany({
|
|
2317
|
+
where: { stepId: flow.stepId, ccId, isActive: true }
|
|
2318
|
+
});
|
|
2319
|
+
this.deps.eventBus.emit("approval:LEVEL_READY", {
|
|
2320
|
+
instanceId: inst.id,
|
|
2321
|
+
subjectType: inst.subjectType,
|
|
2322
|
+
service: inst.service,
|
|
2323
|
+
subjectId: inst.subjectId,
|
|
2324
|
+
level: 1,
|
|
2325
|
+
approvers,
|
|
2326
|
+
ccId
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
async lastLevel(steps) {
|
|
2330
|
+
if (steps.length === 0) throw new Error("No steps defined in the approval flow");
|
|
2331
|
+
return Math.max(...steps.map((s) => s.level));
|
|
2332
|
+
}
|
|
2333
|
+
/** Approver clicks “Approve” or “Reject”. */
|
|
2334
|
+
async act({ instanceId, approverId, action, ccId, comment }) {
|
|
2335
|
+
return this.deps.prisma.$transaction(async (tx) => {
|
|
2336
|
+
const inst = await tx.approvalInstance.findUnique({
|
|
2337
|
+
where: { id: instanceId },
|
|
2338
|
+
include: { flow: { where: { isActive: true }, include: { steps: { where: { isActive: true } } } } }
|
|
2339
|
+
});
|
|
2340
|
+
if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
|
|
2341
|
+
if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
|
|
2342
|
+
const step = inst.flow.steps.find((s) => s.id === inst.currentStep);
|
|
2343
|
+
if (!step) throw new this.deps.helpers.ErrorHandler(400, "Current step not found in the flow");
|
|
2344
|
+
await this.assertPermission(step, approverId, instanceId, ccId, tx);
|
|
2345
|
+
inst.flow.steps = inst.flow.steps.filter((s) => {
|
|
2346
|
+
return s.stepType === "NORMAL" || s.stepType === "MIN_MAX" && Number(inst.netTotal) >= Number(s.minAmount) && Number(inst.netTotal) <= Number(s.maxAmount);
|
|
2347
|
+
});
|
|
2348
|
+
const lastLevel = await this.lastLevel(inst.flow.steps);
|
|
2349
|
+
const newStatus = action === "REJECT" ? 3 /* REJECTED */ : step.level === lastLevel ? 2 /* APPROVED */ : 1 /* PARTIALLY_APPROVED */;
|
|
2350
|
+
await tx.approvalAction.create({
|
|
2351
|
+
data: {
|
|
2352
|
+
instanceId,
|
|
2353
|
+
level: step.level,
|
|
2354
|
+
actedBy: approverId,
|
|
2355
|
+
comment,
|
|
2356
|
+
statusAfter: newStatus
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
let newFlow = null;
|
|
2360
|
+
if (newStatus === 1 /* PARTIALLY_APPROVED */) {
|
|
2361
|
+
newFlow = await this.approvalRepo.findMatchingFlow(
|
|
2362
|
+
tx,
|
|
2363
|
+
inst.flow.subjectType,
|
|
2364
|
+
inst.flow.service,
|
|
2365
|
+
ccId,
|
|
2366
|
+
Number(inst.netTotal || 0),
|
|
2367
|
+
step.level + 1
|
|
2368
|
+
);
|
|
2369
|
+
if (!newFlow) {
|
|
2370
|
+
throw new this.deps.helpers.ErrorHandler(400, "No next step found for the approval flow");
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const updated = await tx.approvalInstance.update({
|
|
2374
|
+
where: { id: instanceId },
|
|
2375
|
+
data: {
|
|
2376
|
+
currentStep: newStatus === 1 /* PARTIALLY_APPROVED */ ? newFlow?.stepId : step.id,
|
|
2377
|
+
status: newStatus
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2380
|
+
setImmediate(() => this.emitEvents(updated, inst.flow?.flowType, approverId, step, comment));
|
|
2381
|
+
this.deps.eventBus.emit("approval:LEVEL_DONE", {
|
|
2382
|
+
instanceId: inst.id,
|
|
2383
|
+
level: step.level,
|
|
2384
|
+
actedBy: approverId,
|
|
2385
|
+
action,
|
|
2386
|
+
comment
|
|
2387
|
+
});
|
|
2388
|
+
if (newStatus === 1 /* PARTIALLY_APPROVED */) {
|
|
2389
|
+
const nextLevel = step.level + 1;
|
|
2390
|
+
const approvers = await tx.approverMapping.findMany({
|
|
2391
|
+
where: { stepId: updated.currentStep, ccId, isActive: true }
|
|
2392
|
+
});
|
|
2393
|
+
this.deps.eventBus.emit("approval:LEVEL_READY", {
|
|
2394
|
+
instanceId: inst.id,
|
|
2395
|
+
subjectType: inst.subjectType,
|
|
2396
|
+
service: inst.service,
|
|
2397
|
+
subjectId: inst.subjectId,
|
|
2398
|
+
level: nextLevel,
|
|
2399
|
+
approvers,
|
|
2400
|
+
ccId
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
return updated;
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
/* ------------ private helpers ------------ */
|
|
2407
|
+
// private async advance(id: number, ccId: number, stepId: number, tx: PrismaClient | PrismaTransactionClient = this.deps.prisma) {
|
|
2408
|
+
// console.log(`Advancing approval instance ${id}`);
|
|
2409
|
+
// // // Fetch the approval instance by its id, including associated flow steps
|
|
2410
|
+
// const inst = await tx.approvalInstance.findUniqueOrThrow({
|
|
2411
|
+
// where: { id },
|
|
2412
|
+
// include: { flow: { include: { steps: true } } },
|
|
2413
|
+
// });
|
|
2414
|
+
// if (!inst) throw new this.deps.helpers.ErrorHandler(400, "Approval instance not found");
|
|
2415
|
+
// if (!inst.flow) throw new this.deps.helpers.ErrorHandler(400, "Approval flow not found");
|
|
2416
|
+
// // Get the current step for the instance
|
|
2417
|
+
// let nextLevel: number;
|
|
2418
|
+
// if (inst.currentLevel === 0) {
|
|
2419
|
+
// nextLevel = 1;
|
|
2420
|
+
// } else {
|
|
2421
|
+
// const currentStep = inst.flow.steps.find((s) => s.level === inst.currentLevel);
|
|
2422
|
+
// if (!currentStep) throw new Error(`Invalid level ${inst.currentLevel} for the instance`);
|
|
2423
|
+
// // If this step is done (e.g., approved or rejected), move on to the next level
|
|
2424
|
+
// nextLevel = inst.currentLevel + 1;
|
|
2425
|
+
// }
|
|
2426
|
+
// // Check if there is a next level defined
|
|
2427
|
+
// const nextStep = inst.flow.steps.find((s) => s.level === nextLevel);
|
|
2428
|
+
// if (nextStep) {
|
|
2429
|
+
// // Update the instance to move to the next level
|
|
2430
|
+
// await tx.approvalInstance.update({
|
|
2431
|
+
// where: { id },
|
|
2432
|
+
// data: {
|
|
2433
|
+
// currentLevel: nextLevel,
|
|
2434
|
+
// status: "PENDING", // Reset to "PENDING" as we are progressing the approval to the next level
|
|
2435
|
+
// },
|
|
2436
|
+
// });
|
|
2437
|
+
// const approvers = await tx.approverMapping.findMany({
|
|
2438
|
+
// where: { stepId: nextStep.id, ccId, isActive: true },
|
|
2439
|
+
// });
|
|
2440
|
+
// // Emit event for the next level approvers
|
|
2441
|
+
// this.deps.eventBus.emit("approval:LEVEL_READY", {
|
|
2442
|
+
// instanceId: inst.id,
|
|
2443
|
+
// subjectType: inst.subjectType,
|
|
2444
|
+
// subjectId: inst.subjectId,
|
|
2445
|
+
// level: nextLevel,
|
|
2446
|
+
// approvers: approvers,
|
|
2447
|
+
// ccId,
|
|
2448
|
+
// });
|
|
2449
|
+
// } else {
|
|
2450
|
+
// // If no next step, mark the instance as fully approved (completed)
|
|
2451
|
+
// await tx.approvalInstance.update({
|
|
2452
|
+
// where: { id },
|
|
2453
|
+
// data: { status: "APPROVED" }, // or REJECTED if the final level is not approved
|
|
2454
|
+
// });
|
|
2455
|
+
// // Emit event for final approval
|
|
2456
|
+
// this.deps.eventBus.emit("approval:APPROVED", {
|
|
2457
|
+
// instanceId: inst.id,
|
|
2458
|
+
// subjectType: inst.subjectType,
|
|
2459
|
+
// subjectId: inst.subjectId,
|
|
2460
|
+
// });
|
|
2461
|
+
// }
|
|
2462
|
+
// }
|
|
2463
|
+
emitEvents(instance, flowType, approverId, step, comment) {
|
|
2464
|
+
this.deps.eventBus.emit(`approval:${instance.status}`, {
|
|
2465
|
+
instanceId: instance.id,
|
|
2466
|
+
flowType,
|
|
2467
|
+
subjectId: instance.subjectId,
|
|
2468
|
+
approverId,
|
|
2469
|
+
step,
|
|
2470
|
+
comment,
|
|
2471
|
+
subjectType: instance.subjectType,
|
|
2472
|
+
service: instance.service
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
async assertPermission(step, approverId, instanceId, ccId, tx) {
|
|
2476
|
+
const result = await tx.$queryRaw(`
|
|
2477
|
+
SELECT COUNT(*) AS count
|
|
2478
|
+
FROM (
|
|
2479
|
+
SELECT am.staff_id AS staff_id
|
|
2480
|
+
FROM core_approver_mapping am
|
|
2481
|
+
WHERE am.step_id = ${step.id}
|
|
2482
|
+
AND am.is_active = TRUE
|
|
2483
|
+
AND am.cc_id = ${ccId}
|
|
2484
|
+
AND am.staff_id = ${approverId}
|
|
2485
|
+
|
|
2486
|
+
UNION
|
|
2487
|
+
|
|
2488
|
+
SELECT scc.staff_id AS staff_id
|
|
2489
|
+
FROM core_approver_mapping am
|
|
2490
|
+
LEFT JOIN staff_roles sr
|
|
2491
|
+
ON sr.role_id = am.role_id
|
|
2492
|
+
LEFT JOIN staff_collection_center scc
|
|
2493
|
+
ON scc.collection_center_id = am.cc_id
|
|
2494
|
+
AND scc.staff_id = sr.staff_id
|
|
2495
|
+
WHERE am.step_id = ${step.id}
|
|
2496
|
+
AND am.is_active = TRUE
|
|
2497
|
+
AND am.cc_id = ${ccId}
|
|
2498
|
+
AND scc.staff_id = ${approverId}
|
|
2499
|
+
) AS staff_union;
|
|
2500
|
+
`);
|
|
2501
|
+
if (Number(result[0].count) === 0) {
|
|
2502
|
+
throw new this.deps.helpers.ErrorHandler(403, "You are not allowed to act on this approval step");
|
|
2503
|
+
}
|
|
2504
|
+
const existingActsQuery = `
|
|
2505
|
+
SELECT COUNT(*) AS count
|
|
2506
|
+
FROM core_approval_action a
|
|
2507
|
+
JOIN core_approval_instance ai ON ai.id = a.instance_id
|
|
2508
|
+
WHERE ai.id = ?
|
|
2509
|
+
AND a.level = ?
|
|
2510
|
+
AND a.acted_by = ?
|
|
2511
|
+
AND a.is_active = true
|
|
2512
|
+
AND ai.is_active = true
|
|
2513
|
+
`;
|
|
2514
|
+
const actionsResult = await tx.$queryRawUnsafe(existingActsQuery, instanceId, step.level, approverId);
|
|
2515
|
+
if (Number(actionsResult[0].count) > 0) {
|
|
2516
|
+
throw new this.deps.helpers.ErrorHandler(409, "You have already submitted a decision for this level");
|
|
2517
|
+
}
|
|
2518
|
+
const prevApproversQuery = `
|
|
2519
|
+
SELECT COUNT(*) AS count
|
|
2520
|
+
FROM core_approval_action a
|
|
2521
|
+
JOIN core_approval_instance ai ON ai.id = a.instance_id
|
|
2522
|
+
WHERE ai.id = ?
|
|
2523
|
+
AND a.level < ?
|
|
2524
|
+
AND a.is_active = true
|
|
2525
|
+
AND ai.is_active = true
|
|
2526
|
+
AND a.acted_by IS NOT NULL
|
|
2527
|
+
`;
|
|
2528
|
+
const prevActionsResult = await tx.$queryRawUnsafe(prevApproversQuery, instanceId, step.level);
|
|
2529
|
+
if (Number(prevActionsResult[0].count) !== step.level - 1) {
|
|
2530
|
+
throw new this.deps.helpers.ErrorHandler(403, "You must wait for previous approvers to act first");
|
|
2531
|
+
}
|
|
2532
|
+
return step;
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
|
|
2247
2536
|
// src/utils/audit.utils.ts
|
|
2248
2537
|
function isValidDate(value) {
|
|
2249
2538
|
if (value instanceof Date) {
|
|
@@ -4237,6 +4526,7 @@ var AuditProxy = class {
|
|
|
4237
4526
|
}
|
|
4238
4527
|
};
|
|
4239
4528
|
export {
|
|
4529
|
+
ApprovalService,
|
|
4240
4530
|
AuditCore,
|
|
4241
4531
|
AuditLogger,
|
|
4242
4532
|
AuditProxy,
|