pwc invoice generation implemented and tables enhanced to support envvoice fields
This commit is contained in:
parent
9060c39f9c
commit
81afd7ec96
@ -1,2 +1,2 @@
|
||||
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-D5monZ70.js.map
|
||||
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};
|
||||
//# sourceMappingURL=conclusionApi-BJO_6JLT.js.map
|
||||
@ -1 +1 @@
|
||||
{"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"}
|
||||
{"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"}
|
||||
67
build/assets/index-7F7W4LDI.js
Normal file
67
build/assets/index-7F7W4LDI.js
Normal file
File diff suppressed because one or more lines are too long
1
build/assets/index-7F7W4LDI.js.map
Normal file
1
build/assets/index-7F7W4LDI.js.map
Normal file
File diff suppressed because one or more lines are too long
1
build/assets/index-AUbBsmWB.css
Normal file
1
build/assets/index-AUbBsmWB.css
Normal file
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
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@
|
||||
<!-- Preload critical fonts and icons -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<script type="module" crossorigin src="/assets/index-PI_IMErM.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-7F7W4LDI.js"></script>
|
||||
<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/utils-vendor-DNMmNUQL.js">
|
||||
@ -21,7 +21,7 @@
|
||||
<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/router-vendor-B_rK4TXr.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D5NCgjQR.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-AUbBsmWB.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
71
docs/CLAIM_FINANCIAL_SETTLEMENT_WORKFLOW.md
Normal file
71
docs/CLAIM_FINANCIAL_SETTLEMENT_WORKFLOW.md
Normal file
@ -0,0 +1,71 @@
|
||||
# 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,6 +36,7 @@
|
||||
"pg": "^8.13.1",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"prom-client": "^15.1.3",
|
||||
"puppeteer": "^24.37.2",
|
||||
"sequelize": "^6.37.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"uuid": "^8.3.2",
|
||||
@ -809,7 +810,6 @@
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
@ -981,7 +981,6 @@
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -2879,6 +2878,27 @@
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||
"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": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
@ -3511,6 +3531,12 @@
|
||||
"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": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
@ -4043,6 +4069,16 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
|
||||
@ -4446,7 +4482,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
@ -4493,6 +4528,18 @@
|
||||
"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": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
@ -4544,6 +4591,20 @@
|
||||
"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": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||
@ -4676,6 +4737,97 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"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": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@ -4733,6 +4885,15 @@
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"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": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
@ -4916,6 +5077,15 @@
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
@ -5009,7 +5179,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -5111,6 +5280,19 @@
|
||||
"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": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
@ -5434,6 +5616,32 @@
|
||||
"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": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
|
||||
@ -5559,6 +5767,20 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -5616,6 +5838,12 @@
|
||||
"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": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
@ -5896,11 +6124,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
@ -5979,6 +6215,27 @@
|
||||
"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": {
|
||||
"version": "9.38.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
|
||||
@ -6151,7 +6408,6 @@
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
@ -6191,7 +6447,6 @@
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
@ -6201,7 +6456,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -6225,6 +6479,15 @@
|
||||
"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": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
@ -6357,6 +6620,41 @@
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"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": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@ -6364,6 +6662,12 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
@ -6453,6 +6757,15 @@
|
||||
"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": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
||||
@ -6879,6 +7192,29 @@
|
||||
"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": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@ -7454,7 +7790,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parent-module": "^1.0.0",
|
||||
@ -7555,6 +7890,15 @@
|
||||
"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": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@ -7568,7 +7912,6 @@
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
@ -8402,14 +8745,12 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@ -8451,7 +8792,6 @@
|
||||
"version": "2.3.1",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
@ -8612,7 +8952,6 @@
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
@ -8935,6 +9274,12 @@
|
||||
"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": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
@ -9104,6 +9449,15 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||
@ -9480,6 +9834,51 @@
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@ -9490,7 +9889,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"callsites": "^3.0.0"
|
||||
@ -9503,7 +9901,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
@ -9642,6 +10039,12 @@
|
||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||
"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": {
|
||||
"version": "8.16.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
@ -9747,7 +10150,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@ -9954,6 +10356,15 @@
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"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": {
|
||||
"version": "15.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
|
||||
@ -10037,6 +10448,47 @@
|
||||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@ -10050,6 +10502,16 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@ -10060,6 +10522,45 @@
|
||||
"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": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||
@ -10259,7 +10760,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@ -10756,6 +11256,16 @@
|
||||
"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": {
|
||||
"version": "7.3.3",
|
||||
"resolved": "https://registry.npmjs.org/snappy/-/snappy-7.3.3.tgz",
|
||||
@ -10903,11 +11413,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -11010,6 +11548,17 @@
|
||||
"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": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
@ -11212,6 +11761,31 @@
|
||||
"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": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
|
||||
@ -11314,6 +11888,15 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||
@ -11706,6 +12289,12 @@
|
||||
"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": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
@ -11716,7 +12305,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@ -11940,6 +12529,12 @@
|
||||
"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": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
@ -12105,12 +12700,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@ -12201,6 +12794,16 @@
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"start": "npm run build && npm run setup && npm run start:prod",
|
||||
"start": "npm install && 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:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
|
||||
"build": "tsc && tsc-alias",
|
||||
@ -50,6 +50,7 @@
|
||||
"pg": "^8.13.1",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"prom-client": "^15.1.3",
|
||||
"puppeteer": "^24.37.2",
|
||||
"sequelize": "^6.37.5",
|
||||
"socket.io": "^4.8.1",
|
||||
"uuid": "^8.3.2",
|
||||
|
||||
@ -75,7 +75,7 @@ export class DealerClaimController {
|
||||
logger.warn('[DealerClaimController] Approver validation error:', { message: error.message });
|
||||
return ResponseHandler.error(res, error.message, 400);
|
||||
}
|
||||
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error('[DealerClaimController] Error creating claim request:', error);
|
||||
return ResponseHandler.error(res, 'Failed to create claim request', 500, errorMessage);
|
||||
@ -301,7 +301,7 @@ export class DealerClaimController {
|
||||
try {
|
||||
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||
|
||||
|
||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||
buffer: fileBuffer,
|
||||
originalName: file.originalname,
|
||||
@ -360,7 +360,7 @@ export class DealerClaimController {
|
||||
try {
|
||||
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||
|
||||
|
||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||
buffer: fileBuffer,
|
||||
originalName: file.originalname,
|
||||
@ -420,7 +420,7 @@ export class DealerClaimController {
|
||||
try {
|
||||
const fileBuffer = file.buffer || (file.path ? fs.readFileSync(file.path) : Buffer.from(''));
|
||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||
|
||||
|
||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||
buffer: fileBuffer,
|
||||
originalName: file.originalname,
|
||||
@ -480,7 +480,7 @@ export class DealerClaimController {
|
||||
try {
|
||||
const fileBuffer = attendanceSheetFile.buffer || (attendanceSheetFile.path ? fs.readFileSync(attendanceSheetFile.path) : Buffer.from(''));
|
||||
const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||
|
||||
|
||||
const uploadResult = await gcsStorageService.uploadFileWithFallback({
|
||||
buffer: fileBuffer,
|
||||
originalName: attendanceSheetFile.originalname,
|
||||
@ -561,18 +561,18 @@ export class DealerClaimController {
|
||||
async validateIO(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { ioNumber } = req.query;
|
||||
|
||||
|
||||
if (!ioNumber || typeof ioNumber !== 'string') {
|
||||
return ResponseHandler.error(res, 'IO number is required', 400);
|
||||
}
|
||||
|
||||
// Fetch IO details from SAP (will return mock data until SAP is integrated)
|
||||
const ioValidation = await sapIntegrationService.validateIONumber(ioNumber.trim());
|
||||
|
||||
|
||||
if (!ioValidation.isValid) {
|
||||
return ResponseHandler.error(res, ioValidation.error || 'Invalid IO number', 400);
|
||||
}
|
||||
|
||||
|
||||
return ResponseHandler.success(res, {
|
||||
ioNumber: ioValidation.ioNumber,
|
||||
availableBalance: ioValidation.availableBalance,
|
||||
@ -623,7 +623,7 @@ export class DealerClaimController {
|
||||
}
|
||||
|
||||
const blockAmount = blockedAmount ? parseFloat(blockedAmount) : 0;
|
||||
|
||||
|
||||
// Log received data for debugging
|
||||
logger.info('[DealerClaimController] updateIODetails received:', {
|
||||
requestId,
|
||||
@ -633,7 +633,7 @@ export class DealerClaimController {
|
||||
receivedBlockedAmount: blockedAmount, // Original value from request
|
||||
userId,
|
||||
});
|
||||
|
||||
|
||||
// Store in database when blocking amount > 0 OR when ioNumber and ioRemark are provided (for Step 3 approval)
|
||||
if (blockAmount > 0) {
|
||||
if (availableBalance === undefined) {
|
||||
@ -649,9 +649,9 @@ export class DealerClaimController {
|
||||
blockedAmount: blockAmount,
|
||||
// remainingBalance will be calculated by the service from SAP's response
|
||||
};
|
||||
|
||||
|
||||
logger.info('[DealerClaimController] Calling updateIODetails service with:', ioData);
|
||||
|
||||
|
||||
await this.dealerClaimService.updateIODetails(
|
||||
requestId,
|
||||
ioData,
|
||||
@ -660,7 +660,7 @@ export class DealerClaimController {
|
||||
|
||||
// Fetch and return the updated IO details from database
|
||||
const updatedIO = await InternalOrder.findOne({ where: { requestId } });
|
||||
|
||||
|
||||
if (updatedIO) {
|
||||
return ResponseHandler.success(res, {
|
||||
message: 'IO blocked successfully in SAP',
|
||||
@ -755,6 +755,64 @@ 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)
|
||||
* PUT /api/v1/dealer-claims/:requestId/credit-note
|
||||
@ -875,7 +933,7 @@ export class DealerClaimController {
|
||||
|
||||
// First validate IO number
|
||||
const ioValidation = await sapIntegrationService.validateIONumber(ioNumber);
|
||||
|
||||
|
||||
if (!ioValidation.isValid) {
|
||||
return ResponseHandler.error(res, `Invalid IO number: ${ioValidation.error || 'IO number not found in SAP'}`, 400);
|
||||
}
|
||||
|
||||
17
src/migrations/20260210-add-raw-pwc-responses.ts
Normal file
17
src/migrations/20260210-add-raw-pwc-responses.ts
Normal file
@ -0,0 +1,17 @@
|
||||
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,6 +36,8 @@ interface ClaimInvoiceAttributes {
|
||||
filePath?: string | null;
|
||||
qrCode?: string | null;
|
||||
qrImage?: string | null;
|
||||
pwcResponse?: any;
|
||||
irpResponse?: any;
|
||||
errorMessage?: string;
|
||||
generatedAt?: Date;
|
||||
description?: string;
|
||||
@ -43,7 +45,7 @@ interface ClaimInvoiceAttributes {
|
||||
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' | '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' | 'pwcResponse' | 'irpResponse' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> { }
|
||||
|
||||
class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAttributes> implements ClaimInvoiceAttributes {
|
||||
public invoiceId!: string;
|
||||
@ -79,6 +81,8 @@ class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAtt
|
||||
public filePath?: string | null;
|
||||
public qrCode?: string | null;
|
||||
public qrImage?: string | null;
|
||||
public pwcResponse?: any;
|
||||
public irpResponse?: any;
|
||||
public errorMessage?: string;
|
||||
public generatedAt?: Date;
|
||||
public description?: string;
|
||||
@ -261,6 +265,16 @@ ClaimInvoice.init(
|
||||
allowNull: true,
|
||||
field: 'qr_image'
|
||||
},
|
||||
pwcResponse: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'pwc_response'
|
||||
},
|
||||
irpResponse: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'irp_response'
|
||||
},
|
||||
errorMessage: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
|
||||
@ -87,6 +87,7 @@ router.put('/:requestId/io', authenticateToken, asyncHandler(dealerClaimControll
|
||||
* @access Private
|
||||
*/
|
||||
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
|
||||
|
||||
@ -158,6 +158,7 @@ async function runMigrations(): Promise<void> {
|
||||
const m43 = require('../migrations/20260113-redesign-dealer-claim-history');
|
||||
const m44 = require('../migrations/20260123-fix-template-id-schema');
|
||||
const m45 = require('../migrations/20260209-add-gst-and-pwc-fields');
|
||||
const m46 = require('../migrations/20260210-add-raw-pwc-responses');
|
||||
|
||||
const migrations = [
|
||||
{ name: '2025103000-create-users', module: m0 },
|
||||
@ -208,6 +209,7 @@ async function runMigrations(): Promise<void> {
|
||||
{ name: '20260113-redesign-dealer-claim-history', module: m43 },
|
||||
{ name: '20260123-fix-template-id-schema', module: m44 },
|
||||
{ name: '20260209-add-gst-and-pwc-fields', module: m45 },
|
||||
{ name: '20260210-add-raw-pwc-responses', module: m46 },
|
||||
];
|
||||
|
||||
// Dynamically import sequelize after secrets are loaded
|
||||
|
||||
@ -24,27 +24,27 @@ async function generateUniqueCode(
|
||||
): Promise<string> {
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100;
|
||||
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
// Generate random 4-digit number (1000-9999)
|
||||
const randomCode = String(Math.floor(1000 + Math.random() * 9000));
|
||||
|
||||
|
||||
// Check if code already exists in database
|
||||
const existing = await Dealer.findOne({
|
||||
where: {
|
||||
[field]: randomCode
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Also check if we've already generated this code in this run
|
||||
if (!existing && !existingCodes.has(randomCode)) {
|
||||
existingCodes.add(randomCode);
|
||||
return randomCode;
|
||||
}
|
||||
|
||||
|
||||
attempts++;
|
||||
}
|
||||
|
||||
|
||||
// Fallback: use timestamp-based code if random generation fails
|
||||
const timestampCode = String(Date.now()).slice(-4);
|
||||
logger.warn(`[Seed Test Dealer] Using timestamp-based code for ${field}: ${timestampCode}`);
|
||||
@ -104,18 +104,18 @@ async function seedTestDealer(): Promise<void> {
|
||||
branchDetails: null,
|
||||
dealerPrincipalName: 'TEST REFLOW',
|
||||
dealerPrincipalEmailId: 'testreflow@example.com',
|
||||
dpContactNumber: null,
|
||||
dpContacts: null,
|
||||
showroomAddress: null,
|
||||
showroomPincode: null,
|
||||
workshopAddress: null,
|
||||
workshopPincode: null,
|
||||
locationDistrict: null,
|
||||
stateWorkshop: null,
|
||||
dpContactNumber: '9998887776',
|
||||
dpContacts: 'TEST CONTACT',
|
||||
showroomAddress: 'No. 335, RE Test Road, Bengaluru - 560098, Karnataka',
|
||||
showroomPincode: '560098',
|
||||
workshopAddress: 'Workshop Area B, Test Location',
|
||||
workshopPincode: '560098',
|
||||
locationDistrict: 'Bangalore',
|
||||
stateWorkshop: 'Karnataka',
|
||||
noOfStudios: 0,
|
||||
websiteUpdate: 'Yes',
|
||||
gst: null,
|
||||
pan: null,
|
||||
gst: '29AAACE3882D1ZZ', // Test GST
|
||||
pan: 'AAACE3882D',
|
||||
firmType: 'Test Firm',
|
||||
propManagingPartnersDirectors: 'TEST REFLOW',
|
||||
totalPropPartnersDirectors: 'TEST REFLOW',
|
||||
@ -128,7 +128,7 @@ async function seedTestDealer(): Promise<void> {
|
||||
|
||||
if (existingDealer) {
|
||||
logger.info('[Seed Test Dealer] Test dealer already exists, updating...');
|
||||
|
||||
|
||||
// Update existing dealer
|
||||
await existingDealer.update(dealerData);
|
||||
|
||||
|
||||
19
src/scripts/trigger-pdf.ts
Normal file
19
src/scripts/trigger-pdf.ts
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
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();
|
||||
@ -30,6 +30,7 @@ export interface DealerInfo {
|
||||
city?: string | null;
|
||||
dealerPrincipalName?: string | null;
|
||||
dealerPrincipalEmailId?: string | null;
|
||||
gstin?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,6 +107,7 @@ export async function getAllDealers(searchTerm?: string, limit: number = 10): Pr
|
||||
city: dealer.city || null,
|
||||
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||
gstin: dealer.gst || null,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
@ -166,6 +168,7 @@ export async function getDealerByCode(dealerCode: string): Promise<DealerInfo |
|
||||
city: dealer.city || null,
|
||||
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||
gstin: dealer.gst || null,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[DealerService] Error fetching dealer by code:', error);
|
||||
@ -225,6 +228,7 @@ export async function getDealerByEmail(email: string): Promise<DealerInfo | null
|
||||
city: dealer.city || null,
|
||||
dealerPrincipalName: dealer.dealerPrincipalName || null,
|
||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||
gstin: dealer.gst || null,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[DealerService] Error fetching dealer by email:', error);
|
||||
|
||||
@ -28,14 +28,39 @@ import logger from '../utils/logger';
|
||||
|
||||
|
||||
|
||||
|
||||
let workflowServiceInstance: any;
|
||||
let approvalServiceInstance: any;
|
||||
let userServiceInstance: any;
|
||||
|
||||
/**
|
||||
* Dealer Claim Service
|
||||
* Handles business logic specific to dealer claim management workflow
|
||||
*/
|
||||
export class DealerClaimService {
|
||||
private workflowService = new WorkflowService();
|
||||
private approvalService = new DealerClaimApprovalService();
|
||||
private userService = new UserService();
|
||||
private getWorkflowService(): WorkflowService {
|
||||
if (!workflowServiceInstance) {
|
||||
const { WorkflowService } = require('./workflow.service');
|
||||
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
|
||||
@ -101,26 +126,92 @@ export class DealerClaimService {
|
||||
throw new Error('Approvers array is required. Please assign approvers for all workflow steps.');
|
||||
}
|
||||
|
||||
// Now create workflow request (manager is validated)
|
||||
// For claim management, requests are submitted immediately (not drafts)
|
||||
// Step 1 will be active for dealer to submit proposal
|
||||
const now = new Date();
|
||||
const workflowRequest = await WorkflowRequest.create({
|
||||
initiatorId: userId,
|
||||
requestNumber,
|
||||
templateType: 'DEALER CLAIM', // Set template type for dealer claim management
|
||||
// 1. Transform approvers and ensure users exist in database
|
||||
const userService = this.getUserService();
|
||||
const transformedLevels = [];
|
||||
|
||||
// Define step names mapping
|
||||
const stepNames: Record<number, string> = {
|
||||
1: 'Dealer Proposal Submission',
|
||||
2: 'Requestor Evaluation',
|
||||
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',
|
||||
title: `${claimData.activityName} - Claim Request`,
|
||||
description: claimData.requestDescription,
|
||||
priority: Priority.STANDARD,
|
||||
status: WorkflowStatus.PENDING, // Submitted, not draft
|
||||
totalLevels: 5, // Fixed 5-step workflow for claim management (Activity Creation, E-Invoice Generation, and Credit Note Confirmation are now activity logs only)
|
||||
currentLevel: 1, // Step 1: Dealer Proposal Submission
|
||||
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)
|
||||
});
|
||||
approvalLevels: transformedLevels,
|
||||
participants: transformedParticipants,
|
||||
isDraft: false
|
||||
} as any);
|
||||
|
||||
// Create claim details
|
||||
await DealerClaimDetails.create({
|
||||
@ -146,107 +237,9 @@ export class DealerClaimService {
|
||||
currency: 'INR',
|
||||
});
|
||||
|
||||
// Create 8 approval levels for claim management workflow from approvers array
|
||||
await this.createClaimApprovalLevelsFromApprovers(workflowRequest.requestId, userId, claimData.dealerEmail, claimData.approvers || []);
|
||||
// Redundant level creation 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
|
||||
// Redundant TAT scheduling removed - handled by workflowService.createWorkflow
|
||||
|
||||
logger.info(`[DealerClaimService] Created claim request: ${workflowRequest.requestNumber}`);
|
||||
return workflowRequest;
|
||||
@ -440,7 +433,8 @@ export class DealerClaimService {
|
||||
// User doesn't exist - create from Okta
|
||||
logger.info(`[DealerClaimService] User ${approver.email} not found in DB, syncing from Okta`);
|
||||
try {
|
||||
user = await this.userService.ensureUserExists({
|
||||
const userService = this.getUserService();
|
||||
user = await userService.ensureUserExists({
|
||||
email: approver.email.toLowerCase(),
|
||||
userId: approver.userId, // Pass Okta ID if provided (ensureUserExists will handle it)
|
||||
}) as any;
|
||||
@ -608,7 +602,8 @@ export class DealerClaimService {
|
||||
if (!dealerUser) {
|
||||
logger.info(`[DealerClaimService] Dealer ${dealerEmail} not found in DB for participants, syncing from Okta`);
|
||||
try {
|
||||
dealerUser = await this.userService.ensureUserExists({
|
||||
const userService = this.getUserService();
|
||||
dealerUser = await userService.ensureUserExists({
|
||||
email: dealerEmail.toLowerCase(),
|
||||
}) as any;
|
||||
logger.info(`[DealerClaimService] Successfully synced dealer ${dealerEmail} from Okta for participants`);
|
||||
@ -752,7 +747,8 @@ export class DealerClaimService {
|
||||
logger.info(`[DealerClaimService] Searching Okta for manager with displayName: "${managerDisplayName}"`);
|
||||
|
||||
// Search Okta by displayName
|
||||
const oktaUsers = await this.userService.searchOktaByDisplayName(managerDisplayName);
|
||||
const userService = this.getUserService();
|
||||
const oktaUsers = await userService.searchOktaByDisplayName(managerDisplayName);
|
||||
|
||||
if (oktaUsers.length === 0) {
|
||||
logger.warn(`[DealerClaimService] No reporting manager found in Okta for displayName: "${managerDisplayName}"`);
|
||||
@ -768,7 +764,7 @@ export class DealerClaimService {
|
||||
logger.info(`[DealerClaimService] Found single manager match: ${managerEmail} for displayName: "${managerDisplayName}"`);
|
||||
|
||||
// Check if user exists in DB, create if doesn't exist
|
||||
const managerUser = await this.userService.ensureUserExists({
|
||||
const managerUser = await userService.ensureUserExists({
|
||||
userId: oktaUser.id,
|
||||
email: managerEmail,
|
||||
displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim(),
|
||||
@ -1324,7 +1320,8 @@ export class DealerClaimService {
|
||||
: 'Dealer proposal submitted';
|
||||
|
||||
// Perform the approval action FIRST - only save snapshot if action succeeds
|
||||
await this.approvalService.approveLevel(
|
||||
const approvalService = this.getApprovalService();
|
||||
await approvalService.approveLevel(
|
||||
dealerProposalLevel.levelId,
|
||||
{ action: 'APPROVE', comments: approvalComment },
|
||||
actualDealerUserId || (request as any).initiatorId || 'system', // Use dealer or initiator ID
|
||||
@ -1485,7 +1482,8 @@ export class DealerClaimService {
|
||||
}
|
||||
|
||||
// Perform the approval action FIRST - only save snapshot if action succeeds
|
||||
await this.approvalService.approveLevel(
|
||||
const approvalService = this.getApprovalService();
|
||||
await approvalService.approveLevel(
|
||||
dealerCompletionLevel.levelId,
|
||||
{ action: 'APPROVE', comments: approvalComment },
|
||||
actualDealerUserId || (request as any).initiatorId || 'system',
|
||||
@ -1905,7 +1903,7 @@ export class DealerClaimService {
|
||||
|
||||
const requestNumber = request ? ((request as any).requestNumber || (request as any).request_number) : 'UNKNOWN';
|
||||
|
||||
// If invoice data not provided, generate via DMS
|
||||
// If invoice data not provided, generate via PWC E-Invoice service
|
||||
if (!invoiceData?.eInvoiceNumber) {
|
||||
const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
|
||||
const invoiceAmount = invoiceData?.amount
|
||||
@ -1914,7 +1912,7 @@ export class DealerClaimService {
|
||||
|| budgetTracking?.initialEstimatedBudget
|
||||
|| 0;
|
||||
|
||||
const invoiceResult = await pwcIntegrationService.generateSignedInvoice(requestId);
|
||||
const invoiceResult = await pwcIntegrationService.generateSignedInvoice(requestId, invoiceAmount);
|
||||
|
||||
if (!invoiceResult.success) {
|
||||
throw new Error(`Failed to generate signed e-invoice via PWC: ${invoiceResult.error}`);
|
||||
@ -1930,6 +1928,8 @@ export class DealerClaimService {
|
||||
signedInvoice: invoiceResult.signedInvoice,
|
||||
qrCode: invoiceResult.qrCode,
|
||||
qrImage: invoiceResult.qrImage,
|
||||
pwcResponse: invoiceResult.rawResponse,
|
||||
irpResponse: invoiceResult.irpResponse,
|
||||
amount: invoiceAmount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
@ -1956,6 +1956,15 @@ export class DealerClaimService {
|
||||
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
|
||||
// Find dynamically by levelName (handles step shifts due to additional approvers)
|
||||
const approvalLevels = await ApprovalLevel.findAll({
|
||||
@ -1975,34 +1984,30 @@ export class DealerClaimService {
|
||||
requestorClaimLevel = approvalLevels.find((level: any) => level.levelNumber === 5);
|
||||
}
|
||||
|
||||
// Validate that we're at the Requestor Claim Approval step before allowing DMS push
|
||||
// Validate that we're at the Requestor Claim Approval step before allowing E-Invoice generation
|
||||
if (requestorClaimLevel && request.currentLevel !== requestorClaimLevel.levelNumber) {
|
||||
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.`);
|
||||
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.`);
|
||||
}
|
||||
|
||||
if (requestorClaimLevel && requestorClaimLevel.status !== ApprovalStatus.APPROVED) {
|
||||
logger.info(`[DealerClaimService] Requestor Claim Approval not approved yet. Auto-approving for request ${requestId}`);
|
||||
// Auto-approve Requestor Claim Approval
|
||||
await this.approvalService.approveLevel(
|
||||
// E-Invoice Generation is successful - auto-approve the Requestor Claim Approval step
|
||||
if (requestorClaimLevel && requestorClaimLevel.status !== 'APPROVED') {
|
||||
const approvalService = this.getApprovalService();
|
||||
await approvalService.approveLevel(
|
||||
requestorClaimLevel.levelId,
|
||||
{ action: 'APPROVE', comments: 'Auto-approved when pushing to DMS. E-Invoice generation will be logged as activity.' },
|
||||
'system',
|
||||
{ ipAddress: null, userAgent: 'System Auto-Process' }
|
||||
{ action: 'APPROVE', comments: 'Auto-approved after successful E-Invoice generation' },
|
||||
'system'
|
||||
);
|
||||
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.`);
|
||||
logger.info(`[DealerClaimService] Step "${requestorClaimLevel.levelName}" auto-approved after E-Invoice generation for request ${requestId}`);
|
||||
}
|
||||
|
||||
// Log E-Invoice generation as activity (no approval level needed)
|
||||
// Log E-Invoice generation as activity
|
||||
await activityService.log({
|
||||
requestId,
|
||||
type: 'status_change',
|
||||
user: { userId: 'system', name: 'System Auto-Process' },
|
||||
timestamp: new Date().toISOString(),
|
||||
action: 'E-Invoice Generation Initiated',
|
||||
details: `E-Invoice generation initiated via DMS integration for request ${requestNumber}. Waiting for DMS webhook confirmation.`,
|
||||
action: 'E-Invoice Generated',
|
||||
details: `E-Invoice generated via PWC integration for request ${requestNumber}. Step "${requestorClaimLevel?.levelName || 'Requestor Claim Approval'}" auto-approved.`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[DealerClaimService] Error updating e-invoice details:', error);
|
||||
@ -2012,7 +2017,7 @@ export class DealerClaimService {
|
||||
|
||||
/**
|
||||
* Log E-Invoice Generation as activity (no longer an approval step)
|
||||
* This method logs the e-invoice generation activity when invoice is generated via DMS webhook
|
||||
* This method logs the e-invoice generation activity when invoice is generated via PWC service
|
||||
*/
|
||||
async logEInvoiceGenerationActivity(requestId: string, invoiceNumber?: string): Promise<void> {
|
||||
try {
|
||||
@ -2040,7 +2045,7 @@ export class DealerClaimService {
|
||||
user: { userId: 'system', name: 'System Auto-Process' },
|
||||
timestamp: new Date().toISOString(),
|
||||
action: 'E-Invoice Generated',
|
||||
details: `E-Invoice generated via DMS. Invoice Number: ${finalInvoiceNumber}. Request: ${requestNumber}`,
|
||||
details: `E-Invoice generated via PWC. Invoice Number: ${finalInvoiceNumber}. Request: ${requestNumber}`,
|
||||
});
|
||||
|
||||
logger.info(`[DealerClaimService] E-Invoice Generation activity logged for request ${requestId} (Invoice: ${finalInvoiceNumber})`);
|
||||
@ -2418,7 +2423,7 @@ export class DealerClaimService {
|
||||
category: 'SUPPORTING',
|
||||
isDeleted: false
|
||||
},
|
||||
order: [['createdAt', 'DESC']]
|
||||
order: [['uploadedAt', 'DESC']]
|
||||
});
|
||||
|
||||
const snapshotData = {
|
||||
@ -2503,7 +2508,7 @@ export class DealerClaimService {
|
||||
category: 'SUPPORTING',
|
||||
isDeleted: false
|
||||
},
|
||||
order: [['createdAt', 'DESC']]
|
||||
order: [['uploadedAt', 'DESC']]
|
||||
});
|
||||
|
||||
// Store all completion data in JSONB
|
||||
|
||||
@ -25,10 +25,17 @@ import { tatSchedulerService } from './tatScheduler.service';
|
||||
import { DealerClaimService } from './dealerClaim.service';
|
||||
import { emitToRequestRoom } from '../realtime/socket';
|
||||
|
||||
|
||||
let dealerClaimServiceInstance: any;
|
||||
|
||||
export class DealerClaimApprovalService {
|
||||
// Use lazy initialization to avoid circular dependency
|
||||
private getDealerClaimService(): DealerClaimService {
|
||||
return new DealerClaimService();
|
||||
if (!dealerClaimServiceInstance) {
|
||||
const { DealerClaimService } = require('./dealerClaim.service');
|
||||
dealerClaimServiceInstance = new DealerClaimService();
|
||||
}
|
||||
return dealerClaimServiceInstance;
|
||||
}
|
||||
/**
|
||||
* Approve a level in a dealer claim workflow
|
||||
@ -331,7 +338,7 @@ export class DealerClaimApprovalService {
|
||||
// Activity Creation is now an activity log only - process it automatically
|
||||
logger.info(`[DealerClaimApproval] Department Lead approved. Processing Activity Creation as activity log.`);
|
||||
try {
|
||||
const dealerClaimService = new DealerClaimService();
|
||||
const dealerClaimService = this.getDealerClaimService();
|
||||
await dealerClaimService.processActivityCreation(level.requestId);
|
||||
logger.info(`[DealerClaimApproval] Activity Creation activity logged for request ${level.requestId}`);
|
||||
} catch (activityError) {
|
||||
|
||||
@ -508,7 +508,7 @@ export class DMSWebhookService {
|
||||
|
||||
// E-Invoice Generation is now an activity log only, not an approval step
|
||||
// Log the activity using the dealerClaimService
|
||||
const { DealerClaimService } = await import('./dealerClaim.service');
|
||||
const { DealerClaimService } = require('./dealerClaim.service');
|
||||
const dealerClaimService = new DealerClaimService();
|
||||
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
||||
const invoiceNumber = invoice?.invoiceNumber || 'N/A';
|
||||
|
||||
206
src/services/pdf.service.ts
Normal file
206
src/services/pdf.service.ts
Normal file
@ -0,0 +1,206 @@
|
||||
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,6 +5,8 @@ import { ActivityType } from '../models/ActivityType';
|
||||
import { WorkflowRequest } from '../models/WorkflowRequest';
|
||||
import { ClaimInvoice } from '../models/ClaimInvoice';
|
||||
import { InternalOrder } from '../models/InternalOrder';
|
||||
import { User } from '../models/User';
|
||||
import { DealerClaimDetails } from '../models/DealerClaimDetails';
|
||||
|
||||
/**
|
||||
* PWC E-Invoice Integration Service
|
||||
@ -12,13 +14,13 @@ import { InternalOrder } from '../models/InternalOrder';
|
||||
*/
|
||||
export class PWCIntegrationService {
|
||||
private apiUrl: string;
|
||||
private appKey: string;
|
||||
private appSecret: string;
|
||||
private customerId: string;
|
||||
private token: string;
|
||||
|
||||
constructor() {
|
||||
this.apiUrl = process.env.PWC_API_URL || 'https://api.qa.einvoice.aw.navigatetax.pwc.co.in';
|
||||
this.appKey = process.env.PWC_APP_KEY || '';
|
||||
this.appSecret = process.env.PWC_APP_SECRET || '';
|
||||
this.apiUrl = process.env.PWC_API_URL || 'https://api.qa.einvoice.aw.navigatetax.pwc.co.in/qa/v1/en/push';
|
||||
this.customerId = process.env.PWC_CUSTOMER_ID || '';
|
||||
this.token = process.env.PWC_TOKEN || '';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,7 +45,7 @@ export class PWCIntegrationService {
|
||||
/**
|
||||
* Generate Signed Invoice via PWC API
|
||||
*/
|
||||
async generateSignedInvoice(requestId: string): Promise<{
|
||||
async generateSignedInvoice(requestId: string, amount?: number): Promise<{
|
||||
success: boolean;
|
||||
irn?: string;
|
||||
ackNo?: string;
|
||||
@ -51,75 +53,155 @@ export class PWCIntegrationService {
|
||||
signedInvoice?: string;
|
||||
qrCode?: string;
|
||||
qrImage?: string;
|
||||
rawResponse?: any;
|
||||
irpResponse?: any;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const request = await WorkflowRequest.findByPk(requestId, {
|
||||
include: ['claimDetails', 'initiator']
|
||||
include: [{ model: User, as: 'initiator' }, { model: DealerClaimDetails, as: 'claimDetails' }]
|
||||
});
|
||||
|
||||
if (!request) return { success: false, error: 'Request not found' };
|
||||
|
||||
const dealer = await Dealer.findOne({ where: { dlrcode: (request as any).claimDetails?.dealerCode } });
|
||||
const activity = await ActivityType.findOne({ where: { title: (request as any).claimDetails?.activityType } });
|
||||
const claimDetails = (request as any).claimDetails;
|
||||
const dealer = await Dealer.findOne({ where: { dlrcode: claimDetails?.dealerCode } });
|
||||
const activity = await ActivityType.findOne({ where: { title: claimDetails?.activityType } });
|
||||
|
||||
if (!dealer || !activity) {
|
||||
return { success: false, error: 'Dealer or Activity details missing' };
|
||||
}
|
||||
|
||||
// Construct PWC Payload (keeping existing logic for now)
|
||||
const payload = {
|
||||
UserGstin: "33AAACE3882D1ZZ",
|
||||
DocDtls: {
|
||||
Typ: "INV",
|
||||
No: `INV-${Date.now()}`,
|
||||
Dt: new Date().toLocaleDateString('en-GB').replace(/\//g, '-')
|
||||
},
|
||||
SellerDtls: {
|
||||
Gstin: dealer.gst || "33AAACE3882D1ZZ",
|
||||
LglNm: dealer.dealership || 'Dealer',
|
||||
Addr1: dealer.showroomAddress || "Address Line 1",
|
||||
Loc: dealer.location || "Location",
|
||||
Pin: 600001,
|
||||
Stcd: "33"
|
||||
},
|
||||
BuyerDtls: {
|
||||
Gstin: "33AAACE3882D1ZZ",
|
||||
LglNm: "ROYAL ENFIELD (A UNIT OF EICHER MOTORS LTD)",
|
||||
Addr1: "No. 2, Thiruvottiyur High Road",
|
||||
Loc: "Thiruvottiyur",
|
||||
Pin: 600019,
|
||||
Stcd: "33",
|
||||
Pos: "33"
|
||||
},
|
||||
ItemList: [
|
||||
{
|
||||
SlNo: "1",
|
||||
PrdDesc: activity.title,
|
||||
IsServc: "Y",
|
||||
HsnCd: activity.hsnCode || activity.sacCode || "9983",
|
||||
Qty: 1,
|
||||
Unit: "OTH",
|
||||
UnitPrce: (request as any).amount,
|
||||
TotAmt: (request as any).amount,
|
||||
GstRt: activity.gstRate || 18,
|
||||
AssAmt: (request as any).amount,
|
||||
IgstAmt: activity.gstRate === 18 ? ((request as any).amount * 0.18) : 0,
|
||||
TotItemVal: (request as any).amount * 1.18
|
||||
// Fallback for amount if not provided
|
||||
const finalAmount = Number(amount || (request as any).amount || 0);
|
||||
|
||||
// Helper to format number to 2 decimal places
|
||||
const formatAmount = (val: number) => Number(val.toFixed(2));
|
||||
|
||||
// Extract State Code from Dealer GSTIN
|
||||
let dealerGst = (dealer as any).gst;
|
||||
|
||||
// HOTFIX: For PWC QA Environment, use a known valid GSTIN if dealer has the invalid test one
|
||||
// The test GSTIN 29AAACE3882D1ZZ is not registered in PWC QA Master, causing Error 701
|
||||
const isQA = this.apiUrl.includes('qa');
|
||||
const invalidTestGst = '29AAACE3882D1ZZ';
|
||||
const validQaGst = '24AAAPI3182M002'; // Registered in PWC QA
|
||||
|
||||
if (isQA && (!dealerGst || dealerGst === invalidTestGst)) {
|
||||
dealerGst = validQaGst;
|
||||
}
|
||||
|
||||
// Final fallback if still empty
|
||||
dealerGst = dealerGst || validQaGst;
|
||||
|
||||
let dealerStateCode = "24"; // Default fallback (Gujarat for 24...)
|
||||
|
||||
// Try to extract from GSTIN (first 2 chars)
|
||||
if (dealerGst && dealerGst.length >= 2 && !isNaN(Number(dealerGst.substring(0, 2)))) {
|
||||
dealerStateCode = dealerGst.substring(0, 2);
|
||||
} else if ((dealer as any).stateCode) {
|
||||
dealerStateCode = (dealer as any).stateCode;
|
||||
}
|
||||
|
||||
// Calculate tax amounts
|
||||
const gstRate = Number(activity.gstRate || 18);
|
||||
const isIGST = dealerStateCode !== "33"; // If dealer state != Buyer state (33), it's IGST
|
||||
|
||||
const assAmt = finalAmount;
|
||||
const igstAmt = isIGST ? (finalAmount * (gstRate / 100)) : 0;
|
||||
const cgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0;
|
||||
const sgstAmt = !isIGST ? (finalAmount * (gstRate / 200)) : 0;
|
||||
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}`);
|
||||
|
||||
const response = await axios.post(`${this.apiUrl}/generate`, payload, {
|
||||
headers: { 'AppKey': this.appKey, 'AppSecret': this.appSecret }
|
||||
const response = await axios.post(this.apiUrl, payload, {
|
||||
headers: {
|
||||
'customerid': this.customerId,
|
||||
'token': this.token
|
||||
}
|
||||
});
|
||||
console.log('PWC Response:', JSON.stringify(response.data));
|
||||
|
||||
// Parse PWC Response based on provided structure
|
||||
// Sample response is an array: [{ pwc_response, irp_response, qr_b64_encoded }]
|
||||
@ -146,9 +228,25 @@ export class PWCIntegrationService {
|
||||
}
|
||||
|
||||
if (!irn) {
|
||||
const errorMsg = responseData?.irp_response?.message || 'E-Invoice generation failed';
|
||||
logger.error(`[PWC] E-Invoice failed for ${request.requestNumber}: ${errorMsg}`);
|
||||
return { success: false, error: errorMsg };
|
||||
const mainMsg = responseData?.pwc_response?.message || responseData?.irp_response?.message || 'E-Invoice generation failed';
|
||||
|
||||
// Extract detailed error messages from irp_response.data.error_details
|
||||
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 {
|
||||
@ -158,7 +256,9 @@ export class PWCIntegrationService {
|
||||
ackDate: ackDate ? new Date(ackDate) : undefined,
|
||||
signedInvoice,
|
||||
qrCode,
|
||||
qrImage: qrB64
|
||||
qrImage: qrB64,
|
||||
rawResponse: responseData?.pwc_response,
|
||||
irpResponse: responseData?.irp_response
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@ -34,6 +34,12 @@ export class SAPIntegrationService {
|
||||
* Check if SAP integration is configured
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -2459,6 +2459,7 @@ export class WorkflowService {
|
||||
requestNumber,
|
||||
initiatorId,
|
||||
templateType: workflowData.templateType,
|
||||
workflowType: workflowData.workflowType || 'NON_TEMPLATIZED',
|
||||
title: workflowData.title ? sanitizeHtml(workflowData.title) : workflowData.title,
|
||||
description: workflowData.description ? sanitizeHtml(workflowData.description) : workflowData.description,
|
||||
priority: workflowData.priority,
|
||||
@ -2471,29 +2472,29 @@ export class WorkflowService {
|
||||
submissionDate: isDraftRequested ? undefined : now
|
||||
});
|
||||
|
||||
// Create approval levels
|
||||
for (const levelData of workflowData.approvalLevels) {
|
||||
await ApprovalLevel.create({
|
||||
requestId: workflow.requestId,
|
||||
levelNumber: levelData.levelNumber,
|
||||
levelName: levelData.levelName,
|
||||
approverId: levelData.approverId,
|
||||
approverEmail: levelData.approverEmail,
|
||||
approverName: levelData.approverName,
|
||||
tatHours: levelData.tatHours,
|
||||
// tatDays is auto-calculated by database as a generated column
|
||||
status: ApprovalStatus.PENDING,
|
||||
elapsedHours: 0,
|
||||
remainingHours: levelData.tatHours,
|
||||
tatPercentageUsed: 0,
|
||||
isFinalApprover: levelData.isFinalApprover || false
|
||||
});
|
||||
// Create approval levels if skipCreation is false
|
||||
if (!workflowData.skipCreation) {
|
||||
for (const levelData of workflowData.approvalLevels) {
|
||||
await ApprovalLevel.create({
|
||||
requestId: workflow.requestId,
|
||||
levelNumber: levelData.levelNumber,
|
||||
levelName: levelData.levelName,
|
||||
approverId: levelData.approverId,
|
||||
approverEmail: levelData.approverEmail,
|
||||
approverName: levelData.approverName,
|
||||
tatHours: levelData.tatHours,
|
||||
// tatDays is auto-calculated by database as a generated column
|
||||
status: ApprovalStatus.PENDING,
|
||||
elapsedHours: 0,
|
||||
remainingHours: levelData.tatHours,
|
||||
tatPercentageUsed: 0,
|
||||
isFinalApprover: levelData.isFinalApprover || false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create participants if provided
|
||||
// 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) {
|
||||
// Create participants if provided and skipCreation is false
|
||||
if (workflowData.participants && !workflowData.skipCreation) {
|
||||
const participantMap = new Map<string, typeof workflowData.participants[0]>();
|
||||
const rolePriority: Record<string, number> = {
|
||||
'INITIATOR': 3,
|
||||
@ -2568,13 +2569,15 @@ export class WorkflowService {
|
||||
}
|
||||
|
||||
return workflow;
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
logWithContext('error', 'Failed to create workflow', {
|
||||
userId: initiatorId,
|
||||
priority: workflowData.priority,
|
||||
error,
|
||||
errorMessage: error.message,
|
||||
errorStack: error.stack,
|
||||
error: error, // Keep for full object in some loggers
|
||||
});
|
||||
throw new Error('Failed to create workflow');
|
||||
throw new Error(`Failed to create workflow: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,8 @@ export interface WorkflowRequest {
|
||||
requestId: string;
|
||||
requestNumber: string;
|
||||
initiatorId: string;
|
||||
templateType: 'CUSTOM' | 'TEMPLATE';
|
||||
templateType: 'CUSTOM' | 'TEMPLATE' | 'DEALER CLAIM';
|
||||
workflowType?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
priority: Priority;
|
||||
@ -23,13 +24,15 @@ export interface WorkflowRequest {
|
||||
}
|
||||
|
||||
export interface CreateWorkflowRequest {
|
||||
templateType: 'CUSTOM' | 'TEMPLATE';
|
||||
templateType: 'CUSTOM' | 'TEMPLATE' | 'DEALER CLAIM';
|
||||
workflowType?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
priority: Priority;
|
||||
approvalLevels: CreateApprovalLevel[];
|
||||
participants?: CreateParticipant[];
|
||||
isDraft?: boolean;
|
||||
skipCreation?: boolean; // Flag to skip record creation if handled manually by calling service
|
||||
}
|
||||
|
||||
export interface UpdateWorkflowRequest {
|
||||
|
||||
@ -20,8 +20,9 @@ const SENSITIVE_PATTERN = new RegExp(
|
||||
|
||||
/**
|
||||
* Mask sensitive values in strings (API keys, passwords, tokens)
|
||||
* Uses a WeakSet to prevent infinite recursion on circular objects
|
||||
*/
|
||||
const maskSensitiveData = (value: any): any => {
|
||||
const maskSensitiveData = (value: any, visited = new WeakSet()): any => {
|
||||
if (typeof value === 'string') {
|
||||
// Mask patterns like "API_KEY = abc123" or "password: secret"
|
||||
let masked = value.replace(SENSITIVE_PATTERN, (match, key, val) => {
|
||||
@ -45,10 +46,24 @@ const maskSensitiveData = (value: any): any => {
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(maskSensitiveData);
|
||||
if (visited.has(value)) return '[Circular]';
|
||||
visited.add(value);
|
||||
return value.map(item => maskSensitiveData(item, visited));
|
||||
}
|
||||
|
||||
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 = {};
|
||||
for (const [k, v] of Object.entries(value)) {
|
||||
const keyLower = k.toLowerCase();
|
||||
@ -56,7 +71,7 @@ const maskSensitiveData = (value: any): any => {
|
||||
if (SENSITIVE_KEYS.some(sk => keyLower.includes(sk))) {
|
||||
masked[k] = typeof v === 'string' && v.length > 0 ? '***REDACTED***' : v;
|
||||
} else {
|
||||
masked[k] = maskSensitiveData(v);
|
||||
masked[k] = maskSensitiveData(v, visited);
|
||||
}
|
||||
}
|
||||
return masked;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user