av6-utils 1.0.0

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.
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ build
3
+ dist
4
+ .vscode
package/.prettierrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "es5",
5
+ "printWidth": 120
6
+ }
package/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # AV6 Core
2
+
3
+ A comprehensive utility library for AV6 Node.js projects, providing common functionality for data operations, caching, and Excel handling.
4
+
5
+ ## Features
6
+
7
+ - **Dynamic Data Operations**: Flexible search, fetch, and CRUD operations with support for dynamic models
8
+ - **Caching Support**: Built-in Redis caching for improved performance
9
+ - **Excel Import/Export**: Seamless Excel file handling for data import and export
10
+ - **Pagination**: Built-in pagination for search results
11
+ - **DTO Mapping**: Support for Data Transfer Object mapping
12
+ - **Type Safety**: Written in TypeScript with comprehensive type definitions
13
+ - **UIN Config Management**: Unique Identification Number generation with configurable segments and automatic reset policies
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install av6-core
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Basic Setup
24
+
25
+ To use the library, you need to provide the necessary dependencies:
26
+
27
+ ```typescript
28
+ import { commonService, Deps } from 'av6-core';
29
+ import { PrismaClient } from '@prisma/client';
30
+ import winston from 'winston';
31
+ import { AsyncLocalStorage } from 'async_hooks';
32
+
33
+ // Initialize dependencies
34
+ const deps: Deps = {
35
+ config: {
36
+ REDIS_PREFIX: 'your-prefix:',
37
+ CACHE_KEY_NAME: 'your-cache-key'
38
+ },
39
+ mapper: {
40
+ dtoMapping: {}, // Your DTO mapping functions
41
+ mappingExport: {}, // Your export mapping functions
42
+ mappingImport: {}, // Your import mapping functions
43
+ },
44
+ helpers: {
45
+ generateErrorMessage: (type, ...variables) => {
46
+ // Your error message generation logic
47
+ },
48
+ ErrorHandler: class ErrorHandler extends Error {
49
+ // Your error handler implementation
50
+ }
51
+ },
52
+ logger: winston.createLogger({
53
+ // Your logger configuration
54
+ }),
55
+ cacheAdapter: {
56
+ // Your cache adapter implementation
57
+ },
58
+ requestStorage: new AsyncLocalStorage(),
59
+ db: new PrismaClient()
60
+ };
61
+
62
+ // Initialize the service
63
+ const service = commonService(deps);
64
+ ```
65
+
66
+ ### Search Operations
67
+
68
+ ```typescript
69
+ // Perform a search operation
70
+ const searchResult = await service.search({
71
+ pageNo: 1,
72
+ pageSize: 10,
73
+ shortCode: 'USER',
74
+ searchColumns: ['name', 'email'],
75
+ searchText: 'john',
76
+ sortBy: 'createdAt',
77
+ sortDir: 'DESC',
78
+ shortCodeData: {
79
+ shortCode: 'USER',
80
+ tableName: 'user',
81
+ isDTO: true,
82
+ isCacheable: true
83
+ }
84
+ });
85
+ ```
86
+
87
+ ### Fetch Record
88
+
89
+ ```typescript
90
+ // Fetch a specific record
91
+ const record = await service.fetch({
92
+ shortCode: 'USER',
93
+ id: 123,
94
+ shortCodeData: {
95
+ shortCode: 'USER',
96
+ tableName: 'user',
97
+ isDTO: true,
98
+ isCacheable: true
99
+ }
100
+ });
101
+ ```
102
+
103
+ ### Excel Operations
104
+
105
+ ```typescript
106
+ // Import data from Excel
107
+ const importResult = await service.commonExcelImport({
108
+ shortCode: 'USER',
109
+ file: excelFile, // File object from upload
110
+ shortCodeData: {
111
+ shortCode: 'USER',
112
+ tableName: 'user'
113
+ }
114
+ });
115
+
116
+ // Export data to Excel
117
+ const workbook = await service.commonExcelExport({
118
+ shortCode: 'USER',
119
+ isSample: false,
120
+ shortCodeData: {
121
+ shortCode: 'USER',
122
+ tableName: 'user'
123
+ }
124
+ });
125
+ ```
126
+
127
+ ### UIN Config Operations
128
+
129
+ ```typescript
130
+ import { uinConfigService, UinDeps, UIN_RESET_POLICY, UinShortCode } from 'av6-core';
131
+
132
+ // Initialize UIN Config dependencies
133
+ const uinDeps: UinDeps = {
134
+ config: {
135
+ REDIS_PREFIX: 'your-prefix:',
136
+ CACHE_KEY_NAME: 'your-cache-key'
137
+ },
138
+ helpers: {
139
+ generateErrorMessage: (type, ...variables) => {
140
+ // Your error message generation logic
141
+ },
142
+ ErrorHandler: class ErrorHandler extends Error {
143
+ // Your error handler implementation
144
+ }
145
+ },
146
+ logger: winston.createLogger({
147
+ // Your logger configuration
148
+ }),
149
+ cacheAdapter: {
150
+ // Your cache adapter implementation
151
+ },
152
+ requestStorage: new AsyncLocalStorage(),
153
+ db: new PrismaClient(),
154
+ prisma: PrismaNamespace,
155
+ shortCode: 'UIN_CONFIG',
156
+ cacheKey: 'uin-config'
157
+ };
158
+
159
+ // Initialize the UIN Config service
160
+ const uinService = uinConfigService(uinDeps);
161
+
162
+ // Create a new UIN Config
163
+ const newUinConfig = await uinService.createUINConfig({
164
+ shortCode: UinShortCode.INVOICE,
165
+ seqResetPolicy: UIN_RESET_POLICY.monthly,
166
+ description: 'Invoice number generator',
167
+ uinSegments: [
168
+ { order: 1, type: 'text', value: 'INV-' },
169
+ { order: 2, type: 'dateFormat', value: 'YYYYMM' },
170
+ { order: 3, type: 'separator', value: '-' },
171
+ { order: 4, type: 'sequenceNo', minSeqLength: 5 }
172
+ ]
173
+ });
174
+
175
+ // Generate a UIN preview
176
+ const uinPreview = await uinService.previewUIN({
177
+ uinSegments: [
178
+ { order: 1, type: 'text', value: 'INV-' },
179
+ { order: 2, type: 'dateFormat', value: 'YYYYMM' },
180
+ { order: 3, type: 'separator', value: '-' },
181
+ { order: 4, type: 'sequenceNo', minSeqLength: 5 }
182
+ ]
183
+ });
184
+
185
+ // Get a UIN Config by ID
186
+ const uinConfig = await uinService.getUINConfigById(1);
187
+
188
+ // Generate a new UIN
189
+ const generatedUin = await uinService.generateUIN(UinShortCode.INVOICE);
190
+ ```
191
+
192
+ ## API Reference
193
+
194
+ ### Common Service
195
+
196
+ The library provides a comprehensive service for common operations:
197
+
198
+ - `search`: Search records with pagination and filtering
199
+ - `dropdownSearch`: Search records for dropdown components
200
+ - `fixedSearch`: Search with fixed criteria
201
+ - `fixedSearchWoPaginationService`: Search with fixed criteria without pagination
202
+ - `commonExcelService`: Generate Excel workbooks
203
+ - `fetch`: Fetch a specific record by ID
204
+ - `commonExcelImport`: Import data from Excel files
205
+ - `commonExcelExport`: Export data to Excel files
206
+ - `delete`: Delete a record
207
+ - `updateStatus`: Update the status of a record
208
+ - `fetchImageStream`: Fetch an image as a stream
209
+
210
+ ### UIN Config Service
211
+
212
+ The library provides a service for managing Unique Identification Numbers:
213
+
214
+ - `createUINConfig`: Create a new UIN configuration
215
+ - `updateUINConfig`: Update an existing UIN configuration
216
+ - `getUINConfigById`: Retrieve a UIN configuration by ID
217
+ - `getAllUINConfig`: Retrieve all UIN configurations
218
+ - `deleteUINConfig`: Delete a UIN configuration
219
+ - `generateUIN`: Generate a new UIN based on a shortcode
220
+ - `previewUIN`: Preview a UIN based on segment configuration
221
+
222
+ ### Utility Functions
223
+
224
+ The library also provides utility functions:
225
+
226
+ - `customOmit`: Omit specific keys from an object
227
+ - `objectTo2DArray`: Convert an object to a 2D array
228
+ - `toRelativeImageUrl`: Convert an absolute path to a relative image URL
229
+
230
+ ## License
231
+
232
+ ISC
233
+
234
+
@@ -0,0 +1,113 @@
1
+ declare function customOmit<T extends object, K extends keyof T>(obj: T, keys: K[]): {
2
+ rest: Omit<T, K>;
3
+ omitted: Pick<T, K>;
4
+ };
5
+ declare function getDynamicValue(obj: Record<string, any> | null | undefined, accessorKey: string): any | null;
6
+ declare function objectTo2DArray<T>(obj: Record<string, T>, maxCols: number): (string | T)[][];
7
+ declare function toNumberOrNull(value: unknown): number | null;
8
+ declare const getPattern: {
9
+ [key: string]: RegExp;
10
+ };
11
+ declare const interpolate: (template: string, vars: Record<string, unknown>) => string;
12
+
13
+ type DiscountMode = "PERCENTAGE" | "AMOUNT";
14
+ type AdditionalDiscountMode = "PERCENTAGE" | "AMOUNT";
15
+ type CoPayType = "PERCENTAGE" | "AMOUNT";
16
+ type TaxMethod = "NONE" | "INCLUSIVE" | "EXCLUSIVE";
17
+ type CoPayMode = "EXCLUSIVE-INCLUSIVE" | "PERCENTAGE-AMOUNT";
18
+ declare const RoundFormat: {
19
+ readonly ROUND: "ROUND";
20
+ readonly SPECIAL_ROUND: "SPECIAL_ROUND";
21
+ readonly TO_FIXED: "TO_FIXED";
22
+ readonly CEIL: "CEIL";
23
+ readonly FLOOR: "FLOOR";
24
+ readonly TRUNC: "TRUNC";
25
+ readonly NONE: "NONE";
26
+ };
27
+ type RoundFormat = (typeof RoundFormat)[keyof typeof RoundFormat];
28
+ interface ChildCalcInput {
29
+ qty: number;
30
+ rate: number;
31
+ otherCharge?: number;
32
+ addonPercentage?: number;
33
+ discountMode?: DiscountMode;
34
+ discountValue?: number;
35
+ taxMethod?: TaxMethod;
36
+ taxValue?: number;
37
+ /** Absolute insurer-covered at line level */
38
+ coPaymentType?: CoPayType;
39
+ coPayValue?: number;
40
+ coPayMethod?: "EXCLUSIVE" | "INCLUSIVE";
41
+ }
42
+ interface MasterAdditionalDiscount {
43
+ mode: AdditionalDiscountMode;
44
+ value: number;
45
+ coPayMode?: CoPayMode;
46
+ }
47
+ interface CalcOptions {
48
+ calculationMethod?: "STEP_WISE" | "FINAL_ONLY";
49
+ lineRound?: RoundFormat;
50
+ headerRound?: RoundFormat;
51
+ precision?: number;
52
+ }
53
+ interface ChildCalculated {
54
+ baseRate: number;
55
+ subtotalAmount: number;
56
+ otherChargeAmount: number;
57
+ discountMode: DiscountMode;
58
+ discountValue: number;
59
+ discountAmount: number;
60
+ taxMethod: TaxMethod;
61
+ taxValue: number;
62
+ taxAmount: number;
63
+ grossAmount: number;
64
+ netAmount: number;
65
+ roundOffAmount: number;
66
+ copayAmount: number;
67
+ }
68
+ interface MasterCalculated {
69
+ additionalDiscountMode: AdditionalDiscountMode;
70
+ additionalDiscountValue: number;
71
+ subtotalAmount: number;
72
+ otherChargeAmount: number;
73
+ discountTotalAmount: number;
74
+ taxAmount: number;
75
+ grossAmount: number;
76
+ netAmount: number;
77
+ roundOffAmount: number;
78
+ copayAmount: number;
79
+ }
80
+ interface BillingCalcResult {
81
+ master: MasterCalculated;
82
+ children: ChildCalculated[];
83
+ }
84
+
85
+ /**
86
+ * calculateBillingFromChildren (master-level additional discount applied AFTER child calculations)
87
+ * - Child: item discount -> tax (INCLUSIVE/EXCLUSIVE/NONE) -> coPay -> patient line net + line rounding
88
+ * - Master: sum children, compute patientRawTotal = gross - Σcopay
89
+ * apply master additional discount ON patientRawTotal (no propagation to children)
90
+ * then header rounding
91
+ */
92
+ declare function calculateBillingFromChildren(inputs: ChildCalcInput[], masterExtra: MasterAdditionalDiscount, options?: CalcOptions): BillingCalcResult;
93
+ declare function calculateSingleChild(it: ChildCalcInput, coPayMode: CoPayMode, options?: CalcOptions): ChildCalculated;
94
+
95
+ interface CreateTransaction {
96
+ field: string;
97
+ changedFrom?: string | null;
98
+ changedTo?: string | null;
99
+ }
100
+
101
+ /**
102
+ * Compares two objects (including nested objects) by flattening them,
103
+ * and returns an array of differences. The returned array contains objects
104
+ * with the field name (underscores replaced by spaces), changedFrom, and
105
+ * changedTo values.
106
+ *
107
+ * @param obj1 - The first object.
108
+ * @param obj2 - The second object.
109
+ * @returns An array of DiffResult objects representing the differences.
110
+ */
111
+ declare function findDifferences<T extends Record<string, any>>(obj1: T, obj2: T): CreateTransaction[];
112
+
113
+ export { type AdditionalDiscountMode, type BillingCalcResult, type CalcOptions, type ChildCalcInput, type ChildCalculated, type CoPayMode, type CoPayType, type DiscountMode, type MasterAdditionalDiscount, type MasterCalculated, RoundFormat, type TaxMethod, calculateBillingFromChildren, calculateSingleChild, customOmit, findDifferences, getDynamicValue, getPattern, interpolate, objectTo2DArray, toNumberOrNull };
@@ -0,0 +1,113 @@
1
+ declare function customOmit<T extends object, K extends keyof T>(obj: T, keys: K[]): {
2
+ rest: Omit<T, K>;
3
+ omitted: Pick<T, K>;
4
+ };
5
+ declare function getDynamicValue(obj: Record<string, any> | null | undefined, accessorKey: string): any | null;
6
+ declare function objectTo2DArray<T>(obj: Record<string, T>, maxCols: number): (string | T)[][];
7
+ declare function toNumberOrNull(value: unknown): number | null;
8
+ declare const getPattern: {
9
+ [key: string]: RegExp;
10
+ };
11
+ declare const interpolate: (template: string, vars: Record<string, unknown>) => string;
12
+
13
+ type DiscountMode = "PERCENTAGE" | "AMOUNT";
14
+ type AdditionalDiscountMode = "PERCENTAGE" | "AMOUNT";
15
+ type CoPayType = "PERCENTAGE" | "AMOUNT";
16
+ type TaxMethod = "NONE" | "INCLUSIVE" | "EXCLUSIVE";
17
+ type CoPayMode = "EXCLUSIVE-INCLUSIVE" | "PERCENTAGE-AMOUNT";
18
+ declare const RoundFormat: {
19
+ readonly ROUND: "ROUND";
20
+ readonly SPECIAL_ROUND: "SPECIAL_ROUND";
21
+ readonly TO_FIXED: "TO_FIXED";
22
+ readonly CEIL: "CEIL";
23
+ readonly FLOOR: "FLOOR";
24
+ readonly TRUNC: "TRUNC";
25
+ readonly NONE: "NONE";
26
+ };
27
+ type RoundFormat = (typeof RoundFormat)[keyof typeof RoundFormat];
28
+ interface ChildCalcInput {
29
+ qty: number;
30
+ rate: number;
31
+ otherCharge?: number;
32
+ addonPercentage?: number;
33
+ discountMode?: DiscountMode;
34
+ discountValue?: number;
35
+ taxMethod?: TaxMethod;
36
+ taxValue?: number;
37
+ /** Absolute insurer-covered at line level */
38
+ coPaymentType?: CoPayType;
39
+ coPayValue?: number;
40
+ coPayMethod?: "EXCLUSIVE" | "INCLUSIVE";
41
+ }
42
+ interface MasterAdditionalDiscount {
43
+ mode: AdditionalDiscountMode;
44
+ value: number;
45
+ coPayMode?: CoPayMode;
46
+ }
47
+ interface CalcOptions {
48
+ calculationMethod?: "STEP_WISE" | "FINAL_ONLY";
49
+ lineRound?: RoundFormat;
50
+ headerRound?: RoundFormat;
51
+ precision?: number;
52
+ }
53
+ interface ChildCalculated {
54
+ baseRate: number;
55
+ subtotalAmount: number;
56
+ otherChargeAmount: number;
57
+ discountMode: DiscountMode;
58
+ discountValue: number;
59
+ discountAmount: number;
60
+ taxMethod: TaxMethod;
61
+ taxValue: number;
62
+ taxAmount: number;
63
+ grossAmount: number;
64
+ netAmount: number;
65
+ roundOffAmount: number;
66
+ copayAmount: number;
67
+ }
68
+ interface MasterCalculated {
69
+ additionalDiscountMode: AdditionalDiscountMode;
70
+ additionalDiscountValue: number;
71
+ subtotalAmount: number;
72
+ otherChargeAmount: number;
73
+ discountTotalAmount: number;
74
+ taxAmount: number;
75
+ grossAmount: number;
76
+ netAmount: number;
77
+ roundOffAmount: number;
78
+ copayAmount: number;
79
+ }
80
+ interface BillingCalcResult {
81
+ master: MasterCalculated;
82
+ children: ChildCalculated[];
83
+ }
84
+
85
+ /**
86
+ * calculateBillingFromChildren (master-level additional discount applied AFTER child calculations)
87
+ * - Child: item discount -> tax (INCLUSIVE/EXCLUSIVE/NONE) -> coPay -> patient line net + line rounding
88
+ * - Master: sum children, compute patientRawTotal = gross - Σcopay
89
+ * apply master additional discount ON patientRawTotal (no propagation to children)
90
+ * then header rounding
91
+ */
92
+ declare function calculateBillingFromChildren(inputs: ChildCalcInput[], masterExtra: MasterAdditionalDiscount, options?: CalcOptions): BillingCalcResult;
93
+ declare function calculateSingleChild(it: ChildCalcInput, coPayMode: CoPayMode, options?: CalcOptions): ChildCalculated;
94
+
95
+ interface CreateTransaction {
96
+ field: string;
97
+ changedFrom?: string | null;
98
+ changedTo?: string | null;
99
+ }
100
+
101
+ /**
102
+ * Compares two objects (including nested objects) by flattening them,
103
+ * and returns an array of differences. The returned array contains objects
104
+ * with the field name (underscores replaced by spaces), changedFrom, and
105
+ * changedTo values.
106
+ *
107
+ * @param obj1 - The first object.
108
+ * @param obj2 - The second object.
109
+ * @returns An array of DiffResult objects representing the differences.
110
+ */
111
+ declare function findDifferences<T extends Record<string, any>>(obj1: T, obj2: T): CreateTransaction[];
112
+
113
+ export { type AdditionalDiscountMode, type BillingCalcResult, type CalcOptions, type ChildCalcInput, type ChildCalculated, type CoPayMode, type CoPayType, type DiscountMode, type MasterAdditionalDiscount, type MasterCalculated, RoundFormat, type TaxMethod, calculateBillingFromChildren, calculateSingleChild, customOmit, findDifferences, getDynamicValue, getPattern, interpolate, objectTo2DArray, toNumberOrNull };
package/dist/index.js ADDED
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ RoundFormat: () => RoundFormat,
24
+ calculateBillingFromChildren: () => calculateBillingFromChildren,
25
+ calculateSingleChild: () => calculateSingleChild,
26
+ customOmit: () => customOmit,
27
+ findDifferences: () => findDifferences,
28
+ getDynamicValue: () => getDynamicValue,
29
+ getPattern: () => getPattern,
30
+ interpolate: () => interpolate,
31
+ objectTo2DArray: () => objectTo2DArray,
32
+ toNumberOrNull: () => toNumberOrNull
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/utils/helper.utils.ts
37
+ function customOmit(obj, keys) {
38
+ const rest = { ...obj };
39
+ const omitted = {};
40
+ for (const key of keys) {
41
+ if (key in obj) {
42
+ omitted[key] = obj[key];
43
+ delete rest[key];
44
+ }
45
+ }
46
+ return { rest, omitted };
47
+ }
48
+ function getDynamicValue(obj, accessorKey) {
49
+ if (!accessorKey || obj == null || typeof obj !== "object") {
50
+ return null;
51
+ }
52
+ const keys = accessorKey.split(".");
53
+ let result = obj;
54
+ for (const key of keys) {
55
+ if (result == null || typeof result !== "object" || !(key in result)) {
56
+ return null;
57
+ }
58
+ result = result[key];
59
+ }
60
+ return result === void 0 ? null : result;
61
+ }
62
+ function objectTo2DArray(obj, maxCols) {
63
+ if (maxCols < 2) {
64
+ throw new Error("maxCols must be at least 2");
65
+ }
66
+ const rows = [];
67
+ let currentRow = [];
68
+ for (const [key, value] of Object.entries(obj)) {
69
+ const pair = [key, value];
70
+ if (currentRow.length + pair.length > maxCols) {
71
+ if (currentRow.length > 0) {
72
+ rows.push(currentRow);
73
+ }
74
+ currentRow = [];
75
+ }
76
+ currentRow.push(...pair);
77
+ }
78
+ if (currentRow.length > 0) {
79
+ rows.push(currentRow);
80
+ }
81
+ return rows;
82
+ }
83
+ function toNumberOrNull(value) {
84
+ if (typeof value === "number") {
85
+ return value;
86
+ }
87
+ const converted = Number(value);
88
+ return isNaN(converted) ? null : converted;
89
+ }
90
+ var getPattern = {
91
+ stringBaseNum: /^[1-9][0-9]*$/,
92
+ licenseTitle: /^(?=.{3,100}$)[A-Za-z0-9][A-Za-z0-9\s\-/&,.()]*$/,
93
+ categoryName: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};'":\\|,.<>/?]).{3,30}$/,
94
+ namePattern: /^[A-Za-z\s]+$/,
95
+ skuPattern: /^[A-Z0-9_-]+$/i,
96
+ SlNoPattern: /^[A-Za-z0-9_-]+$/,
97
+ nameWithNumPattern: /^[A-Za-z0-9\s]+$/,
98
+ emailPattern: /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/,
99
+ // phonePattern:
100
+ // /^(?:\+?(\d{1,3})[\s.-]?)?(?:\(?(\d{3})\)?[\s.-]?)?(\d{3})[\s.-]?(\d{4})(?:\s*(?:x|ext)\s*(\d+))?$/,
101
+ phonePattern: /^\d{9}$/,
102
+ postalCodePattern: /^[0-9]{5}$/,
103
+ alphanumericPattern: /^[a-zA-Z0-9]+$/,
104
+ numericPattern: /^[0-9]+$/,
105
+ datePattern: /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/,
106
+ timePattern: /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])$/,
107
+ uuidPattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
108
+ passwordPattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{8,}$/,
109
+ jsonPattern: /^(\[.+?\]|\{.+?\})$/,
110
+ ipPattern: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/,
111
+ macAddressPattern: /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/,
112
+ hexColorPattern: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/,
113
+ hexPattern: /^[0-9A-Fa-f]+$/,
114
+ binaryPattern: /^[01]+$/,
115
+ base64Pattern: /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/,
116
+ alphanumericDashPattern: /^[a-zA-Z0-9-]+$/,
117
+ alphanumericDotPattern: /^[a-zA-Z0-9.]+$/,
118
+ alphanumericUnderscorePattern: /^[a-zA-Z0-9_]+$/,
119
+ alphanumericPlusPattern: /^[a-zA-Z0-9+]+$/,
120
+ alphanumericSlashPattern: /^[a-zA-Z0-9/]+$/,
121
+ alphanumericColonPattern: /^[a-zA-Z0-9:]+$/,
122
+ alphanumericQuestionMarkPattern: /^[a-zA-Z0-9?]+$/,
123
+ alphanumericAtPattern: /^[a-zA-Z0-9@]+$/,
124
+ alphanumericHashPattern: /^[a-zA-Z0-9#]+$/,
125
+ alphanumericDollarPattern: /^[a-zA-Z0-9$]+$/,
126
+ alphanumericPercentPattern: /^[a-zA-Z0-9%]+$/,
127
+ alphanumericAmpersandPattern: /^[a-zA-Z0-9&]+$/,
128
+ alphanumericVerticalBarPattern: /^[a-zA-Z0-9|]+$/,
129
+ alphanumericTildePattern: /^[a-zA-Z0-9~]+$/,
130
+ alphanumericExclamationPattern: /^[a-zA-Z0-9!]+$/,
131
+ alphanumericAndPattern: /^[a-zA-Z0-9&]+$/,
132
+ alphanumericAsteriskPattern: /^[a-zA-Z0-9*]+$/,
133
+ imagePattern: /^.*\.(jpe?g|png|gif)$/i,
134
+ videoPattern: /\.(mp4|webm|ogg)$/i,
135
+ audioPattern: /\.(mp3|wav)$/i,
136
+ pdfPattern: /\.(pdf)$/i,
137
+ docPattern: /\.(doc|docx)$/i,
138
+ xlsPattern: /\.(xls|xlsx)$/i,
139
+ pptPattern: /\.(ppt|pptx)$/i,
140
+ zipPattern: /\.(zip)$/i,
141
+ rarPattern: /\.(rar)$/i,
142
+ tarPattern: /\.(tar)$/i,
143
+ gzipPattern: /\.(gz|gzip)$/i,
144
+ bz2Pattern: /\.(bz2)$/i,
145
+ isoPattern: /\.(iso)$/i,
146
+ txtPattern: /\.(txt)$/i
147
+ };
148
+ var interpolate = (template, vars) => {
149
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, key) => {
150
+ key = key.trim();
151
+ return key in vars ? String(vars[key]) : "";
152
+ });
153
+ };
154
+
155
+ // src/types/calculation.ts
156
+ var RoundFormat = {
157
+ ROUND: "ROUND",
158
+ SPECIAL_ROUND: "SPECIAL_ROUND",
159
+ TO_FIXED: "TO_FIXED",
160
+ CEIL: "CEIL",
161
+ FLOOR: "FLOOR",
162
+ TRUNC: "TRUNC",
163
+ NONE: "NONE"
164
+ };
165
+
166
+ // src/utils/calculation.utils.ts
167
+ function applyRound(value, format, precision = 2) {
168
+ switch (format) {
169
+ case RoundFormat.NONE:
170
+ return value;
171
+ case RoundFormat.ROUND:
172
+ return Math.round(value);
173
+ case RoundFormat.SPECIAL_ROUND:
174
+ return value < 1 ? Math.ceil(value) : Math.round(value);
175
+ case RoundFormat.CEIL:
176
+ return Math.ceil(value);
177
+ case RoundFormat.FLOOR:
178
+ return Math.floor(value);
179
+ case RoundFormat.TRUNC:
180
+ return Math.trunc(value);
181
+ case RoundFormat.TO_FIXED:
182
+ default:
183
+ return Number(value.toFixed(Math.max(0, precision | 0)));
184
+ }
185
+ }
186
+ var maybeStep = (v, stepwise, fmt, p) => stepwise ? applyRound(v, fmt, p) : v;
187
+ function clamp(n, min, max) {
188
+ return Math.max(min, Math.min(max, n));
189
+ }
190
+ var percentage = (amount, percentage2) => {
191
+ return amount * percentage2 / 100;
192
+ };
193
+ function calculateBillingFromChildren(inputs, masterExtra, options = {}) {
194
+ const stepwise = options.calculationMethod === "STEP_WISE";
195
+ const lineFmt = options.lineRound ?? RoundFormat.TO_FIXED;
196
+ const headFmt = options.headerRound ?? lineFmt;
197
+ const precision = options.precision ?? 2;
198
+ const children = inputs.map((it) => {
199
+ return calculateSingleChild(it, masterExtra.coPayMode ?? "PERCENTAGE-AMOUNT", options);
200
+ });
201
+ const subtotalAmount = children.reduce((a, c) => a + c.subtotalAmount, 0);
202
+ const otherChargeAmount = children.reduce((a, c) => a + c.otherChargeAmount, 0);
203
+ const itemDiscountSum = children.reduce((a, c) => a + c.discountAmount, 0);
204
+ const taxAmount = children.reduce((a, c) => a + c.taxAmount, 0);
205
+ const grossAmount = children.reduce((a, c) => a + c.grossAmount, 0);
206
+ const copayAmount = children.reduce((a, c) => a + c.copayAmount, 0);
207
+ const patientRawTotal = grossAmount - copayAmount;
208
+ let masterExtraApplied = 0;
209
+ if (masterExtra.mode === "PERCENTAGE") {
210
+ masterExtraApplied = percentage(patientRawTotal, masterExtra.value);
211
+ } else if (masterExtra.mode === "AMOUNT") {
212
+ masterExtraApplied = Math.min(masterExtra.value, patientRawTotal);
213
+ }
214
+ masterExtraApplied = maybeStep(masterExtraApplied, stepwise, headFmt, precision);
215
+ const discountTotalAmount = maybeStep(itemDiscountSum + masterExtraApplied, stepwise, headFmt, precision);
216
+ const patientAfterExtra = applyRound(Math.max(0, patientRawTotal - masterExtraApplied), "TO_FIXED", 2);
217
+ const netAmount = applyRound(patientAfterExtra, headFmt, precision);
218
+ const roundOffAmount = netAmount - patientAfterExtra;
219
+ const master = {
220
+ additionalDiscountMode: masterExtra.mode,
221
+ additionalDiscountValue: masterExtra.value,
222
+ subtotalAmount: applyRound(subtotalAmount, headFmt, precision),
223
+ otherChargeAmount: applyRound(otherChargeAmount, headFmt, precision),
224
+ discountTotalAmount: applyRound(discountTotalAmount, headFmt, precision),
225
+ // includes master extra
226
+ taxAmount: applyRound(taxAmount, headFmt, precision),
227
+ grossAmount: applyRound(grossAmount, headFmt, precision),
228
+ netAmount,
229
+ // patient payable after master extra + rounding
230
+ roundOffAmount: applyRound(roundOffAmount, "TO_FIXED", 2),
231
+ // rounded - raw
232
+ copayAmount: applyRound(copayAmount, headFmt, precision)
233
+ // Σ line copay (insurer covered)
234
+ };
235
+ return { master, children };
236
+ }
237
+ function calculateSingleChild(it, coPayMode, options = {}) {
238
+ const stepwise = options.calculationMethod === "STEP_WISE";
239
+ const lineFmt = options.lineRound ?? RoundFormat.TO_FIXED;
240
+ const precision = options.precision ?? 2;
241
+ let baseRate = it.rate;
242
+ if (it.addonPercentage) {
243
+ baseRate = baseRate + percentage(baseRate, it.addonPercentage);
244
+ baseRate = applyRound(baseRate, lineFmt, precision);
245
+ }
246
+ const subtotal = applyRound(it.qty * baseRate, lineFmt, precision);
247
+ const other = applyRound(it.otherCharge ?? 0, lineFmt, precision);
248
+ const basePreTax = applyRound(subtotal + other, lineFmt, precision);
249
+ let copayAmount = 0;
250
+ if (coPayMode === "PERCENTAGE-AMOUNT") {
251
+ const copayType = it.coPaymentType ?? "AMOUNT";
252
+ const copayValue = it.coPayValue ?? 0;
253
+ copayAmount = copayType === "PERCENTAGE" ? percentage(subtotal, copayValue) : Math.min(copayValue, subtotal);
254
+ }
255
+ const dMode = it.discountMode ?? "AMOUNT";
256
+ const dVal = it.discountValue ?? 0;
257
+ const tMethod = it.taxMethod ?? "NONE";
258
+ const tVal = Math.max(0, it.taxValue ?? 0);
259
+ let baseAfterTaxDisc = basePreTax;
260
+ if (tMethod === "INCLUSIVE") {
261
+ const inclusiveTaxMultiplier = (100 + tVal) / 100;
262
+ baseAfterTaxDisc = basePreTax / inclusiveTaxMultiplier;
263
+ }
264
+ baseAfterTaxDisc = maybeStep(baseAfterTaxDisc, stepwise, lineFmt, precision);
265
+ const inclDiff = applyRound(basePreTax - baseAfterTaxDisc, lineFmt, precision);
266
+ let discountValue = 0;
267
+ if (dMode === "AMOUNT") {
268
+ discountValue = dVal;
269
+ } else if (dMode === "PERCENTAGE") {
270
+ discountValue = percentage(baseAfterTaxDisc, dVal);
271
+ }
272
+ discountValue = maybeStep(discountValue, stepwise, lineFmt, precision);
273
+ let afterDisc = Math.max(0, baseAfterTaxDisc - discountValue);
274
+ afterDisc = maybeStep(afterDisc, stepwise, lineFmt, precision);
275
+ let calculatedTax = percentage(afterDisc, tVal);
276
+ calculatedTax = maybeStep(calculatedTax, stepwise, lineFmt, precision);
277
+ let grossAmount = afterDisc + calculatedTax;
278
+ grossAmount = maybeStep(grossAmount, stepwise, lineFmt, precision);
279
+ if (coPayMode === "EXCLUSIVE-INCLUSIVE" && it.coPayMethod === "INCLUSIVE") {
280
+ copayAmount = grossAmount;
281
+ }
282
+ copayAmount = applyRound(copayAmount, lineFmt, precision);
283
+ const copay = clamp(Math.max(0, copayAmount), 0, grossAmount);
284
+ const patientRaw = applyRound(grossAmount - copay, RoundFormat.TO_FIXED, 2);
285
+ const patientRounded = applyRound(patientRaw, lineFmt, precision);
286
+ const lineRoundOff = applyRound(patientRounded - patientRaw, RoundFormat.TO_FIXED, 2);
287
+ return {
288
+ // include baseRate if your ChildCalculated type has it (as in your bulk function)
289
+ baseRate: applyRound(baseRate, lineFmt, precision),
290
+ subtotalAmount: applyRound(subtotal + other - inclDiff, lineFmt, precision),
291
+ otherChargeAmount: other,
292
+ discountMode: dMode,
293
+ discountValue: dVal,
294
+ discountAmount: applyRound(discountValue, lineFmt, precision),
295
+ taxMethod: tMethod,
296
+ taxValue: tVal,
297
+ taxAmount: applyRound(calculatedTax, lineFmt, precision),
298
+ grossAmount: applyRound(grossAmount, lineFmt, precision),
299
+ netAmount: patientRounded,
300
+ roundOffAmount: lineRoundOff,
301
+ copayAmount: copay
302
+ };
303
+ }
304
+
305
+ // src/utils/audit.utils.ts
306
+ function isValidDate(value) {
307
+ if (value instanceof Date) {
308
+ return !isNaN(value.getTime());
309
+ }
310
+ if (typeof value === "string" || typeof value === "number") {
311
+ const parsed = new Date(value);
312
+ return !isNaN(parsed.getTime());
313
+ }
314
+ return false;
315
+ }
316
+ function flattenObject(obj, parentKey = "") {
317
+ const result = {};
318
+ for (const key of Object.keys(obj)) {
319
+ const raw = obj[key];
320
+ const newKey = parentKey ? `${parentKey}_${key}` : key;
321
+ if (typeof raw !== "number" && isValidDate(raw)) {
322
+ const date = raw instanceof Date ? raw : new Date(raw);
323
+ result[newKey] = date.toISOString().split("T")[0];
324
+ continue;
325
+ }
326
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
327
+ Object.assign(result, flattenObject(raw, newKey));
328
+ continue;
329
+ }
330
+ result[newKey] = raw;
331
+ }
332
+ return result;
333
+ }
334
+ function findDifferences(obj1, obj2) {
335
+ const flatObj1 = flattenObject(obj1);
336
+ const flatObj2 = flattenObject(obj2);
337
+ const differences = [];
338
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(flatObj1)]);
339
+ allKeys.forEach((key) => {
340
+ if (flatObj1[key] !== flatObj2[key]) {
341
+ differences.push({
342
+ // Replace underscores with spaces for a nicer field output
343
+ field: key.replace(/_/g, " "),
344
+ changedFrom: typeof flatObj1[key] !== "string" ? JSON.stringify(flatObj1[key]) : flatObj1[key],
345
+ changedTo: typeof flatObj2[key] !== "string" ? JSON.stringify(flatObj2[key]) : flatObj2[key]
346
+ });
347
+ }
348
+ });
349
+ return differences;
350
+ }
351
+ // Annotate the CommonJS export names for ESM import in node:
352
+ 0 && (module.exports = {
353
+ RoundFormat,
354
+ calculateBillingFromChildren,
355
+ calculateSingleChild,
356
+ customOmit,
357
+ findDifferences,
358
+ getDynamicValue,
359
+ getPattern,
360
+ interpolate,
361
+ objectTo2DArray,
362
+ toNumberOrNull
363
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,327 @@
1
+ // src/utils/helper.utils.ts
2
+ function customOmit(obj, keys) {
3
+ const rest = { ...obj };
4
+ const omitted = {};
5
+ for (const key of keys) {
6
+ if (key in obj) {
7
+ omitted[key] = obj[key];
8
+ delete rest[key];
9
+ }
10
+ }
11
+ return { rest, omitted };
12
+ }
13
+ function getDynamicValue(obj, accessorKey) {
14
+ if (!accessorKey || obj == null || typeof obj !== "object") {
15
+ return null;
16
+ }
17
+ const keys = accessorKey.split(".");
18
+ let result = obj;
19
+ for (const key of keys) {
20
+ if (result == null || typeof result !== "object" || !(key in result)) {
21
+ return null;
22
+ }
23
+ result = result[key];
24
+ }
25
+ return result === void 0 ? null : result;
26
+ }
27
+ function objectTo2DArray(obj, maxCols) {
28
+ if (maxCols < 2) {
29
+ throw new Error("maxCols must be at least 2");
30
+ }
31
+ const rows = [];
32
+ let currentRow = [];
33
+ for (const [key, value] of Object.entries(obj)) {
34
+ const pair = [key, value];
35
+ if (currentRow.length + pair.length > maxCols) {
36
+ if (currentRow.length > 0) {
37
+ rows.push(currentRow);
38
+ }
39
+ currentRow = [];
40
+ }
41
+ currentRow.push(...pair);
42
+ }
43
+ if (currentRow.length > 0) {
44
+ rows.push(currentRow);
45
+ }
46
+ return rows;
47
+ }
48
+ function toNumberOrNull(value) {
49
+ if (typeof value === "number") {
50
+ return value;
51
+ }
52
+ const converted = Number(value);
53
+ return isNaN(converted) ? null : converted;
54
+ }
55
+ var getPattern = {
56
+ stringBaseNum: /^[1-9][0-9]*$/,
57
+ licenseTitle: /^(?=.{3,100}$)[A-Za-z0-9][A-Za-z0-9\s\-/&,.()]*$/,
58
+ categoryName: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};'":\\|,.<>/?]).{3,30}$/,
59
+ namePattern: /^[A-Za-z\s]+$/,
60
+ skuPattern: /^[A-Z0-9_-]+$/i,
61
+ SlNoPattern: /^[A-Za-z0-9_-]+$/,
62
+ nameWithNumPattern: /^[A-Za-z0-9\s]+$/,
63
+ emailPattern: /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/,
64
+ // phonePattern:
65
+ // /^(?:\+?(\d{1,3})[\s.-]?)?(?:\(?(\d{3})\)?[\s.-]?)?(\d{3})[\s.-]?(\d{4})(?:\s*(?:x|ext)\s*(\d+))?$/,
66
+ phonePattern: /^\d{9}$/,
67
+ postalCodePattern: /^[0-9]{5}$/,
68
+ alphanumericPattern: /^[a-zA-Z0-9]+$/,
69
+ numericPattern: /^[0-9]+$/,
70
+ datePattern: /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/,
71
+ timePattern: /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])$/,
72
+ uuidPattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
73
+ passwordPattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{8,}$/,
74
+ jsonPattern: /^(\[.+?\]|\{.+?\})$/,
75
+ ipPattern: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/,
76
+ macAddressPattern: /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/,
77
+ hexColorPattern: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/,
78
+ hexPattern: /^[0-9A-Fa-f]+$/,
79
+ binaryPattern: /^[01]+$/,
80
+ base64Pattern: /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/,
81
+ alphanumericDashPattern: /^[a-zA-Z0-9-]+$/,
82
+ alphanumericDotPattern: /^[a-zA-Z0-9.]+$/,
83
+ alphanumericUnderscorePattern: /^[a-zA-Z0-9_]+$/,
84
+ alphanumericPlusPattern: /^[a-zA-Z0-9+]+$/,
85
+ alphanumericSlashPattern: /^[a-zA-Z0-9/]+$/,
86
+ alphanumericColonPattern: /^[a-zA-Z0-9:]+$/,
87
+ alphanumericQuestionMarkPattern: /^[a-zA-Z0-9?]+$/,
88
+ alphanumericAtPattern: /^[a-zA-Z0-9@]+$/,
89
+ alphanumericHashPattern: /^[a-zA-Z0-9#]+$/,
90
+ alphanumericDollarPattern: /^[a-zA-Z0-9$]+$/,
91
+ alphanumericPercentPattern: /^[a-zA-Z0-9%]+$/,
92
+ alphanumericAmpersandPattern: /^[a-zA-Z0-9&]+$/,
93
+ alphanumericVerticalBarPattern: /^[a-zA-Z0-9|]+$/,
94
+ alphanumericTildePattern: /^[a-zA-Z0-9~]+$/,
95
+ alphanumericExclamationPattern: /^[a-zA-Z0-9!]+$/,
96
+ alphanumericAndPattern: /^[a-zA-Z0-9&]+$/,
97
+ alphanumericAsteriskPattern: /^[a-zA-Z0-9*]+$/,
98
+ imagePattern: /^.*\.(jpe?g|png|gif)$/i,
99
+ videoPattern: /\.(mp4|webm|ogg)$/i,
100
+ audioPattern: /\.(mp3|wav)$/i,
101
+ pdfPattern: /\.(pdf)$/i,
102
+ docPattern: /\.(doc|docx)$/i,
103
+ xlsPattern: /\.(xls|xlsx)$/i,
104
+ pptPattern: /\.(ppt|pptx)$/i,
105
+ zipPattern: /\.(zip)$/i,
106
+ rarPattern: /\.(rar)$/i,
107
+ tarPattern: /\.(tar)$/i,
108
+ gzipPattern: /\.(gz|gzip)$/i,
109
+ bz2Pattern: /\.(bz2)$/i,
110
+ isoPattern: /\.(iso)$/i,
111
+ txtPattern: /\.(txt)$/i
112
+ };
113
+ var interpolate = (template, vars) => {
114
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (match, key) => {
115
+ key = key.trim();
116
+ return key in vars ? String(vars[key]) : "";
117
+ });
118
+ };
119
+
120
+ // src/types/calculation.ts
121
+ var RoundFormat = {
122
+ ROUND: "ROUND",
123
+ SPECIAL_ROUND: "SPECIAL_ROUND",
124
+ TO_FIXED: "TO_FIXED",
125
+ CEIL: "CEIL",
126
+ FLOOR: "FLOOR",
127
+ TRUNC: "TRUNC",
128
+ NONE: "NONE"
129
+ };
130
+
131
+ // src/utils/calculation.utils.ts
132
+ function applyRound(value, format, precision = 2) {
133
+ switch (format) {
134
+ case RoundFormat.NONE:
135
+ return value;
136
+ case RoundFormat.ROUND:
137
+ return Math.round(value);
138
+ case RoundFormat.SPECIAL_ROUND:
139
+ return value < 1 ? Math.ceil(value) : Math.round(value);
140
+ case RoundFormat.CEIL:
141
+ return Math.ceil(value);
142
+ case RoundFormat.FLOOR:
143
+ return Math.floor(value);
144
+ case RoundFormat.TRUNC:
145
+ return Math.trunc(value);
146
+ case RoundFormat.TO_FIXED:
147
+ default:
148
+ return Number(value.toFixed(Math.max(0, precision | 0)));
149
+ }
150
+ }
151
+ var maybeStep = (v, stepwise, fmt, p) => stepwise ? applyRound(v, fmt, p) : v;
152
+ function clamp(n, min, max) {
153
+ return Math.max(min, Math.min(max, n));
154
+ }
155
+ var percentage = (amount, percentage2) => {
156
+ return amount * percentage2 / 100;
157
+ };
158
+ function calculateBillingFromChildren(inputs, masterExtra, options = {}) {
159
+ const stepwise = options.calculationMethod === "STEP_WISE";
160
+ const lineFmt = options.lineRound ?? RoundFormat.TO_FIXED;
161
+ const headFmt = options.headerRound ?? lineFmt;
162
+ const precision = options.precision ?? 2;
163
+ const children = inputs.map((it) => {
164
+ return calculateSingleChild(it, masterExtra.coPayMode ?? "PERCENTAGE-AMOUNT", options);
165
+ });
166
+ const subtotalAmount = children.reduce((a, c) => a + c.subtotalAmount, 0);
167
+ const otherChargeAmount = children.reduce((a, c) => a + c.otherChargeAmount, 0);
168
+ const itemDiscountSum = children.reduce((a, c) => a + c.discountAmount, 0);
169
+ const taxAmount = children.reduce((a, c) => a + c.taxAmount, 0);
170
+ const grossAmount = children.reduce((a, c) => a + c.grossAmount, 0);
171
+ const copayAmount = children.reduce((a, c) => a + c.copayAmount, 0);
172
+ const patientRawTotal = grossAmount - copayAmount;
173
+ let masterExtraApplied = 0;
174
+ if (masterExtra.mode === "PERCENTAGE") {
175
+ masterExtraApplied = percentage(patientRawTotal, masterExtra.value);
176
+ } else if (masterExtra.mode === "AMOUNT") {
177
+ masterExtraApplied = Math.min(masterExtra.value, patientRawTotal);
178
+ }
179
+ masterExtraApplied = maybeStep(masterExtraApplied, stepwise, headFmt, precision);
180
+ const discountTotalAmount = maybeStep(itemDiscountSum + masterExtraApplied, stepwise, headFmt, precision);
181
+ const patientAfterExtra = applyRound(Math.max(0, patientRawTotal - masterExtraApplied), "TO_FIXED", 2);
182
+ const netAmount = applyRound(patientAfterExtra, headFmt, precision);
183
+ const roundOffAmount = netAmount - patientAfterExtra;
184
+ const master = {
185
+ additionalDiscountMode: masterExtra.mode,
186
+ additionalDiscountValue: masterExtra.value,
187
+ subtotalAmount: applyRound(subtotalAmount, headFmt, precision),
188
+ otherChargeAmount: applyRound(otherChargeAmount, headFmt, precision),
189
+ discountTotalAmount: applyRound(discountTotalAmount, headFmt, precision),
190
+ // includes master extra
191
+ taxAmount: applyRound(taxAmount, headFmt, precision),
192
+ grossAmount: applyRound(grossAmount, headFmt, precision),
193
+ netAmount,
194
+ // patient payable after master extra + rounding
195
+ roundOffAmount: applyRound(roundOffAmount, "TO_FIXED", 2),
196
+ // rounded - raw
197
+ copayAmount: applyRound(copayAmount, headFmt, precision)
198
+ // Σ line copay (insurer covered)
199
+ };
200
+ return { master, children };
201
+ }
202
+ function calculateSingleChild(it, coPayMode, options = {}) {
203
+ const stepwise = options.calculationMethod === "STEP_WISE";
204
+ const lineFmt = options.lineRound ?? RoundFormat.TO_FIXED;
205
+ const precision = options.precision ?? 2;
206
+ let baseRate = it.rate;
207
+ if (it.addonPercentage) {
208
+ baseRate = baseRate + percentage(baseRate, it.addonPercentage);
209
+ baseRate = applyRound(baseRate, lineFmt, precision);
210
+ }
211
+ const subtotal = applyRound(it.qty * baseRate, lineFmt, precision);
212
+ const other = applyRound(it.otherCharge ?? 0, lineFmt, precision);
213
+ const basePreTax = applyRound(subtotal + other, lineFmt, precision);
214
+ let copayAmount = 0;
215
+ if (coPayMode === "PERCENTAGE-AMOUNT") {
216
+ const copayType = it.coPaymentType ?? "AMOUNT";
217
+ const copayValue = it.coPayValue ?? 0;
218
+ copayAmount = copayType === "PERCENTAGE" ? percentage(subtotal, copayValue) : Math.min(copayValue, subtotal);
219
+ }
220
+ const dMode = it.discountMode ?? "AMOUNT";
221
+ const dVal = it.discountValue ?? 0;
222
+ const tMethod = it.taxMethod ?? "NONE";
223
+ const tVal = Math.max(0, it.taxValue ?? 0);
224
+ let baseAfterTaxDisc = basePreTax;
225
+ if (tMethod === "INCLUSIVE") {
226
+ const inclusiveTaxMultiplier = (100 + tVal) / 100;
227
+ baseAfterTaxDisc = basePreTax / inclusiveTaxMultiplier;
228
+ }
229
+ baseAfterTaxDisc = maybeStep(baseAfterTaxDisc, stepwise, lineFmt, precision);
230
+ const inclDiff = applyRound(basePreTax - baseAfterTaxDisc, lineFmt, precision);
231
+ let discountValue = 0;
232
+ if (dMode === "AMOUNT") {
233
+ discountValue = dVal;
234
+ } else if (dMode === "PERCENTAGE") {
235
+ discountValue = percentage(baseAfterTaxDisc, dVal);
236
+ }
237
+ discountValue = maybeStep(discountValue, stepwise, lineFmt, precision);
238
+ let afterDisc = Math.max(0, baseAfterTaxDisc - discountValue);
239
+ afterDisc = maybeStep(afterDisc, stepwise, lineFmt, precision);
240
+ let calculatedTax = percentage(afterDisc, tVal);
241
+ calculatedTax = maybeStep(calculatedTax, stepwise, lineFmt, precision);
242
+ let grossAmount = afterDisc + calculatedTax;
243
+ grossAmount = maybeStep(grossAmount, stepwise, lineFmt, precision);
244
+ if (coPayMode === "EXCLUSIVE-INCLUSIVE" && it.coPayMethod === "INCLUSIVE") {
245
+ copayAmount = grossAmount;
246
+ }
247
+ copayAmount = applyRound(copayAmount, lineFmt, precision);
248
+ const copay = clamp(Math.max(0, copayAmount), 0, grossAmount);
249
+ const patientRaw = applyRound(grossAmount - copay, RoundFormat.TO_FIXED, 2);
250
+ const patientRounded = applyRound(patientRaw, lineFmt, precision);
251
+ const lineRoundOff = applyRound(patientRounded - patientRaw, RoundFormat.TO_FIXED, 2);
252
+ return {
253
+ // include baseRate if your ChildCalculated type has it (as in your bulk function)
254
+ baseRate: applyRound(baseRate, lineFmt, precision),
255
+ subtotalAmount: applyRound(subtotal + other - inclDiff, lineFmt, precision),
256
+ otherChargeAmount: other,
257
+ discountMode: dMode,
258
+ discountValue: dVal,
259
+ discountAmount: applyRound(discountValue, lineFmt, precision),
260
+ taxMethod: tMethod,
261
+ taxValue: tVal,
262
+ taxAmount: applyRound(calculatedTax, lineFmt, precision),
263
+ grossAmount: applyRound(grossAmount, lineFmt, precision),
264
+ netAmount: patientRounded,
265
+ roundOffAmount: lineRoundOff,
266
+ copayAmount: copay
267
+ };
268
+ }
269
+
270
+ // src/utils/audit.utils.ts
271
+ function isValidDate(value) {
272
+ if (value instanceof Date) {
273
+ return !isNaN(value.getTime());
274
+ }
275
+ if (typeof value === "string" || typeof value === "number") {
276
+ const parsed = new Date(value);
277
+ return !isNaN(parsed.getTime());
278
+ }
279
+ return false;
280
+ }
281
+ function flattenObject(obj, parentKey = "") {
282
+ const result = {};
283
+ for (const key of Object.keys(obj)) {
284
+ const raw = obj[key];
285
+ const newKey = parentKey ? `${parentKey}_${key}` : key;
286
+ if (typeof raw !== "number" && isValidDate(raw)) {
287
+ const date = raw instanceof Date ? raw : new Date(raw);
288
+ result[newKey] = date.toISOString().split("T")[0];
289
+ continue;
290
+ }
291
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
292
+ Object.assign(result, flattenObject(raw, newKey));
293
+ continue;
294
+ }
295
+ result[newKey] = raw;
296
+ }
297
+ return result;
298
+ }
299
+ function findDifferences(obj1, obj2) {
300
+ const flatObj1 = flattenObject(obj1);
301
+ const flatObj2 = flattenObject(obj2);
302
+ const differences = [];
303
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(flatObj1)]);
304
+ allKeys.forEach((key) => {
305
+ if (flatObj1[key] !== flatObj2[key]) {
306
+ differences.push({
307
+ // Replace underscores with spaces for a nicer field output
308
+ field: key.replace(/_/g, " "),
309
+ changedFrom: typeof flatObj1[key] !== "string" ? JSON.stringify(flatObj1[key]) : flatObj1[key],
310
+ changedTo: typeof flatObj2[key] !== "string" ? JSON.stringify(flatObj2[key]) : flatObj2[key]
311
+ });
312
+ }
313
+ });
314
+ return differences;
315
+ }
316
+ export {
317
+ RoundFormat,
318
+ calculateBillingFromChildren,
319
+ calculateSingleChild,
320
+ customOmit,
321
+ findDifferences,
322
+ getDynamicValue,
323
+ getPattern,
324
+ interpolate,
325
+ objectTo2DArray,
326
+ toNumberOrNull
327
+ };
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "av6-utils",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "description": "All utility function for av6 node js projects.",
8
+ "author": "Aniket Sarkar",
9
+ "license": "ISC",
10
+ "scripts": {
11
+ "build": "npm run format && tsup",
12
+ "format": "prettier --write **/*.ts"
13
+ },
14
+ "devDependencies": {
15
+ "tsup": "^8.5.0",
16
+ "typescript": "^5.9.2"
17
+ },
18
+ "peerDependencies": {},
19
+ "dependencies": {
20
+ "prettier": "^3.6.2"
21
+ }
22
+ }