formula-parser-payroll 2.1.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.
- package/README.md +202 -0
- package/dist/database/database-connector.d.ts +60 -0
- package/dist/database/database-connector.d.ts.map +1 -0
- package/dist/database/database-connector.js +9 -0
- package/dist/database/database-connector.js.map +1 -0
- package/dist/database/helpers.d.ts +44 -0
- package/dist/database/helpers.d.ts.map +1 -0
- package/dist/database/helpers.js +120 -0
- package/dist/database/helpers.js.map +1 -0
- package/dist/database/index.d.ts +11 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +29 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/payroll-formula.service.d.ts +118 -0
- package/dist/database/payroll-formula.service.d.ts.map +1 -0
- package/dist/database/payroll-formula.service.js +794 -0
- package/dist/database/payroll-formula.service.js.map +1 -0
- package/dist/database/types.d.ts +117 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +9 -0
- package/dist/database/types.js.map +1 -0
- package/dist/formula-engine.d.ts +18 -0
- package/dist/formula-engine.d.ts.map +1 -0
- package/dist/formula-engine.js +356 -0
- package/dist/formula-engine.js.map +1 -0
- package/dist/formula-parser.d.ts +60 -0
- package/dist/formula-parser.d.ts.map +1 -0
- package/dist/formula-parser.js +366 -0
- package/dist/formula-parser.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/nestjs/database-connector.d.ts +60 -0
- package/dist/nestjs/database-connector.d.ts.map +1 -0
- package/dist/nestjs/database-connector.js +9 -0
- package/dist/nestjs/database-connector.js.map +1 -0
- package/dist/nestjs/helpers.d.ts +44 -0
- package/dist/nestjs/helpers.d.ts.map +1 -0
- package/dist/nestjs/helpers.js +120 -0
- package/dist/nestjs/helpers.js.map +1 -0
- package/dist/nestjs/index.d.ts +11 -0
- package/dist/nestjs/index.d.ts.map +1 -0
- package/dist/nestjs/index.js +29 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/nestjs/payroll-formula.service.d.ts +91 -0
- package/dist/nestjs/payroll-formula.service.d.ts.map +1 -0
- package/dist/nestjs/payroll-formula.service.js +640 -0
- package/dist/nestjs/payroll-formula.service.js.map +1 -0
- package/dist/nestjs/types.d.ts +117 -0
- package/dist/nestjs/types.d.ts.map +1 -0
- package/dist/nestjs/types.js +9 -0
- package/dist/nestjs/types.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# payroll-formula-parser
|
|
2
|
+
|
|
3
|
+
> A lightweight, high-performance payroll formula parser. Parse and evaluate payroll formulas with employee data variables.
|
|
4
|
+
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install payroll-formula-parser
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 🚀 Quick Start
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { PayrollFormulaService } from 'payroll-formula-parser';
|
|
17
|
+
|
|
18
|
+
const service = new PayrollFormulaService(n7);
|
|
19
|
+
|
|
20
|
+
// Parse formula for a single employee
|
|
21
|
+
const result = await service.parseByEmpId({
|
|
22
|
+
formula: 'SALARY * 0.1 + AL_001',
|
|
23
|
+
empId: 'EMP001',
|
|
24
|
+
auth: {
|
|
25
|
+
conn: mysqlConnection, // Your database connection
|
|
26
|
+
connFin: mysqlConnection, // Financial DB connection
|
|
27
|
+
userData: {
|
|
28
|
+
companyId: '1',
|
|
29
|
+
companyCode: 'COMP001',
|
|
30
|
+
taxCountry: 'ID'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log(result.hasil); // Formula result
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Processing Multiple Employees
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// Process multiple employees in parallel (with automatic chunking)
|
|
42
|
+
const results = await service.parseByEmpId({
|
|
43
|
+
formula: 'SALARY * 0.1 + AL_001',
|
|
44
|
+
listEmpId: ['EMP001', 'EMP002', 'EMP003', ...], // Can handle thousands!
|
|
45
|
+
auth: authContext
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Results: { EMP001: 5000, EMP002: 6000, EMP003: 5500, ... }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Formula Validation
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Validate formula syntax before execution
|
|
55
|
+
const validation = await service.formulaValidityCheck({
|
|
56
|
+
formula: 'IF(GRADE = "A", SALARY * 1.2, SALARY)',
|
|
57
|
+
auth: authContext
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.log(validation.result); // 'success' or error message
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 📚 Supported Formula Syntax
|
|
64
|
+
|
|
65
|
+
### Variables
|
|
66
|
+
|
|
67
|
+
- **Employee Data**: `SALARY`, `GRADE`, `POSITION`, `DEPARTMENT`, `COSTCENTER`, etc.
|
|
68
|
+
- **Payroll Components**: `AL_001`, `DE_002`, `NE_003` (Allowances, Deductions, Net Earnings)
|
|
69
|
+
- **Custom Fields**: `CUSTOMFIELD1` through `CUSTOMFIELD10`
|
|
70
|
+
- **Dates**: `JOINDATE`, `STARTDATE`, `ENDDATE`, `BIRTHDATE`, etc.
|
|
71
|
+
|
|
72
|
+
### Functions
|
|
73
|
+
|
|
74
|
+
#### Logical Functions
|
|
75
|
+
- `IF(condition, true_value, false_value)`
|
|
76
|
+
- `AND(condition1, condition2, ...)`
|
|
77
|
+
- `OR(condition1, condition2, ...)`
|
|
78
|
+
- `NOT(condition)`
|
|
79
|
+
|
|
80
|
+
#### Math Functions
|
|
81
|
+
- `ROUND(number, decimals)`
|
|
82
|
+
- `ROUNDUP(number, decimals)`
|
|
83
|
+
- `ROUNDDOWN(number, decimals)`
|
|
84
|
+
- `TRUNC(number)`
|
|
85
|
+
- `MIN(value1, value2, ...)`
|
|
86
|
+
- `MAX(value1, value2, ...)`
|
|
87
|
+
- `MOD(number, divisor)`
|
|
88
|
+
|
|
89
|
+
#### Date Functions
|
|
90
|
+
- `TODAY()` - Current date
|
|
91
|
+
- `DATETIME_NOW()` - Current datetime
|
|
92
|
+
- `DATE(year, month, day)`
|
|
93
|
+
- `YEAR(date)`, `MONTH(date)`, `DAY(date)`
|
|
94
|
+
- `DATEDIFF(date1, date2, unit)`
|
|
95
|
+
- `DATEADD(date, number, unit)`
|
|
96
|
+
- `DAYSINMONTH(date)`
|
|
97
|
+
- `DAYOFWEEK(date)`
|
|
98
|
+
- `LENGTHOFSERVICE(startDate, endDate, unit)`
|
|
99
|
+
|
|
100
|
+
#### String Functions
|
|
101
|
+
- `CONCATENATE(str1, str2, ...)`
|
|
102
|
+
- `CONCATENATESKIPNULL(str1, str2, ...)`
|
|
103
|
+
- `FINDLIST(value, list, delimiter)`
|
|
104
|
+
|
|
105
|
+
#### Other Functions
|
|
106
|
+
- `ISNUMERIC(value)`
|
|
107
|
+
- `ISDATE(value)`
|
|
108
|
+
- `CHILDDEPENDENTS` - Count of child dependents
|
|
109
|
+
|
|
110
|
+
### Example Formulas
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Conditional bonus based on grade
|
|
114
|
+
'IF(GRADE = "A", SALARY * 0.2, SALARY * 0.1)'
|
|
115
|
+
|
|
116
|
+
// Complex calculation with multiple conditions
|
|
117
|
+
'IF(AND(GRADE = "A", LENGTHOFSERVICE(JOINDATE, TODAY(), "Y") > 5), SALARY * 1.5, SALARY)'
|
|
118
|
+
|
|
119
|
+
// Date-based calculation
|
|
120
|
+
'IF(DATEDIFF(TODAY(), JOINDATE, "M") > 12, AL_001 * 1.1, AL_001)'
|
|
121
|
+
|
|
122
|
+
// Rounding
|
|
123
|
+
'ROUND(SALARY * 0.15, 2)'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## ⚡ Performance Optimizations
|
|
127
|
+
|
|
128
|
+
### Automatic Chunking
|
|
129
|
+
When processing large employee lists, the service automatically chunks them into batches of 50 to prevent memory overflow while maintaining parallel processing speed.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// Processes 1000 employees in 20 chunks of 50 (parallel within each chunk)
|
|
133
|
+
const results = await service.parseByEmpId({
|
|
134
|
+
formula: 'SALARY * 0.1',
|
|
135
|
+
listEmpId: [...1000employees], // Automatically chunked!
|
|
136
|
+
auth: authContext
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Smart Component Detection
|
|
141
|
+
Only fetches payroll components that are actually used in the formula:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Formula: 'AL_001 + AL_002'
|
|
145
|
+
// ✅ Only fetches AL_001 and AL_002 from database
|
|
146
|
+
// ❌ Doesn't fetch all 100+ components
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Chunked Decryption
|
|
150
|
+
Decrypts employee component values in batches of 20 to balance speed and resource usage.
|
|
151
|
+
|
|
152
|
+
### Caching
|
|
153
|
+
- **Reserved words cache**: 5-minute TTL
|
|
154
|
+
- **Component list cache**: 5-minute TTL
|
|
155
|
+
- Clear cache manually: `service.clearCache()` or `service.clearCacheForCompany(companyId, taxCountry)`
|
|
156
|
+
|
|
157
|
+
## 📖 API Reference
|
|
158
|
+
|
|
159
|
+
### `PayrollFormulaService`
|
|
160
|
+
|
|
161
|
+
#### `parseByEmpId(input: ParseByEmpIdInput)`
|
|
162
|
+
|
|
163
|
+
Parse formula for one or multiple employees.
|
|
164
|
+
|
|
165
|
+
**Parameters:**
|
|
166
|
+
- `formula` (string): The formula to parse
|
|
167
|
+
- `empId` (string, optional): Single employee ID
|
|
168
|
+
- `listEmpId` (string[], optional): Multiple employee IDs
|
|
169
|
+
- `auth` (AuthContext): Authentication and database context
|
|
170
|
+
|
|
171
|
+
**Returns:**
|
|
172
|
+
- Single employee: `FormulaParseResultLegacy` with `hasil` and `message`
|
|
173
|
+
- Multiple employees: `Record<empId, result>`
|
|
174
|
+
|
|
175
|
+
#### `formulaValidityCheck(input: { formula: string; auth: AuthContext })`
|
|
176
|
+
|
|
177
|
+
Validate formula syntax.
|
|
178
|
+
|
|
179
|
+
**Returns:**
|
|
180
|
+
- `{ result: 'success' }` or throws error with details
|
|
181
|
+
|
|
182
|
+
#### `clearCache()`
|
|
183
|
+
|
|
184
|
+
Clear all caches.
|
|
185
|
+
|
|
186
|
+
#### `clearCacheForCompany(companyId: string, taxCountry: string)`
|
|
187
|
+
|
|
188
|
+
Clear cache for specific company.
|
|
189
|
+
|
|
190
|
+
## 🏗️ Architecture
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
src/
|
|
194
|
+
├── formula-engine.ts # Core formula parser (hot-formula-parser wrapper)
|
|
195
|
+
├── formula-parser.ts # Variable processing logic
|
|
196
|
+
├── types.ts # Type definitions
|
|
197
|
+
└── database/
|
|
198
|
+
├── payroll-formula.service.ts # Main service with optimizations
|
|
199
|
+
├── database-connector.ts # Database interface
|
|
200
|
+
├── helpers.ts # Utility functions
|
|
201
|
+
└── types.ts # Database-specific types
|
|
202
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Connector Interface
|
|
3
|
+
*
|
|
4
|
+
* Generic database connector that can work with any database client
|
|
5
|
+
* (MySQL2, TypeORM, Knex, Sequelize, etc.)
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generic database connection interface
|
|
9
|
+
* Any database client that implements this interface can be used
|
|
10
|
+
*/
|
|
11
|
+
export interface IDatabaseConnection {
|
|
12
|
+
/**
|
|
13
|
+
* Execute a raw SQL query with optional parameters
|
|
14
|
+
* @param sql SQL query string with placeholders (?)
|
|
15
|
+
* @param params Query parameters
|
|
16
|
+
* @returns Promise with query results
|
|
17
|
+
*/
|
|
18
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Configuration for database connections
|
|
22
|
+
*/
|
|
23
|
+
export interface DatabaseConfig {
|
|
24
|
+
/** Main database connection (for employee/organization data) */
|
|
25
|
+
mainConnection: IDatabaseConnection;
|
|
26
|
+
/** Finance database connection (for payroll/component data) */
|
|
27
|
+
financeConnection: IDatabaseConnection;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Company and user context for queries
|
|
31
|
+
*/
|
|
32
|
+
export interface CompanyContext {
|
|
33
|
+
/** Company ID */
|
|
34
|
+
companyId: string | number;
|
|
35
|
+
/** Company Code */
|
|
36
|
+
companyCode: string;
|
|
37
|
+
/** Tax Country Code (ID, TH, MY, PH) */
|
|
38
|
+
taxCountry: 'ID' | 'TH' | 'MY' | 'PH';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encryption configuration for decrypting component values
|
|
42
|
+
*/
|
|
43
|
+
export interface EncryptionConfig {
|
|
44
|
+
/** P Number for decryption function */
|
|
45
|
+
pNumber: string;
|
|
46
|
+
/** L Number for decryption function */
|
|
47
|
+
lNumber: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Complete context for formula parsing with database
|
|
51
|
+
*/
|
|
52
|
+
export interface FormulaContext {
|
|
53
|
+
/** Database connections */
|
|
54
|
+
database: DatabaseConfig;
|
|
55
|
+
/** Company and user context */
|
|
56
|
+
company: CompanyContext;
|
|
57
|
+
/** Encryption config (optional - needed only if components are encrypted) */
|
|
58
|
+
encryption?: EncryptionConfig;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=database-connector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-connector.d.ts","sourceRoot":"","sources":["../../src/database/database-connector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,cAAc,EAAE,mBAAmB,CAAC;IAEpC,+DAA+D;IAC/D,iBAAiB,EAAE,mBAAmB,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iBAAiB;IACjB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3B,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,wCAAwC;IACxC,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAEhB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,QAAQ,EAAE,cAAc,CAAC;IAEzB,+BAA+B;IAC/B,OAAO,EAAE,cAAc,CAAC;IAExB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Database Connector Interface
|
|
4
|
+
*
|
|
5
|
+
* Generic database connector that can work with any database client
|
|
6
|
+
* (MySQL2, TypeORM, Knex, Sequelize, etc.)
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
//# sourceMappingURL=database-connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-connector.js","sourceRoot":"","sources":["../../src/database/database-connector.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS Module Helpers for Payroll Formula Parser
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for database operations and data processing.
|
|
5
|
+
*/
|
|
6
|
+
import { AuthContext, FormulaContext } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Get currency code based on tax country
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCurrencyCode(taxCountry: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Decrypt a value using database function
|
|
13
|
+
*/
|
|
14
|
+
export declare function decryptValue(value: string, auth: AuthContext, empId: string): Promise<number>;
|
|
15
|
+
/**
|
|
16
|
+
* Check if position-related data is needed
|
|
17
|
+
*/
|
|
18
|
+
export declare function needsPositionData(words: Set<string>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Check if cost center data is needed
|
|
21
|
+
*/
|
|
22
|
+
export declare function needsCostCenterData(words: Set<string>): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if grade data is needed
|
|
25
|
+
*/
|
|
26
|
+
export declare function needsGradeData(words: Set<string>): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Check if work location data is needed
|
|
29
|
+
*/
|
|
30
|
+
export declare function needsWorkLocationData(words: Set<string>): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Check if custom field data is needed
|
|
33
|
+
*/
|
|
34
|
+
export declare function needsCustomFieldData(words: Set<string>): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Format name for formula with proper case handling
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatNameForFormula(name: string, formula: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Convert legacy AuthContext to new FormulaContext
|
|
41
|
+
* This helps with backward compatibility
|
|
42
|
+
*/
|
|
43
|
+
export declare function convertAuthToFormulaContext(auth: AuthContext): FormulaContext;
|
|
44
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/database/helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAoB,cAAc,EAAE,MAAM,SAAS,CAAC;AAExE;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAK7D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAE1D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAEjE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAKhE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAc1E;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,cAAc,CAgB7E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NestJS Module Helpers for Payroll Formula Parser
|
|
4
|
+
*
|
|
5
|
+
* Helper functions for database operations and data processing.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.getCurrencyCode = getCurrencyCode;
|
|
9
|
+
exports.decryptValue = decryptValue;
|
|
10
|
+
exports.needsPositionData = needsPositionData;
|
|
11
|
+
exports.needsCostCenterData = needsCostCenterData;
|
|
12
|
+
exports.needsGradeData = needsGradeData;
|
|
13
|
+
exports.needsWorkLocationData = needsWorkLocationData;
|
|
14
|
+
exports.needsCustomFieldData = needsCustomFieldData;
|
|
15
|
+
exports.formatNameForFormula = formatNameForFormula;
|
|
16
|
+
exports.convertAuthToFormulaContext = convertAuthToFormulaContext;
|
|
17
|
+
/**
|
|
18
|
+
* Get currency code based on tax country
|
|
19
|
+
*/
|
|
20
|
+
function getCurrencyCode(taxCountry) {
|
|
21
|
+
switch (taxCountry) {
|
|
22
|
+
case 'TH': return 'THB';
|
|
23
|
+
case 'PH': return 'PHP';
|
|
24
|
+
case 'MY': return 'MYR';
|
|
25
|
+
default: return 'IDR';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decrypt a value using database function
|
|
30
|
+
*/
|
|
31
|
+
async function decryptValue(value, auth, empId) {
|
|
32
|
+
try {
|
|
33
|
+
const q = `SELECT SF2356896('${value}',
|
|
34
|
+
'${auth.accessToken.confParse.PNUMBER}',
|
|
35
|
+
'${auth.accessToken.confParse.LNUMBER}',
|
|
36
|
+
'${empId}') AS result`;
|
|
37
|
+
const result = await auth.connFin.query(q);
|
|
38
|
+
if (result.length > 0) {
|
|
39
|
+
return result[0].result;
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if position-related data is needed
|
|
49
|
+
*/
|
|
50
|
+
function needsPositionData(words) {
|
|
51
|
+
return words.has('DEPTNAME') ||
|
|
52
|
+
words.has('POSITIONNAME') ||
|
|
53
|
+
words.has('POSITIONCODE') ||
|
|
54
|
+
words.has('DEPTCODE');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if cost center data is needed
|
|
58
|
+
*/
|
|
59
|
+
function needsCostCenterData(words) {
|
|
60
|
+
return words.has('COSTCENTER') || words.has('COSTCENTERCODE');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if grade data is needed
|
|
64
|
+
*/
|
|
65
|
+
function needsGradeData(words) {
|
|
66
|
+
return words.has('GRADE') || words.has('GRDCODE');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if work location data is needed
|
|
70
|
+
*/
|
|
71
|
+
function needsWorkLocationData(words) {
|
|
72
|
+
return words.has('WORKLOCATION') || words.has('WORKLOCATIONCODE');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if custom field data is needed
|
|
76
|
+
*/
|
|
77
|
+
function needsCustomFieldData(words) {
|
|
78
|
+
for (let i = 1; i <= 10; i++) {
|
|
79
|
+
if (words.has(`CUSTOMFIELD${i}`))
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Format name for formula with proper case handling
|
|
86
|
+
*/
|
|
87
|
+
function formatNameForFormula(name, formula) {
|
|
88
|
+
if (!name)
|
|
89
|
+
return '""';
|
|
90
|
+
const upperName = name.replace(/ /g, '').toUpperCase();
|
|
91
|
+
const normalName = name;
|
|
92
|
+
const hasUpperInFormula = formula.includes(`"${upperName}"`) ||
|
|
93
|
+
formula.includes(`'${upperName}'`);
|
|
94
|
+
if (hasUpperInFormula) {
|
|
95
|
+
return `"${upperName}"`;
|
|
96
|
+
}
|
|
97
|
+
return `"${normalName}"`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Convert legacy AuthContext to new FormulaContext
|
|
101
|
+
* This helps with backward compatibility
|
|
102
|
+
*/
|
|
103
|
+
function convertAuthToFormulaContext(auth) {
|
|
104
|
+
return {
|
|
105
|
+
database: {
|
|
106
|
+
mainConnection: auth.conn,
|
|
107
|
+
financeConnection: auth.connFin,
|
|
108
|
+
},
|
|
109
|
+
company: {
|
|
110
|
+
companyId: auth.userData.companyId,
|
|
111
|
+
companyCode: auth.userData.companyCode || auth.userData.currentCompanyCode || '',
|
|
112
|
+
taxCountry: (auth.userData.taxCountry || 'ID'),
|
|
113
|
+
},
|
|
114
|
+
encryption: auth.accessToken?.confParse ? {
|
|
115
|
+
pNumber: auth.accessToken.confParse.PNUMBER,
|
|
116
|
+
lNumber: auth.accessToken.confParse.LNUMBER,
|
|
117
|
+
} : undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/database/helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS Module for Payroll Formula Parser
|
|
3
|
+
*
|
|
4
|
+
* This module provides database-integrated formula parsing.
|
|
5
|
+
* Works with any database client that implements the IDatabaseConnection interface.
|
|
6
|
+
*/
|
|
7
|
+
export * from './database-connector';
|
|
8
|
+
export * from './types';
|
|
9
|
+
export * from './helpers';
|
|
10
|
+
export { PayrollFormulaService } from './payroll-formula.service';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NestJS Module for Payroll Formula Parser
|
|
4
|
+
*
|
|
5
|
+
* This module provides database-integrated formula parsing.
|
|
6
|
+
* Works with any database client that implements the IDatabaseConnection interface.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.PayrollFormulaService = void 0;
|
|
24
|
+
__exportStar(require("./database-connector"), exports);
|
|
25
|
+
__exportStar(require("./types"), exports);
|
|
26
|
+
__exportStar(require("./helpers"), exports);
|
|
27
|
+
var payroll_formula_service_1 = require("./payroll-formula.service");
|
|
28
|
+
Object.defineProperty(exports, "PayrollFormulaService", { enumerable: true, get: function () { return payroll_formula_service_1.PayrollFormulaService; } });
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;AAEH,uDAAqC;AACrC,0CAAwB;AACxB,4CAA0B;AAC1B,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS Payroll Formula Parser Service
|
|
3
|
+
*
|
|
4
|
+
* This service provides database-integrated formula parsing.
|
|
5
|
+
* It fetches employee data and component values from the database
|
|
6
|
+
* and then uses the core formula parser.
|
|
7
|
+
*/
|
|
8
|
+
import { AuthContext, ParseByEmpIdInput, FormulaParseResultLegacy } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Payroll Formula Parser Service
|
|
11
|
+
*
|
|
12
|
+
* Use this service when you need to parse formulas with just formula and empId.
|
|
13
|
+
* This service handles fetching employee data and component values from the database.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const service = new PayrollFormulaService();
|
|
18
|
+
* const result = await service.parseByEmpId({
|
|
19
|
+
* formula: 'SALARY * 0.1 + AL_001',
|
|
20
|
+
* empId: 'EMP001',
|
|
21
|
+
* auth: authContext,
|
|
22
|
+
* });
|
|
23
|
+
* console.log(result.result);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class PayrollFormulaService {
|
|
27
|
+
private reservedWordsCache;
|
|
28
|
+
private componentListCache;
|
|
29
|
+
private readonly CACHE_TTL;
|
|
30
|
+
private readonly CHUNK_SIZE;
|
|
31
|
+
/**
|
|
32
|
+
* Parse formula by employee ID(s)
|
|
33
|
+
* This method fetches all required data from database and evaluates the formula
|
|
34
|
+
*
|
|
35
|
+
* When listEmpId is provided: returns Record<empId, hasil>
|
|
36
|
+
* When empId is provided: returns FormulaParseResultLegacy
|
|
37
|
+
*/
|
|
38
|
+
parseByEmpId(input: ParseByEmpIdInput): Promise<FormulaParseResultLegacy | Record<string, number | string | boolean>>;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a formula is valid
|
|
41
|
+
* This ports the logic from payroll-server's formulaValidtyCheck
|
|
42
|
+
*/
|
|
43
|
+
formulaValidityCheck(input: {
|
|
44
|
+
formula: string;
|
|
45
|
+
auth: AuthContext;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
result: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Clear all caches (useful for testing or when data is updated)
|
|
51
|
+
*/
|
|
52
|
+
clearCache(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Clear cache for specific company
|
|
55
|
+
*/
|
|
56
|
+
clearCacheForCompany(companyId: string, taxCountry: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Parse formula for a single employee
|
|
59
|
+
* Internal method used by parseByEmpId
|
|
60
|
+
*/
|
|
61
|
+
private parseForSingleEmployee;
|
|
62
|
+
/**
|
|
63
|
+
* Parse formula for multiple employees
|
|
64
|
+
*/
|
|
65
|
+
parseForMultipleEmployees(formula: string, empIds: string[], auth: AuthContext): Promise<Record<string, FormulaParseResultLegacy>>;
|
|
66
|
+
/**
|
|
67
|
+
* Get list of component codes
|
|
68
|
+
*/
|
|
69
|
+
private getComponentList;
|
|
70
|
+
/**
|
|
71
|
+
* Get all components for company
|
|
72
|
+
*/
|
|
73
|
+
private getAllComponents;
|
|
74
|
+
/**
|
|
75
|
+
* Get employee component values
|
|
76
|
+
*/
|
|
77
|
+
private getEmployeeComponents;
|
|
78
|
+
/**
|
|
79
|
+
* OPTIMIZATION: Batch get employee components for multiple employees
|
|
80
|
+
* This reduces database roundtrips when processing multiple employees
|
|
81
|
+
*/
|
|
82
|
+
private getBatchEmployeeComponents;
|
|
83
|
+
/**
|
|
84
|
+
* Get employee data with relations
|
|
85
|
+
*/
|
|
86
|
+
private getEmployeeData;
|
|
87
|
+
/**
|
|
88
|
+
* OPTIMIZATION: Batch get employee data for multiple employees
|
|
89
|
+
* This reduces database roundtrips significantly
|
|
90
|
+
*/
|
|
91
|
+
private getBatchEmployeeData;
|
|
92
|
+
/**
|
|
93
|
+
* Get reserved words list for formula processing
|
|
94
|
+
*/
|
|
95
|
+
private getReservedWords;
|
|
96
|
+
/**
|
|
97
|
+
* Extract required words from formula
|
|
98
|
+
*/
|
|
99
|
+
private extractRequiredWordsFromFormula;
|
|
100
|
+
/**
|
|
101
|
+
* Batch load all required data for formula parsing
|
|
102
|
+
*/
|
|
103
|
+
private batchLoadFormulaData;
|
|
104
|
+
/**
|
|
105
|
+
* Convert raw employee data to EmployeeData interface
|
|
106
|
+
*/
|
|
107
|
+
private convertToEmployeeData;
|
|
108
|
+
/**
|
|
109
|
+
* Convert cache to OrganizationalData interface
|
|
110
|
+
*/
|
|
111
|
+
private convertToOrganizationalData;
|
|
112
|
+
/**
|
|
113
|
+
* OPTIMIZATION: Utility to chunk array for batched processing
|
|
114
|
+
* This prevents memory overflow when processing thousands of items
|
|
115
|
+
*/
|
|
116
|
+
private chunkArray;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=payroll-formula.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payroll-formula.service.d.ts","sourceRoot":"","sources":["../../src/database/payroll-formula.service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,EACL,WAAW,EACX,iBAAiB,EAGjB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AAWjB;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAqB;IAEhC,OAAO,CAAC,kBAAkB,CAA2E;IACrG,OAAO,CAAC,kBAAkB,CAA2F;IACrH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAM;IAEjC;;;;;;OAMG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IA0C3H;;;OAGG;IACG,oBAAoB,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IA0KtG;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAMjE;;;OAGG;YACW,sBAAsB;IAyLpC;;OAEG;IACG,yBAAyB,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAYpD;;OAEG;YACW,gBAAgB;IAiC9B;;OAEG;YACW,gBAAgB;IAmB9B;;OAEG;YACW,qBAAqB;IAqBnC;;;OAGG;YACW,0BAA0B;IAqCxC;;OAEG;YACW,eAAe;IAkC7B;;;OAGG;YACW,oBAAoB;IA4ClC;;OAEG;YACW,gBAAgB;IAuC9B;;OAEG;IACH,OAAO,CAAC,+BAA+B;IAavC;;OAEG;YACW,oBAAoB;IAyHlC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA8B7B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAgBnC;;;OAGG;IACH,OAAO,CAAC,UAAU;CAOnB"}
|