Compare commits
No commits in common. "81afd7ec963ff60b4ae93239ede2762a45096edf" and "17c62d2b451755c8c2527c29a5f39ea4dfd77e4d" have entirely different histories.
81afd7ec96
...
17c62d2b45
@ -1,2 +1,2 @@
|
|||||||
import{a as s}from"./index-7F7W4LDI.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DfwWW08H.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
import{a as s}from"./index-PI_IMErM.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DfwWW08H.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
||||||
//# sourceMappingURL=conclusionApi-BJO_6JLT.js.map
|
//# sourceMappingURL=conclusionApi-D5monZ70.js.map
|
||||||
@ -1 +1 @@
|
|||||||
{"version":3,"file":"conclusionApi-BJO_6JLT.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
|
{"version":3,"file":"conclusionApi-D5monZ70.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
build/assets/index-D5NCgjQR.css
Normal file
1
build/assets/index-D5NCgjQR.css
Normal file
File diff suppressed because one or more lines are too long
67
build/assets/index-PI_IMErM.js
Normal file
67
build/assets/index-PI_IMErM.js
Normal file
File diff suppressed because one or more lines are too long
1
build/assets/index-PI_IMErM.js.map
Normal file
1
build/assets/index-PI_IMErM.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@
|
|||||||
<!-- Preload critical fonts and icons -->
|
<!-- Preload critical fonts and icons -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<script type="module" crossorigin src="/assets/index-7F7W4LDI.js"></script>
|
<script type="module" crossorigin src="/assets/index-PI_IMErM.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-BVfwAPj-.js">
|
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-BVfwAPj-.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CYvDqP9X.js">
|
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CYvDqP9X.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DNMmNUQL.js">
|
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DNMmNUQL.js">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B_rK4TXr.js">
|
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B_rK4TXr.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-AUbBsmWB.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D5NCgjQR.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
# Dealer Claim Financial Settlement Workflow
|
|
||||||
|
|
||||||
This document outlines the workflow for financial settlement of dealer claims within the Royal Enfield platform, following the transition from direct DMS integration to an Azure File Storage (AFS) based data exchange with SAP.
|
|
||||||
|
|
||||||
## Workflow Overview
|
|
||||||
|
|
||||||
The financial settlement process ensures that dealer claims are legally documented and financially settled through Royal Enfield's SAP system.
|
|
||||||
|
|
||||||
### 1. Legal Compliance: PWC E-Invoicing
|
|
||||||
Once the **Dealer Completion Documents** are submitted and approved by the **Initiator (Requestor Evaluation)**, the system triggers the legal compliance step.
|
|
||||||
|
|
||||||
- **Service**: `PWCIntegrationService`
|
|
||||||
- **Action**: Generates a signed E-Invoice via PWC API.
|
|
||||||
- **Output**: IRN (Invoice Reference Number), Ack No, Ack Date, Signed Invoice (PDF/B64), and QR Code.
|
|
||||||
- **Purpose**: Ensures the claim is legally recognized under GST regulations.
|
|
||||||
|
|
||||||
### 2. Financial Posting: AFS/CSV Integration
|
|
||||||
The financial settlement is handled by exchanging data files with SAP via **Azure File Storage (AFS)**.
|
|
||||||
|
|
||||||
- **Action**: The system generates a **CSV file** containing the following details:
|
|
||||||
- Invoice Number (from PWC)
|
|
||||||
- Invoice Amount (with/without GST as per activity type)
|
|
||||||
- GL Code (Resolved based on Activity Type/IO)
|
|
||||||
- Internal Order (IO) Number
|
|
||||||
- Dealer Code
|
|
||||||
- **Storage**: CSV is uploaded to a designated folder in AFS.
|
|
||||||
- **SAP Role**: SAP periodically polls AFS, picks up the CSV, and posts the transaction internally.
|
|
||||||
|
|
||||||
### 3. Payment Outcome: Credit Note
|
|
||||||
The result of the financial posting in SAP is a **Credit Note**.
|
|
||||||
|
|
||||||
- **Workflow**:
|
|
||||||
- SAP generates a Credit Note and uploads it back to AFS.
|
|
||||||
- RE Backend monitors the AFS folder.
|
|
||||||
- Once a Credit Note is detected, the system retrieves it and attaches it to the workflow request.
|
|
||||||
- An email notification (using `creditNoteSent.template.ts`) is sent to the dealer.
|
|
||||||
|
|
||||||
## Sequence Diagram
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant Dealer
|
|
||||||
participant Backend
|
|
||||||
participant PWC
|
|
||||||
participant AFS as Azure File Storage
|
|
||||||
participant SAP
|
|
||||||
|
|
||||||
Dealer->>Backend: Submit Completion Docs (Actuals)
|
|
||||||
Backend->>Backend: Initiator Approval
|
|
||||||
Backend->>PWC: Generate Signed E-Invoice
|
|
||||||
PWC-->>Backend: Return IRN & QR Code
|
|
||||||
Backend->>Backend: Generate Settlement CSV
|
|
||||||
Backend->>AFS: Upload CSV
|
|
||||||
SAP->>AFS: Pick up CSV
|
|
||||||
SAP->>SAP: Post Financials
|
|
||||||
SAP->>AFS: Upload Credit Note
|
|
||||||
Backend->>AFS: Poll/Retrieve Credit Note
|
|
||||||
Backend->>Dealer: Send Credit Note Notification
|
|
||||||
```
|
|
||||||
|
|
||||||
## GL Code Resolution
|
|
||||||
The GL Code is solved dynamically based on:
|
|
||||||
1. **Activity Type**: Each activity (e.g., Marketing Event, Demo) has a primary GL mapping.
|
|
||||||
2. **Internal Order (IO)**: If specific IO logic is required, the GL can be overridden.
|
|
||||||
|
|
||||||
## Summary of Integration Points
|
|
||||||
| Component | Integration Type | Responsibility |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **PWC** | REST API | Legal E-Invoice |
|
|
||||||
| **AFS (Azure)** | File Storage SDK | CSV Exchange |
|
|
||||||
| **SAP** | Batch Processing | Financial Posting & Credit Note |
|
|
||||||
653
package-lock.json
generated
653
package-lock.json
generated
@ -36,7 +36,6 @@
|
|||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"puppeteer": "^24.37.2",
|
|
||||||
"sequelize": "^6.37.5",
|
"sequelize": "^6.37.5",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
@ -810,6 +809,7 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.27.1",
|
"@babel/helper-validator-identifier": "^7.27.1",
|
||||||
@ -981,6 +981,7 @@
|
|||||||
"version": "7.28.5",
|
"version": "7.28.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -2878,27 +2879,6 @@
|
|||||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/@puppeteer/browsers": {
|
|
||||||
"version": "2.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.12.0.tgz",
|
|
||||||
"integrity": "sha512-Xuq42yxcQJ54ti8ZHNzF5snFvtpgXzNToJ1bXUGQRaiO8t+B6UM8sTUJfvV+AJnqtkJU/7hdy6nbKyA12aHtRw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.4.3",
|
|
||||||
"extract-zip": "^2.0.1",
|
|
||||||
"progress": "^2.0.3",
|
|
||||||
"proxy-agent": "^6.5.0",
|
|
||||||
"semver": "^7.7.3",
|
|
||||||
"tar-fs": "^3.1.1",
|
|
||||||
"yargs": "^17.7.2"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"browsers": "lib/cjs/main-cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
@ -3531,12 +3511,6 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tootallnate/quickjs-emscripten": {
|
|
||||||
"version": "0.23.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
|
|
||||||
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||||
@ -4069,16 +4043,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/yauzl": {
|
|
||||||
"version": "2.10.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
|
||||||
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.46.2",
|
"version": "8.46.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
|
||||||
@ -4482,6 +4446,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/array-flatten": {
|
"node_modules/array-flatten": {
|
||||||
@ -4528,18 +4493,6 @@
|
|||||||
"safer-buffer": "^2.1.0"
|
"safer-buffer": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ast-types": {
|
|
||||||
"version": "0.13.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
|
||||||
"integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||||
@ -4591,20 +4544,6 @@
|
|||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/b4a": {
|
|
||||||
"version": "1.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
|
|
||||||
"integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react-native-b4a": "*"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react-native-b4a": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||||
@ -4737,97 +4676,6 @@
|
|||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bare-events": {
|
|
||||||
"version": "2.8.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
|
|
||||||
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"peerDependencies": {
|
|
||||||
"bare-abort-controller": "*"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bare-abort-controller": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bare-fs": {
|
|
||||||
"version": "4.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz",
|
|
||||||
"integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bare-events": "^2.5.4",
|
|
||||||
"bare-path": "^3.0.0",
|
|
||||||
"bare-stream": "^2.6.4",
|
|
||||||
"bare-url": "^2.2.2",
|
|
||||||
"fast-fifo": "^1.3.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"bare": ">=1.16.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bare-buffer": "*"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bare-buffer": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bare-os": {
|
|
||||||
"version": "3.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz",
|
|
||||||
"integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"bare": ">=1.14.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bare-path": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bare-os": "^3.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bare-stream": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"streamx": "^2.21.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bare-buffer": "*",
|
|
||||||
"bare-events": "*"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bare-buffer": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"bare-events": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bare-url": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
|
|
||||||
"integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bare-path": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
@ -4885,15 +4733,6 @@
|
|||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/basic-ftp": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bcryptjs": {
|
"node_modules/bcryptjs": {
|
||||||
"version": "2.4.3",
|
"version": "2.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||||
@ -5077,15 +4916,6 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer-crc32": {
|
|
||||||
"version": "0.2.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
|
||||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/buffer-equal-constant-time": {
|
"node_modules/buffer-equal-constant-time": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
@ -5179,6 +5009,7 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@ -5280,19 +5111,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromium-bidi": {
|
|
||||||
"version": "13.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.1.1.tgz",
|
|
||||||
"integrity": "sha512-zB9MpoPd7VJwjowQqiW3FKOvQwffFMjQ8Iejp5ZW+sJaKLRhZX1sTxzl3Zt22TDB4zP0OOqs8lRoY7eAW5geyQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"mitt": "^3.0.1",
|
|
||||||
"zod": "^3.24.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"devtools-protocol": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ci-info": {
|
"node_modules/ci-info": {
|
||||||
"version": "3.9.0",
|
"version": "3.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||||
@ -5616,32 +5434,6 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cosmiconfig": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"env-paths": "^2.2.1",
|
|
||||||
"import-fresh": "^3.3.0",
|
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"parse-json": "^5.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/d-fischer"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": ">=4.9.5"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"typescript": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/create-jest": {
|
"node_modules/create-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
|
||||||
@ -5767,20 +5559,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/degenerator": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ast-types": "^0.13.4",
|
|
||||||
"escodegen": "^2.1.0",
|
|
||||||
"esprima": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@ -5838,12 +5616,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/devtools-protocol": {
|
|
||||||
"version": "0.0.1566079",
|
|
||||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1566079.tgz",
|
|
||||||
"integrity": "sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==",
|
|
||||||
"license": "BSD-3-Clause"
|
|
||||||
},
|
|
||||||
"node_modules/dezalgo": {
|
"node_modules/dezalgo": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||||
@ -6124,19 +5896,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/env-paths": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
|
||||||
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/error-ex": {
|
"node_modules/error-ex": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||||
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-arrayish": "^0.2.1"
|
"is-arrayish": "^0.2.1"
|
||||||
@ -6215,27 +5979,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escodegen": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"esprima": "^4.0.1",
|
|
||||||
"estraverse": "^5.2.0",
|
|
||||||
"esutils": "^2.0.2"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"escodegen": "bin/escodegen.js",
|
|
||||||
"esgenerate": "bin/esgenerate.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"source-map": "~0.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.38.0",
|
"version": "9.38.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
|
||||||
@ -6408,6 +6151,7 @@
|
|||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"bin": {
|
"bin": {
|
||||||
"esparse": "bin/esparse.js",
|
"esparse": "bin/esparse.js",
|
||||||
@ -6447,6 +6191,7 @@
|
|||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
@ -6456,6 +6201,7 @@
|
|||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -6479,15 +6225,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/events-universal": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"bare-events": "^2.7.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||||
@ -6620,41 +6357,6 @@
|
|||||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/extract-zip": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.1.1",
|
|
||||||
"get-stream": "^5.1.0",
|
|
||||||
"yauzl": "^2.10.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"extract-zip": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.17.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@types/yauzl": "^2.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/extract-zip/node_modules/get-stream": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"pump": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@ -6662,12 +6364,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-fifo": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||||
@ -6757,15 +6453,6 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fd-slicer": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"pend": "~1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fecha": {
|
"node_modules/fecha": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
||||||
@ -7192,29 +6879,6 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-uri": {
|
|
||||||
"version": "6.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
|
|
||||||
"integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"basic-ftp": "^5.0.2",
|
|
||||||
"data-uri-to-buffer": "^6.0.2",
|
|
||||||
"debug": "^4.3.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/get-uri/node_modules/data-uri-to-buffer": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
@ -7790,6 +7454,7 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parent-module": "^1.0.0",
|
"parent-module": "^1.0.0",
|
||||||
@ -7890,15 +7555,6 @@
|
|||||||
"url": "https://opencollective.com/ioredis"
|
"url": "https://opencollective.com/ioredis"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ip-address": {
|
|
||||||
"version": "10.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
|
||||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@ -7912,6 +7568,7 @@
|
|||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/is-binary-path": {
|
"node_modules/is-binary-path": {
|
||||||
@ -8745,12 +8402,14 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@ -8792,6 +8451,7 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
@ -8952,6 +8612,7 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
@ -9274,12 +8935,6 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mitt": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
"version": "0.5.6",
|
"version": "0.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
@ -9449,15 +9104,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/netmask": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-abort-controller": {
|
"node_modules/node-abort-controller": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
@ -9834,51 +9480,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pac-proxy-agent": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@tootallnate/quickjs-emscripten": "^0.23.0",
|
|
||||||
"agent-base": "^7.1.2",
|
|
||||||
"debug": "^4.3.4",
|
|
||||||
"get-uri": "^6.0.1",
|
|
||||||
"http-proxy-agent": "^7.0.0",
|
|
||||||
"https-proxy-agent": "^7.0.6",
|
|
||||||
"pac-resolver": "^7.0.1",
|
|
||||||
"socks-proxy-agent": "^8.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pac-proxy-agent/node_modules/http-proxy-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"agent-base": "^7.1.0",
|
|
||||||
"debug": "^4.3.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pac-resolver": {
|
|
||||||
"version": "7.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
|
|
||||||
"integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"degenerator": "^5.0.0",
|
|
||||||
"netmask": "^2.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/package-json-from-dist": {
|
"node_modules/package-json-from-dist": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||||
@ -9889,6 +9490,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"callsites": "^3.0.0"
|
"callsites": "^3.0.0"
|
||||||
@ -9901,6 +9503,7 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
@ -10039,12 +9642,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||||
},
|
},
|
||||||
"node_modules/pend": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/pg": {
|
"node_modules/pg": {
|
||||||
"version": "8.16.3",
|
"version": "8.16.3",
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
@ -10150,6 +9747,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@ -10356,15 +9954,6 @@
|
|||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/progress": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prom-client": {
|
"node_modules/prom-client": {
|
||||||
"version": "15.1.3",
|
"version": "15.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
|
||||||
@ -10448,47 +10037,6 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/proxy-agent": {
|
|
||||||
"version": "6.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
|
|
||||||
"integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"agent-base": "^7.1.2",
|
|
||||||
"debug": "^4.3.4",
|
|
||||||
"http-proxy-agent": "^7.0.1",
|
|
||||||
"https-proxy-agent": "^7.0.6",
|
|
||||||
"lru-cache": "^7.14.1",
|
|
||||||
"pac-proxy-agent": "^7.1.0",
|
|
||||||
"proxy-from-env": "^1.1.0",
|
|
||||||
"socks-proxy-agent": "^8.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/proxy-agent/node_modules/http-proxy-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"agent-base": "^7.1.0",
|
|
||||||
"debug": "^4.3.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/proxy-agent/node_modules/lru-cache": {
|
|
||||||
"version": "7.18.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
|
||||||
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
@ -10502,16 +10050,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pump": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@ -10522,45 +10060,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer": {
|
|
||||||
"version": "24.37.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.37.2.tgz",
|
|
||||||
"integrity": "sha512-FV1W/919ve0y0oiS/3Rp5XY4MUNUokpZOH/5M4MMDfrrvh6T9VbdKvAHrAFHBuCxvluDxhjra20W7Iz6HJUcIQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@puppeteer/browsers": "2.12.0",
|
|
||||||
"chromium-bidi": "13.1.1",
|
|
||||||
"cosmiconfig": "^9.0.0",
|
|
||||||
"devtools-protocol": "0.0.1566079",
|
|
||||||
"puppeteer-core": "24.37.2",
|
|
||||||
"typed-query-selector": "^2.12.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/puppeteer-core": {
|
|
||||||
"version": "24.37.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.37.2.tgz",
|
|
||||||
"integrity": "sha512-nN8qwE3TGF2vA/+xemPxbesntTuqD9vCGOiZL2uh8HES3pPzLX20MyQjB42dH2rhQ3W3TljZ4ZaKZ0yX/abQuw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@puppeteer/browsers": "2.12.0",
|
|
||||||
"chromium-bidi": "13.1.1",
|
|
||||||
"debug": "^4.4.3",
|
|
||||||
"devtools-protocol": "0.0.1566079",
|
|
||||||
"typed-query-selector": "^2.12.0",
|
|
||||||
"webdriver-bidi-protocol": "0.4.0",
|
|
||||||
"ws": "^8.19.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pure-rand": {
|
"node_modules/pure-rand": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||||
@ -10760,6 +10259,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
@ -11256,16 +10756,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smart-buffer": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6.0.0",
|
|
||||||
"npm": ">= 3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/snappy": {
|
"node_modules/snappy": {
|
||||||
"version": "7.3.3",
|
"version": "7.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/snappy/-/snappy-7.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/snappy/-/snappy-7.3.3.tgz",
|
||||||
@ -11413,39 +10903,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socks": {
|
|
||||||
"version": "2.8.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
|
||||||
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ip-address": "^10.0.1",
|
|
||||||
"smart-buffer": "^4.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.0.0",
|
|
||||||
"npm": ">= 3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/socks-proxy-agent": {
|
|
||||||
"version": "8.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
|
|
||||||
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"agent-base": "^7.1.2",
|
|
||||||
"debug": "^4.3.4",
|
|
||||||
"socks": "^2.8.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -11548,17 +11010,6 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/streamx": {
|
|
||||||
"version": "2.23.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
|
|
||||||
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"events-universal": "^1.0.0",
|
|
||||||
"fast-fifo": "^1.3.2",
|
|
||||||
"text-decoder": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
@ -11761,31 +11212,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"tar-stream": "^3.1.5"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"bare-fs": "^4.0.1",
|
|
||||||
"bare-path": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar-stream": {
|
|
||||||
"version": "3.1.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
|
|
||||||
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"b4a": "^1.6.4",
|
|
||||||
"fast-fifo": "^1.2.0",
|
|
||||||
"streamx": "^2.15.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tdigest": {
|
"node_modules/tdigest": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
|
||||||
@ -11888,15 +11314,6 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/text-decoder": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
|
|
||||||
"integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"b4a": "^1.6.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/text-hex": {
|
"node_modules/text-hex": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||||
@ -12289,12 +11706,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typed-query-selector": {
|
|
||||||
"version": "2.12.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
|
|
||||||
"integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/typedarray": {
|
"node_modules/typedarray": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
@ -12305,7 +11716,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@ -12529,12 +11940,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webdriver-bidi-protocol": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==",
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
@ -12700,10 +12105,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.19.0",
|
"version": "8.18.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@ -12794,16 +12201,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yauzl": {
|
|
||||||
"version": "2.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
|
||||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer-crc32": "~0.2.3",
|
|
||||||
"fd-slicer": "~1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yn": {
|
"node_modules/yn": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
|
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
|
||||||
"main": "dist/server.js",
|
"main": "dist/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm install && npm run build && npm run setup && npm run start:prod",
|
"start": "npm run build && npm run setup && npm run start:prod",
|
||||||
"dev": "npm run setup && nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
|
"dev": "npm run setup && nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
|
||||||
"dev:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
|
"dev:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
|
||||||
"build": "tsc && tsc-alias",
|
"build": "tsc && tsc-alias",
|
||||||
@ -50,7 +50,6 @@
|
|||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"puppeteer": "^24.37.2",
|
|
||||||
"sequelize": "^6.37.5",
|
"sequelize": "^6.37.5",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export class DealerClaimController {
|
|||||||
logger.warn('[DealerClaimController] Approver validation error:', { message: error.message });
|
logger.warn('[DealerClaimController] Approver validation error:', { message: error.message });
|
||||||
return ResponseHandler.error(res, error.message, 400);
|
return ResponseHandler.error(res, error.message, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
logger.error('[DealerClaimController] Error creating claim request:', error);
|
logger.error('[DealerClaimController] Error creating claim request:', error);
|
||||||
return ResponseHandler.error(res, 'Failed to create claim request', 500, errorMessage);
|
return ResponseHandler.error(res, 'Failed to create claim request', 500, errorMessage);
|
||||||
@ -301,7 +301,7 @@ export class DealerClaimController {
|
|||||||
try {
|
try {
|
||||||
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
||||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||||
|
|
||||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||||
buffer: fileBuffer,
|
buffer: fileBuffer,
|
||||||
originalName: file.originalname,
|
originalName: file.originalname,
|
||||||
@ -360,7 +360,7 @@ export class DealerClaimController {
|
|||||||
try {
|
try {
|
||||||
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
||||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||||
|
|
||||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||||
buffer: fileBuffer,
|
buffer: fileBuffer,
|
||||||
originalName: file.originalname,
|
originalName: file.originalname,
|
||||||
@ -420,7 +420,7 @@ export class DealerClaimController {
|
|||||||
try {
|
try {
|
||||||
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
||||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||||
|
|
||||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||||
buffer: fileBuffer,
|
buffer: fileBuffer,
|
||||||
originalName: file.originalname,
|
originalName: file.originalname,
|
||||||
@ -480,7 +480,7 @@ export class DealerClaimController {
|
|||||||
try {
|
try {
|
||||||
const fileBuffer = attendanceSheetFile.buffer || (attendanceSheetFile.path ? fs.readFileSync(attendanceSheetFile.path) : Buffer.from(''));
|
const fileBuffer = attendanceSheetFile.buffer || (attendanceSheetFile.path ? fs.readFileSync(attendanceSheetFile.path) : Buffer.from(''));
|
||||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||||
|
|
||||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||||
buffer: fileBuffer,
|
buffer: fileBuffer,
|
||||||
originalName: attendanceSheetFile.originalname,
|
originalName: attendanceSheetFile.originalname,
|
||||||
@ -561,18 +561,18 @@ export class DealerClaimController {
|
|||||||
async validateIO(req: AuthenticatedRequest, res: Response): Promise<void> {
|
async validateIO(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { ioNumber } = req.query;
|
const { ioNumber } = req.query;
|
||||||
|
|
||||||
if (!ioNumber || typeof ioNumber !== 'string') {
|
if (!ioNumber || typeof ioNumber !== 'string') {
|
||||||
return ResponseHandler.error(res, 'IO number is required', 400);
|
return ResponseHandler.error(res, 'IO number is required', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch IO details from SAP (will return mock data until SAP is integrated)
|
// Fetch IO details from SAP (will return mock data until SAP is integrated)
|
||||||
const ioValidation = await sapIntegrationService.validateIONumber(ioNumber.trim());
|
const ioValidation = await sapIntegrationService.validateIONumber(ioNumber.trim());
|
||||||
|
|
||||||
if (!ioValidation.isValid) {
|
if (!ioValidation.isValid) {
|
||||||
return ResponseHandler.error(res, ioValidation.error || 'Invalid IO number', 400);
|
return ResponseHandler.error(res, ioValidation.error || 'Invalid IO number', 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseHandler.success(res, {
|
return ResponseHandler.success(res, {
|
||||||
ioNumber: ioValidation.ioNumber,
|
ioNumber: ioValidation.ioNumber,
|
||||||
availableBalance: ioValidation.availableBalance,
|
availableBalance: ioValidation.availableBalance,
|
||||||
@ -623,7 +623,7 @@ export class DealerClaimController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blockAmount = blockedAmount ? parseFloat(blockedAmount) : 0;
|
const blockAmount = blockedAmount ? parseFloat(blockedAmount) : 0;
|
||||||
|
|
||||||
// Log received data for debugging
|
// Log received data for debugging
|
||||||
logger.info('[DealerClaimController] updateIODetails received:', {
|
logger.info('[DealerClaimController] updateIODetails received:', {
|
||||||
requestId,
|
requestId,
|
||||||
@ -633,7 +633,7 @@ export class DealerClaimController {
|
|||||||
receivedBlockedAmount: blockedAmount, // Original value from request
|
receivedBlockedAmount: blockedAmount, // Original value from request
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store in database when blocking amount > 0 OR when ioNumber and ioRemark are provided (for Step 3 approval)
|
// Store in database when blocking amount > 0 OR when ioNumber and ioRemark are provided (for Step 3 approval)
|
||||||
if (blockAmount > 0) {
|
if (blockAmount > 0) {
|
||||||
if (availableBalance === undefined) {
|
if (availableBalance === undefined) {
|
||||||
@ -649,9 +649,9 @@ export class DealerClaimController {
|
|||||||
blockedAmount: blockAmount,
|
blockedAmount: blockAmount,
|
||||||
// remainingBalance will be calculated by the service from SAP's response
|
// remainingBalance will be calculated by the service from SAP's response
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info('[DealerClaimController] Calling updateIODetails service with:', ioData);
|
logger.info('[DealerClaimController] Calling updateIODetails service with:', ioData);
|
||||||
|
|
||||||
await this.dealerClaimService.updateIODetails(
|
await this.dealerClaimService.updateIODetails(
|
||||||
requestId,
|
requestId,
|
||||||
ioData,
|
ioData,
|
||||||
@ -660,7 +660,7 @@ export class DealerClaimController {
|
|||||||
|
|
||||||
// Fetch and return the updated IO details from database
|
// Fetch and return the updated IO details from database
|
||||||
const updatedIO = await InternalOrder.findOne({ where: { requestId } });
|
const updatedIO = await InternalOrder.findOne({ where: { requestId } });
|
||||||
|
|
||||||
if (updatedIO) {
|
if (updatedIO) {
|
||||||
return ResponseHandler.success(res, {
|
return ResponseHandler.success(res, {
|
||||||
message: 'IO blocked successfully in SAP',
|
message: 'IO blocked successfully in SAP',
|
||||||
@ -755,64 +755,6 @@ export class DealerClaimController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Download E-Invoice PDF
|
|
||||||
* GET /api/v1/dealer-claims/:requestId/e-invoice/pdf
|
|
||||||
*/
|
|
||||||
async downloadInvoicePdf(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
const identifier = req.params.requestId; // Can be UUID or requestNumber
|
|
||||||
|
|
||||||
// Find workflow to get actual UUID
|
|
||||||
const workflow = await this.findWorkflowByIdentifier(identifier);
|
|
||||||
if (!workflow) {
|
|
||||||
return ResponseHandler.error(res, 'Workflow request not found', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestId = (workflow as any).requestId || (workflow as any).request_id;
|
|
||||||
if (!requestId) {
|
|
||||||
return ResponseHandler.error(res, 'Invalid workflow request', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ClaimInvoice } = await import('../models/ClaimInvoice');
|
|
||||||
let invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
|
||||||
|
|
||||||
if (!invoice) {
|
|
||||||
return ResponseHandler.error(res, 'Invoice record not found', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatically regenerate PDF to ensure latest template/data is used (useful during testing/fixes)
|
|
||||||
try {
|
|
||||||
const { pdfService } = await import('../services/pdf.service');
|
|
||||||
await pdfService.generateInvoicePdf(requestId);
|
|
||||||
// Re-fetch invoice to get the new filePath
|
|
||||||
invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
|
||||||
} catch (pdfError) {
|
|
||||||
logger.error(`[DealerClaimController] Failed to auto-regenerate PDF:`, pdfError);
|
|
||||||
// Continue with existing file if regeneration fails
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!invoice || !invoice.filePath) {
|
|
||||||
return ResponseHandler.error(res, 'Invoice PDF not found', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = path.join(process.cwd(), 'storage', 'invoices', invoice.filePath);
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
return ResponseHandler.error(res, 'Invoice file not found on server', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'application/pdf');
|
|
||||||
res.setHeader('Content-Disposition', `inline; filename="${invoice.filePath}"`);
|
|
||||||
|
|
||||||
const fileStream = fs.createReadStream(filePath);
|
|
||||||
fileStream.pipe(res);
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
||||||
logger.error('[DealerClaimController] Error downloading invoice PDF:', error);
|
|
||||||
return ResponseHandler.error(res, 'Failed to download invoice PDF', 500, errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update credit note details (Step 8)
|
* Update credit note details (Step 8)
|
||||||
* PUT /api/v1/dealer-claims/:requestId/credit-note
|
* PUT /api/v1/dealer-claims/:requestId/credit-note
|
||||||
@ -933,7 +875,7 @@ export class DealerClaimController {
|
|||||||
|
|
||||||
// First validate IO number
|
// First validate IO number
|
||||||
const ioValidation = await sapIntegrationService.validateIONumber(ioNumber);
|
const ioValidation = await sapIntegrationService.validateIONumber(ioNumber);
|
||||||
|
|
||||||
if (!ioValidation.isValid) {
|
if (!ioValidation.isValid) {
|
||||||
return ResponseHandler.error(res, `Invalid IO number: ${ioValidation.error || 'IO number not found in SAP'}`, 400);
|
return ResponseHandler.error(res, `Invalid IO number: ${ioValidation.error || 'IO number not found in SAP'}`, 400);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { QueryInterface, DataTypes } from 'sequelize';
|
|
||||||
|
|
||||||
export async function up(queryInterface: QueryInterface): Promise<void> {
|
|
||||||
await queryInterface.addColumn('claim_invoices', 'pwc_response', {
|
|
||||||
type: DataTypes.JSON,
|
|
||||||
allowNull: true,
|
|
||||||
});
|
|
||||||
await queryInterface.addColumn('claim_invoices', 'irp_response', {
|
|
||||||
type: DataTypes.JSON,
|
|
||||||
allowNull: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
|
||||||
await queryInterface.removeColumn('claim_invoices', 'pwc_response');
|
|
||||||
await queryInterface.removeColumn('claim_invoices', 'irp_response');
|
|
||||||
}
|
|
||||||
@ -36,8 +36,6 @@ interface ClaimInvoiceAttributes {
|
|||||||
filePath?: string | null;
|
filePath?: string | null;
|
||||||
qrCode?: string | null;
|
qrCode?: string | null;
|
||||||
qrImage?: string | null;
|
qrImage?: string | null;
|
||||||
pwcResponse?: any;
|
|
||||||
irpResponse?: any;
|
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
generatedAt?: Date;
|
generatedAt?: Date;
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -45,7 +43,7 @@ interface ClaimInvoiceAttributes {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClaimInvoiceCreationAttributes extends Optional<ClaimInvoiceAttributes, 'invoiceId' | 'invoiceNumber' | 'dmsNumber' | 'invoiceDate' | 'irn' | 'ackNo' | 'ackDate' | 'signedInvoice' | 'signedInvoiceUrl' | 'dealerClaimNumber' | 'dealerClaimDate' | 'billingNo' | 'billingDate' | 'taxableValue' | 'cgstTotal' | 'sgstTotal' | 'igstTotal' | 'utgstTotal' | 'cessTotal' | 'tcsAmt' | 'roundOffAmt' | 'placeOfSupply' | 'totalValueInWords' | 'taxValueInWords' | 'creditNature' | 'consignorGsin' | 'gstinDate' | 'filePath' | 'qrCode' | 'qrImage' | 'pwcResponse' | 'irpResponse' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> { }
|
interface ClaimInvoiceCreationAttributes extends Optional<ClaimInvoiceAttributes, 'invoiceId' | 'invoiceNumber' | 'dmsNumber' | 'invoiceDate' | 'irn' | 'ackNo' | 'ackDate' | 'signedInvoice' | 'signedInvoiceUrl' | 'dealerClaimNumber' | 'dealerClaimDate' | 'billingNo' | 'billingDate' | 'taxableValue' | 'cgstTotal' | 'sgstTotal' | 'igstTotal' | 'utgstTotal' | 'cessTotal' | 'tcsAmt' | 'roundOffAmt' | 'placeOfSupply' | 'totalValueInWords' | 'taxValueInWords' | 'creditNature' | 'consignorGsin' | 'gstinDate' | 'filePath' | 'qrCode' | 'qrImage' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> { }
|
||||||
|
|
||||||
class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAttributes> implements ClaimInvoiceAttributes {
|
class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAttributes> implements ClaimInvoiceAttributes {
|
||||||
public invoiceId!: string;
|
public invoiceId!: string;
|
||||||
@ -81,8 +79,6 @@ class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAtt
|
|||||||
public filePath?: string | null;
|
public filePath?: string | null;
|
||||||
public qrCode?: string | null;
|
public qrCode?: string | null;
|
||||||
public qrImage?: string | null;
|
public qrImage?: string | null;
|
||||||
public pwcResponse?: any;
|
|
||||||
public irpResponse?: any;
|
|
||||||
public errorMessage?: string;
|
public errorMessage?: string;
|
||||||
public generatedAt?: Date;
|
public generatedAt?: Date;
|
||||||
public description?: string;
|
public description?: string;
|
||||||
@ -265,16 +261,6 @@ ClaimInvoice.init(
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
field: 'qr_image'
|
field: 'qr_image'
|
||||||
},
|
},
|
||||||
pwcResponse: {
|
|
||||||
type: DataTypes.JSON,
|
|
||||||
allowNull: true,
|
|
||||||
field: 'pwc_response'
|
|
||||||
},
|
|
||||||
irpResponse: {
|
|
||||||
type: DataTypes.JSON,
|
|
||||||
allowNull: true,
|
|
||||||
field: 'irp_response'
|
|
||||||
},
|
|
||||||
errorMessage: {
|
errorMessage: {
|
||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@ -87,7 +87,6 @@ router.put('/:requestId/io', authenticateToken, asyncHandler(dealerClaimControll
|
|||||||
* @access Private
|
* @access Private
|
||||||
*/
|
*/
|
||||||
router.put('/:requestId/e-invoice', authenticateToken, asyncHandler(dealerClaimController.updateEInvoice.bind(dealerClaimController)));
|
router.put('/:requestId/e-invoice', authenticateToken, asyncHandler(dealerClaimController.updateEInvoice.bind(dealerClaimController)));
|
||||||
router.get('/:requestId/e-invoice/pdf', authenticateToken, asyncHandler(dealerClaimController.downloadInvoicePdf.bind(dealerClaimController)));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route PUT /api/v1/dealer-claims/:requestId/credit-note
|
* @route PUT /api/v1/dealer-claims/:requestId/credit-note
|
||||||
|
|||||||
@ -49,7 +49,7 @@ router.use('/templates', templateRoutes);
|
|||||||
router.use('/dealers', dealerRoutes);
|
router.use('/dealers', dealerRoutes);
|
||||||
router.use('/webhooks/dms', dmsWebhookRoutes);
|
router.use('/webhooks/dms', dmsWebhookRoutes);
|
||||||
|
|
||||||
// Add other route modules as they are implemented
|
// TODO: Add other route modules as they are implemented
|
||||||
// router.use('/approvals', approvalRoutes);
|
// router.use('/approvals', approvalRoutes);
|
||||||
// router.use('/participants', participantRoutes);
|
// router.use('/participants', participantRoutes);
|
||||||
|
|
||||||
|
|||||||
@ -158,7 +158,6 @@ async function runMigrations(): Promise<void> {
|
|||||||
const m43 = require('../migrations/20260113-redesign-dealer-claim-history');
|
const m43 = require('../migrations/20260113-redesign-dealer-claim-history');
|
||||||
const m44 = require('../migrations/20260123-fix-template-id-schema');
|
const m44 = require('../migrations/20260123-fix-template-id-schema');
|
||||||
const m45 = require('../migrations/20260209-add-gst-and-pwc-fields');
|
const m45 = require('../migrations/20260209-add-gst-and-pwc-fields');
|
||||||
const m46 = require('../migrations/20260210-add-raw-pwc-responses');
|
|
||||||
|
|
||||||
const migrations = [
|
const migrations = [
|
||||||
{ name: '2025103000-create-users', module: m0 },
|
{ name: '2025103000-create-users', module: m0 },
|
||||||
@ -209,7 +208,6 @@ async function runMigrations(): Promise<void> {
|
|||||||
{ name: '20260113-redesign-dealer-claim-history', module: m43 },
|
{ name: '20260113-redesign-dealer-claim-history', module: m43 },
|
||||||
{ name: '20260123-fix-template-id-schema', module: m44 },
|
{ name: '20260123-fix-template-id-schema', module: m44 },
|
||||||
{ name: '20260209-add-gst-and-pwc-fields', module: m45 },
|
{ name: '20260209-add-gst-and-pwc-fields', module: m45 },
|
||||||
{ name: '20260210-add-raw-pwc-responses', module: m46 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Dynamically import sequelize after secrets are loaded
|
// Dynamically import sequelize after secrets are loaded
|
||||||
|
|||||||
@ -58,7 +58,7 @@ interface DealerSeedData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sample data based on the provided table
|
// Sample data based on the provided table
|
||||||
// Replace with your actual dealer data from Excel/CSV
|
// TODO: Replace with your actual dealer data from Excel/CSV
|
||||||
const dealersData: DealerSeedData[] = [
|
const dealersData: DealerSeedData[] = [
|
||||||
{
|
{
|
||||||
salesCode: '5124',
|
salesCode: '5124',
|
||||||
@ -116,7 +116,7 @@ async function seedDealersTable(): Promise<void> {
|
|||||||
for (const dealerData of dealersData) {
|
for (const dealerData of dealersData) {
|
||||||
// Use dlrcode or domainId as unique identifier if available
|
// Use dlrcode or domainId as unique identifier if available
|
||||||
const uniqueIdentifier = dealerData.dlrcode || dealerData.domainId || dealerData.salesCode;
|
const uniqueIdentifier = dealerData.dlrcode || dealerData.domainId || dealerData.salesCode;
|
||||||
|
|
||||||
if (!uniqueIdentifier) {
|
if (!uniqueIdentifier) {
|
||||||
logger.warn('[Seed Dealers Table] Skipping dealer record without unique identifier');
|
logger.warn('[Seed Dealers Table] Skipping dealer record without unique identifier');
|
||||||
continue;
|
continue;
|
||||||
@ -130,15 +130,15 @@ async function seedDealersTable(): Promise<void> {
|
|||||||
|
|
||||||
const existingDealer = whereConditions.length > 0
|
const existingDealer = whereConditions.length > 0
|
||||||
? await Dealer.findOne({
|
? await Dealer.findOne({
|
||||||
where: {
|
where: {
|
||||||
[Op.or]: whereConditions
|
[Op.or]: whereConditions
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (existingDealer) {
|
if (existingDealer) {
|
||||||
logger.info(`[Seed Dealers Table] Dealer ${uniqueIdentifier} already exists, updating...`);
|
logger.info(`[Seed Dealers Table] Dealer ${uniqueIdentifier} already exists, updating...`);
|
||||||
|
|
||||||
// Update existing dealer
|
// Update existing dealer
|
||||||
await existingDealer.update({
|
await existingDealer.update({
|
||||||
...dealerData,
|
...dealerData,
|
||||||
|
|||||||
@ -24,27 +24,27 @@ async function generateUniqueCode(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 100;
|
const maxAttempts = 100;
|
||||||
|
|
||||||
while (attempts < maxAttempts) {
|
while (attempts < maxAttempts) {
|
||||||
// Generate random 4-digit number (1000-9999)
|
// Generate random 4-digit number (1000-9999)
|
||||||
const randomCode = String(Math.floor(1000 + Math.random() * 9000));
|
const randomCode = String(Math.floor(1000 + Math.random() * 9000));
|
||||||
|
|
||||||
// Check if code already exists in database
|
// Check if code already exists in database
|
||||||
const existing = await Dealer.findOne({
|
const existing = await Dealer.findOne({
|
||||||
where: {
|
where: {
|
||||||
[field]: randomCode
|
[field]: randomCode
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also check if we've already generated this code in this run
|
// Also check if we've already generated this code in this run
|
||||||
if (!existing && !existingCodes.has(randomCode)) {
|
if (!existing && !existingCodes.has(randomCode)) {
|
||||||
existingCodes.add(randomCode);
|
existingCodes.add(randomCode);
|
||||||
return randomCode;
|
return randomCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: use timestamp-based code if random generation fails
|
// Fallback: use timestamp-based code if random generation fails
|
||||||
const timestampCode = String(Date.now()).slice(-4);
|
const timestampCode = String(Date.now()).slice(-4);
|
||||||
logger.warn(`[Seed Test Dealer] Using timestamp-based code for ${field}: ${timestampCode}`);
|
logger.warn(`[Seed Test Dealer] Using timestamp-based code for ${field}: ${timestampCode}`);
|
||||||
@ -104,18 +104,18 @@ async function seedTestDealer(): Promise<void> {
|
|||||||
branchDetails: null,
|
branchDetails: null,
|
||||||
dealerPrincipalName: 'TEST REFLOW',
|
dealerPrincipalName: 'TEST REFLOW',
|
||||||
dealerPrincipalEmailId: 'testreflow@example.com',
|
dealerPrincipalEmailId: 'testreflow@example.com',
|
||||||
dpContactNumber: '9998887776',
|
dpContactNumber: null,
|
||||||
dpContacts: 'TEST CONTACT',
|
dpContacts: null,
|
||||||
showroomAddress: 'No. 335, RE Test Road, Bengaluru - 560098, Karnataka',
|
showroomAddress: null,
|
||||||
showroomPincode: '560098',
|
showroomPincode: null,
|
||||||
workshopAddress: 'Workshop Area B, Test Location',
|
workshopAddress: null,
|
||||||
workshopPincode: '560098',
|
workshopPincode: null,
|
||||||
locationDistrict: 'Bangalore',
|
locationDistrict: null,
|
||||||
stateWorkshop: 'Karnataka',
|
stateWorkshop: null,
|
||||||
noOfStudios: 0,
|
noOfStudios: 0,
|
||||||
websiteUpdate: 'Yes',
|
websiteUpdate: 'Yes',
|
||||||
gst: '29AAACE3882D1ZZ', // Test GST
|
gst: null,
|
||||||
pan: 'AAACE3882D',
|
pan: null,
|
||||||
firmType: 'Test Firm',
|
firmType: 'Test Firm',
|
||||||
propManagingPartnersDirectors: 'TEST REFLOW',
|
propManagingPartnersDirectors: 'TEST REFLOW',
|
||||||
totalPropPartnersDirectors: 'TEST REFLOW',
|
totalPropPartnersDirectors: 'TEST REFLOW',
|
||||||
@ -128,7 +128,7 @@ async function seedTestDealer(): Promise<void> {
|
|||||||
|
|
||||||
if (existingDealer) {
|
if (existingDealer) {
|
||||||
logger.info('[Seed Test Dealer] Test dealer already exists, updating...');
|
logger.info('[Seed Test Dealer] Test dealer already exists, updating...');
|
||||||
|
|
||||||
// Update existing dealer
|
// Update existing dealer
|
||||||
await existingDealer.update(dealerData);
|
await existingDealer.update(dealerData);
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
import { pdfService } from '../services/pdf.service';
|
|
||||||
import logger from '../utils/logger';
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const requestId = '468d45f9-2ea3-4b4d-82fc-6721b60cf8bb';
|
|
||||||
console.log(`Starting PDF generation for request: ${requestId}...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fileName = await pdfService.generateInvoicePdf(requestId);
|
|
||||||
console.log(`✅ Success! PDF generated: ${fileName}`);
|
|
||||||
process.exit(0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to generate PDF:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@ -673,7 +673,7 @@ export class DashboardService {
|
|||||||
totalCompleted,
|
totalCompleted,
|
||||||
compliantWorkflows: compliantCount,
|
compliantWorkflows: compliantCount,
|
||||||
changeFromPrevious: {
|
changeFromPrevious: {
|
||||||
compliance: '+5.8%', // Calculate actual change
|
compliance: '+5.8%', // TODO: Calculate actual change
|
||||||
cycleTime: '-0.5h'
|
cycleTime: '-0.5h'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,7 +30,6 @@ export interface DealerInfo {
|
|||||||
city?: string | null;
|
city?: string | null;
|
||||||
dealerPrincipalName?: string | null;
|
dealerPrincipalName?: string | null;
|
||||||
dealerPrincipalEmailId?: string | null;
|
dealerPrincipalEmailId?: string | null;
|
||||||
gstin?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,7 +106,6 @@ export async function getAllDealers(searchTerm?: string, limit: number = 10): Pr
|
|||||||
city: dealer.city || null,
|
city: dealer.city || null,
|
||||||
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
||||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||||
gstin: dealer.gst || null,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -168,7 +166,6 @@ export async function getDealerByCode(dealerCode: string): Promise<DealerInfo |
|
|||||||
city: dealer.city || null,
|
city: dealer.city || null,
|
||||||
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
||||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||||
gstin: dealer.gst || null,
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[DealerService] Error fetching dealer by code:', error);
|
logger.error('[DealerService] Error fetching dealer by code:', error);
|
||||||
@ -228,7 +225,6 @@ export async function getDealerByEmail(email: string): Promise<DealerInfo | null
|
|||||||
city: dealer.city || null,
|
city: dealer.city || null,
|
||||||
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
||||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||||
gstin: dealer.gst || null,
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[DealerService] Error fetching dealer by email:', error);
|
logger.error('[DealerService] Error fetching dealer by email:', error);
|
||||||
|
|||||||
@ -28,39 +28,14 @@ import logger from '../utils/logger';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let workflowServiceInstance: any;
|
|
||||||
let approvalServiceInstance: any;
|
|
||||||
let userServiceInstance: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dealer Claim Service
|
* Dealer Claim Service
|
||||||
* Handles business logic specific to dealer claim management workflow
|
* Handles business logic specific to dealer claim management workflow
|
||||||
*/
|
*/
|
||||||
export class DealerClaimService {
|
export class DealerClaimService {
|
||||||
private getWorkflowService(): WorkflowService {
|
private workflowService = new WorkflowService();
|
||||||
if (!workflowServiceInstance) {
|
private approvalService = new DealerClaimApprovalService();
|
||||||
const { WorkflowService } = require('./workflow.service');
|
private userService = new UserService();
|
||||||
workflowServiceInstance = new WorkflowService();
|
|
||||||
}
|
|
||||||
return workflowServiceInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getApprovalService(): DealerClaimApprovalService {
|
|
||||||
if (!approvalServiceInstance) {
|
|
||||||
const { DealerClaimApprovalService } = require('./dealerClaimApproval.service');
|
|
||||||
approvalServiceInstance = new DealerClaimApprovalService();
|
|
||||||
}
|
|
||||||
return approvalServiceInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUserService(): UserService {
|
|
||||||
if (!userServiceInstance) {
|
|
||||||
const { UserService } = require('./user.service');
|
|
||||||
userServiceInstance = new UserService();
|
|
||||||
}
|
|
||||||
return userServiceInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new dealer claim request
|
* Create a new dealer claim request
|
||||||
@ -126,92 +101,26 @@ export class DealerClaimService {
|
|||||||
throw new Error('Approvers array is required. Please assign approvers for all workflow steps.');
|
throw new Error('Approvers array is required. Please assign approvers for all workflow steps.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Transform approvers and ensure users exist in database
|
// Now create workflow request (manager is validated)
|
||||||
const userService = this.getUserService();
|
// For claim management, requests are submitted immediately (not drafts)
|
||||||
const transformedLevels = [];
|
// Step 1 will be active for dealer to submit proposal
|
||||||
|
const now = new Date();
|
||||||
// Define step names mapping
|
const workflowRequest = await WorkflowRequest.create({
|
||||||
const stepNames: Record<number, string> = {
|
initiatorId: userId,
|
||||||
1: 'Dealer Proposal Submission',
|
requestNumber,
|
||||||
2: 'Requestor Evaluation',
|
templateType: 'DEALER CLAIM', // Set template type for dealer claim management
|
||||||
3: 'Department Lead Approval',
|
|
||||||
4: 'Dealer Completion Documents',
|
|
||||||
5: 'Requestor Claim Approval'
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const a of claimData.approvers) {
|
|
||||||
let approverUserId = a.userId;
|
|
||||||
|
|
||||||
// If userId missing, ensure user exists by email
|
|
||||||
if (!approverUserId && a.email) {
|
|
||||||
try {
|
|
||||||
const user = await userService.ensureUserExists({ email: a.email });
|
|
||||||
approverUserId = user.userId;
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(`[DealerClaimService] Could not resolve user for email ${a.email}:`, e);
|
|
||||||
// If it fails, keep it empty and let the workflow service handle it (or fail early)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tatHours = 24; // Default
|
|
||||||
if (a.tat) {
|
|
||||||
const val = typeof a.tat === 'number' ? a.tat : parseInt(a.tat as string);
|
|
||||||
tatHours = a.tatType === 'days' ? val * 24 : val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine level name - use mapped name or fallback to "Step X"
|
|
||||||
// Also handle "Additional Approver" case if provided
|
|
||||||
let levelName = stepNames[a.level] || `Step ${a.level}`;
|
|
||||||
|
|
||||||
// If it's an additional approver (not one of the standard steps), label it clearly
|
|
||||||
// Note: The frontend might send extra steps if approvers are added dynamically
|
|
||||||
// But for initial creation, we usually stick to the standard flow
|
|
||||||
|
|
||||||
transformedLevels.push({
|
|
||||||
levelNumber: a.level,
|
|
||||||
levelName: levelName,
|
|
||||||
approverId: approverUserId || '', // Fallback to empty string if still not resolved
|
|
||||||
approverEmail: a.email,
|
|
||||||
approverName: a.name || a.email,
|
|
||||||
tatHours: tatHours,
|
|
||||||
// New 5-step flow: Level 5 is the final approver (Requestor Claim Approval)
|
|
||||||
isFinalApprover: a.level === 5
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Transform participants
|
|
||||||
const transformedParticipants = [
|
|
||||||
{
|
|
||||||
userId: userId,
|
|
||||||
userName: initiator.displayName || initiator.email,
|
|
||||||
userEmail: initiator.email,
|
|
||||||
participantType: 'INITIATOR' as any,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add approvers as participants
|
|
||||||
for (const level of transformedLevels) {
|
|
||||||
if (level.approverId) {
|
|
||||||
transformedParticipants.push({
|
|
||||||
userId: level.approverId,
|
|
||||||
userName: level.approverName,
|
|
||||||
userEmail: level.approverEmail,
|
|
||||||
participantType: 'APPROVER' as any
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowService = this.getWorkflowService();
|
|
||||||
const workflowRequest = await workflowService.createWorkflow(userId, {
|
|
||||||
templateType: 'DEALER CLAIM' as any,
|
|
||||||
workflowType: 'CLAIM_MANAGEMENT',
|
workflowType: 'CLAIM_MANAGEMENT',
|
||||||
title: `${claimData.activityName} - Claim Request`,
|
title: `${claimData.activityName} - Claim Request`,
|
||||||
description: claimData.requestDescription,
|
description: claimData.requestDescription,
|
||||||
priority: Priority.STANDARD,
|
priority: Priority.STANDARD,
|
||||||
approvalLevels: transformedLevels,
|
status: WorkflowStatus.PENDING, // Submitted, not draft
|
||||||
participants: transformedParticipants,
|
totalLevels: 5, // Fixed 5-step workflow for claim management (Activity Creation, E-Invoice Generation, and Credit Note Confirmation are now activity logs only)
|
||||||
isDraft: false
|
currentLevel: 1, // Step 1: Dealer Proposal Submission
|
||||||
} as any);
|
totalTatHours: 0, // Will be calculated from approval levels
|
||||||
|
isDraft: false, // Not a draft - submitted and ready for workflow
|
||||||
|
isDeleted: false,
|
||||||
|
submissionDate: now, // Set submission date for SLA tracking (required for overall SLA calculation)
|
||||||
|
});
|
||||||
|
|
||||||
// Create claim details
|
// Create claim details
|
||||||
await DealerClaimDetails.create({
|
await DealerClaimDetails.create({
|
||||||
@ -237,9 +146,107 @@ export class DealerClaimService {
|
|||||||
currency: 'INR',
|
currency: 'INR',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redundant level creation removed - handled by workflowService.createWorkflow
|
// Create 8 approval levels for claim management workflow from approvers array
|
||||||
|
await this.createClaimApprovalLevelsFromApprovers(workflowRequest.requestId, userId, claimData.dealerEmail, claimData.approvers || []);
|
||||||
|
|
||||||
// Redundant TAT scheduling removed - handled by workflowService.createWorkflow
|
// Schedule TAT jobs for Step 1 (Dealer Proposal Submission) - first active step
|
||||||
|
// This ensures SLA tracking starts immediately from request creation
|
||||||
|
const { tatSchedulerService } = await import('./tatScheduler.service');
|
||||||
|
const dealerLevel = await ApprovalLevel.findOne({
|
||||||
|
where: {
|
||||||
|
requestId: workflowRequest.requestId,
|
||||||
|
levelNumber: 1 // Step 1: Dealer Proposal Submission
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dealerLevel && dealerLevel.approverId && dealerLevel.levelStartTime) {
|
||||||
|
try {
|
||||||
|
const workflowPriority = (workflowRequest as any)?.priority || 'STANDARD';
|
||||||
|
await tatSchedulerService.scheduleTatJobs(
|
||||||
|
workflowRequest.requestId,
|
||||||
|
(dealerLevel as any).levelId,
|
||||||
|
dealerLevel.approverId,
|
||||||
|
Number(dealerLevel.tatHours || 0),
|
||||||
|
dealerLevel.levelStartTime,
|
||||||
|
workflowPriority
|
||||||
|
);
|
||||||
|
logger.info(`[DealerClaimService] TAT jobs scheduled for Step 1 (Dealer Proposal Submission) - Priority: ${workflowPriority}`);
|
||||||
|
} catch (tatError) {
|
||||||
|
logger.error(`[DealerClaimService] Failed to schedule TAT jobs for Step 1:`, tatError);
|
||||||
|
// Don't fail request creation if TAT scheduling fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create participants (initiator, dealer, department lead, finance - exclude system)
|
||||||
|
await this.createClaimParticipants(workflowRequest.requestId, userId, claimData.dealerEmail);
|
||||||
|
|
||||||
|
// Get initiator details for activity logging and notifications
|
||||||
|
const initiatorName = initiator.displayName || initiator.email || 'User';
|
||||||
|
|
||||||
|
// Log creation activity
|
||||||
|
await activityService.log({
|
||||||
|
requestId: workflowRequest.requestId,
|
||||||
|
type: 'created',
|
||||||
|
user: { userId: userId, name: initiatorName },
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
action: 'Claim request created',
|
||||||
|
details: `Claim request "${workflowRequest.title}" created by ${initiatorName} for dealer ${claimData.dealerName}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send notification to INITIATOR confirming submission
|
||||||
|
await notificationService.sendToUsers([userId], {
|
||||||
|
title: 'Claim Request Submitted Successfully',
|
||||||
|
body: `Your claim request "${workflowRequest.title}" has been submitted successfully.`,
|
||||||
|
requestNumber: requestNumber,
|
||||||
|
requestId: workflowRequest.requestId,
|
||||||
|
url: `/request/${requestNumber}`,
|
||||||
|
type: 'request_submitted',
|
||||||
|
priority: 'MEDIUM'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get approval levels for notifications
|
||||||
|
// Step 1: Dealer Proposal Submission (first active step - log assignment at creation)
|
||||||
|
// Subsequent steps will have assignment logged when they become active (via approval service)
|
||||||
|
|
||||||
|
// Notify Step 1 (Dealer) - dealerLevel was already fetched above for TAT scheduling
|
||||||
|
|
||||||
|
if (dealerLevel && dealerLevel.approverId) {
|
||||||
|
// Skip notifications for system processes
|
||||||
|
const approverEmail = dealerLevel.approverEmail || '';
|
||||||
|
const isSystemProcess = approverEmail.toLowerCase() === 'system@royalenfield.com'
|
||||||
|
|| approverEmail.toLowerCase().includes('system')
|
||||||
|
|| dealerLevel.approverId === 'system'
|
||||||
|
|| dealerLevel.approverName === 'System Auto-Process';
|
||||||
|
|
||||||
|
if (!isSystemProcess) {
|
||||||
|
// Send notification to Dealer (Step 1) for proposal submission
|
||||||
|
await notificationService.sendToUsers([dealerLevel.approverId], {
|
||||||
|
title: 'New Claim Request - Proposal Required',
|
||||||
|
body: `Claim request "${workflowRequest.title}" requires your proposal submission.`,
|
||||||
|
requestNumber: requestNumber,
|
||||||
|
requestId: workflowRequest.requestId,
|
||||||
|
url: `/request/${requestNumber}`,
|
||||||
|
type: 'assignment',
|
||||||
|
priority: 'HIGH',
|
||||||
|
actionRequired: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log assignment activity for dealer (Step 1 - first active step)
|
||||||
|
await activityService.log({
|
||||||
|
requestId: workflowRequest.requestId,
|
||||||
|
type: 'assignment',
|
||||||
|
user: { userId: userId, name: initiatorName },
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
action: 'Assigned to dealer',
|
||||||
|
details: `Claim request assigned to dealer ${dealerLevel.approverName || dealerLevel.approverEmail || claimData.dealerName} for proposal submission`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.info(`[DealerClaimService] Skipping notification for system process: ${approverEmail} at Step 1`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Step 2, 3, and subsequent steps will have assignment activities logged
|
||||||
|
// when they become active (when previous step is approved) via the approval service
|
||||||
|
|
||||||
logger.info(`[DealerClaimService] Created claim request: ${workflowRequest.requestNumber}`);
|
logger.info(`[DealerClaimService] Created claim request: ${workflowRequest.requestNumber}`);
|
||||||
return workflowRequest;
|
return workflowRequest;
|
||||||
@ -433,8 +440,7 @@ export class DealerClaimService {
|
|||||||
// User doesn't exist - create from Okta
|
// User doesn't exist - create from Okta
|
||||||
logger.info(`[DealerClaimService] User ${approver.email} not found in DB, syncing from Okta`);
|
logger.info(`[DealerClaimService] User ${approver.email} not found in DB, syncing from Okta`);
|
||||||
try {
|
try {
|
||||||
const userService = this.getUserService();
|
user = await this.userService.ensureUserExists({
|
||||||
user = await userService.ensureUserExists({
|
|
||||||
email: approver.email.toLowerCase(),
|
email: approver.email.toLowerCase(),
|
||||||
userId: approver.userId, // Pass Okta ID if provided (ensureUserExists will handle it)
|
userId: approver.userId, // Pass Okta ID if provided (ensureUserExists will handle it)
|
||||||
}) as any;
|
}) as any;
|
||||||
@ -602,8 +608,7 @@ export class DealerClaimService {
|
|||||||
if (!dealerUser) {
|
if (!dealerUser) {
|
||||||
logger.info(`[DealerClaimService] Dealer ${dealerEmail} not found in DB for participants, syncing from Okta`);
|
logger.info(`[DealerClaimService] Dealer ${dealerEmail} not found in DB for participants, syncing from Okta`);
|
||||||
try {
|
try {
|
||||||
const userService = this.getUserService();
|
dealerUser = await this.userService.ensureUserExists({
|
||||||
dealerUser = await userService.ensureUserExists({
|
|
||||||
email: dealerEmail.toLowerCase(),
|
email: dealerEmail.toLowerCase(),
|
||||||
}) as any;
|
}) as any;
|
||||||
logger.info(`[DealerClaimService] Successfully synced dealer ${dealerEmail} from Okta for participants`);
|
logger.info(`[DealerClaimService] Successfully synced dealer ${dealerEmail} from Okta for participants`);
|
||||||
@ -747,8 +752,7 @@ export class DealerClaimService {
|
|||||||
logger.info(`[DealerClaimService] Searching Okta for manager with displayName: "${managerDisplayName}"`);
|
logger.info(`[DealerClaimService] Searching Okta for manager with displayName: "${managerDisplayName}"`);
|
||||||
|
|
||||||
// Search Okta by displayName
|
// Search Okta by displayName
|
||||||
const userService = this.getUserService();
|
const oktaUsers = await this.userService.searchOktaByDisplayName(managerDisplayName);
|
||||||
const oktaUsers = await userService.searchOktaByDisplayName(managerDisplayName);
|
|
||||||
|
|
||||||
if (oktaUsers.length === 0) {
|
if (oktaUsers.length === 0) {
|
||||||
logger.warn(`[DealerClaimService] No reporting manager found in Okta for displayName: "${managerDisplayName}"`);
|
logger.warn(`[DealerClaimService] No reporting manager found in Okta for displayName: "${managerDisplayName}"`);
|
||||||
@ -764,7 +768,7 @@ export class DealerClaimService {
|
|||||||
logger.info(`[DealerClaimService] Found single manager match: ${managerEmail} for displayName: "${managerDisplayName}"`);
|
logger.info(`[DealerClaimService] Found single manager match: ${managerEmail} for displayName: "${managerDisplayName}"`);
|
||||||
|
|
||||||
// Check if user exists in DB, create if doesn't exist
|
// Check if user exists in DB, create if doesn't exist
|
||||||
const managerUser = await userService.ensureUserExists({
|
const managerUser = await this.userService.ensureUserExists({
|
||||||
userId: oktaUser.id,
|
userId: oktaUser.id,
|
||||||
email: managerEmail,
|
email: managerEmail,
|
||||||
displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim(),
|
displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim(),
|
||||||
@ -1320,8 +1324,7 @@ export class DealerClaimService {
|
|||||||
: 'Dealer proposal submitted';
|
: 'Dealer proposal submitted';
|
||||||
|
|
||||||
// Perform the approval action FIRST - only save snapshot if action succeeds
|
// Perform the approval action FIRST - only save snapshot if action succeeds
|
||||||
const approvalService = this.getApprovalService();
|
await this.approvalService.approveLevel(
|
||||||
await approvalService.approveLevel(
|
|
||||||
dealerProposalLevel.levelId,
|
dealerProposalLevel.levelId,
|
||||||
{ action: 'APPROVE', comments: approvalComment },
|
{ action: 'APPROVE', comments: approvalComment },
|
||||||
actualDealerUserId || (request as any).initiatorId || 'system', // Use dealer or initiator ID
|
actualDealerUserId || (request as any).initiatorId || 'system', // Use dealer or initiator ID
|
||||||
@ -1482,8 +1485,7 @@ export class DealerClaimService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform the approval action FIRST - only save snapshot if action succeeds
|
// Perform the approval action FIRST - only save snapshot if action succeeds
|
||||||
const approvalService = this.getApprovalService();
|
await this.approvalService.approveLevel(
|
||||||
await approvalService.approveLevel(
|
|
||||||
dealerCompletionLevel.levelId,
|
dealerCompletionLevel.levelId,
|
||||||
{ action: 'APPROVE', comments: approvalComment },
|
{ action: 'APPROVE', comments: approvalComment },
|
||||||
actualDealerUserId || (request as any).initiatorId || 'system',
|
actualDealerUserId || (request as any).initiatorId || 'system',
|
||||||
@ -1903,7 +1905,7 @@ export class DealerClaimService {
|
|||||||
|
|
||||||
const requestNumber = request ? ((request as any).requestNumber || (request as any).request_number) : 'UNKNOWN';
|
const requestNumber = request ? ((request as any).requestNumber || (request as any).request_number) : 'UNKNOWN';
|
||||||
|
|
||||||
// If invoice data not provided, generate via PWC E-Invoice service
|
// If invoice data not provided, generate via DMS
|
||||||
if (!invoiceData?.eInvoiceNumber) {
|
if (!invoiceData?.eInvoiceNumber) {
|
||||||
const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
|
const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
|
||||||
const invoiceAmount = invoiceData?.amount
|
const invoiceAmount = invoiceData?.amount
|
||||||
@ -1912,7 +1914,7 @@ export class DealerClaimService {
|
|||||||
|| budgetTracking?.initialEstimatedBudget
|
|| budgetTracking?.initialEstimatedBudget
|
||||||
|| 0;
|
|| 0;
|
||||||
|
|
||||||
const invoiceResult = await pwcIntegrationService.generateSignedInvoice(requestId, invoiceAmount);
|
const invoiceResult = await pwcIntegrationService.generateSignedInvoice(requestId);
|
||||||
|
|
||||||
if (!invoiceResult.success) {
|
if (!invoiceResult.success) {
|
||||||
throw new Error(`Failed to generate signed e-invoice via PWC: ${invoiceResult.error}`);
|
throw new Error(`Failed to generate signed e-invoice via PWC: ${invoiceResult.error}`);
|
||||||
@ -1928,8 +1930,6 @@ export class DealerClaimService {
|
|||||||
signedInvoice: invoiceResult.signedInvoice,
|
signedInvoice: invoiceResult.signedInvoice,
|
||||||
qrCode: invoiceResult.qrCode,
|
qrCode: invoiceResult.qrCode,
|
||||||
qrImage: invoiceResult.qrImage,
|
qrImage: invoiceResult.qrImage,
|
||||||
pwcResponse: invoiceResult.rawResponse,
|
|
||||||
irpResponse: invoiceResult.irpResponse,
|
|
||||||
amount: invoiceAmount,
|
amount: invoiceAmount,
|
||||||
status: 'GENERATED',
|
status: 'GENERATED',
|
||||||
generatedAt: new Date(),
|
generatedAt: new Date(),
|
||||||
@ -1956,15 +1956,6 @@ export class DealerClaimService {
|
|||||||
logger.info(`[DealerClaimService] E-Invoice details manually updated for request: ${requestId}`);
|
logger.info(`[DealerClaimService] E-Invoice details manually updated for request: ${requestId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate PDF Invoice
|
|
||||||
try {
|
|
||||||
const { pdfService } = require('./pdf.service');
|
|
||||||
await pdfService.generateInvoicePdf(requestId);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[DealerClaimService] Failed to generate PDF for request ${requestId}:`, error);
|
|
||||||
// Don't throw, we still want to proceed with auto-approval
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Requestor Claim Approval is approved - if not, approve it first
|
// Check if Requestor Claim Approval is approved - if not, approve it first
|
||||||
// Find dynamically by levelName (handles step shifts due to additional approvers)
|
// Find dynamically by levelName (handles step shifts due to additional approvers)
|
||||||
const approvalLevels = await ApprovalLevel.findAll({
|
const approvalLevels = await ApprovalLevel.findAll({
|
||||||
@ -1984,30 +1975,34 @@ export class DealerClaimService {
|
|||||||
requestorClaimLevel = approvalLevels.find((level: any) => level.levelNumber === 5);
|
requestorClaimLevel = approvalLevels.find((level: any) => level.levelNumber === 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that we're at the Requestor Claim Approval step before allowing E-Invoice generation
|
// Validate that we're at the Requestor Claim Approval step before allowing DMS push
|
||||||
if (requestorClaimLevel && request.currentLevel !== requestorClaimLevel.levelNumber) {
|
if (requestorClaimLevel && request.currentLevel !== requestorClaimLevel.levelNumber) {
|
||||||
throw new Error(`Cannot generate E-Invoice. Request is currently at step ${request.currentLevel}, but Requestor Claim Approval is at step ${requestorClaimLevel.levelNumber}. Please complete all previous steps first.`);
|
throw new Error(`Cannot push to DMS. Request is currently at step ${request.currentLevel}, but Requestor Claim Approval is at step ${requestorClaimLevel.levelNumber}. Please complete all previous steps first.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// E-Invoice Generation is successful - auto-approve the Requestor Claim Approval step
|
if (requestorClaimLevel && requestorClaimLevel.status !== ApprovalStatus.APPROVED) {
|
||||||
if (requestorClaimLevel && requestorClaimLevel.status !== 'APPROVED') {
|
logger.info(`[DealerClaimService] Requestor Claim Approval not approved yet. Auto-approving for request ${requestId}`);
|
||||||
const approvalService = this.getApprovalService();
|
// Auto-approve Requestor Claim Approval
|
||||||
await approvalService.approveLevel(
|
await this.approvalService.approveLevel(
|
||||||
requestorClaimLevel.levelId,
|
requestorClaimLevel.levelId,
|
||||||
{ action: 'APPROVE', comments: 'Auto-approved after successful E-Invoice generation' },
|
{ action: 'APPROVE', comments: 'Auto-approved when pushing to DMS. E-Invoice generation will be logged as activity.' },
|
||||||
'system'
|
'system',
|
||||||
|
{ ipAddress: null, userAgent: 'System Auto-Process' }
|
||||||
);
|
);
|
||||||
logger.info(`[DealerClaimService] Step "${requestorClaimLevel.levelName}" auto-approved after E-Invoice generation for request ${requestId}`);
|
logger.info(`[DealerClaimService] Requestor Claim Approval approved. E-Invoice generation will be logged as activity when DMS webhook is received.`);
|
||||||
|
} else {
|
||||||
|
// Requestor Claim Approval already approved
|
||||||
|
logger.info(`[DealerClaimService] Requestor Claim Approval already approved. E-Invoice generation will be logged as activity when DMS webhook is received.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log E-Invoice generation as activity
|
// Log E-Invoice generation as activity (no approval level needed)
|
||||||
await activityService.log({
|
await activityService.log({
|
||||||
requestId,
|
requestId,
|
||||||
type: 'status_change',
|
type: 'status_change',
|
||||||
user: { userId: 'system', name: 'System Auto-Process' },
|
user: { userId: 'system', name: 'System Auto-Process' },
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
action: 'E-Invoice Generated',
|
action: 'E-Invoice Generation Initiated',
|
||||||
details: `E-Invoice generated via PWC integration for request ${requestNumber}. Step "${requestorClaimLevel?.levelName || 'Requestor Claim Approval'}" auto-approved.`,
|
details: `E-Invoice generation initiated via DMS integration for request ${requestNumber}. Waiting for DMS webhook confirmation.`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[DealerClaimService] Error updating e-invoice details:', error);
|
logger.error('[DealerClaimService] Error updating e-invoice details:', error);
|
||||||
@ -2017,7 +2012,7 @@ export class DealerClaimService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Log E-Invoice Generation as activity (no longer an approval step)
|
* Log E-Invoice Generation as activity (no longer an approval step)
|
||||||
* This method logs the e-invoice generation activity when invoice is generated via PWC service
|
* This method logs the e-invoice generation activity when invoice is generated via DMS webhook
|
||||||
*/
|
*/
|
||||||
async logEInvoiceGenerationActivity(requestId: string, invoiceNumber?: string): Promise<void> {
|
async logEInvoiceGenerationActivity(requestId: string, invoiceNumber?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@ -2045,7 +2040,7 @@ export class DealerClaimService {
|
|||||||
user: { userId: 'system', name: 'System Auto-Process' },
|
user: { userId: 'system', name: 'System Auto-Process' },
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
action: 'E-Invoice Generated',
|
action: 'E-Invoice Generated',
|
||||||
details: `E-Invoice generated via PWC. Invoice Number: ${finalInvoiceNumber}. Request: ${requestNumber}`,
|
details: `E-Invoice generated via DMS. Invoice Number: ${finalInvoiceNumber}. Request: ${requestNumber}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`[DealerClaimService] E-Invoice Generation activity logged for request ${requestId} (Invoice: ${finalInvoiceNumber})`);
|
logger.info(`[DealerClaimService] E-Invoice Generation activity logged for request ${requestId} (Invoice: ${finalInvoiceNumber})`);
|
||||||
@ -2220,6 +2215,14 @@ export class DealerClaimService {
|
|||||||
dealerName: claimDetails.dealerName,
|
dealerName: claimDetails.dealerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: Implement email service to send credit note to dealer
|
||||||
|
// await emailService.sendCreditNoteToDealer({
|
||||||
|
// dealerEmail: claimDetails.dealerEmail,
|
||||||
|
// dealerName: claimDetails.dealerName,
|
||||||
|
// creditNoteNumber: creditNote.creditNoteNumber,
|
||||||
|
// creditNoteAmount: creditNote.creditNoteAmount,
|
||||||
|
// requestNumber: requestNumber,
|
||||||
|
// });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[DealerClaimService] Error sending credit note to dealer:', error);
|
logger.error('[DealerClaimService] Error sending credit note to dealer:', error);
|
||||||
@ -2423,7 +2426,7 @@ export class DealerClaimService {
|
|||||||
category: 'SUPPORTING',
|
category: 'SUPPORTING',
|
||||||
isDeleted: false
|
isDeleted: false
|
||||||
},
|
},
|
||||||
order: [['uploadedAt', 'DESC']]
|
order: [['createdAt', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshotData = {
|
const snapshotData = {
|
||||||
@ -2508,7 +2511,7 @@ export class DealerClaimService {
|
|||||||
category: 'SUPPORTING',
|
category: 'SUPPORTING',
|
||||||
isDeleted: false
|
isDeleted: false
|
||||||
},
|
},
|
||||||
order: [['uploadedAt', 'DESC']]
|
order: [['createdAt', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store all completion data in JSONB
|
// Store all completion data in JSONB
|
||||||
|
|||||||
@ -25,17 +25,10 @@ import { tatSchedulerService } from './tatScheduler.service';
|
|||||||
import { DealerClaimService } from './dealerClaim.service';
|
import { DealerClaimService } from './dealerClaim.service';
|
||||||
import { emitToRequestRoom } from '../realtime/socket';
|
import { emitToRequestRoom } from '../realtime/socket';
|
||||||
|
|
||||||
|
|
||||||
let dealerClaimServiceInstance: any;
|
|
||||||
|
|
||||||
export class DealerClaimApprovalService {
|
export class DealerClaimApprovalService {
|
||||||
// Use lazy initialization to avoid circular dependency
|
// Use lazy initialization to avoid circular dependency
|
||||||
private getDealerClaimService(): DealerClaimService {
|
private getDealerClaimService(): DealerClaimService {
|
||||||
if (!dealerClaimServiceInstance) {
|
return new DealerClaimService();
|
||||||
const { DealerClaimService } = require('./dealerClaim.service');
|
|
||||||
dealerClaimServiceInstance = new DealerClaimService();
|
|
||||||
}
|
|
||||||
return dealerClaimServiceInstance;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Approve a level in a dealer claim workflow
|
* Approve a level in a dealer claim workflow
|
||||||
@ -338,7 +331,7 @@ export class DealerClaimApprovalService {
|
|||||||
// Activity Creation is now an activity log only - process it automatically
|
// Activity Creation is now an activity log only - process it automatically
|
||||||
logger.info(`[DealerClaimApproval] Department Lead approved. Processing Activity Creation as activity log.`);
|
logger.info(`[DealerClaimApproval] Department Lead approved. Processing Activity Creation as activity log.`);
|
||||||
try {
|
try {
|
||||||
const dealerClaimService = this.getDealerClaimService();
|
const dealerClaimService = new DealerClaimService();
|
||||||
await dealerClaimService.processActivityCreation(level.requestId);
|
await dealerClaimService.processActivityCreation(level.requestId);
|
||||||
logger.info(`[DealerClaimApproval] Activity Creation activity logged for request ${level.requestId}`);
|
logger.info(`[DealerClaimApproval] Activity Creation activity logged for request ${level.requestId}`);
|
||||||
} catch (activityError) {
|
} catch (activityError) {
|
||||||
|
|||||||
@ -64,6 +64,30 @@ export class DMSIntegrationService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement actual DMS API call
|
||||||
|
// Example:
|
||||||
|
// const response = await axios.post(`${this.dmsBaseUrl}/api/invoices/generate`, {
|
||||||
|
// request_number: invoiceData.requestNumber,
|
||||||
|
// dealer_code: invoiceData.dealerCode,
|
||||||
|
// dealer_name: invoiceData.dealerName,
|
||||||
|
// amount: invoiceData.amount,
|
||||||
|
// description: invoiceData.description,
|
||||||
|
// io_number: invoiceData.ioNumber,
|
||||||
|
// tax_details: invoiceData.taxDetails
|
||||||
|
// }, {
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': `Bearer ${this.dmsApiKey}`,
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// success: response.data.success,
|
||||||
|
// eInvoiceNumber: response.data.e_invoice_number,
|
||||||
|
// dmsNumber: response.data.dms_number,
|
||||||
|
// invoiceDate: new Date(response.data.invoice_date),
|
||||||
|
// invoiceUrl: response.data.invoice_url
|
||||||
|
// };
|
||||||
|
|
||||||
logger.warn('[DMS] DMS e-invoice generation not implemented, generating mock invoice');
|
logger.warn('[DMS] DMS e-invoice generation not implemented, generating mock invoice');
|
||||||
const mockInvoiceNumber = `EINV-${Date.now()}`;
|
const mockInvoiceNumber = `EINV-${Date.now()}`;
|
||||||
@ -121,6 +145,31 @@ export class DMSIntegrationService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement actual DMS API call
|
||||||
|
// Example:
|
||||||
|
// const response = await axios.post(`${this.dmsBaseUrl}/api/credit-notes/generate`, {
|
||||||
|
// request_number: creditNoteData.requestNumber,
|
||||||
|
// e_invoice_number: creditNoteData.eInvoiceNumber,
|
||||||
|
// dealer_code: creditNoteData.dealerCode,
|
||||||
|
// dealer_name: creditNoteData.dealerName,
|
||||||
|
// amount: creditNoteData.amount,
|
||||||
|
// reason: creditNoteData.reason,
|
||||||
|
// description: creditNoteData.description
|
||||||
|
// }, {
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': `Bearer ${this.dmsApiKey}`,
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// success: response.data.success,
|
||||||
|
// creditNoteNumber: response.data.credit_note_number,
|
||||||
|
// creditNoteDate: new Date(response.data.credit_note_date),
|
||||||
|
// creditNoteAmount: response.data.credit_note_amount,
|
||||||
|
// creditNoteUrl: response.data.credit_note_url
|
||||||
|
// };
|
||||||
|
|
||||||
logger.warn('[DMS] DMS credit note generation not implemented, generating mock credit note');
|
logger.warn('[DMS] DMS credit note generation not implemented, generating mock credit note');
|
||||||
const mockCreditNoteNumber = `CN-${Date.now()}`;
|
const mockCreditNoteNumber = `CN-${Date.now()}`;
|
||||||
return {
|
return {
|
||||||
@ -168,7 +217,23 @@ export class DMSIntegrationService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
;
|
// TODO: Implement actual DMS API call
|
||||||
|
// Example:
|
||||||
|
// const response = await axios.get(`${this.dmsBaseUrl}/api/invoices/${eInvoiceNumber}/status`, {
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': `Bearer ${this.dmsApiKey}`,
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// success: true,
|
||||||
|
// status: response.data.status,
|
||||||
|
// invoiceNumber: response.data.invoice_number,
|
||||||
|
// dmsNumber: response.data.dms_number,
|
||||||
|
// invoiceDate: new Date(response.data.invoice_date),
|
||||||
|
// amount: response.data.amount
|
||||||
|
// };
|
||||||
|
|
||||||
logger.warn('[DMS] DMS invoice status check not implemented, returning mock status');
|
logger.warn('[DMS] DMS invoice status check not implemented, returning mock status');
|
||||||
return {
|
return {
|
||||||
@ -212,7 +277,20 @@ export class DMSIntegrationService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement actual DMS API call
|
||||||
|
// Example:
|
||||||
|
// const response = await axios.get(`${this.dmsBaseUrl}/api/invoices/${eInvoiceNumber}/download`, {
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': `Bearer ${this.dmsApiKey}`
|
||||||
|
// },
|
||||||
|
// responseType: 'arraybuffer'
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// success: true,
|
||||||
|
// documentBuffer: Buffer.from(response.data),
|
||||||
|
// mimeType: response.headers['content-type'] || 'application/pdf'
|
||||||
|
// };
|
||||||
|
|
||||||
logger.warn('[DMS] DMS invoice download not implemented, returning mock URL');
|
logger.warn('[DMS] DMS invoice download not implemented, returning mock URL');
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -508,7 +508,7 @@ export class DMSWebhookService {
|
|||||||
|
|
||||||
// E-Invoice Generation is now an activity log only, not an approval step
|
// E-Invoice Generation is now an activity log only, not an approval step
|
||||||
// Log the activity using the dealerClaimService
|
// Log the activity using the dealerClaimService
|
||||||
const { DealerClaimService } = require('./dealerClaim.service');
|
const { DealerClaimService } = await import('./dealerClaim.service');
|
||||||
const dealerClaimService = new DealerClaimService();
|
const dealerClaimService = new DealerClaimService();
|
||||||
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
||||||
const invoiceNumber = invoice?.invoiceNumber || 'N/A';
|
const invoiceNumber = invoice?.invoiceNumber || 'N/A';
|
||||||
|
|||||||
@ -1,206 +0,0 @@
|
|||||||
import puppeteer from 'puppeteer';
|
|
||||||
import { WorkflowRequest } from '@models/WorkflowRequest';
|
|
||||||
import { ClaimInvoice } from '@models/ClaimInvoice';
|
|
||||||
import { DealerClaimDetails } from '@models/DealerClaimDetails';
|
|
||||||
import { DealerProposalDetails } from '@models/DealerProposalDetails';
|
|
||||||
import { findDealerLocally } from './dealer.service';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
|
||||||
import logger from '@utils/logger';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
export class PdfService {
|
|
||||||
private storagePath = path.join(process.cwd(), 'storage', 'invoices');
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (!fs.existsSync(this.storagePath)) {
|
|
||||||
fs.mkdirSync(this.storagePath, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateInvoicePdf(requestId: string): Promise<string> {
|
|
||||||
const browser = await puppeteer.launch({
|
|
||||||
headless: true,
|
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const page = await browser.newPage();
|
|
||||||
|
|
||||||
const request = await WorkflowRequest.findByPk(requestId);
|
|
||||||
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
|
||||||
const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
|
|
||||||
const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
|
|
||||||
const dealer = await findDealerLocally(claimDetails?.dealerCode, claimDetails?.dealerEmail);
|
|
||||||
|
|
||||||
if (!request || !invoice) {
|
|
||||||
throw new Error('Request or Invoice not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlContent = this.getInvoiceHtmlTemplate({
|
|
||||||
request,
|
|
||||||
invoice,
|
|
||||||
claimDetails,
|
|
||||||
proposalDetails,
|
|
||||||
dealer
|
|
||||||
});
|
|
||||||
|
|
||||||
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
|
|
||||||
|
|
||||||
const fileName = `invoice_${requestId}_${Date.now()}.pdf`;
|
|
||||||
const filePath = path.join(this.storagePath, fileName);
|
|
||||||
|
|
||||||
await page.pdf({
|
|
||||||
path: filePath,
|
|
||||||
format: 'A4',
|
|
||||||
printBackground: true,
|
|
||||||
margin: { top: '20px', bottom: '20px', left: '20px', right: '20px' }
|
|
||||||
});
|
|
||||||
|
|
||||||
await invoice.update({ filePath: fileName });
|
|
||||||
|
|
||||||
return fileName;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[PdfService] Error generating PDF for request ${requestId}:`, error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getInvoiceHtmlTemplate(data: any): string {
|
|
||||||
const { request, invoice, dealer, claimDetails } = data;
|
|
||||||
const qrImage = invoice.qrImage ? `data:image/png;base64,${invoice.qrImage}` : '';
|
|
||||||
const logoUrl = 'https://www.royalenfield.com/content/dam/royal-enfield/india/logos/logo.png';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; color: #333; }
|
|
||||||
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; }
|
|
||||||
.logo { height: 40px; }
|
|
||||||
.qr-code { width: 150px; height: 150px; }
|
|
||||||
.irn-details { font-size: 11px; margin-top: 10px; }
|
|
||||||
.irn-details div { margin-bottom: 4px; }
|
|
||||||
.title { text-align: center; font-size: 24px; font-weight: bold; margin: 30px 0; border-top: 1px solid #ccc; padding-top: 20px; }
|
|
||||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 30px; font-size: 12px; }
|
|
||||||
.info-section h3 { font-size: 14px; margin-bottom: 10px; }
|
|
||||||
.info-row { display: flex; margin-bottom: 5px; }
|
|
||||||
.info-label { width: 120px; font-weight: bold; }
|
|
||||||
.info-value { flex: 1; }
|
|
||||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 10px; }
|
|
||||||
th, td { border: 1px solid #333; padding: 6px; text-align: left; }
|
|
||||||
th { background-color: #f2f2f2; }
|
|
||||||
.totals { margin-top: 20px; width: 300px; margin-left: auto; font-size: 12px; }
|
|
||||||
.totals-row { display: flex; justify-content: space-between; padding: 5px 0; }
|
|
||||||
.totals-row.grand-total { border-top: 2px solid #333; font-weight: bold; margin-top: 10px; }
|
|
||||||
.words { margin-top: 30px; font-size: 11px; font-style: italic; }
|
|
||||||
.footer { margin-top: 60px; text-align: right; font-size: 12px; }
|
|
||||||
.signature { margin-top: 40px; border-top: 1px solid #333; width: 200px; margin-left: auto; text-align: center; padding-top: 5px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header">
|
|
||||||
<div>
|
|
||||||
<img src="${logoUrl}" class="logo" />
|
|
||||||
<div class="irn-details">
|
|
||||||
<div><strong>IRN No :</strong> ${invoice.irn || 'N/A'}</div>
|
|
||||||
<div><strong>Ack No :</strong> ${invoice.ackNo || 'N/A'}</div>
|
|
||||||
<div><strong>Ack Date & Time :</strong> ${invoice.ackDate ? dayjs(invoice.ackDate).format('YYYY-MM-DD HH:mm:ss') : 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img src="${qrImage}" class="qr-code" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="title">WARRANTY CLAIM TAX INVOICE</div>
|
|
||||||
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-section">
|
|
||||||
<div class="info-row"><div class="info-label">Customer Name</div><div class="info-value">Royal Enfield</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Customer GSTIN</div><div class="info-value">33AAACE3882D1ZZ</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Customer Address</div><div class="info-value">State Highway 48, Vallam Industrial Corridor, Vallakottai Chennai, Tamil Nadu - 631604</div></div>
|
|
||||||
<br/>
|
|
||||||
<div class="info-row"><div class="info-label">Vehicle Owner</div><div class="info-value">N/A</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Invoice No.</div><div class="info-value">${invoice.invoiceNumber || 'N/A'}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Invoice Date</div><div class="info-value">${invoice.invoiceDate ? dayjs(invoice.invoiceDate).format('DD-MM-YYYY') : 'N/A'}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Chassis No.</div><div class="info-value">N/A</div></div>
|
|
||||||
</div>
|
|
||||||
<div class="info-section">
|
|
||||||
<div class="info-row"><div class="info-label">Dealer</div><div class="info-value">${dealer?.dealerName || claimDetails?.dealerName || 'N/A'}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Store</div><div class="info-value">${dealer?.dealerName || claimDetails?.dealerName || 'N/A'}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Supplier GSTIN</div><div class="info-value">${dealer?.gstin || 'N/A'}</div></div>
|
|
||||||
<br/>
|
|
||||||
<div class="info-row"><div class="info-label">POS</div><div class="info-value">${dealer?.state || dealer?.city || 'N/A'}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Claim Invoice No.</div><div class="info-value">${request.requestNumber || 'N/A'}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Claim Invoice Date</div><div class="info-value">${dayjs().format('DD-MM-YYYY')}</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Job Card No.</div><div class="info-value">N/A</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">KMS.Reading</div><div class="info-value">N/A</div></div>
|
|
||||||
<div class="info-row"><div class="info-label">Last Approval Date</div><div class="info-value">${invoice.generatedAt ? dayjs(invoice.generatedAt).format('DD-MM-YYYY') : 'N/A'}</div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Part</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>HSN/SAC</th>
|
|
||||||
<th>Qty</th>
|
|
||||||
<th>Rate</th>
|
|
||||||
<th>Discount</th>
|
|
||||||
<th>UOM</th>
|
|
||||||
<th>Taxable Value</th>
|
|
||||||
<th>IGST %</th>
|
|
||||||
<th>IGST</th>
|
|
||||||
<th>CGST %</th>
|
|
||||||
<th>CGST</th>
|
|
||||||
<th>SGST %</th>
|
|
||||||
<th>SGST</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>CLAIM</td>
|
|
||||||
<td>${request.title || 'Warranty Claim'}</td>
|
|
||||||
<td>998881</td>
|
|
||||||
<td>1.00</td>
|
|
||||||
<td>${Number(invoice.amount || 0).toFixed(2)}</td>
|
|
||||||
<td>0.00</td>
|
|
||||||
<td>EA</td>
|
|
||||||
<td>${Number(invoice.amount || 0).toFixed(2)}</td>
|
|
||||||
<td>18.00</td>
|
|
||||||
<td>${(Number(invoice.amount || 0) * 0.18).toFixed(2)}</td>
|
|
||||||
<td>0.00</td>
|
|
||||||
<td>0.00</td>
|
|
||||||
<td>0.00</td>
|
|
||||||
<td>0.00</td>
|
|
||||||
<td>${(Number(invoice.amount || 0) * 1.18).toFixed(2)}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="totals">
|
|
||||||
<div class="totals-row"><span>TOTAL:</span><span>${Number(invoice.amount || 0).toFixed(2)}</span></div>
|
|
||||||
<div class="totals-row"><span>TOTAL TAX:</span><span>${(Number(invoice.amount || 0) * 0.18).toFixed(2)}</span></div>
|
|
||||||
<div class="totals-row grand-total"><span>GRAND TOTAL:</span><span>${(Number(invoice.amount || 0) * 1.18).toFixed(2)}</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="words">
|
|
||||||
<div><strong>TOTAL VALUE IN WORDS:</strong> Rupees ${invoice.totalValueInWords || 'N/A'}</div>
|
|
||||||
<div><strong>TOTAL TAX IN WORDS:</strong> Rupees ${invoice.taxValueInWords || 'N/A'}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>For ${dealer?.name || 'Authorised Dealer'}</p>
|
|
||||||
<div class="signature">Authorised signatory</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pdfService = new PdfService();
|
|
||||||
@ -5,8 +5,6 @@ import { ActivityType } from '../models/ActivityType';
|
|||||||
import { WorkflowRequest } from '../models/WorkflowRequest';
|
import { WorkflowRequest } from '../models/WorkflowRequest';
|
||||||
import { ClaimInvoice } from '../models/ClaimInvoice';
|
import { ClaimInvoice } from '../models/ClaimInvoice';
|
||||||
import { InternalOrder } from '../models/InternalOrder';
|
import { InternalOrder } from '../models/InternalOrder';
|
||||||
import { User } from '../models/User';
|
|
||||||
import { DealerClaimDetails } from '../models/DealerClaimDetails';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PWC E-Invoice Integration Service
|
* PWC E-Invoice Integration Service
|
||||||
@ -14,13 +12,13 @@ import { DealerClaimDetails } from '../models/DealerClaimDetails';
|
|||||||
*/
|
*/
|
||||||
export class PWCIntegrationService {
|
export class PWCIntegrationService {
|
||||||
private apiUrl: string;
|
private apiUrl: string;
|
||||||
private customerId: string;
|
private appKey: string;
|
||||||
private token: string;
|
private appSecret: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.apiUrl = process.env.PWC_API_URL || 'https://api.qa.einvoice.aw.navigatetax.pwc.co.in/qa/v1/en/push';
|
this.apiUrl = process.env.PWC_API_URL || 'https://api.qa.einvoice.aw.navigatetax.pwc.co.in';
|
||||||
this.customerId = process.env.PWC_CUSTOMER_ID || '';
|
this.appKey = process.env.PWC_APP_KEY || '';
|
||||||
this.token = process.env.PWC_TOKEN || '';
|
this.appSecret = process.env.PWC_APP_SECRET || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +43,7 @@ export class PWCIntegrationService {
|
|||||||
/**
|
/**
|
||||||
* Generate Signed Invoice via PWC API
|
* Generate Signed Invoice via PWC API
|
||||||
*/
|
*/
|
||||||
async generateSignedInvoice(requestId: string, amount?: number): Promise<{
|
async generateSignedInvoice(requestId: string): Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
irn?: string;
|
irn?: string;
|
||||||
ackNo?: string;
|
ackNo?: string;
|
||||||
@ -53,155 +51,75 @@ export class PWCIntegrationService {
|
|||||||
signedInvoice?: string;
|
signedInvoice?: string;
|
||||||
qrCode?: string;
|
qrCode?: string;
|
||||||
qrImage?: string;
|
qrImage?: string;
|
||||||
rawResponse?: any;
|
|
||||||
irpResponse?: any;
|
|
||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const request = await WorkflowRequest.findByPk(requestId, {
|
const request = await WorkflowRequest.findByPk(requestId, {
|
||||||
include: [{ model: User, as: 'initiator' }, { model: DealerClaimDetails, as: 'claimDetails' }]
|
include: ['claimDetails', 'initiator']
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!request) return { success: false, error: 'Request not found' };
|
if (!request) return { success: false, error: 'Request not found' };
|
||||||
|
|
||||||
const claimDetails = (request as any).claimDetails;
|
const dealer = await Dealer.findOne({ where: { dlrcode: (request as any).claimDetails?.dealerCode } });
|
||||||
const dealer = await Dealer.findOne({ where: { dlrcode: claimDetails?.dealerCode } });
|
const activity = await ActivityType.findOne({ where: { title: (request as any).claimDetails?.activityType } });
|
||||||
const activity = await ActivityType.findOne({ where: { title: claimDetails?.activityType } });
|
|
||||||
|
|
||||||
if (!dealer || !activity) {
|
if (!dealer || !activity) {
|
||||||
return { success: false, error: 'Dealer or Activity details missing' };
|
return { success: false, error: 'Dealer or Activity details missing' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for amount if not provided
|
// Construct PWC Payload (keeping existing logic for now)
|
||||||
const finalAmount = Number(amount || (request as any).amount || 0);
|
const payload = {
|
||||||
|
UserGstin: "33AAACE3882D1ZZ",
|
||||||
// Helper to format number to 2 decimal places
|
DocDtls: {
|
||||||
const formatAmount = (val: number) => Number(val.toFixed(2));
|
Typ: "INV",
|
||||||
|
No: `INV-${Date.now()}`,
|
||||||
// Extract State Code from Dealer GSTIN
|
Dt: new Date().toLocaleDateString('en-GB').replace(/\//g, '-')
|
||||||
let dealerGst = (dealer as any).gst;
|
},
|
||||||
|
SellerDtls: {
|
||||||
// HOTFIX: For PWC QA Environment, use a known valid GSTIN if dealer has the invalid test one
|
Gstin: dealer.gst || "33AAACE3882D1ZZ",
|
||||||
// The test GSTIN 29AAACE3882D1ZZ is not registered in PWC QA Master, causing Error 701
|
LglNm: dealer.dealership || 'Dealer',
|
||||||
const isQA = this.apiUrl.includes('qa');
|
Addr1: dealer.showroomAddress || "Address Line 1",
|
||||||
const invalidTestGst = '29AAACE3882D1ZZ';
|
Loc: dealer.location || "Location",
|
||||||
const validQaGst = '24AAAPI3182M002'; // Registered in PWC QA
|
Pin: 600001,
|
||||||
|
Stcd: "33"
|
||||||
if (isQA && (!dealerGst || dealerGst === invalidTestGst)) {
|
},
|
||||||
dealerGst = validQaGst;
|
BuyerDtls: {
|
||||||
}
|
Gstin: "33AAACE3882D1ZZ",
|
||||||
|
LglNm: "ROYAL ENFIELD (A UNIT OF EICHER MOTORS LTD)",
|
||||||
// Final fallback if still empty
|
Addr1: "No. 2, Thiruvottiyur High Road",
|
||||||
dealerGst = dealerGst || validQaGst;
|
Loc: "Thiruvottiyur",
|
||||||
|
Pin: 600019,
|
||||||
let dealerStateCode = "24"; // Default fallback (Gujarat for 24...)
|
Stcd: "33",
|
||||||
|
Pos: "33"
|
||||||
// Try to extract from GSTIN (first 2 chars)
|
},
|
||||||
if (dealerGst && dealerGst.length >= 2 && !isNaN(Number(dealerGst.substring(0, 2)))) {
|
ItemList: [
|
||||||
dealerStateCode = dealerGst.substring(0, 2);
|
{
|
||||||
} else if ((dealer as any).stateCode) {
|
SlNo: "1",
|
||||||
dealerStateCode = (dealer as any).stateCode;
|
PrdDesc: activity.title,
|
||||||
}
|
IsServc: "Y",
|
||||||
|
HsnCd: activity.hsnCode || activity.sacCode || "9983",
|
||||||
// Calculate tax amounts
|
Qty: 1,
|
||||||
const gstRate = Number(activity.gstRate || 18);
|
Unit: "OTH",
|
||||||
const isIGST = dealerStateCode !== "33"; // If dealer state != Buyer state (33), it's IGST
|
UnitPrce: (request as any).amount,
|
||||||
|
TotAmt: (request as any).amount,
|
||||||
const assAmt = finalAmount;
|
GstRt: activity.gstRate || 18,
|
||||||
const igstAmt = isIGST ? (finalAmount * (gstRate / 100)) : 0;
|
AssAmt: (request as any).amount,
|
||||||
const cgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0;
|
IgstAmt: activity.gstRate === 18 ? ((request as any).amount * 0.18) : 0,
|
||||||
const sgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0;
|
TotItemVal: (request as any).amount * 1.18
|
||||||
const totalTax = igstAmt + cgstAmt + sgstAmt;
|
|
||||||
const totalItemVal = finalAmount + totalTax;
|
|
||||||
|
|
||||||
// Construct PWC Payload - Aligned with sample format provided by user
|
|
||||||
const payload = [
|
|
||||||
{
|
|
||||||
User_GSTIN: dealerGst, // Use Dealer GSTIN as User_GSTIN to ensure match
|
|
||||||
Version: "1.01",
|
|
||||||
IRN: "",
|
|
||||||
SourceSystem: "RE_WORKFLOW",
|
|
||||||
is_irn: "Y",
|
|
||||||
is_ewb: "N",
|
|
||||||
email: (request as any).initiator?.email || "system@royalenfield.com",
|
|
||||||
TranDtls: {
|
|
||||||
TaxSch: "GST",
|
|
||||||
SubType: "SUPPLY",
|
|
||||||
SubTypeDescription: "Others",
|
|
||||||
SupTyp: "B2B",
|
|
||||||
RegRev: "N",
|
|
||||||
Typ: "REG",
|
|
||||||
DiffPercentage: "0",
|
|
||||||
Taxability: "Taxable",
|
|
||||||
InterIntra: isIGST ? "Inter" : "Intra",
|
|
||||||
CancelFlag: "N"
|
|
||||||
},
|
|
||||||
DocDtls: {
|
|
||||||
Typ: "Inv",
|
|
||||||
No: (request as any).requestNumber || `INV-${Date.now()}`,
|
|
||||||
Dt: new Date().toLocaleDateString('en-GB') // DD/MM/YYYY
|
|
||||||
},
|
|
||||||
SellerDtls: {
|
|
||||||
Gstin: dealerGst,
|
|
||||||
LglNm: (dealer as any).dealership || 'Dealer',
|
|
||||||
TrdNm: (dealer as any).dealership || 'Dealer',
|
|
||||||
Addr1: (dealer as any).showroomAddress || "Address Line 1",
|
|
||||||
Loc: (dealer as any).location || "Location",
|
|
||||||
Pin: (dealerGst === validQaGst && String((dealer as any).showroomPincode || '').substring(0, 1) !== '3')
|
|
||||||
? 380001
|
|
||||||
: Number((dealer as any).showroomPincode) || 600001,
|
|
||||||
Stcd: dealerStateCode,
|
|
||||||
Ph: (dealer as any).dpContactNumber || "9998887776",
|
|
||||||
Em: (dealer as any).dealerPrincipalEmailId || "Supplier@inv.com"
|
|
||||||
},
|
|
||||||
BuyerDtls: {
|
|
||||||
Gstin: "33AAACE3882D1ZZ", // Royal Enfield GST
|
|
||||||
LglNm: "ROYAL ENFIELD (A UNIT OF EICHER MOTORS LTD)",
|
|
||||||
TrdNm: "ROYAL ENFIELD",
|
|
||||||
Addr1: "No. 2, Thiruvottiyur High Road",
|
|
||||||
Loc: "Thiruvottiyur",
|
|
||||||
Pin: 600019,
|
|
||||||
Stcd: "33",
|
|
||||||
Pos: "33"
|
|
||||||
},
|
|
||||||
ItemList: [
|
|
||||||
{
|
|
||||||
SlNo: "1",
|
|
||||||
PrdNm: activity.title,
|
|
||||||
PrdDesc: activity.title,
|
|
||||||
HsnCd: activity.hsnCode || activity.sacCode || "9983",
|
|
||||||
IsServc: "Y",
|
|
||||||
Qty: 1,
|
|
||||||
Unit: "OTH",
|
|
||||||
UnitPrice: formatAmount(finalAmount), // Ensure number
|
|
||||||
TotAmt: formatAmount(finalAmount), // Ensure number
|
|
||||||
AssAmt: formatAmount(assAmt), // Ensure number
|
|
||||||
GstRt: gstRate,
|
|
||||||
IgstAmt: formatAmount(igstAmt),
|
|
||||||
CgstAmt: formatAmount(cgstAmt),
|
|
||||||
SgstAmt: formatAmount(sgstAmt),
|
|
||||||
TotItemVal: formatAmount(totalItemVal)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
ValDtls: {
|
|
||||||
AssVal: formatAmount(assAmt),
|
|
||||||
IgstVal: formatAmount(igstAmt),
|
|
||||||
CgstVal: formatAmount(cgstAmt),
|
|
||||||
SgstVal: formatAmount(sgstAmt),
|
|
||||||
TotInvVal: formatAmount(totalItemVal)
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
ValDtls: {
|
||||||
|
AssVal: (request as any).amount,
|
||||||
|
IgstVal: activity.gstRate === 18 ? ((request as any).amount * 0.18) : 0,
|
||||||
|
TotInvVal: (request as any).amount * 1.18
|
||||||
}
|
}
|
||||||
];
|
};
|
||||||
|
|
||||||
logger.info(`[PWC] Sending e-invoice request for ${request.requestNumber}`);
|
logger.info(`[PWC] Sending e-invoice request for ${request.requestNumber}`);
|
||||||
|
|
||||||
const response = await axios.post(this.apiUrl, payload, {
|
const response = await axios.post(`${this.apiUrl}/generate`, payload, {
|
||||||
headers: {
|
headers: { 'AppKey': this.appKey, 'AppSecret': this.appSecret }
|
||||||
'customerid': this.customerId,
|
|
||||||
'token': this.token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
console.log('PWC Response:', JSON.stringify(response.data));
|
|
||||||
|
|
||||||
// Parse PWC Response based on provided structure
|
// Parse PWC Response based on provided structure
|
||||||
// Sample response is an array: [{ pwc_response, irp_response, qr_b64_encoded }]
|
// Sample response is an array: [{ pwc_response, irp_response, qr_b64_encoded }]
|
||||||
@ -228,25 +146,9 @@ export class PWCIntegrationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!irn) {
|
if (!irn) {
|
||||||
const mainMsg = responseData?.pwc_response?.message || responseData?.irp_response?.message || 'E-Invoice generation failed';
|
const errorMsg = responseData?.irp_response?.message || 'E-Invoice generation failed';
|
||||||
|
logger.error(`[PWC] E-Invoice failed for ${request.requestNumber}: ${errorMsg}`);
|
||||||
// Extract detailed error messages from irp_response.data.error_details
|
return { success: false, error: errorMsg };
|
||||||
const errorDetails = responseData?.irp_response?.data?.error_details;
|
|
||||||
let errorMessage = mainMsg;
|
|
||||||
|
|
||||||
if (Array.isArray(errorDetails) && errorDetails.length > 0) {
|
|
||||||
const detailsStr = errorDetails.map((e: any) => `${e.ErrorCode}: ${e.ErrorMessage}`).join('; ');
|
|
||||||
errorMessage = `${mainMsg} - [${detailsStr}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also check validation remarks
|
|
||||||
const validationRemarks = responseData?.pwc_response?.validation_remarks;
|
|
||||||
if (validationRemarks && Object.keys(validationRemarks).length > 0) {
|
|
||||||
errorMessage += ` - Validation: ${JSON.stringify(validationRemarks)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.error(`[PWC] E-Invoice failed for ${request.requestNumber}: ${errorMessage}`);
|
|
||||||
return { success: false, error: errorMessage };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -256,9 +158,7 @@ export class PWCIntegrationService {
|
|||||||
ackDate: ackDate ? new Date(ackDate) : undefined,
|
ackDate: ackDate ? new Date(ackDate) : undefined,
|
||||||
signedInvoice,
|
signedInvoice,
|
||||||
qrCode,
|
qrCode,
|
||||||
qrImage: qrB64,
|
qrImage: qrB64
|
||||||
rawResponse: responseData?.pwc_response,
|
|
||||||
irpResponse: responseData?.irp_response
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -34,12 +34,6 @@ export class SAPIntegrationService {
|
|||||||
* Check if SAP integration is configured
|
* Check if SAP integration is configured
|
||||||
*/
|
*/
|
||||||
private isConfigured(): boolean {
|
private isConfigured(): boolean {
|
||||||
// Check if SAP bypass is explicitly enabled
|
|
||||||
if (process.env.SAP_BYPASS === 'true') {
|
|
||||||
logger.info('[SAP] SAP integration explicitly bypassed via SAP_BYPASS env variable');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!this.sapBaseUrl && !!this.sapUsername && !!this.sapPassword;
|
return !!this.sapBaseUrl && !!this.sapUsername && !!this.sapPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,10 +59,10 @@ export class SAPIntegrationService {
|
|||||||
'sap-client': '200'
|
'sap-client': '200'
|
||||||
});
|
});
|
||||||
const fullUrl = `${this.sapBaseUrl}${serviceRootUrl}?${queryParams.toString()}`;
|
const fullUrl = `${this.sapBaseUrl}${serviceRootUrl}?${queryParams.toString()}`;
|
||||||
|
|
||||||
logger.debug(`[SAP] Fetching CSRF token from service: ${serviceName}`);
|
logger.debug(`[SAP] Fetching CSRF token from service: ${serviceName}`);
|
||||||
logger.debug(`[SAP] CSRF token request URL: ${fullUrl}`);
|
logger.debug(`[SAP] CSRF token request URL: ${fullUrl}`);
|
||||||
|
|
||||||
// Use standalone axios request with Basic Auth in header
|
// Use standalone axios request with Basic Auth in header
|
||||||
// We need to capture cookies from this response to use in POST request
|
// We need to capture cookies from this response to use in POST request
|
||||||
const response = await axios.get(fullUrl, {
|
const response = await axios.get(fullUrl, {
|
||||||
@ -91,16 +85,16 @@ export class SAPIntegrationService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// SAP returns CSRF token in response headers (check multiple case variations)
|
// SAP returns CSRF token in response headers (check multiple case variations)
|
||||||
const csrfToken = response.headers['x-csrf-token'] ||
|
const csrfToken = response.headers['x-csrf-token'] ||
|
||||||
response.headers['X-CSRF-Token'] ||
|
response.headers['X-CSRF-Token'] ||
|
||||||
response.headers['X-Csrf-Token'] ||
|
response.headers['X-Csrf-Token'] ||
|
||||||
response.headers['x-csrf-token'];
|
response.headers['x-csrf-token'];
|
||||||
|
|
||||||
// Extract cookies from response headers
|
// Extract cookies from response headers
|
||||||
// SAP sets cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
|
// SAP sets cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
|
||||||
const setCookieHeaders = response.headers['set-cookie'] as string | string[] | undefined;
|
const setCookieHeaders = response.headers['set-cookie'] as string | string[] | undefined;
|
||||||
let cookies = '';
|
let cookies = '';
|
||||||
|
|
||||||
if (setCookieHeaders) {
|
if (setCookieHeaders) {
|
||||||
if (Array.isArray(setCookieHeaders)) {
|
if (Array.isArray(setCookieHeaders)) {
|
||||||
// Extract cookie values from Set-Cookie headers
|
// Extract cookie values from Set-Cookie headers
|
||||||
@ -113,36 +107,36 @@ export class SAPIntegrationService {
|
|||||||
cookies = setCookieHeaders.split(';')[0].trim();
|
cookies = setCookieHeaders.split(';')[0].trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log full GET response for debugging
|
// Log full GET response for debugging
|
||||||
logger.info(`[SAP] GET Response Status: ${response.status} ${response.statusText || ''}`);
|
logger.info(`[SAP] GET Response Status: ${response.status} ${response.statusText || ''}`);
|
||||||
logger.info(`[SAP] GET Response Headers:`, JSON.stringify(response.headers, null, 2));
|
logger.info(`[SAP] GET Response Headers:`, JSON.stringify(response.headers, null, 2));
|
||||||
logger.info(`[SAP] GET Response Data:`, JSON.stringify(response.data, null, 2));
|
logger.info(`[SAP] GET Response Data:`, JSON.stringify(response.data, null, 2));
|
||||||
|
|
||||||
if (csrfToken && typeof csrfToken === 'string' && csrfToken !== 'fetch') {
|
if (csrfToken && typeof csrfToken === 'string' && csrfToken !== 'fetch') {
|
||||||
logger.info(`[SAP] CSRF token obtained successfully (length: ${csrfToken.length})`);
|
logger.info(`[SAP] CSRF token obtained successfully (length: ${csrfToken.length})`);
|
||||||
logger.debug(`[SAP] CSRF token preview: ${csrfToken.substring(0, 20)}...`);
|
logger.debug(`[SAP] CSRF token preview: ${csrfToken.substring(0, 20)}...`);
|
||||||
|
|
||||||
if (cookies) {
|
if (cookies) {
|
||||||
logger.debug(`[SAP] Session cookies captured: ${cookies.substring(0, 50)}...`);
|
logger.debug(`[SAP] Session cookies captured: ${cookies.substring(0, 50)}...`);
|
||||||
} else {
|
} else {
|
||||||
logger.warn('[SAP] No cookies found in CSRF token response - POST may fail');
|
logger.warn('[SAP] No cookies found in CSRF token response - POST may fail');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { csrfToken, cookies };
|
return { csrfToken, cookies };
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('[SAP] CSRF token not found in response headers or invalid');
|
logger.warn('[SAP] CSRF token not found in response headers or invalid');
|
||||||
logger.debug('[SAP] Response status:', response.status);
|
logger.debug('[SAP] Response status:', response.status);
|
||||||
logger.debug('[SAP] Available headers:', Object.keys(response.headers).filter(h => h.toLowerCase().includes('csrf')));
|
logger.debug('[SAP] Available headers:', Object.keys(response.headers).filter(h => h.toLowerCase().includes('csrf')));
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
|
|
||||||
if (axiosError.response) {
|
if (axiosError.response) {
|
||||||
logger.error(`[SAP] Failed to get CSRF token: ${axiosError.response.status} ${axiosError.response.statusText}`);
|
logger.error(`[SAP] Failed to get CSRF token: ${axiosError.response.status} ${axiosError.response.statusText}`);
|
||||||
logger.error(`[SAP] Response data:`, axiosError.response.data);
|
logger.error(`[SAP] Response data:`, axiosError.response.data);
|
||||||
|
|
||||||
if (axiosError.response.status === 401 || axiosError.response.status === 403) {
|
if (axiosError.response.status === 401 || axiosError.response.status === 403) {
|
||||||
logger.error('[SAP] Authentication failed while fetching CSRF token - check SAP credentials');
|
logger.error('[SAP] Authentication failed while fetching CSRF token - check SAP credentials');
|
||||||
} else if (axiosError.response.status === 404) {
|
} else if (axiosError.response.status === 404) {
|
||||||
@ -158,7 +152,7 @@ export class SAPIntegrationService {
|
|||||||
} else {
|
} else {
|
||||||
logger.error('[SAP] Error setting up CSRF token request:', error instanceof Error ? error.message : 'Unknown error');
|
logger.error('[SAP] Error setting up CSRF token request:', error instanceof Error ? error.message : 'Unknown error');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +163,7 @@ export class SAPIntegrationService {
|
|||||||
private createSapClient() {
|
private createSapClient() {
|
||||||
// Check if SSL verification should be disabled (for testing with self-signed certs)
|
// Check if SSL verification should be disabled (for testing with self-signed certs)
|
||||||
const disableSSLVerification = process.env.SAP_DISABLE_SSL_VERIFY === 'true';
|
const disableSSLVerification = process.env.SAP_DISABLE_SSL_VERIFY === 'true';
|
||||||
|
|
||||||
const client = axios.create({
|
const client = axios.create({
|
||||||
baseURL: this.sapBaseUrl,
|
baseURL: this.sapBaseUrl,
|
||||||
timeout: this.sapTimeout,
|
timeout: this.sapTimeout,
|
||||||
@ -271,11 +265,11 @@ export class SAPIntegrationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sapClient = this.createSapClient();
|
const sapClient = this.createSapClient();
|
||||||
|
|
||||||
// SAP OData endpoint: GetSenderDataSet with filter on IONumber
|
// SAP OData endpoint: GetSenderDataSet with filter on IONumber
|
||||||
// Service name is configurable via SAP_SERVICE_NAME env variable
|
// Service name is configurable via SAP_SERVICE_NAME env variable
|
||||||
const endpoint = this.buildODataEndpoint('GetSenderDataSet');
|
const endpoint = this.buildODataEndpoint('GetSenderDataSet');
|
||||||
|
|
||||||
// Build OData query parameters matching the working URL format
|
// Build OData query parameters matching the working URL format
|
||||||
// $filter: Filter by IO number
|
// $filter: Filter by IO number
|
||||||
// $select: Select specific fields (Sender, ResponseDate, GetIODetailsSet01)
|
// $select: Select specific fields (Sender, ResponseDate, GetIODetailsSet01)
|
||||||
@ -287,18 +281,18 @@ export class SAPIntegrationService {
|
|||||||
'$expand': 'GetIODetailsSet01',
|
'$expand': 'GetIODetailsSet01',
|
||||||
'$format': 'json'
|
'$format': 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
const fullUrl = `${endpoint}?${queryParams.toString()}`;
|
const fullUrl = `${endpoint}?${queryParams.toString()}`;
|
||||||
|
|
||||||
logger.info(`[SAP] Validating IO number: ${ioNumber} using service: ${this.sapServiceName}`);
|
logger.info(`[SAP] Validating IO number: ${ioNumber} using service: ${this.sapServiceName}`);
|
||||||
logger.debug(`[SAP] Request URL: ${this.sapBaseUrl}${fullUrl}`);
|
logger.debug(`[SAP] Request URL: ${this.sapBaseUrl}${fullUrl}`);
|
||||||
|
|
||||||
const response = await sapClient.get(fullUrl);
|
const response = await sapClient.get(fullUrl);
|
||||||
|
|
||||||
if (response.status === 200 && response.data) {
|
if (response.status === 200 && response.data) {
|
||||||
// SAP OData response format: { d: { results: [...] } }
|
// SAP OData response format: { d: { results: [...] } }
|
||||||
const results = response.data.d?.results || response.data.results || [];
|
const results = response.data.d?.results || response.data.results || [];
|
||||||
|
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
logger.warn(`[SAP] IO number ${ioNumber} not found in SAP`);
|
logger.warn(`[SAP] IO number ${ioNumber} not found in SAP`);
|
||||||
return {
|
return {
|
||||||
@ -314,11 +308,11 @@ export class SAPIntegrationService {
|
|||||||
|
|
||||||
// Get first result (should be only one for a specific IO number)
|
// Get first result (should be only one for a specific IO number)
|
||||||
const senderData = results[0];
|
const senderData = results[0];
|
||||||
|
|
||||||
// IO details are in the expanded GetIODetailsSet01 entity set
|
// IO details are in the expanded GetIODetailsSet01 entity set
|
||||||
// Structure: senderData.GetIODetailsSet01.results[0]
|
// Structure: senderData.GetIODetailsSet01.results[0]
|
||||||
const ioDetailsSet = senderData.GetIODetailsSet01;
|
const ioDetailsSet = senderData.GetIODetailsSet01;
|
||||||
|
|
||||||
if (!ioDetailsSet || !ioDetailsSet.results || !Array.isArray(ioDetailsSet.results) || ioDetailsSet.results.length === 0) {
|
if (!ioDetailsSet || !ioDetailsSet.results || !Array.isArray(ioDetailsSet.results) || ioDetailsSet.results.length === 0) {
|
||||||
logger.warn(`[SAP] No IO details found in expanded GetIODetailsSet01 for IO ${ioNumber}`);
|
logger.warn(`[SAP] No IO details found in expanded GetIODetailsSet01 for IO ${ioNumber}`);
|
||||||
return {
|
return {
|
||||||
@ -331,34 +325,34 @@ export class SAPIntegrationService {
|
|||||||
error: 'IO details not found in SAP response'
|
error: 'IO details not found in SAP response'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the first IO detail from the results array
|
// Get the first IO detail from the results array
|
||||||
const ioDetails = ioDetailsSet.results[0];
|
const ioDetails = ioDetailsSet.results[0];
|
||||||
|
|
||||||
// Map SAP response fields to our format based on actual response structure:
|
// Map SAP response fields to our format based on actual response structure:
|
||||||
// - AvailableAmount: string with trailing space (e.g., "14333415.00 ")
|
// - AvailableAmount: string with trailing space (e.g., "14333415.00 ")
|
||||||
// - IODescription: description text
|
// - IODescription: description text
|
||||||
// - IONumber: IO number
|
// - IONumber: IO number
|
||||||
// - BlockedAmount: may not be present in response, default to 0
|
// - BlockedAmount: may not be present in response, default to 0
|
||||||
// - Currency: may not be present, default to INR
|
// - Currency: may not be present, default to INR
|
||||||
|
|
||||||
// Parse AvailableAmount - it's a string that may have trailing spaces
|
// Parse AvailableAmount - it's a string that may have trailing spaces
|
||||||
const availableAmountStr = (ioDetails.AvailableAmount || '0').toString().trim();
|
const availableAmountStr = (ioDetails.AvailableAmount || '0').toString().trim();
|
||||||
const availableBalance = parseFloat(availableAmountStr) || 0;
|
const availableBalance = parseFloat(availableAmountStr) || 0;
|
||||||
|
|
||||||
// BlockedAmount may not be in the response, default to 0
|
// BlockedAmount may not be in the response, default to 0
|
||||||
// If it exists, it might also be a string with trailing space
|
// If it exists, it might also be a string with trailing space
|
||||||
const blockedAmountStr = (ioDetails.BlockedAmount || ioDetails.Blocked || '0').toString().trim();
|
const blockedAmountStr = (ioDetails.BlockedAmount || ioDetails.Blocked || '0').toString().trim();
|
||||||
const blockedAmount = parseFloat(blockedAmountStr) || 0;
|
const blockedAmount = parseFloat(blockedAmountStr) || 0;
|
||||||
|
|
||||||
const remainingBalance = availableBalance - blockedAmount;
|
const remainingBalance = availableBalance - blockedAmount;
|
||||||
|
|
||||||
// Currency may not be in response, default to INR
|
// Currency may not be in response, default to INR
|
||||||
const currency = (ioDetails.Currency || ioDetails.CurrencyCode || ioDetails.Curr || 'INR').toString().trim();
|
const currency = (ioDetails.Currency || ioDetails.CurrencyCode || ioDetails.Curr || 'INR').toString().trim();
|
||||||
|
|
||||||
// Description from IODescription field
|
// Description from IODescription field
|
||||||
const description = ioDetails.IODescription || ioDetails.Description || ioDetails.Text || ioDetails.ShortText || undefined;
|
const description = ioDetails.IODescription || ioDetails.Description || ioDetails.Text || ioDetails.ShortText || undefined;
|
||||||
|
|
||||||
// IO Number from the IO details
|
// IO Number from the IO details
|
||||||
const validatedIONumber = ioDetails.IONumber || ioDetails.InternalOrder || ioNumber;
|
const validatedIONumber = ioDetails.IONumber || ioDetails.InternalOrder || ioNumber;
|
||||||
|
|
||||||
@ -422,7 +416,7 @@ export class SAPIntegrationService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
|
|
||||||
if (axiosError.response) {
|
if (axiosError.response) {
|
||||||
// SAP returned an error response
|
// SAP returned an error response
|
||||||
logger.error(`[SAP] Error validating IO number ${ioNumber}:`, {
|
logger.error(`[SAP] Error validating IO number ${ioNumber}:`, {
|
||||||
@ -430,7 +424,7 @@ export class SAPIntegrationService {
|
|||||||
statusText: axiosError.response.statusText,
|
statusText: axiosError.response.statusText,
|
||||||
data: axiosError.response.data
|
data: axiosError.response.data
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
ioNumber,
|
ioNumber,
|
||||||
@ -500,16 +494,16 @@ export class SAPIntegrationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sapClient = this.createSapClient();
|
const sapClient = this.createSapClient();
|
||||||
|
|
||||||
// SAP OData endpoint for budget blocking
|
// SAP OData endpoint for budget blocking
|
||||||
// Service: ZFI_BUDGET_BLOCK_API_SRV
|
// Service: ZFI_BUDGET_BLOCK_API_SRV
|
||||||
// Entity Set: RequesterInputSet
|
// Entity Set: RequesterInputSet
|
||||||
const endpoint = `/sap/opu/odata/sap/${this.sapBlockServiceName}/RequesterInputSet`;
|
const endpoint = `/sap/opu/odata/sap/${this.sapBlockServiceName}/RequesterInputSet`;
|
||||||
|
|
||||||
// Format current date/time in ISO format: "2025-08-29T10:51:00"
|
// Format current date/time in ISO format: "2025-08-29T10:51:00"
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const requestDateTime = now.toISOString().replace(/\.\d{3}Z$/, ''); // Remove milliseconds and Z
|
const requestDateTime = now.toISOString().replace(/\.\d{3}Z$/, ''); // Remove milliseconds and Z
|
||||||
|
|
||||||
// Build request payload matching SAP API structure
|
// Build request payload matching SAP API structure
|
||||||
const requestPayload = {
|
const requestPayload = {
|
||||||
Request_Date_Time: requestDateTime,
|
Request_Date_Time: requestDateTime,
|
||||||
@ -523,26 +517,26 @@ export class SAPIntegrationService {
|
|||||||
lt_io_output: [],
|
lt_io_output: [],
|
||||||
ls_response: []
|
ls_response: []
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info(`[SAP] Blocking budget for IO ${ioNumber}, Amount: ${amount}, Request: ${requestNumber}`);
|
logger.info(`[SAP] Blocking budget for IO ${ioNumber}, Amount: ${amount}, Request: ${requestNumber}`);
|
||||||
logger.debug(`[SAP] Budget block request payload:`, JSON.stringify(requestPayload, null, 2));
|
logger.debug(`[SAP] Budget block request payload:`, JSON.stringify(requestPayload, null, 2));
|
||||||
|
|
||||||
// Get CSRF token and cookies for POST request (SAP OData requires both)
|
// Get CSRF token and cookies for POST request (SAP OData requires both)
|
||||||
// SAP sets session cookies during CSRF token fetch that must be included in POST
|
// SAP sets session cookies during CSRF token fetch that must be included in POST
|
||||||
const csrfData = await this.getCsrfToken(this.sapBlockServiceName);
|
const csrfData = await this.getCsrfToken(this.sapBlockServiceName);
|
||||||
|
|
||||||
if (!csrfData || !csrfData.csrfToken) {
|
if (!csrfData || !csrfData.csrfToken) {
|
||||||
logger.warn('[SAP] CSRF token not available, request may fail with CSRF validation error');
|
logger.warn('[SAP] CSRF token not available, request may fail with CSRF validation error');
|
||||||
logger.warn('[SAP] This is expected if SAP requires CSRF tokens for POST requests');
|
logger.warn('[SAP] This is expected if SAP requires CSRF tokens for POST requests');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build headers with CSRF token, cookies, and other required headers
|
// Build headers with CSRF token, cookies, and other required headers
|
||||||
// Force JSON format via Accept header (SAP returns XML by default for POST)
|
// Force JSON format via Accept header (SAP returns XML by default for POST)
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Accept': 'application/json, application/atom+xml;q=0.9', // Prefer JSON, fallback to XML
|
'Accept': 'application/json, application/atom+xml;q=0.9', // Prefer JSON, fallback to XML
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add CSRF token if available (required by SAP for POST/PUT/DELETE)
|
// Add CSRF token if available (required by SAP for POST/PUT/DELETE)
|
||||||
// Use lowercase 'x-csrf-token' as per SAP requirement
|
// Use lowercase 'x-csrf-token' as per SAP requirement
|
||||||
if (csrfData?.csrfToken) {
|
if (csrfData?.csrfToken) {
|
||||||
@ -551,7 +545,7 @@ export class SAPIntegrationService {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn('[SAP] CSRF token not available - request may fail with CSRF validation error');
|
logger.warn('[SAP] CSRF token not available - request may fail with CSRF validation error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cookies if available (SAP session cookies required for POST)
|
// Add cookies if available (SAP session cookies required for POST)
|
||||||
// Cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
|
// Cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
|
||||||
if (csrfData?.cookies) {
|
if (csrfData?.cookies) {
|
||||||
@ -560,24 +554,24 @@ export class SAPIntegrationService {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn('[SAP] No session cookies available - request may fail');
|
logger.warn('[SAP] No session cookies available - request may fail');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some SAP systems also require these headers
|
// Some SAP systems also require these headers
|
||||||
headers['X-Requested-With'] = 'XMLHttpRequest';
|
headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
|
||||||
// NOTE: Do NOT add query parameters ($format, sap-client) to POST requests
|
// NOTE: Do NOT add query parameters ($format, sap-client) to POST requests
|
||||||
// SAP OData does not allow SystemQueryOptions in POST requests
|
// SAP OData does not allow SystemQueryOptions in POST requests
|
||||||
// Query parameters are only allowed for GET requests
|
// Query parameters are only allowed for GET requests
|
||||||
// Use the endpoint directly without query parameters
|
// Use the endpoint directly without query parameters
|
||||||
const urlWithParams = endpoint;
|
const urlWithParams = endpoint;
|
||||||
|
|
||||||
logger.debug(`[SAP] POST request URL: ${urlWithParams}`);
|
logger.debug(`[SAP] POST request URL: ${urlWithParams}`);
|
||||||
logger.debug(`[SAP] Request headers (CSRF token and cookies masked):`, {
|
logger.debug(`[SAP] Request headers (CSRF token and cookies masked):`, {
|
||||||
...headers,
|
...headers,
|
||||||
'x-csrf-token': csrfData?.csrfToken ? `${csrfData.csrfToken.substring(0, 10)}...` : 'not set',
|
'x-csrf-token': csrfData?.csrfToken ? `${csrfData.csrfToken.substring(0, 10)}...` : 'not set',
|
||||||
'Cookie': csrfData?.cookies ? `${csrfData.cookies.substring(0, 30)}...` : 'not set'
|
'Cookie': csrfData?.cookies ? `${csrfData.cookies.substring(0, 30)}...` : 'not set'
|
||||||
});
|
});
|
||||||
logger.debug(`[SAP] Using username: ${this.sapUsername}`);
|
logger.debug(`[SAP] Using username: ${this.sapUsername}`);
|
||||||
|
|
||||||
// Ensure auth is explicitly included in POST request config
|
// Ensure auth is explicitly included in POST request config
|
||||||
// The axios instance has auth configured, but we'll include it explicitly to be safe
|
// The axios instance has auth configured, but we'll include it explicitly to be safe
|
||||||
// This ensures auth is sent even if the instance config is overridden
|
// This ensures auth is sent even if the instance config is overridden
|
||||||
@ -593,21 +587,21 @@ export class SAPIntegrationService {
|
|||||||
}) : undefined,
|
}) : undefined,
|
||||||
validateStatus: (status: number) => status < 500 // Don't throw on 4xx
|
validateStatus: (status: number) => status < 500 // Don't throw on 4xx
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug(`[SAP] POST request config prepared (auth included)`);
|
logger.debug(`[SAP] POST request config prepared (auth included)`);
|
||||||
const response = await sapClient.post(urlWithParams, requestPayload, postConfig);
|
const response = await sapClient.post(urlWithParams, requestPayload, postConfig);
|
||||||
|
|
||||||
// Log full response for debugging
|
// Log full response for debugging
|
||||||
logger.info(`[SAP] POST Response Status: ${response.status} ${response.statusText || ''}`);
|
logger.info(`[SAP] POST Response Status: ${response.status} ${response.statusText || ''}`);
|
||||||
logger.info(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2));
|
logger.info(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2));
|
||||||
|
|
||||||
// Check if response is XML (SAP returns XML/Atom by default for POST)
|
// Check if response is XML (SAP returns XML/Atom by default for POST)
|
||||||
const contentType = response.headers['content-type'] || '';
|
const contentType = response.headers['content-type'] || '';
|
||||||
const isXML = contentType.includes('xml') || contentType.includes('atom') ||
|
const isXML = contentType.includes('xml') || contentType.includes('atom') ||
|
||||||
(typeof response.data === 'string' && response.data.trim().startsWith('<'));
|
(typeof response.data === 'string' && response.data.trim().startsWith('<'));
|
||||||
|
|
||||||
let responseData: any = response.data;
|
let responseData: any = response.data;
|
||||||
|
|
||||||
// Parse XML if needed
|
// Parse XML if needed
|
||||||
if (isXML && typeof response.data === 'string') {
|
if (isXML && typeof response.data === 'string') {
|
||||||
logger.info(`[SAP] Response is XML, parsing to JSON...`);
|
logger.info(`[SAP] Response is XML, parsing to JSON...`);
|
||||||
@ -630,7 +624,7 @@ export class SAPIntegrationService {
|
|||||||
// Continue with original data
|
// Continue with original data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log response data summary
|
// Log response data summary
|
||||||
if (responseData) {
|
if (responseData) {
|
||||||
if (responseData.entry) {
|
if (responseData.entry) {
|
||||||
@ -639,11 +633,11 @@ export class SAPIntegrationService {
|
|||||||
logger.info(`[SAP] Response has OData 'd' wrapper`);
|
logger.info(`[SAP] Response has OData 'd' wrapper`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also log the request that was sent
|
// Also log the request that was sent
|
||||||
logger.info(`[SAP] POST Request URL: ${urlWithParams}`);
|
logger.info(`[SAP] POST Request URL: ${urlWithParams}`);
|
||||||
logger.info(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2));
|
logger.info(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2));
|
||||||
|
|
||||||
if (response.status === 200 || response.status === 201) {
|
if (response.status === 200 || response.status === 201) {
|
||||||
// Parse SAP response
|
// Parse SAP response
|
||||||
// Response structure may vary, but typically contains:
|
// Response structure may vary, but typically contains:
|
||||||
@ -651,7 +645,7 @@ export class SAPIntegrationService {
|
|||||||
// - Blocked amount confirmation
|
// - Blocked amount confirmation
|
||||||
// - Remaining balance (in lt_io_output[0].Available_Amount for XML)
|
// - Remaining balance (in lt_io_output[0].Available_Amount for XML)
|
||||||
// - Block ID or reference number
|
// - Block ID or reference number
|
||||||
|
|
||||||
// Helper function to extract remaining balance from various field names
|
// Helper function to extract remaining balance from various field names
|
||||||
// For XML: Available_Amount in lt_io_output[0] (may be prefixed with 'd:' namespace)
|
// For XML: Available_Amount in lt_io_output[0] (may be prefixed with 'd:' namespace)
|
||||||
// For JSON: RemainingBalance, Remaining, Available_Amount, etc.
|
// For JSON: RemainingBalance, Remaining, Available_Amount, etc.
|
||||||
@ -660,43 +654,43 @@ export class SAPIntegrationService {
|
|||||||
logger.debug(`[SAP] extractRemainingBalance: obj is null/undefined`);
|
logger.debug(`[SAP] extractRemainingBalance: obj is null/undefined`);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to extract value from field (handles both direct values and nested #text nodes)
|
// Helper to extract value from field (handles both direct values and nested #text nodes)
|
||||||
const getFieldValue = (fieldName: string): any => {
|
const getFieldValue = (fieldName: string): any => {
|
||||||
const field = obj[fieldName];
|
const field = obj[fieldName];
|
||||||
if (field === undefined || field === null) return null;
|
if (field === undefined || field === null) return null;
|
||||||
|
|
||||||
// If it's an object with #text property (XML parser sometimes does this)
|
// If it's an object with #text property (XML parser sometimes does this)
|
||||||
if (typeof field === 'object' && field['#text'] !== undefined) {
|
if (typeof field === 'object' && field['#text'] !== undefined) {
|
||||||
return field['#text'];
|
return field['#text'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct value
|
// Direct value
|
||||||
return field;
|
return field;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try various field name variations (both JSON and XML formats)
|
// Try various field name variations (both JSON and XML formats)
|
||||||
// XML namespace prefixes: 'd:Available_Amount', 'd:RemainingBalance', etc.
|
// XML namespace prefixes: 'd:Available_Amount', 'd:RemainingBalance', etc.
|
||||||
// IMPORTANT: Check 'd:Available_Amount' first as that's what SAP returns in XML
|
// IMPORTANT: Check 'd:Available_Amount' first as that's what SAP returns in XML
|
||||||
// Also check without namespace prefix as parser might strip it
|
// Also check without namespace prefix as parser might strip it
|
||||||
const value = getFieldValue('d:Available_Amount') ?? // XML format with namespace prefix (PRIORITY)
|
const value = getFieldValue('d:Available_Amount') ?? // XML format with namespace prefix (PRIORITY)
|
||||||
getFieldValue('Available_Amount') ?? // XML format without prefix (parser might strip 'd:')
|
getFieldValue('Available_Amount') ?? // XML format without prefix (parser might strip 'd:')
|
||||||
getFieldValue('d:AvailableAmount') ?? // CamelCase variation with prefix
|
getFieldValue('d:AvailableAmount') ?? // CamelCase variation with prefix
|
||||||
getFieldValue('AvailableAmount') ?? // CamelCase variation without prefix
|
getFieldValue('AvailableAmount') ?? // CamelCase variation without prefix
|
||||||
getFieldValue('d:RemainingBalance') ??
|
getFieldValue('d:RemainingBalance') ??
|
||||||
getFieldValue('RemainingBalance') ??
|
getFieldValue('RemainingBalance') ??
|
||||||
getFieldValue('RemainingAmount') ??
|
getFieldValue('RemainingAmount') ??
|
||||||
getFieldValue('Remaining') ??
|
getFieldValue('Remaining') ??
|
||||||
getFieldValue('AvailableBalance') ??
|
getFieldValue('AvailableBalance') ??
|
||||||
getFieldValue('Balance') ??
|
getFieldValue('Balance') ??
|
||||||
getFieldValue('Available') ??
|
getFieldValue('Available') ??
|
||||||
null;
|
null;
|
||||||
|
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
logger.debug(`[SAP] extractRemainingBalance: No value found. Object keys:`, Object.keys(obj));
|
logger.debug(`[SAP] extractRemainingBalance: No value found. Object keys:`, Object.keys(obj));
|
||||||
// Log all keys that might be relevant
|
// Log all keys that might be relevant
|
||||||
const relevantKeys = Object.keys(obj).filter(k =>
|
const relevantKeys = Object.keys(obj).filter(k =>
|
||||||
k.toLowerCase().includes('available') ||
|
k.toLowerCase().includes('available') ||
|
||||||
k.toLowerCase().includes('amount') ||
|
k.toLowerCase().includes('amount') ||
|
||||||
k.toLowerCase().includes('remaining') ||
|
k.toLowerCase().includes('remaining') ||
|
||||||
k.toLowerCase().includes('balance')
|
k.toLowerCase().includes('balance')
|
||||||
@ -706,47 +700,47 @@ export class SAPIntegrationService {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to string first, then parse (handles both string "14291525.00" and number)
|
// Convert to string first, then parse (handles both string "14291525.00" and number)
|
||||||
const valueStr = value?.toString().trim() || '0';
|
const valueStr = value?.toString().trim() || '0';
|
||||||
const parsed = parseFloat(valueStr);
|
const parsed = parseFloat(valueStr);
|
||||||
|
|
||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
logger.warn(`[SAP] extractRemainingBalance: Failed to parse value "${valueStr}" as number`);
|
logger.warn(`[SAP] extractRemainingBalance: Failed to parse value "${valueStr}" as number`);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`[SAP] extractRemainingBalance: Extracted value "${valueStr}" -> ${parsed}`);
|
logger.debug(`[SAP] extractRemainingBalance: Extracted value "${valueStr}" -> ${parsed}`);
|
||||||
return parsed;
|
return parsed;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to extract blocked amount
|
// Helper function to extract blocked amount
|
||||||
const extractBlockedAmount = (obj: any): number => {
|
const extractBlockedAmount = (obj: any): number => {
|
||||||
if (!obj) return amount;
|
if (!obj) return amount;
|
||||||
|
|
||||||
const value = obj.BlockedAmount ||
|
const value = obj.BlockedAmount ||
|
||||||
obj.Amount ||
|
obj.Amount ||
|
||||||
obj.Blocked ||
|
obj.Blocked ||
|
||||||
amount.toString();
|
amount.toString();
|
||||||
|
|
||||||
const parsed = parseFloat(value?.toString() || amount.toString());
|
const parsed = parseFloat(value?.toString() || amount.toString());
|
||||||
return isNaN(parsed) ? amount : parsed;
|
return isNaN(parsed) ? amount : parsed;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle different possible response structures
|
// Handle different possible response structures
|
||||||
let success = false;
|
let success = false;
|
||||||
let blockedAmount = amount;
|
let blockedAmount = amount;
|
||||||
let remainingBalance = 0;
|
let remainingBalance = 0;
|
||||||
let blockId: string | undefined;
|
let blockId: string | undefined;
|
||||||
|
|
||||||
// Parse XML structure: entry -> link[@rel='lt_io_output'] -> inline -> feed -> entry -> content -> properties
|
// Parse XML structure: entry -> link[@rel='lt_io_output'] -> inline -> feed -> entry -> content -> properties
|
||||||
// Or JSON structure: { d: {...} } or { lt_io_output: [...] }
|
// Or JSON structure: { d: {...} } or { lt_io_output: [...] }
|
||||||
|
|
||||||
// Check for XML structure first (parsed XML from fast-xml-parser)
|
// Check for XML structure first (parsed XML from fast-xml-parser)
|
||||||
let ioOutputData: any = null;
|
let ioOutputData: any = null;
|
||||||
let message = '';
|
let message = '';
|
||||||
let mainEntryProperties: any = null;
|
let mainEntryProperties: any = null;
|
||||||
|
|
||||||
// XML structure: entry.link (array) -> find link with @rel='lt_io_output' -> inline.feed.entry
|
// XML structure: entry.link (array) -> find link with @rel='lt_io_output' -> inline.feed.entry
|
||||||
// OR JSON OData format: { d: { lt_io_output: { results: [...] } } }
|
// OR JSON OData format: { d: { lt_io_output: { results: [...] } } }
|
||||||
if (responseData.d) {
|
if (responseData.d) {
|
||||||
@ -779,7 +773,7 @@ export class SAPIntegrationService {
|
|||||||
responseData = responseData.d;
|
responseData = responseData.d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes XML parser might create the root element with a different name
|
// Sometimes XML parser might create the root element with a different name
|
||||||
// Check if responseData itself IS the entry (if root element was <entry>)
|
// Check if responseData itself IS the entry (if root element was <entry>)
|
||||||
let actualEntry = responseData.entry;
|
let actualEntry = responseData.entry;
|
||||||
@ -789,25 +783,25 @@ export class SAPIntegrationService {
|
|||||||
actualEntry = responseData;
|
actualEntry = responseData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if responseData might be an array (sometimes XML parser returns arrays)
|
// Check if responseData might be an array (sometimes XML parser returns arrays)
|
||||||
if (Array.isArray(responseData) && responseData.length > 0 && responseData[0]?.entry) {
|
if (Array.isArray(responseData) && responseData.length > 0 && responseData[0]?.entry) {
|
||||||
responseData = responseData[0];
|
responseData = responseData[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use actualEntry if we found it, otherwise try responseData.entry
|
// Use actualEntry if we found it, otherwise try responseData.entry
|
||||||
const entry = actualEntry || responseData.entry;
|
const entry = actualEntry || responseData.entry;
|
||||||
|
|
||||||
if (entry && !ioOutputData) {
|
if (entry && !ioOutputData) {
|
||||||
|
|
||||||
// Also check main entry properties (sometimes Available_Amount is here)
|
// Also check main entry properties (sometimes Available_Amount is here)
|
||||||
const mainContent = entry.content || {};
|
const mainContent = entry.content || {};
|
||||||
mainEntryProperties = mainContent['m:properties'] || mainContent.properties || (mainContent['@_type'] === 'application/xml' ? mainContent : null);
|
mainEntryProperties = mainContent['m:properties'] || mainContent.properties || (mainContent['@_type'] === 'application/xml' ? mainContent : null);
|
||||||
|
|
||||||
if (mainEntryProperties) {
|
if (mainEntryProperties) {
|
||||||
logger.info(`[SAP] Found main entry properties, keys:`, Object.keys(mainEntryProperties));
|
logger.info(`[SAP] Found main entry properties, keys:`, Object.keys(mainEntryProperties));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find lt_io_output link in XML structure
|
// Find lt_io_output link in XML structure
|
||||||
const links = Array.isArray(entry.link) ? entry.link : (entry.link ? [entry.link] : []);
|
const links = Array.isArray(entry.link) ? entry.link : (entry.link ? [entry.link] : []);
|
||||||
const ioOutputLink = links.find((link: any) => {
|
const ioOutputLink = links.find((link: any) => {
|
||||||
@ -815,15 +809,15 @@ export class SAPIntegrationService {
|
|||||||
const title = link['@_title'] || link.title || '';
|
const title = link['@_title'] || link.title || '';
|
||||||
return rel.includes('lt_io_output') || title === 'IOOutputSet' || title === 'lt_io_output';
|
return rel.includes('lt_io_output') || title === 'IOOutputSet' || title === 'lt_io_output';
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ioOutputLink?.inline?.feed?.entry) {
|
if (ioOutputLink?.inline?.feed?.entry) {
|
||||||
const ioEntry = Array.isArray(ioOutputLink.inline.feed.entry)
|
const ioEntry = Array.isArray(ioOutputLink.inline.feed.entry)
|
||||||
? ioOutputLink.inline.feed.entry[0]
|
? ioOutputLink.inline.feed.entry[0]
|
||||||
: ioOutputLink.inline.feed.entry;
|
: ioOutputLink.inline.feed.entry;
|
||||||
|
|
||||||
const content = ioEntry.content || {};
|
const content = ioEntry.content || {};
|
||||||
const properties = content['m:properties'] || content.properties || (content['@_type'] === 'application/xml' ? content : null);
|
const properties = content['m:properties'] || content.properties || (content['@_type'] === 'application/xml' ? content : null);
|
||||||
|
|
||||||
if (properties) {
|
if (properties) {
|
||||||
ioOutputData = properties;
|
ioOutputData = properties;
|
||||||
message = ioOutputData['d:Message'] || ioOutputData.Message || ioOutputData['#text'] || '';
|
message = ioOutputData['d:Message'] || ioOutputData.Message || ioOutputData['#text'] || '';
|
||||||
@ -831,7 +825,7 @@ export class SAPIntegrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract data from ioOutputData (already extracted above for both XML and JSON formats)
|
// Extract data from ioOutputData (already extracted above for both XML and JSON formats)
|
||||||
if (ioOutputData) {
|
if (ioOutputData) {
|
||||||
// XML parsed structure - extract from lt_io_output properties
|
// XML parsed structure - extract from lt_io_output properties
|
||||||
@ -839,7 +833,7 @@ export class SAPIntegrationService {
|
|||||||
success = message.includes('Successful') || message.includes('Success') || !message.includes('Error');
|
success = message.includes('Successful') || message.includes('Success') || !message.includes('Error');
|
||||||
blockedAmount = amount; // Use the amount we sent (from lt_io_input)
|
blockedAmount = amount; // Use the amount we sent (from lt_io_input)
|
||||||
remainingBalance = extractRemainingBalance(ioOutputData); // Available_Amount from XML
|
remainingBalance = extractRemainingBalance(ioOutputData); // Available_Amount from XML
|
||||||
|
|
||||||
// If not found in lt_io_output, try main entry properties
|
// If not found in lt_io_output, try main entry properties
|
||||||
if (remainingBalance === 0 && mainEntryProperties) {
|
if (remainingBalance === 0 && mainEntryProperties) {
|
||||||
logger.info(`[SAP] Available_Amount not found in lt_io_output, trying main entry properties`);
|
logger.info(`[SAP] Available_Amount not found in lt_io_output, trying main entry properties`);
|
||||||
@ -848,51 +842,51 @@ export class SAPIntegrationService {
|
|||||||
logger.info(`[SAP] Found Available_Amount in main entry properties: ${remainingBalance}`);
|
logger.info(`[SAP] Found Available_Amount in main entry properties: ${remainingBalance}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to extract SAP reference number (similar to extractRemainingBalance)
|
// Helper function to extract SAP reference number (similar to extractRemainingBalance)
|
||||||
const extractSapReference = (obj: any): string | undefined => {
|
const extractSapReference = (obj: any): string | undefined => {
|
||||||
if (!obj) return undefined;
|
if (!obj) return undefined;
|
||||||
|
|
||||||
const getFieldValue = (fieldName: string): any => {
|
const getFieldValue = (fieldName: string): any => {
|
||||||
const field = obj[fieldName];
|
const field = obj[fieldName];
|
||||||
if (field === undefined || field === null) return null;
|
if (field === undefined || field === null) return null;
|
||||||
|
|
||||||
// If it's an object with #text property (XML parser sometimes does this)
|
// If it's an object with #text property (XML parser sometimes does this)
|
||||||
if (typeof field === 'object' && field['#text'] !== undefined) {
|
if (typeof field === 'object' && field['#text'] !== undefined) {
|
||||||
return field['#text'];
|
return field['#text'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct value
|
// Direct value
|
||||||
return field;
|
return field;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try various field name variations for SAP reference number
|
// Try various field name variations for SAP reference number
|
||||||
const value = getFieldValue('d:Sap_Reference_no') ?? // XML format with namespace prefix (PRIORITY)
|
const value = getFieldValue('d:Sap_Reference_no') ?? // XML format with namespace prefix (PRIORITY)
|
||||||
getFieldValue('Sap_Reference_no') ?? // XML format without prefix
|
getFieldValue('Sap_Reference_no') ?? // XML format without prefix
|
||||||
getFieldValue('d:SapReferenceNo') ??
|
getFieldValue('d:SapReferenceNo') ??
|
||||||
getFieldValue('SapReferenceNo') ??
|
getFieldValue('SapReferenceNo') ??
|
||||||
getFieldValue('d:Reference') ??
|
getFieldValue('d:Reference') ??
|
||||||
getFieldValue('Reference') ??
|
getFieldValue('Reference') ??
|
||||||
getFieldValue('d:BlockId') ??
|
getFieldValue('d:BlockId') ??
|
||||||
getFieldValue('BlockId') ??
|
getFieldValue('BlockId') ??
|
||||||
getFieldValue('d:DocumentNumber') ??
|
getFieldValue('d:DocumentNumber') ??
|
||||||
getFieldValue('DocumentNumber') ??
|
getFieldValue('DocumentNumber') ??
|
||||||
null;
|
null;
|
||||||
|
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
logger.debug(`[SAP] extractSapReference: No value found. Object keys:`, Object.keys(obj));
|
logger.debug(`[SAP] extractSapReference: No value found. Object keys:`, Object.keys(obj));
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to string and trim
|
// Convert to string and trim
|
||||||
const valueStr = String(value).trim();
|
const valueStr = String(value).trim();
|
||||||
logger.debug(`[SAP] extractSapReference: Extracted value "${valueStr}"`);
|
logger.debug(`[SAP] extractSapReference: Extracted value "${valueStr}"`);
|
||||||
return valueStr || undefined;
|
return valueStr || undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract SAP reference number using helper function
|
// Extract SAP reference number using helper function
|
||||||
blockId = extractSapReference(ioOutputData) || extractSapReference(mainEntryProperties) || undefined;
|
blockId = extractSapReference(ioOutputData) || extractSapReference(mainEntryProperties) || undefined;
|
||||||
|
|
||||||
// Log detailed information for debugging
|
// Log detailed information for debugging
|
||||||
logger.info(`[SAP] Extracted from XML lt_io_output:`, {
|
logger.info(`[SAP] Extracted from XML lt_io_output:`, {
|
||||||
message,
|
message,
|
||||||
@ -902,10 +896,10 @@ export class SAPIntegrationService {
|
|||||||
sampleKeys: Object.keys(ioOutputData).slice(0, 10), // First 10 keys for debugging
|
sampleKeys: Object.keys(ioOutputData).slice(0, 10), // First 10 keys for debugging
|
||||||
foundInMainEntry: remainingBalance > 0 && mainEntryProperties ? true : false,
|
foundInMainEntry: remainingBalance > 0 && mainEntryProperties ? true : false,
|
||||||
ioOutputDataSample: Object.keys(ioOutputData).reduce((acc: any, key: string) => {
|
ioOutputDataSample: Object.keys(ioOutputData).reduce((acc: any, key: string) => {
|
||||||
if (key.toLowerCase().includes('available') ||
|
if (key.toLowerCase().includes('available') ||
|
||||||
key.toLowerCase().includes('amount') ||
|
key.toLowerCase().includes('amount') ||
|
||||||
key.toLowerCase().includes('reference') ||
|
key.toLowerCase().includes('reference') ||
|
||||||
key.toLowerCase().includes('sap')) {
|
key.toLowerCase().includes('sap')) {
|
||||||
acc[key] = ioOutputData[key];
|
acc[key] = ioOutputData[key];
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
@ -939,7 +933,7 @@ export class SAPIntegrationService {
|
|||||||
logger.warn('[SAP] Budget block response structure unclear, assuming success');
|
logger.warn('[SAP] Budget block response structure unclear, assuming success');
|
||||||
logger.warn('[SAP] Response data keys:', Object.keys(responseData || {}));
|
logger.warn('[SAP] Response data keys:', Object.keys(responseData || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log what we extracted
|
// Log what we extracted
|
||||||
logger.info(`[SAP] Extracted from response:`, {
|
logger.info(`[SAP] Extracted from response:`, {
|
||||||
success,
|
success,
|
||||||
@ -953,7 +947,7 @@ export class SAPIntegrationService {
|
|||||||
hasMainEntryProperties: !!mainEntryProperties,
|
hasMainEntryProperties: !!mainEntryProperties,
|
||||||
mainEntryPropertiesKeys: mainEntryProperties ? Object.keys(mainEntryProperties) : null
|
mainEntryPropertiesKeys: mainEntryProperties ? Object.keys(mainEntryProperties) : null
|
||||||
});
|
});
|
||||||
|
|
||||||
// If ioOutputData exists but we didn't extract values, log detailed info
|
// If ioOutputData exists but we didn't extract values, log detailed info
|
||||||
if (ioOutputData && (remainingBalance === 0 || !blockId)) {
|
if (ioOutputData && (remainingBalance === 0 || !blockId)) {
|
||||||
logger.warn(`[SAP] ⚠️ ioOutputData exists but extraction failed. Full ioOutputData:`, JSON.stringify(ioOutputData, null, 2));
|
logger.warn(`[SAP] ⚠️ ioOutputData exists but extraction failed. Full ioOutputData:`, JSON.stringify(ioOutputData, null, 2));
|
||||||
@ -963,7 +957,7 @@ export class SAPIntegrationService {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {}));
|
}, {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If remaining balance is 0, log the full response structure for debugging
|
// If remaining balance is 0, log the full response structure for debugging
|
||||||
if (remainingBalance === 0 && response.status === 200 || response.status === 201) {
|
if (remainingBalance === 0 && response.status === 200 || response.status === 201) {
|
||||||
logger.warn(`[SAP] ⚠️ Remaining balance is 0, but request was successful. Full response structure:`, JSON.stringify(responseData, null, 2));
|
logger.warn(`[SAP] ⚠️ Remaining balance is 0, but request was successful. Full response structure:`, JSON.stringify(responseData, null, 2));
|
||||||
@ -971,7 +965,7 @@ export class SAPIntegrationService {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
logger.info(`[SAP] Budget blocked successfully for IO ${ioNumber}. Blocked: ${blockedAmount}, Remaining: ${remainingBalance}`);
|
logger.info(`[SAP] Budget blocked successfully for IO ${ioNumber}. Blocked: ${blockedAmount}, Remaining: ${remainingBalance}`);
|
||||||
|
|
||||||
// Only return blockId if SAP provided a reference number
|
// Only return blockId if SAP provided a reference number
|
||||||
// Don't generate a fallback - we want the actual SAP document number
|
// Don't generate a fallback - we want the actual SAP document number
|
||||||
if (blockId) {
|
if (blockId) {
|
||||||
@ -979,7 +973,7 @@ export class SAPIntegrationService {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn(`[SAP] ⚠️ No SAP Reference Number (Sap_Reference_no) found in response`);
|
logger.warn(`[SAP] ⚠️ No SAP Reference Number (Sap_Reference_no) found in response`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
blockId: blockId || undefined, // Only return actual SAP reference number, no fallback
|
blockId: blockId || undefined, // Only return actual SAP reference number, no fallback
|
||||||
@ -1000,7 +994,7 @@ export class SAPIntegrationService {
|
|||||||
logger.error(`[SAP] Authentication failed during budget blocking (Status: ${response.status}) - check SAP credentials`);
|
logger.error(`[SAP] Authentication failed during budget blocking (Status: ${response.status}) - check SAP credentials`);
|
||||||
logger.error(`[SAP] Response data:`, response.data);
|
logger.error(`[SAP] Response data:`, response.data);
|
||||||
logger.error(`[SAP] Response headers:`, response.headers);
|
logger.error(`[SAP] Response headers:`, response.headers);
|
||||||
|
|
||||||
// Check if it's actually a CSRF error disguised as auth error
|
// Check if it's actually a CSRF error disguised as auth error
|
||||||
const responseText = JSON.stringify(response.data || {});
|
const responseText = JSON.stringify(response.data || {});
|
||||||
if (responseText.includes('CSRF') || responseText.includes('csrf') || responseText.includes('token')) {
|
if (responseText.includes('CSRF') || responseText.includes('csrf') || responseText.includes('token')) {
|
||||||
@ -1012,7 +1006,7 @@ export class SAPIntegrationService {
|
|||||||
error: 'SAP CSRF token validation failed - token may have expired or be invalid'
|
error: 'SAP CSRF token validation failed - token may have expired or be invalid'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
blockedAmount: 0,
|
blockedAmount: 0,
|
||||||
@ -1022,15 +1016,15 @@ export class SAPIntegrationService {
|
|||||||
} else {
|
} else {
|
||||||
// Handle 400 Bad Request - usually means invalid request format
|
// Handle 400 Bad Request - usually means invalid request format
|
||||||
let errorMessage = `SAP API returned status ${response.status}`;
|
let errorMessage = `SAP API returned status ${response.status}`;
|
||||||
|
|
||||||
if (response.status === 400) {
|
if (response.status === 400) {
|
||||||
errorMessage = 'SAP API returned 400 Bad Request - check request payload format';
|
errorMessage = 'SAP API returned 400 Bad Request - check request payload format';
|
||||||
|
|
||||||
// Try to extract error message from response
|
// Try to extract error message from response
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
try {
|
try {
|
||||||
const errorData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
|
const errorData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
|
||||||
|
|
||||||
// Check for common SAP error fields
|
// Check for common SAP error fields
|
||||||
if (errorData.error) {
|
if (errorData.error) {
|
||||||
errorMessage = errorData.error.message?.value || errorData.error.message || errorMessage;
|
errorMessage = errorData.error.message?.value || errorData.error.message || errorMessage;
|
||||||
@ -1041,14 +1035,14 @@ export class SAPIntegrationService {
|
|||||||
} else if (errorData.d?.error) {
|
} else if (errorData.d?.error) {
|
||||||
errorMessage = errorData.d.error.message?.value || errorData.d.error.message || errorMessage;
|
errorMessage = errorData.d.error.message?.value || errorData.d.error.message || errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error(`[SAP] SAP Error Details:`, JSON.stringify(errorData, null, 2));
|
logger.error(`[SAP] SAP Error Details:`, JSON.stringify(errorData, null, 2));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`[SAP] Error parsing response data:`, response.data);
|
logger.error(`[SAP] Error parsing response data:`, response.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.error(`[SAP] Unexpected response status during budget blocking: ${response.status}`);
|
logger.error(`[SAP] Unexpected response status during budget blocking: ${response.status}`);
|
||||||
logger.error(`[SAP] Response data:`, response.data);
|
logger.error(`[SAP] Response data:`, response.data);
|
||||||
return {
|
return {
|
||||||
@ -1060,7 +1054,7 @@ export class SAPIntegrationService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
|
|
||||||
if (axiosError.response) {
|
if (axiosError.response) {
|
||||||
// SAP returned an error response
|
// SAP returned an error response
|
||||||
logger.error(`[SAP] Error blocking budget for IO ${ioNumber}:`, {
|
logger.error(`[SAP] Error blocking budget for IO ${ioNumber}:`, {
|
||||||
@ -1068,7 +1062,7 @@ export class SAPIntegrationService {
|
|||||||
statusText: axiosError.response.statusText,
|
statusText: axiosError.response.statusText,
|
||||||
data: axiosError.response.data
|
data: axiosError.response.data
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
blockedAmount: 0,
|
blockedAmount: 0,
|
||||||
@ -1123,7 +1117,22 @@ export class SAPIntegrationService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement actual SAP API call to release budget
|
||||||
|
// Example:
|
||||||
|
// const response = await axios.post(`${this.sapBaseUrl}/api/io/${ioNumber}/release`, {
|
||||||
|
// block_id: blockId,
|
||||||
|
// reference: requestNumber
|
||||||
|
// }, {
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': `Bearer ${this.sapApiKey}`,
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// success: response.data.success,
|
||||||
|
// releasedAmount: response.data.released_amount
|
||||||
|
// };
|
||||||
|
|
||||||
logger.warn('[SAP] SAP budget release not implemented, simulating release');
|
logger.warn('[SAP] SAP budget release not implemented, simulating release');
|
||||||
return {
|
return {
|
||||||
@ -1168,7 +1177,23 @@ export class SAPIntegrationService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Implement actual SAP API call to get dealer info
|
||||||
|
// Example:
|
||||||
|
// const response = await axios.get(`${this.sapBaseUrl}/api/dealers/${dealerCode}`, {
|
||||||
|
// headers: {
|
||||||
|
// 'Authorization': `Bearer ${this.sapApiKey}`,
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// isValid: response.data.valid,
|
||||||
|
// dealerCode: response.data.dealer_code,
|
||||||
|
// dealerName: response.data.dealer_name,
|
||||||
|
// dealerEmail: response.data.dealer_email,
|
||||||
|
// dealerPhone: response.data.dealer_phone,
|
||||||
|
// dealerAddress: response.data.dealer_address
|
||||||
|
// };
|
||||||
|
|
||||||
logger.warn('[SAP] SAP dealer lookup not implemented, returning mock data');
|
logger.warn('[SAP] SAP dealer lookup not implemented, returning mock data');
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -2459,7 +2459,6 @@ export class WorkflowService {
|
|||||||
requestNumber,
|
requestNumber,
|
||||||
initiatorId,
|
initiatorId,
|
||||||
templateType: workflowData.templateType,
|
templateType: workflowData.templateType,
|
||||||
workflowType: workflowData.workflowType || 'NON_TEMPLATIZED',
|
|
||||||
title: workflowData.title ? sanitizeHtml(workflowData.title) : workflowData.title,
|
title: workflowData.title ? sanitizeHtml(workflowData.title) : workflowData.title,
|
||||||
description: workflowData.description ? sanitizeHtml(workflowData.description) : workflowData.description,
|
description: workflowData.description ? sanitizeHtml(workflowData.description) : workflowData.description,
|
||||||
priority: workflowData.priority,
|
priority: workflowData.priority,
|
||||||
@ -2472,29 +2471,29 @@ export class WorkflowService {
|
|||||||
submissionDate: isDraftRequested ? undefined : now
|
submissionDate: isDraftRequested ? undefined : now
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create approval levels if skipCreation is false
|
// Create approval levels
|
||||||
if (!workflowData.skipCreation) {
|
for (const levelData of workflowData.approvalLevels) {
|
||||||
for (const levelData of workflowData.approvalLevels) {
|
await ApprovalLevel.create({
|
||||||
await ApprovalLevel.create({
|
requestId: workflow.requestId,
|
||||||
requestId: workflow.requestId,
|
levelNumber: levelData.levelNumber,
|
||||||
levelNumber: levelData.levelNumber,
|
levelName: levelData.levelName,
|
||||||
levelName: levelData.levelName,
|
approverId: levelData.approverId,
|
||||||
approverId: levelData.approverId,
|
approverEmail: levelData.approverEmail,
|
||||||
approverEmail: levelData.approverEmail,
|
approverName: levelData.approverName,
|
||||||
approverName: levelData.approverName,
|
tatHours: levelData.tatHours,
|
||||||
tatHours: levelData.tatHours,
|
// tatDays is auto-calculated by database as a generated column
|
||||||
// tatDays is auto-calculated by database as a generated column
|
status: ApprovalStatus.PENDING,
|
||||||
status: ApprovalStatus.PENDING,
|
elapsedHours: 0,
|
||||||
elapsedHours: 0,
|
remainingHours: levelData.tatHours,
|
||||||
remainingHours: levelData.tatHours,
|
tatPercentageUsed: 0,
|
||||||
tatPercentageUsed: 0,
|
isFinalApprover: levelData.isFinalApprover || false
|
||||||
isFinalApprover: levelData.isFinalApprover || false
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create participants if provided and skipCreation is false
|
// Create participants if provided
|
||||||
if (workflowData.participants && !workflowData.skipCreation) {
|
// Deduplicate participants by userId (database has unique constraint on request_id + user_id)
|
||||||
|
// Priority: INITIATOR > APPROVER > SPECTATOR (keep the highest privilege role)
|
||||||
|
if (workflowData.participants) {
|
||||||
const participantMap = new Map<string, typeof workflowData.participants[0]>();
|
const participantMap = new Map<string, typeof workflowData.participants[0]>();
|
||||||
const rolePriority: Record<string, number> = {
|
const rolePriority: Record<string, number> = {
|
||||||
'INITIATOR': 3,
|
'INITIATOR': 3,
|
||||||
@ -2569,15 +2568,13 @@ export class WorkflowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
logWithContext('error', 'Failed to create workflow', {
|
logWithContext('error', 'Failed to create workflow', {
|
||||||
userId: initiatorId,
|
userId: initiatorId,
|
||||||
priority: workflowData.priority,
|
priority: workflowData.priority,
|
||||||
errorMessage: error.message,
|
error,
|
||||||
errorStack: error.stack,
|
|
||||||
error: error, // Keep for full object in some loggers
|
|
||||||
});
|
});
|
||||||
throw new Error(`Failed to create workflow: ${error.message}`);
|
throw new Error('Failed to create workflow');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,7 @@ export interface WorkflowRequest {
|
|||||||
requestId: string;
|
requestId: string;
|
||||||
requestNumber: string;
|
requestNumber: string;
|
||||||
initiatorId: string;
|
initiatorId: string;
|
||||||
templateType: 'CUSTOM' | 'TEMPLATE' | 'DEALER CLAIM';
|
templateType: 'CUSTOM' | 'TEMPLATE';
|
||||||
workflowType?: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
priority: Priority;
|
priority: Priority;
|
||||||
@ -24,15 +23,13 @@ export interface WorkflowRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateWorkflowRequest {
|
export interface CreateWorkflowRequest {
|
||||||
templateType: 'CUSTOM' | 'TEMPLATE' | 'DEALER CLAIM';
|
templateType: 'CUSTOM' | 'TEMPLATE';
|
||||||
workflowType?: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
priority: Priority;
|
priority: Priority;
|
||||||
approvalLevels: CreateApprovalLevel[];
|
approvalLevels: CreateApprovalLevel[];
|
||||||
participants?: CreateParticipant[];
|
participants?: CreateParticipant[];
|
||||||
isDraft?: boolean;
|
isDraft?: boolean;
|
||||||
skipCreation?: boolean; // Flag to skip record creation if handled manually by calling service
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWorkflowRequest {
|
export interface UpdateWorkflowRequest {
|
||||||
|
|||||||
@ -20,9 +20,8 @@ const SENSITIVE_PATTERN = new RegExp(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Mask sensitive values in strings (API keys, passwords, tokens)
|
* Mask sensitive values in strings (API keys, passwords, tokens)
|
||||||
* Uses a WeakSet to prevent infinite recursion on circular objects
|
|
||||||
*/
|
*/
|
||||||
const maskSensitiveData = (value: any, visited = new WeakSet()): any => {
|
const maskSensitiveData = (value: any): any => {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
// Mask patterns like "API_KEY = abc123" or "password: secret"
|
// Mask patterns like "API_KEY = abc123" or "password: secret"
|
||||||
let masked = value.replace(SENSITIVE_PATTERN, (match, key, val) => {
|
let masked = value.replace(SENSITIVE_PATTERN, (match, key, val) => {
|
||||||
@ -46,24 +45,10 @@ const maskSensitiveData = (value: any, visited = new WeakSet()): any => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (visited.has(value)) return '[Circular]';
|
return value.map(maskSensitiveData);
|
||||||
visited.add(value);
|
|
||||||
return value.map(item => maskSensitiveData(item, visited));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value && typeof value === 'object') {
|
if (value && typeof value === 'object') {
|
||||||
// Special handling for common non-recursive objects to improve performance
|
|
||||||
if (value instanceof Date || value instanceof RegExp) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visited.has(value)) return '[Circular]';
|
|
||||||
visited.add(value);
|
|
||||||
|
|
||||||
// Prevent deep recursion into huge circular objects like Sequelize instances, Request, Response
|
|
||||||
// If it looks like a Sequelize instance or complex object, we might want to be careful
|
|
||||||
// but visited.has() should handle it.
|
|
||||||
|
|
||||||
const masked: any = {};
|
const masked: any = {};
|
||||||
for (const [k, v] of Object.entries(value)) {
|
for (const [k, v] of Object.entries(value)) {
|
||||||
const keyLower = k.toLowerCase();
|
const keyLower = k.toLowerCase();
|
||||||
@ -71,7 +56,7 @@ const maskSensitiveData = (value: any, visited = new WeakSet()): any => {
|
|||||||
if (SENSITIVE_KEYS.some(sk => keyLower.includes(sk))) {
|
if (SENSITIVE_KEYS.some(sk => keyLower.includes(sk))) {
|
||||||
masked[k] = typeof v === 'string' && v.length > 0 ? '***REDACTED***' : v;
|
masked[k] = typeof v === 'string' && v.length > 0 ? '***REDACTED***' : v;
|
||||||
} else {
|
} else {
|
||||||
masked[k] = maskSensitiveData(v, visited);
|
masked[k] = maskSensitiveData(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return masked;
|
return masked;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user