Compare commits
No commits in common. "17c62d2b451755c8c2527c29a5f39ea4dfd77e4d" and "798688e4c2523270dce6518e73da9db32cc67b15" have entirely different histories.
17c62d2b45
...
798688e4c2
@ -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-BgkDE8Pi.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DyksGUTu.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-VENY18zj.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-VENY18zj.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-BgkDE8Pi.js
Normal file
67
build/assets/index-BgkDE8Pi.js
Normal file
File diff suppressed because one or more lines are too long
1
build/assets/index-BgkDE8Pi.js.map
Normal file
1
build/assets/index-BgkDE8Pi.js.map
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
File diff suppressed because one or more lines are too long
@ -1,31 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/royal_enfield_logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description"
|
||||
content="Royal Enfield Approval & Request Management Portal - Streamlined approval workflows for enterprise operations" />
|
||||
<meta name="theme-color" content="#2d4a3e" />
|
||||
<title>Royal Enfield | Approval Portal</title>
|
||||
|
||||
<!-- 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>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/royal_enfield_logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description"
|
||||
content="Royal Enfield Approval & Request Management Portal - Streamlined approval workflows for enterprise operations" />
|
||||
<meta name="theme-color" content="#2d4a3e" />
|
||||
<title>Royal Enfield | Approval Portal</title>
|
||||
|
||||
<!-- 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-BgkDE8Pi.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">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-DfwWW08H.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-DyksGUTu.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B_rK4TXr.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D5NCgjQR.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -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 run build && npm run start:prod && npm run setup",
|
||||
"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",
|
||||
@ -93,4 +93,4 @@
|
||||
"node": ">=22.0.0",
|
||||
"npm": ">=10.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/app.ts
11
src/app.ts
@ -85,17 +85,18 @@ app.use(cookieParser());
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
// Initializer for database connection (called from server.ts)
|
||||
export const initializeAppDatabase = async () => {
|
||||
// Initialize database connection
|
||||
const initializeDatabase = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ App database connection established');
|
||||
} catch (error) {
|
||||
console.error('❌ App database connection failed:', error);
|
||||
throw error;
|
||||
console.error('❌ Database connection failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize database
|
||||
initializeDatabase();
|
||||
|
||||
// Trust proxy - Enable this when behind a reverse proxy (nginx, load balancer, etc.)
|
||||
// This allows Express to read X-Forwarded-* headers correctly
|
||||
// Set to true in production, false in development
|
||||
|
||||
@ -132,13 +132,10 @@ export class AuthController {
|
||||
|
||||
// Set new access token in cookie if using cookie-based auth
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
const isSecureEnv = isProduction || isUat;
|
||||
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isSecureEnv,
|
||||
sameSite: isSecureEnv ? 'lax' as const : 'lax' as const, // 'lax' is safer and works on same-domain
|
||||
secure: isProduction,
|
||||
sameSite: isProduction ? 'lax' as const : 'lax' as const, // 'lax' is safer and works on same-domain
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
||||
};
|
||||
|
||||
@ -209,13 +206,10 @@ export class AuthController {
|
||||
|
||||
// Set tokens in httpOnly cookies (production) or return in body (development)
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
const isSecureEnv = isProduction || isUat;
|
||||
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isSecureEnv,
|
||||
sameSite: isSecureEnv ? ('lax' as const) : ('lax' as const),
|
||||
secure: isProduction,
|
||||
sameSite: isProduction ? ('lax' as const) : ('lax' as const),
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
||||
path: '/',
|
||||
};
|
||||
@ -262,13 +256,10 @@ export class AuthController {
|
||||
|
||||
// Set new access token in cookie
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
const isSecureEnv = isProduction || isUat;
|
||||
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isSecureEnv,
|
||||
sameSite: isSecureEnv ? ('lax' as const) : ('lax' as const),
|
||||
secure: isProduction,
|
||||
sameSite: isProduction ? ('lax' as const) : ('lax' as const),
|
||||
maxAge: 24 * 60 * 60 * 1000,
|
||||
path: '/',
|
||||
};
|
||||
@ -302,16 +293,13 @@ export class AuthController {
|
||||
|
||||
// Helper function to clear cookies with all possible option combinations
|
||||
const clearCookiesCompletely = () => {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
const isSecureEnv = isProduction || isUat;
|
||||
const cookieNames = ['accessToken', 'refreshToken'];
|
||||
|
||||
// Get the EXACT options used when setting cookies (from exchangeToken)
|
||||
// These MUST match exactly: httpOnly, secure, sameSite, path
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isSecureEnv,
|
||||
secure: isProduction,
|
||||
sameSite: 'lax' as const,
|
||||
path: '/',
|
||||
};
|
||||
@ -481,13 +469,10 @@ export class AuthController {
|
||||
|
||||
// Set cookies for web clients
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
const isSecureEnv = isProduction || isUat;
|
||||
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isSecureEnv,
|
||||
sameSite: isSecureEnv ? 'lax' as const : 'lax' as const,
|
||||
secure: isProduction,
|
||||
sameSite: isProduction ? 'lax' as const : 'lax' as const,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
||||
};
|
||||
|
||||
@ -564,13 +549,10 @@ export class AuthController {
|
||||
|
||||
// Set cookies with httpOnly flag for security
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
const isSecureEnv = isProduction || isUat;
|
||||
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: isSecureEnv,
|
||||
sameSite: isSecureEnv ? 'lax' as const : 'lax' as const, // 'lax' for same-domain
|
||||
secure: isProduction,
|
||||
sameSite: isProduction ? 'lax' as const : 'lax' as const, // 'lax' for same-domain
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 hours for access token
|
||||
};
|
||||
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
import { QueryInterface, DataTypes } from 'sequelize';
|
||||
|
||||
/**
|
||||
* Helper function to check if a column exists in a table
|
||||
*/
|
||||
async function columnExists(
|
||||
queryInterface: QueryInterface,
|
||||
tableName: string,
|
||||
columnName: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const tableDescription = await queryInterface.describeTable(tableName);
|
||||
return columnName in tableDescription;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function up(queryInterface: QueryInterface): Promise<void> {
|
||||
// 1. ActivityType
|
||||
const activityCols = {
|
||||
hsn_code: { type: DataTypes.STRING(20), allowNull: true },
|
||||
sac_code: { type: DataTypes.STRING(20), allowNull: true },
|
||||
gst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
gl_code: { type: DataTypes.STRING(20), allowNull: true },
|
||||
credit_nature: { type: DataTypes.STRING(50), allowNull: true }
|
||||
};
|
||||
|
||||
for (const [col, spec] of Object.entries(activityCols)) {
|
||||
if (!(await columnExists(queryInterface, 'activity_types', col))) {
|
||||
await queryInterface.addColumn('activity_types', col, spec);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. GST Fields mapping for Multiple Tables
|
||||
const gstFields = {
|
||||
gst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
gst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cgst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
cgst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
sgst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
sgst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
igst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
igst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
utgst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
utgst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cess_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
cess_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
total_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
};
|
||||
|
||||
const tables = ['dealer_proposal_cost_items', 'dealer_completion_expenses', 'claim_credit_notes'];
|
||||
|
||||
for (const table of tables) {
|
||||
for (const [col, spec] of Object.entries(gstFields)) {
|
||||
if (!(await columnExists(queryInterface, table, col))) {
|
||||
await queryInterface.addColumn(table, col, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing expense_date to DealerCompletionExpense
|
||||
if (!(await columnExists(queryInterface, 'dealer_completion_expenses', 'expense_date'))) {
|
||||
await queryInterface.addColumn('dealer_completion_expenses', 'expense_date', { type: DataTypes.DATEONLY, allowNull: true });
|
||||
}
|
||||
|
||||
// 3. ClaimInvoice
|
||||
const invoiceCols = {
|
||||
irn: { type: DataTypes.STRING(500), allowNull: true },
|
||||
ack_no: { type: DataTypes.STRING(255), allowNull: true },
|
||||
ack_date: { type: DataTypes.DATE, allowNull: true },
|
||||
signed_invoice: { type: DataTypes.TEXT, allowNull: true },
|
||||
signed_invoice_url: { type: DataTypes.STRING(500), allowNull: true },
|
||||
dealer_claim_number: { type: DataTypes.STRING(100), allowNull: true },
|
||||
qr_code: { type: DataTypes.TEXT, allowNull: true },
|
||||
qr_image: { type: DataTypes.TEXT, allowNull: true },
|
||||
dealer_claim_date: { type: DataTypes.DATEONLY, allowNull: true },
|
||||
billing_no: { type: DataTypes.STRING(100), allowNull: true },
|
||||
billing_date: { type: DataTypes.DATEONLY, allowNull: true },
|
||||
taxable_value: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cgst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
sgst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
igst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
utgst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cess_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
tcs_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
round_off_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
place_of_supply: { type: DataTypes.STRING(255), allowNull: true },
|
||||
total_value_in_words: { type: DataTypes.STRING(500), allowNull: true },
|
||||
tax_value_in_words: { type: DataTypes.STRING(500), allowNull: true },
|
||||
credit_nature: { type: DataTypes.STRING(100), allowNull: true },
|
||||
consignor_gsin: { type: DataTypes.STRING(255), allowNull: true },
|
||||
gstin_date: { type: DataTypes.DATEONLY, allowNull: true }
|
||||
};
|
||||
|
||||
for (const [col, spec] of Object.entries(invoiceCols)) {
|
||||
if (!(await columnExists(queryInterface, 'claim_invoices', col))) {
|
||||
await queryInterface.addColumn('claim_invoices', col, spec);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure file_path exists as 'file_path'
|
||||
try {
|
||||
if (!(await columnExists(queryInterface, 'claim_invoices', 'file_path'))) {
|
||||
if (await columnExists(queryInterface, 'claim_invoices', 'invoice_file_path')) {
|
||||
await queryInterface.renameColumn('claim_invoices', 'invoice_file_path', 'file_path');
|
||||
} else {
|
||||
await queryInterface.addColumn('claim_invoices', 'file_path', { type: DataTypes.STRING(500), allowNull: true });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently continue
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||
// Note: Best effort rollback (usually not recommended to drop columns in shared dev unless necessary)
|
||||
await queryInterface.removeColumn('dealer_completion_expenses', 'expense_date').catch(() => { });
|
||||
}
|
||||
@ -9,18 +9,13 @@ interface ActivityTypeAttributes {
|
||||
taxationType?: string;
|
||||
sapRefNo?: string;
|
||||
isActive: boolean;
|
||||
hsnCode?: string | null;
|
||||
sacCode?: string | null;
|
||||
gstRate?: number | null;
|
||||
glCode?: string | null;
|
||||
creditNature?: 'Commercial' | 'GST' | null;
|
||||
createdBy: string;
|
||||
updatedBy?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ActivityTypeCreationAttributes extends Optional<ActivityTypeAttributes, 'activityTypeId' | 'itemCode' | 'taxationType' | 'sapRefNo' | 'isActive' | 'updatedBy' | 'createdAt' | 'updatedAt'> { }
|
||||
interface ActivityTypeCreationAttributes extends Optional<ActivityTypeAttributes, 'activityTypeId' | 'itemCode' | 'taxationType' | 'sapRefNo' | 'isActive' | 'updatedBy' | 'createdAt' | 'updatedAt'> {}
|
||||
|
||||
class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAttributes> implements ActivityTypeAttributes {
|
||||
public activityTypeId!: string;
|
||||
@ -29,11 +24,6 @@ class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAtt
|
||||
public taxationType?: string;
|
||||
public sapRefNo?: string;
|
||||
public isActive!: boolean;
|
||||
public hsnCode?: string | null;
|
||||
public sacCode?: string | null;
|
||||
public gstRate?: number | null;
|
||||
public glCode?: string | null;
|
||||
public creditNature?: 'Commercial' | 'GST' | null;
|
||||
public createdBy!: string;
|
||||
public updatedBy?: string;
|
||||
public createdAt!: Date;
|
||||
@ -81,31 +71,6 @@ ActivityType.init(
|
||||
defaultValue: true,
|
||||
field: 'is_active'
|
||||
},
|
||||
hsnCode: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
field: 'hsn_code'
|
||||
},
|
||||
sacCode: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
field: 'sac_code'
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
glCode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'gl_code'
|
||||
},
|
||||
creditNature: {
|
||||
type: DataTypes.ENUM('Commercial', 'GST'),
|
||||
allowNull: true,
|
||||
field: 'credit_nature'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
|
||||
@ -9,20 +9,7 @@ interface ClaimCreditNoteAttributes {
|
||||
invoiceId?: string;
|
||||
creditNoteNumber?: string;
|
||||
creditNoteDate?: Date;
|
||||
creditNoteAmount: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
utgstRate?: number;
|
||||
utgstAmt?: number;
|
||||
cessRate?: number;
|
||||
cessAmt?: number;
|
||||
totalAmt?: number;
|
||||
creditNoteAmount?: number;
|
||||
sapDocumentNumber?: string;
|
||||
creditNoteFilePath?: string;
|
||||
status?: string;
|
||||
@ -35,7 +22,7 @@ interface ClaimCreditNoteAttributes {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ClaimCreditNoteCreationAttributes extends Optional<ClaimCreditNoteAttributes, 'creditNoteId' | 'invoiceId' | 'creditNoteNumber' | 'creditNoteDate' | 'gstRate' | 'gstAmt' | 'cgstRate' | 'cgstAmt' | 'sgstRate' | 'sgstAmt' | 'igstRate' | 'igstAmt' | 'utgstRate' | 'utgstAmt' | 'cessRate' | 'cessAmt' | 'totalAmt' | 'sapDocumentNumber' | 'creditNoteFilePath' | 'status' | 'errorMessage' | 'confirmedBy' | 'confirmedAt' | 'reason' | 'description' | 'createdAt' | 'updatedAt'> { }
|
||||
interface ClaimCreditNoteCreationAttributes extends Optional<ClaimCreditNoteAttributes, 'creditNoteId' | 'invoiceId' | 'creditNoteNumber' | 'creditNoteDate' | 'creditNoteAmount' | 'sapDocumentNumber' | 'creditNoteFilePath' | 'status' | 'errorMessage' | 'confirmedBy' | 'confirmedAt' | 'reason' | 'description' | 'createdAt' | 'updatedAt'> {}
|
||||
|
||||
class ClaimCreditNote extends Model<ClaimCreditNoteAttributes, ClaimCreditNoteCreationAttributes> implements ClaimCreditNoteAttributes {
|
||||
public creditNoteId!: string;
|
||||
@ -43,20 +30,7 @@ class ClaimCreditNote extends Model<ClaimCreditNoteAttributes, ClaimCreditNoteCr
|
||||
public invoiceId?: string;
|
||||
public creditNoteNumber?: string;
|
||||
public creditNoteDate?: Date;
|
||||
public creditNoteAmount!: number;
|
||||
public gstRate?: number;
|
||||
public gstAmt?: number;
|
||||
public cgstRate?: number;
|
||||
public cgstAmt?: number;
|
||||
public sgstRate?: number;
|
||||
public sgstAmt?: number;
|
||||
public igstRate?: number;
|
||||
public igstAmt?: number;
|
||||
public utgstRate?: number;
|
||||
public utgstAmt?: number;
|
||||
public cessRate?: number;
|
||||
public cessAmt?: number;
|
||||
public totalAmt?: number;
|
||||
public creditNoteAmount?: number;
|
||||
public sapDocumentNumber?: string;
|
||||
public creditNoteFilePath?: string;
|
||||
public status?: string;
|
||||
@ -112,73 +86,8 @@ ClaimCreditNote.init(
|
||||
},
|
||||
creditNoteAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
field: 'credit_amount'
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
gstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_amt'
|
||||
},
|
||||
cgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_rate'
|
||||
},
|
||||
cgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_amt'
|
||||
},
|
||||
sgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_rate'
|
||||
},
|
||||
sgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_amt'
|
||||
},
|
||||
igstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_rate'
|
||||
},
|
||||
igstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_amt'
|
||||
},
|
||||
utgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_rate'
|
||||
},
|
||||
utgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_amt'
|
||||
},
|
||||
cessRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_rate'
|
||||
},
|
||||
cessAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_amt'
|
||||
},
|
||||
totalAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'total_amt'
|
||||
field: 'credit_amount',
|
||||
},
|
||||
sapDocumentNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
|
||||
@ -6,36 +6,11 @@ interface ClaimInvoiceAttributes {
|
||||
invoiceId: string;
|
||||
requestId: string;
|
||||
invoiceNumber?: string;
|
||||
dmsNumber?: string;
|
||||
invoiceDate?: Date;
|
||||
amount: number;
|
||||
status: string;
|
||||
irn?: string | null;
|
||||
ackNo?: string | null;
|
||||
ackDate?: Date | null;
|
||||
signedInvoice?: string | null;
|
||||
signedInvoiceUrl?: string | null;
|
||||
dealerClaimNumber?: string | null;
|
||||
dealerClaimDate?: Date | null;
|
||||
billingNo?: string | null;
|
||||
billingDate?: Date | null;
|
||||
taxableValue?: number | null;
|
||||
cgstTotal?: number | null;
|
||||
sgstTotal?: number | null;
|
||||
igstTotal?: number | null;
|
||||
utgstTotal?: number | null;
|
||||
cessTotal?: number | null;
|
||||
tcsAmt?: number | null;
|
||||
roundOffAmt?: number | null;
|
||||
placeOfSupply?: string | null;
|
||||
totalValueInWords?: string | null;
|
||||
taxValueInWords?: string | null;
|
||||
creditNature?: string | null;
|
||||
consignorGsin?: string | null;
|
||||
gstinDate?: Date | null;
|
||||
filePath?: string | null;
|
||||
qrCode?: string | null;
|
||||
qrImage?: string | null;
|
||||
amount?: number;
|
||||
dmsNumber?: string;
|
||||
invoiceFilePath?: string;
|
||||
status?: string;
|
||||
errorMessage?: string;
|
||||
generatedAt?: Date;
|
||||
description?: string;
|
||||
@ -43,42 +18,17 @@ 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' | 'invoiceDate' | 'amount' | 'dmsNumber' | 'invoiceFilePath' | 'status' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> {}
|
||||
|
||||
class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAttributes> implements ClaimInvoiceAttributes {
|
||||
public invoiceId!: string;
|
||||
public requestId!: string;
|
||||
public invoiceNumber?: string;
|
||||
public dmsNumber?: string;
|
||||
public invoiceDate?: Date;
|
||||
public amount!: number;
|
||||
public status!: string;
|
||||
public irn?: string | null;
|
||||
public ackNo?: string | null;
|
||||
public ackDate?: Date | null;
|
||||
public signedInvoice?: string | null;
|
||||
public signedInvoiceUrl?: string | null;
|
||||
public dealerClaimNumber?: string | null;
|
||||
public dealerClaimDate?: Date | null;
|
||||
public billingNo?: string | null;
|
||||
public billingDate?: Date | null;
|
||||
public taxableValue?: number | null;
|
||||
public cgstTotal?: number | null;
|
||||
public sgstTotal?: number | null;
|
||||
public igstTotal?: number | null;
|
||||
public utgstTotal?: number | null;
|
||||
public cessTotal?: number | null;
|
||||
public tcsAmt?: number | null;
|
||||
public roundOffAmt?: number | null;
|
||||
public placeOfSupply?: string | null;
|
||||
public totalValueInWords?: string | null;
|
||||
public taxValueInWords?: string | null;
|
||||
public creditNature?: string | null;
|
||||
public consignorGsin?: string | null;
|
||||
public gstinDate?: Date | null;
|
||||
public filePath?: string | null;
|
||||
public qrCode?: string | null;
|
||||
public qrImage?: string | null;
|
||||
public amount?: number;
|
||||
public dmsNumber?: string;
|
||||
public invoiceFilePath?: string;
|
||||
public status?: string;
|
||||
public errorMessage?: string;
|
||||
public generatedAt?: Date;
|
||||
public description?: string;
|
||||
@ -111,11 +61,6 @@ ClaimInvoice.init(
|
||||
allowNull: true,
|
||||
field: 'invoice_number',
|
||||
},
|
||||
dmsNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'dms_number',
|
||||
},
|
||||
invoiceDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
@ -123,143 +68,23 @@ ClaimInvoice.init(
|
||||
},
|
||||
amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
allowNull: true,
|
||||
field: 'invoice_amount',
|
||||
},
|
||||
dmsNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'dms_number',
|
||||
},
|
||||
invoiceFilePath: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'invoice_file_path',
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 'PENDING',
|
||||
field: 'generation_status'
|
||||
},
|
||||
irn: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true
|
||||
},
|
||||
ackNo: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'ack_no'
|
||||
},
|
||||
ackDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'ack_date'
|
||||
},
|
||||
signedInvoice: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'signed_invoice'
|
||||
},
|
||||
signedInvoiceUrl: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'signed_invoice_url'
|
||||
},
|
||||
dealerClaimNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'dealer_claim_number'
|
||||
},
|
||||
dealerClaimDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
field: 'dealer_claim_date'
|
||||
},
|
||||
billingNo: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'billing_no'
|
||||
},
|
||||
billingDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
field: 'billing_date'
|
||||
},
|
||||
taxableValue: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'taxable_value'
|
||||
},
|
||||
cgstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_total'
|
||||
},
|
||||
sgstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_total'
|
||||
},
|
||||
igstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_total'
|
||||
},
|
||||
utgstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_total'
|
||||
},
|
||||
cessTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_total'
|
||||
},
|
||||
tcsAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'tcs_amt'
|
||||
},
|
||||
roundOffAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'round_off_amt'
|
||||
},
|
||||
placeOfSupply: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'place_of_supply'
|
||||
},
|
||||
totalValueInWords: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'total_value_in_words'
|
||||
},
|
||||
taxValueInWords: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'tax_value_in_words'
|
||||
},
|
||||
creditNature: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'credit_nature'
|
||||
},
|
||||
consignorGsin: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'consignor_gsin'
|
||||
},
|
||||
gstinDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
field: 'gstin_date'
|
||||
},
|
||||
filePath: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'file_path'
|
||||
},
|
||||
qrCode: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'qr_code'
|
||||
},
|
||||
qrImage: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'qr_image'
|
||||
field: 'generation_status',
|
||||
},
|
||||
errorMessage: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
@ -9,25 +9,11 @@ interface DealerCompletionExpenseAttributes {
|
||||
completionId?: string | null;
|
||||
description: string;
|
||||
amount: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
utgstRate?: number;
|
||||
utgstAmt?: number;
|
||||
cessRate?: number;
|
||||
cessAmt?: number;
|
||||
totalAmt?: number;
|
||||
expenseDate: Date;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface DealerCompletionExpenseCreationAttributes extends Optional<DealerCompletionExpenseAttributes, 'expenseId' | 'completionId' | 'createdAt' | 'updatedAt'> { }
|
||||
interface DealerCompletionExpenseCreationAttributes extends Optional<DealerCompletionExpenseAttributes, 'expenseId' | 'completionId' | 'createdAt' | 'updatedAt'> {}
|
||||
|
||||
class DealerCompletionExpense extends Model<DealerCompletionExpenseAttributes, DealerCompletionExpenseCreationAttributes> implements DealerCompletionExpenseAttributes {
|
||||
public expenseId!: string;
|
||||
@ -35,20 +21,6 @@ class DealerCompletionExpense extends Model<DealerCompletionExpenseAttributes, D
|
||||
public completionId?: string | null;
|
||||
public description!: string;
|
||||
public amount!: number;
|
||||
public gstRate?: number;
|
||||
public gstAmt?: number;
|
||||
public cgstRate?: number;
|
||||
public cgstAmt?: number;
|
||||
public sgstRate?: number;
|
||||
public sgstAmt?: number;
|
||||
public igstRate?: number;
|
||||
public igstAmt?: number;
|
||||
public utgstRate?: number;
|
||||
public utgstAmt?: number;
|
||||
public cessRate?: number;
|
||||
public cessAmt?: number;
|
||||
public totalAmt?: number;
|
||||
public expenseDate!: Date;
|
||||
public createdAt!: Date;
|
||||
public updatedAt!: Date;
|
||||
}
|
||||
@ -91,76 +63,6 @@ DealerCompletionExpense.init(
|
||||
allowNull: false,
|
||||
field: 'amount',
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
gstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_amt'
|
||||
},
|
||||
cgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_rate'
|
||||
},
|
||||
cgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_amt'
|
||||
},
|
||||
sgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_rate'
|
||||
},
|
||||
sgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_amt'
|
||||
},
|
||||
igstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_rate'
|
||||
},
|
||||
igstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_amt'
|
||||
},
|
||||
utgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_rate'
|
||||
},
|
||||
utgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_amt'
|
||||
},
|
||||
cessRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_rate'
|
||||
},
|
||||
cessAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_amt'
|
||||
},
|
||||
totalAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'total_amt'
|
||||
},
|
||||
expenseDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
field: 'expense_date',
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
|
||||
@ -9,25 +9,12 @@ interface DealerProposalCostItemAttributes {
|
||||
requestId: string;
|
||||
itemDescription: string;
|
||||
amount: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
utgstRate?: number;
|
||||
utgstAmt?: number;
|
||||
cessRate?: number;
|
||||
cessAmt?: number;
|
||||
totalAmt?: number;
|
||||
itemOrder: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface DealerProposalCostItemCreationAttributes extends Optional<DealerProposalCostItemAttributes, 'costItemId' | 'itemOrder' | 'createdAt' | 'updatedAt'> { }
|
||||
interface DealerProposalCostItemCreationAttributes extends Optional<DealerProposalCostItemAttributes, 'costItemId' | 'itemOrder' | 'createdAt' | 'updatedAt'> {}
|
||||
|
||||
class DealerProposalCostItem extends Model<DealerProposalCostItemAttributes, DealerProposalCostItemCreationAttributes> implements DealerProposalCostItemAttributes {
|
||||
public costItemId!: string;
|
||||
@ -35,19 +22,6 @@ class DealerProposalCostItem extends Model<DealerProposalCostItemAttributes, Dea
|
||||
public requestId!: string;
|
||||
public itemDescription!: string;
|
||||
public amount!: number;
|
||||
public gstRate?: number;
|
||||
public gstAmt?: number;
|
||||
public cgstRate?: number;
|
||||
public cgstAmt?: number;
|
||||
public sgstRate?: number;
|
||||
public sgstAmt?: number;
|
||||
public igstRate?: number;
|
||||
public igstAmt?: number;
|
||||
public utgstRate?: number;
|
||||
public utgstAmt?: number;
|
||||
public cessRate?: number;
|
||||
public cessAmt?: number;
|
||||
public totalAmt?: number;
|
||||
public itemOrder!: number;
|
||||
public createdAt!: Date;
|
||||
public updatedAt!: Date;
|
||||
@ -92,71 +66,6 @@ DealerProposalCostItem.init(
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
gstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_amt'
|
||||
},
|
||||
cgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_rate'
|
||||
},
|
||||
cgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_amt'
|
||||
},
|
||||
sgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_rate'
|
||||
},
|
||||
sgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_amt'
|
||||
},
|
||||
igstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_rate'
|
||||
},
|
||||
igstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_amt'
|
||||
},
|
||||
utgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_rate'
|
||||
},
|
||||
utgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_amt'
|
||||
},
|
||||
cessRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_rate'
|
||||
},
|
||||
cessAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_amt'
|
||||
},
|
||||
totalAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'total_amt'
|
||||
},
|
||||
itemOrder: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
|
||||
@ -10,42 +10,25 @@
|
||||
* 5. Configs are auto-seeded by configSeed.service.ts on server start (30 configs)
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// 1. Initialize dotenv FIRST before any other internal imports
|
||||
// This ensures that modules imported later (like GoogleSecretManagerService)
|
||||
// can see the environment variables in their constructor/initialization
|
||||
const envPath = path.resolve(process.cwd(), '.env');
|
||||
if (fs.existsSync(envPath)) {
|
||||
dotenv.config({ path: envPath });
|
||||
} else {
|
||||
const parentEnvPath = path.resolve(process.cwd(), '..', '.env');
|
||||
if (fs.existsSync(parentEnvPath)) {
|
||||
dotenv.config({ path: parentEnvPath });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Now import internal modules
|
||||
import { Client } from 'pg';
|
||||
import { QueryTypes } from 'sequelize';
|
||||
import { initializeGoogleSecretManager } from '../services/googleSecretManager.service';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// DB constants moved inside functions to ensure secrets are loaded first
|
||||
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
|
||||
async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
// DB constants moved inside functions to ensure secrets are loaded first
|
||||
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
|
||||
const DB_HOST = process.env.DB_HOST || 'localhost';
|
||||
const DB_PORT = parseInt(process.env.DB_PORT || '5432');
|
||||
const DB_USER = process.env.DB_USER || 'postgres';
|
||||
const DB_PASSWORD = typeof process.env.DB_PASSWORD === 'string' ? process.env.DB_PASSWORD : '';
|
||||
const DB_NAME = process.env.DB_NAME || 're_workflow_db';
|
||||
|
||||
console.log(`[Setup Debug] DB_HOST: ${DB_HOST}, DB_USER: ${DB_USER}, SSL: ${isSSL}, HasPassword: ${!!DB_PASSWORD}`);
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD || '';
|
||||
const DB_NAME = process.env.DB_NAME || 'royal_enfield_workflow';
|
||||
|
||||
const client = new Client({
|
||||
host: DB_HOST,
|
||||
@ -157,7 +140,6 @@ async function runMigrations(): Promise<void> {
|
||||
const m42 = require('../migrations/20250125-create-activity-types');
|
||||
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 migrations = [
|
||||
{ name: '2025103000-create-users', module: m0 },
|
||||
@ -207,7 +189,6 @@ async function runMigrations(): Promise<void> {
|
||||
{ name: '20250125-create-activity-types', module: m42 },
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
// Dynamically import sequelize after secrets are loaded
|
||||
|
||||
@ -47,7 +47,6 @@ import * as m41 from '../migrations/20250120-create-dealers-table';
|
||||
import * as m42 from '../migrations/20250125-create-activity-types';
|
||||
import * as m43 from '../migrations/20260113-redesign-dealer-claim-history';
|
||||
import * as m44 from '../migrations/20260123-fix-template-id-schema';
|
||||
import * as m45 from '../migrations/20260209-add-gst-and-pwc-fields';
|
||||
|
||||
interface Migration {
|
||||
name: string;
|
||||
@ -109,7 +108,6 @@ const migrations: Migration[] = [
|
||||
{ name: '20250125-create-activity-types', module: m42 },
|
||||
{ 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 }
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@ -4,10 +4,13 @@ import path from 'path';
|
||||
|
||||
// Load environment variables from .env file FIRST
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
||||
// Stop queue metrics collection on shutdown
|
||||
// Note: This is imported statically but doesn't trigger database/queue activity until called
|
||||
import { initializeGoogleSecretManager } from './services/googleSecretManager.service';
|
||||
import { seedDefaultActivityTypes } from './services/activityTypeSeed.service';
|
||||
import { stopQueueMetrics } from './utils/queueMetrics';
|
||||
|
||||
// Dynamic imports will be used inside startServer to ensure secrets are loaded first
|
||||
import { emailService } from './services/email.service';
|
||||
|
||||
const PORT: number = parseInt(process.env.PORT || '5000', 10);
|
||||
|
||||
// Start server
|
||||
@ -15,16 +18,12 @@ const startServer = async (): Promise<void> => {
|
||||
try {
|
||||
// Initialize Google Secret Manager before starting server
|
||||
// This will merge secrets from GCS into process.env if enabled
|
||||
const { initializeGoogleSecretManager } = require('./services/googleSecretManager.service');
|
||||
console.log('🔐 Initializing secrets...');
|
||||
await initializeGoogleSecretManager();
|
||||
|
||||
const { default: app, initializeAppDatabase } = require('./app');
|
||||
// Dynamically import everything else after secrets are loaded
|
||||
const app = require('./app').default;
|
||||
const { initSocket } = require('./realtime/socket');
|
||||
|
||||
// Initialize database connection explicitly after secrets are loaded
|
||||
await initializeAppDatabase();
|
||||
|
||||
require('./queues/tatWorker'); // Initialize TAT worker
|
||||
const { logTatConfig } = require('./config/tat.config');
|
||||
const { logSystemConfig } = require('./config/system.config');
|
||||
@ -35,12 +34,22 @@ const startServer = async (): Promise<void> => {
|
||||
const { initializeQueueMetrics } = require('./utils/queueMetrics');
|
||||
const { emailService } = require('./services/email.service');
|
||||
|
||||
// Initialize email service after secrets are loaded
|
||||
// Re-initialize email service after secrets are loaded (in case SMTP credentials were loaded)
|
||||
// This ensures the email service uses production SMTP if credentials are available
|
||||
try {
|
||||
await emailService.initialize();
|
||||
console.log('📧 Email service initialized');
|
||||
console.log('📧 Email service re-initialized after secrets loaded');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Email service initialization warning (will use test account if SMTP not configured):', error);
|
||||
console.warn('⚠️ Email service re-initialization warning (will use test account if SMTP not configured):', error);
|
||||
}
|
||||
|
||||
// Re-initialize email service after secrets are loaded (in case SMTP credentials were loaded)
|
||||
// This ensures the email service uses production SMTP if credentials are available
|
||||
try {
|
||||
await emailService.initialize();
|
||||
console.log('📧 Email service re-initialized after secrets loaded');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Email service re-initialization warning (will use test account if SMTP not configured):', error);
|
||||
}
|
||||
|
||||
const server = http.createServer(app);
|
||||
@ -54,7 +63,6 @@ const startServer = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
// Seed default activity types if table is empty
|
||||
const { seedDefaultActivityTypes } = require('./services/activityTypeSeed.service');
|
||||
try {
|
||||
await seedDefaultActivityTypes();
|
||||
} catch (error) {
|
||||
|
||||
@ -247,42 +247,3 @@ export async function searchDealers(searchTerm: string, limit: number = 10): Pro
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find dealer in local table by code or email (tiered lookup)
|
||||
* Used as a fallback until external API is available
|
||||
* @param dealerCode - Optional dealer code (dlrcode)
|
||||
* @param email - Optional email (domainId or dealerPrincipalEmailId)
|
||||
*/
|
||||
export async function findDealerLocally(dealerCode?: string, email?: string): Promise<DealerInfo | null> {
|
||||
try {
|
||||
// 1. Try Lookup by Dealer Code
|
||||
if (dealerCode) {
|
||||
const dealer = await getDealerByCode(dealerCode);
|
||||
if (dealer) return dealer;
|
||||
}
|
||||
|
||||
// 2. Try Lookup by Email (checks both domainId and dealerPrincipalEmailId)
|
||||
if (email) {
|
||||
const dealer = await Dealer.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ domainId: { [Op.iLike]: email.toLowerCase() } as any },
|
||||
{ dealerPrincipalEmailId: { [Op.iLike]: email.toLowerCase() } as any }
|
||||
],
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
|
||||
if (dealer) {
|
||||
// reuse same logic as getDealerByEmail for consistency
|
||||
return getDealerByEmail(dealer.domainId || dealer.dealerPrincipalEmailId || email);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.error('[DealerService] Error in local dealer fallback lookup:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,16 +18,12 @@ import { DealerClaimApprovalService } from './dealerClaimApproval.service';
|
||||
import { generateRequestNumber } from '../utils/helpers';
|
||||
import { Priority, WorkflowStatus, ApprovalStatus, ParticipantType } from '../types/common.types';
|
||||
import { sapIntegrationService } from './sapIntegration.service';
|
||||
import { pwcIntegrationService } from './pwcIntegration.service';
|
||||
import { dmsIntegrationService } from './dmsIntegration.service';
|
||||
import { notificationService } from './notification.service';
|
||||
import { activityService } from './activity.service';
|
||||
import { UserService } from './user.service';
|
||||
import { dmsIntegrationService } from './dmsIntegration.service';
|
||||
import { findDealerLocally } from './dealer.service';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dealer Claim Service
|
||||
* Handles business logic specific to dealer claim management workflow
|
||||
@ -87,15 +83,6 @@ export class DealerClaimService {
|
||||
throw new Error('Initiator not found');
|
||||
}
|
||||
|
||||
// Fallback: Enrichment from local dealer table if data is missing or incomplete
|
||||
const localDealer = await findDealerLocally(claimData.dealerCode, claimData.dealerEmail);
|
||||
if (localDealer) {
|
||||
logger.info(`[DealerClaimService] Enriched claim request with local dealer data: ${localDealer.dealerCode}`);
|
||||
claimData.dealerName = claimData.dealerName || localDealer.dealerName;
|
||||
claimData.dealerEmail = claimData.dealerEmail || localDealer.dealerPrincipalEmailId || localDealer.email;
|
||||
claimData.dealerPhone = claimData.dealerPhone || localDealer.phone;
|
||||
}
|
||||
|
||||
// Validate approvers array is provided
|
||||
if (!claimData.approvers || !Array.isArray(claimData.approvers) || claimData.approvers.length === 0) {
|
||||
throw new Error('Approvers array is required. Please assign approvers for all workflow steps.');
|
||||
@ -1273,19 +1260,6 @@ export class DealerClaimService {
|
||||
requestId,
|
||||
itemDescription: item.description || item.itemDescription || '',
|
||||
amount: Number(item.amount) || 0,
|
||||
gstRate: Number(item.gstRate) || 0,
|
||||
gstAmt: Number(item.gstAmt) || 0,
|
||||
cgstRate: Number(item.cgstRate) || 0,
|
||||
cgstAmt: Number(item.cgstAmt) || 0,
|
||||
sgstRate: Number(item.sgstRate) || 0,
|
||||
sgstAmt: Number(item.sgstAmt) || 0,
|
||||
igstRate: Number(item.igstRate) || 0,
|
||||
igstAmt: Number(item.igstAmt) || 0,
|
||||
utgstRate: Number(item.utgstRate) || 0,
|
||||
utgstAmt: Number(item.utgstAmt) || 0,
|
||||
cessRate: Number(item.cessRate) || 0,
|
||||
cessAmt: Number(item.cessAmt) || 0,
|
||||
totalAmt: Number(item.totalAmt) || Number(item.amount) || 0,
|
||||
itemOrder: index
|
||||
}));
|
||||
|
||||
@ -1423,21 +1397,7 @@ export class DealerClaimService {
|
||||
requestId,
|
||||
completionId,
|
||||
description: item.description,
|
||||
amount: Number(item.amount) || 0,
|
||||
gstRate: Number(item.gstRate) || 0,
|
||||
gstAmt: Number(item.gstAmt) || 0,
|
||||
cgstRate: Number(item.cgstRate) || 0,
|
||||
cgstAmt: Number(item.cgstAmt) || 0,
|
||||
sgstRate: Number(item.sgstRate) || 0,
|
||||
sgstAmt: Number(item.sgstAmt) || 0,
|
||||
igstRate: Number(item.igstRate) || 0,
|
||||
igstAmt: Number(item.igstAmt) || 0,
|
||||
utgstRate: Number(item.utgstRate) || 0,
|
||||
utgstAmt: Number(item.utgstAmt) || 0,
|
||||
cessRate: Number(item.cessRate) || 0,
|
||||
cessAmt: Number(item.cessAmt) || 0,
|
||||
totalAmt: Number(item.totalAmt) || Number(item.amount) || 0,
|
||||
expenseDate: item.date instanceof Date ? item.date : (item.date ? new Date(item.date) : (completionData.activityCompletionDate || new Date())),
|
||||
amount: item.amount,
|
||||
}));
|
||||
await DealerCompletionExpense.bulkCreate(expenseRows);
|
||||
}
|
||||
@ -1914,31 +1874,33 @@ export class DealerClaimService {
|
||||
|| budgetTracking?.initialEstimatedBudget
|
||||
|| 0;
|
||||
|
||||
const invoiceResult = await pwcIntegrationService.generateSignedInvoice(requestId);
|
||||
const invoiceResult = await dmsIntegrationService.generateEInvoice({
|
||||
requestNumber,
|
||||
dealerCode: claimDetails.dealerCode,
|
||||
dealerName: claimDetails.dealerName,
|
||||
amount: invoiceAmount,
|
||||
description: invoiceData?.description || `E-Invoice for claim request ${requestNumber}`,
|
||||
ioNumber: internalOrder?.ioNumber || undefined,
|
||||
});
|
||||
|
||||
if (!invoiceResult.success) {
|
||||
throw new Error(`Failed to generate signed e-invoice via PWC: ${invoiceResult.error}`);
|
||||
throw new Error(`Failed to generate e-invoice: ${invoiceResult.error}`);
|
||||
}
|
||||
|
||||
await ClaimInvoice.upsert({
|
||||
requestId,
|
||||
invoiceNumber: invoiceResult.ackNo, // Using Ack No as primary identifier for now
|
||||
invoiceDate: invoiceResult.ackDate instanceof Date ? invoiceResult.ackDate : (invoiceResult.ackDate ? new Date(invoiceResult.ackDate) : new Date()),
|
||||
irn: invoiceResult.irn,
|
||||
ackNo: invoiceResult.ackNo,
|
||||
ackDate: invoiceResult.ackDate instanceof Date ? invoiceResult.ackDate : (invoiceResult.ackDate ? new Date(invoiceResult.ackDate) : null),
|
||||
signedInvoice: invoiceResult.signedInvoice,
|
||||
qrCode: invoiceResult.qrCode,
|
||||
qrImage: invoiceResult.qrImage,
|
||||
invoiceNumber: invoiceResult.eInvoiceNumber,
|
||||
invoiceDate: invoiceResult.invoiceDate || new Date(),
|
||||
dmsNumber: invoiceResult.dmsNumber,
|
||||
amount: invoiceAmount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
description: invoiceData?.description || `PWC Signed Invoice for claim request ${requestNumber}`,
|
||||
description: invoiceData?.description || `E-Invoice for claim request ${requestNumber}`,
|
||||
});
|
||||
|
||||
logger.info(`[DealerClaimService] Signed Invoice generated via PWC for request: ${requestId}`, {
|
||||
ackNo: invoiceResult.ackNo,
|
||||
irn: invoiceResult.irn
|
||||
logger.info(`[DealerClaimService] E-Invoice generated via DMS for request: ${requestId}`, {
|
||||
eInvoiceNumber: invoiceResult.eInvoiceNumber,
|
||||
dmsNumber: invoiceResult.dmsNumber
|
||||
});
|
||||
} else {
|
||||
// Manual entry - just update the fields
|
||||
@ -1947,7 +1909,7 @@ export class DealerClaimService {
|
||||
invoiceNumber: invoiceData.eInvoiceNumber,
|
||||
invoiceDate: invoiceData.eInvoiceDate || new Date(),
|
||||
dmsNumber: invoiceData.dmsNumber,
|
||||
amount: Number(invoiceData.amount) || 0,
|
||||
amount: invoiceData.amount,
|
||||
status: 'UPDATED',
|
||||
generatedAt: new Date(),
|
||||
description: invoiceData.description,
|
||||
@ -2105,7 +2067,7 @@ export class DealerClaimService {
|
||||
invoiceId: claimInvoice.invoiceId,
|
||||
creditNoteNumber: creditNoteResult.creditNoteNumber,
|
||||
creditNoteDate: creditNoteResult.creditNoteDate || new Date(),
|
||||
creditNoteAmount: Number(creditNoteResult.creditNoteAmount) || 0,
|
||||
creditNoteAmount: creditNoteResult.creditNoteAmount,
|
||||
status: 'GENERATED',
|
||||
confirmedAt: new Date(),
|
||||
reason: creditNoteData?.reason || 'Claim settlement',
|
||||
@ -2138,7 +2100,7 @@ export class DealerClaimService {
|
||||
invoiceId: claimInvoice?.invoiceId || undefined, // Allow undefined if no invoice
|
||||
creditNoteNumber: creditNoteData.creditNoteNumber,
|
||||
creditNoteDate: creditNoteData.creditNoteDate || new Date(),
|
||||
creditNoteAmount: Number(creditNoteData.creditNoteAmount) || 0,
|
||||
creditNoteAmount: creditNoteData.creditNoteAmount,
|
||||
status: 'UPDATED',
|
||||
confirmedAt: new Date(),
|
||||
reason: creditNoteData?.reason,
|
||||
|
||||
@ -119,7 +119,7 @@ export class DMSWebhookService {
|
||||
amount: payload.total_amount || payload.claim_amount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
filePath: payload.invoice_file_path || null,
|
||||
invoiceFilePath: payload.invoice_file_path || null,
|
||||
errorMessage: payload.error_message || null,
|
||||
description: this.buildInvoiceDescription(payload),
|
||||
});
|
||||
@ -137,7 +137,7 @@ export class DMSWebhookService {
|
||||
amount: payload.total_amount || payload.claim_amount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
filePath: payload.invoice_file_path || null,
|
||||
invoiceFilePath: payload.invoice_file_path || null,
|
||||
errorMessage: payload.error_message || null,
|
||||
// Store additional DMS data in description or separate fields if needed
|
||||
description: this.buildInvoiceDescription(payload),
|
||||
@ -296,7 +296,7 @@ export class DMSWebhookService {
|
||||
*/
|
||||
private buildInvoiceDescription(payload: any): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
|
||||
if (payload.irn_no) {
|
||||
parts.push(`IRN: ${payload.irn_no}`);
|
||||
}
|
||||
@ -318,7 +318,7 @@ export class DMSWebhookService {
|
||||
*/
|
||||
private buildCreditNoteDescription(payload: any): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
|
||||
if (payload.irn_no) {
|
||||
parts.push(`IRN: ${payload.irn_no}`);
|
||||
}
|
||||
@ -404,7 +404,7 @@ export class DMSWebhookService {
|
||||
attributes: ['userId'],
|
||||
});
|
||||
dealerUserId = dealerUser?.userId || null;
|
||||
|
||||
|
||||
if (dealerUserId) {
|
||||
logger.info('[DMSWebhook] Found dealer user for notification', {
|
||||
requestId,
|
||||
@ -512,9 +512,9 @@ export class DMSWebhookService {
|
||||
const dealerClaimService = new DealerClaimService();
|
||||
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
||||
const invoiceNumber = invoice?.invoiceNumber || 'N/A';
|
||||
|
||||
|
||||
await dealerClaimService.logEInvoiceGenerationActivity(requestId, invoiceNumber);
|
||||
|
||||
|
||||
logger.info('[DMSWebhook] E-Invoice Generation activity logged successfully', {
|
||||
requestId,
|
||||
requestNumber,
|
||||
|
||||
@ -264,6 +264,10 @@ export class EmailService {
|
||||
// Singleton instance
|
||||
export const emailService = new EmailService();
|
||||
|
||||
// Note: initialize() is now called explicitly in server.ts
|
||||
// to ensure it happens after secrets are loaded.
|
||||
// Initialize on import (will use test account if SMTP not configured)
|
||||
// Note: If secrets are loaded later, the service will re-initialize automatically
|
||||
// when sendEmail is called (if SMTP credentials become available)
|
||||
emailService.initialize().catch(error => {
|
||||
logger.error('Failed to initialize email service:', error);
|
||||
});
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ class GoogleSecretManagerService {
|
||||
private secretPrefix: string;
|
||||
private secretMap: Record<string, string> = {};
|
||||
private isInitialized: boolean = false;
|
||||
private isLoaded: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.projectId = process.env.GCP_PROJECT_ID || '';
|
||||
@ -207,15 +206,6 @@ class GoogleSecretManagerService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isLoaded && !secretNames) {
|
||||
logger.debug('[Secret Manager] ℹ️ Secrets already loaded in this process, skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.projectId) {
|
||||
this.projectId = process.env.GCP_PROJECT_ID || '';
|
||||
}
|
||||
|
||||
if (!this.projectId) {
|
||||
logger.warn('[Secret Manager] GCP_PROJECT_ID not set, skipping Google Secret Manager');
|
||||
return;
|
||||
@ -247,40 +237,33 @@ class GoogleSecretManagerService {
|
||||
}
|
||||
|
||||
// Load each secret
|
||||
const uatSecrets = ['OKTA_CLIENT_ID', 'OKTA_CLIENT_SECRET', 'OKTA_API_TOKEN', 'OKTA_DOMAIN', 'DB_PASSWORD'];
|
||||
const isUat = process.env.NODE_ENV === 'uat';
|
||||
|
||||
for (const secretName of secretsToLoad) {
|
||||
// Handle UAT-specific secret names if in UAT environment
|
||||
let secretNameToFetch = secretName;
|
||||
if (isUat && uatSecrets.includes(secretName)) {
|
||||
secretNameToFetch = `${secretName}_UAT`;
|
||||
logger.info(`[Secret Manager] UAT mode: Fetching source ${secretNameToFetch} for ${secretName}`);
|
||||
const fullSecretName = this.secretPrefix
|
||||
? `${this.secretPrefix}-${secretName}`
|
||||
: secretName;
|
||||
|
||||
// Log OKTA and EMAIL secret attempts in detail
|
||||
const isOktaSecret = /^OKTA_/i.test(secretName);
|
||||
const isEmailSecret = /^EMAIL_|^SMTP_/i.test(secretName);
|
||||
if (isOktaSecret || isEmailSecret) {
|
||||
logger.info(`[Secret Manager] Attempting to load: ${secretName} (full name: ${fullSecretName})`);
|
||||
}
|
||||
|
||||
const fullSecretName = this.secretPrefix
|
||||
? `${this.secretPrefix}-${secretNameToFetch}`
|
||||
: secretNameToFetch;
|
||||
|
||||
const rawValue = await this.getSecret(secretNameToFetch);
|
||||
const secretValue = rawValue ? rawValue.trim() : null;
|
||||
const secretValue = await this.getSecret(secretName);
|
||||
|
||||
if (secretValue !== null) {
|
||||
const envVarName = this.getEnvVarName(secretName);
|
||||
loadedSecrets[envVarName] = secretValue;
|
||||
loadedCount++;
|
||||
|
||||
// Print masked value for verification as requested by user
|
||||
// Note: Trailing/leading whitespace is now trimmed to prevent auth errors
|
||||
const maskedValue = secretValue.length > 8
|
||||
? `${secretValue.substring(0, 3)}...${secretValue.substring(secretValue.length - 3)}`
|
||||
: '***';
|
||||
|
||||
logger.info(`[Secret Manager] ✅ Loaded: ${secretNameToFetch} -> process.env.${envVarName} (Value: ${maskedValue})`);
|
||||
if (isOktaSecret || isEmailSecret) {
|
||||
logger.info(`[Secret Manager] ✅ Successfully loaded: ${secretName} -> ${envVarName}`);
|
||||
}
|
||||
} else {
|
||||
// Track which secrets weren't found for better logging
|
||||
notFoundSecrets.push(fullSecretName);
|
||||
logger.warn(`[Secret Manager] ❌ Not found: ${secretNameToFetch} (searched as: ${fullSecretName})`);
|
||||
if (isOktaSecret || isEmailSecret) {
|
||||
logger.warn(`[Secret Manager] ❌ Not found: ${secretName} (searched as: ${fullSecretName})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,18 +271,18 @@ class GoogleSecretManagerService {
|
||||
// Log when overriding existing env vars vs setting new ones
|
||||
for (const [envVar, value] of Object.entries(loadedSecrets)) {
|
||||
const existingValue = process.env[envVar];
|
||||
const isAlreadySet = existingValue !== undefined && existingValue !== '';
|
||||
|
||||
if (isAlreadySet) {
|
||||
logger.debug(`[Secret Manager] ℹ️ Skipping ${envVar}: Already set in local environment`);
|
||||
continue;
|
||||
}
|
||||
const isOverriding = existingValue !== undefined;
|
||||
|
||||
process.env[envVar] = value;
|
||||
logger.debug(`[Secret Manager] ✨ Set new env var: ${envVar} (from Secret Manager)`);
|
||||
|
||||
// Log override behavior for debugging
|
||||
if (isOverriding) {
|
||||
logger.debug(`[Secret Manager] 🔄 Overrode existing env var: ${envVar} (was: ${existingValue ? 'set' : 'undefined'}, now: from Secret Manager)`);
|
||||
} else {
|
||||
logger.debug(`[Secret Manager] ✨ Set new env var: ${envVar} (from Secret Manager)`);
|
||||
}
|
||||
}
|
||||
|
||||
this.isLoaded = true;
|
||||
logger.info(`[Secret Manager] ✅ Successfully loaded ${loadedCount}/${secretsToLoad.length} secrets`);
|
||||
|
||||
if (loadedCount > 0) {
|
||||
@ -362,10 +345,9 @@ class GoogleSecretManagerService {
|
||||
'SESSION_SECRET',
|
||||
|
||||
// Okta/SSO
|
||||
'OKTA_CLIENT_ID',
|
||||
'OKTA_CLIENT_SECRET',
|
||||
'OKTA_API_TOKEN',
|
||||
'OKTA_DOMAIN',
|
||||
//'OKTA_CLIENT_ID',
|
||||
//'OKTA_CLIENT_SECRET',
|
||||
//'OKTA_API_TOKEN',
|
||||
|
||||
// Email
|
||||
'SMTP_HOST',
|
||||
|
||||
@ -1,171 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import logger from '../utils/logger';
|
||||
import { Dealer } from '../models/Dealer';
|
||||
import { ActivityType } from '../models/ActivityType';
|
||||
import { WorkflowRequest } from '../models/WorkflowRequest';
|
||||
import { ClaimInvoice } from '../models/ClaimInvoice';
|
||||
import { InternalOrder } from '../models/InternalOrder';
|
||||
|
||||
/**
|
||||
* PWC E-Invoice Integration Service
|
||||
* Handles communication with PWC API for signed invoice generation
|
||||
*/
|
||||
export class PWCIntegrationService {
|
||||
private apiUrl: string;
|
||||
private appKey: string;
|
||||
private appSecret: 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 || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve GL Code based on Activity Type and Internal Order
|
||||
*/
|
||||
private async resolveGLCode(activityTypeId: string, ioNumber?: string): Promise<string> {
|
||||
const activity = await ActivityType.findByPk(activityTypeId);
|
||||
if (activity?.glCode) {
|
||||
return activity.glCode;
|
||||
}
|
||||
|
||||
// Default Fallback or IO based logic if required
|
||||
// Based on "IO GL will be changed" comment in user screenshot
|
||||
if (ioNumber) {
|
||||
const io = await InternalOrder.findOne({ where: { ioNumber } });
|
||||
// Logic to derive GL from IO if needed
|
||||
}
|
||||
|
||||
return '610000'; // Default placeholder
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Signed Invoice via PWC API
|
||||
*/
|
||||
async generateSignedInvoice(requestId: string): Promise<{
|
||||
success: boolean;
|
||||
irn?: string;
|
||||
ackNo?: string;
|
||||
ackDate?: Date | string;
|
||||
signedInvoice?: string;
|
||||
qrCode?: string;
|
||||
qrImage?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const request = await WorkflowRequest.findByPk(requestId, {
|
||||
include: ['claimDetails', 'initiator']
|
||||
});
|
||||
|
||||
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 } });
|
||||
|
||||
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
|
||||
}
|
||||
],
|
||||
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 }
|
||||
});
|
||||
|
||||
// Parse PWC Response based on provided structure
|
||||
// Sample response is an array: [{ pwc_response, irp_response, qr_b64_encoded }]
|
||||
const responseData = Array.isArray(response.data) ? response.data[0] : response.data;
|
||||
const irpData = responseData?.irp_response?.data;
|
||||
const nicData = irpData?.nic_response_data;
|
||||
const qrB64 = responseData?.qr_b64_encoded;
|
||||
|
||||
let irn = nicData?.Irn;
|
||||
let ackNo = nicData?.AckNo;
|
||||
let ackDate = nicData?.AckDt;
|
||||
let signedInvoice = nicData?.SignedInvoice;
|
||||
let qrCode = nicData?.SignedQRCode;
|
||||
|
||||
// Handle Duplicate IRN Case
|
||||
if (!irn && irpData?.InfoDtls) {
|
||||
const dupInfo = irpData.InfoDtls.find((info: any) => info.InfCd === 'DUPIRN');
|
||||
if (dupInfo?.Desc) {
|
||||
irn = dupInfo.Desc.Irn;
|
||||
ackNo = dupInfo.Desc.AckNo;
|
||||
ackDate = dupInfo.Desc.AckDt;
|
||||
logger.info(`[PWC] Handled duplicate IRN for ${request.requestNumber}: ${irn}`);
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
irn,
|
||||
ackNo: String(ackNo),
|
||||
ackDate: ackDate ? new Date(ackDate) : undefined,
|
||||
signedInvoice,
|
||||
qrCode,
|
||||
qrImage: qrB64
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[PWC] Error generating e-invoice:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const pwcIntegrationService = new PWCIntegrationService();
|
||||
@ -26,28 +26,28 @@ const maskSensitiveData = (value: any): any => {
|
||||
// Mask patterns like "API_KEY = abc123" or "password: secret"
|
||||
let masked = value.replace(SENSITIVE_PATTERN, (match, key, val) => {
|
||||
if (val && val.length > 0) {
|
||||
const maskedVal = val.length > 4
|
||||
const maskedVal = val.length > 4
|
||||
? val.substring(0, 2) + '***' + val.substring(val.length - 2)
|
||||
: '***';
|
||||
return `${key}=${maskedVal}`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
|
||||
// Mask standalone tokens/keys (long alphanumeric strings that look like secrets)
|
||||
// e.g., "sk-abc123xyz789..." or "ghp_xxxx..."
|
||||
masked = masked.replace(
|
||||
/\b(sk-|ghp_|gho_|github_pat_|xox[baprs]-|Bearer\s+)([A-Za-z0-9_-]{20,})/gi,
|
||||
(match, prefix, token) => `${prefix}${'*'.repeat(8)}...`
|
||||
);
|
||||
|
||||
|
||||
return masked;
|
||||
}
|
||||
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(maskSensitiveData);
|
||||
}
|
||||
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
const masked: any = {};
|
||||
for (const [k, v] of Object.entries(value)) {
|
||||
@ -61,7 +61,7 @@ const maskSensitiveData = (value: any): any => {
|
||||
}
|
||||
return masked;
|
||||
}
|
||||
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
@ -96,7 +96,7 @@ const transports: winston.transport[] = [
|
||||
if (process.env.LOKI_HOST) {
|
||||
try {
|
||||
const LokiTransport = require('winston-loki');
|
||||
|
||||
|
||||
const lokiTransportOptions: any = {
|
||||
host: process.env.LOKI_HOST,
|
||||
labels: appMeta,
|
||||
@ -124,21 +124,22 @@ if (process.env.LOKI_HOST) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============ CONSOLE TRANSPORT ============
|
||||
// Enabled for all environments to ensure visibility in terminal/container logs
|
||||
transports.push(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize({ all: !isProduction }),
|
||||
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
||||
const metaStr = Object.keys(meta).length && !meta.service
|
||||
? ` ${JSON.stringify(meta)}`
|
||||
: '';
|
||||
return `${timestamp} [${level}]: ${message}${metaStr}`;
|
||||
})
|
||||
),
|
||||
})
|
||||
);
|
||||
// ============ CONSOLE TRANSPORT (Development) ============
|
||||
if (!isProduction) {
|
||||
transports.push(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
||||
const metaStr = Object.keys(meta).length && !meta.service
|
||||
? ` ${JSON.stringify(meta)}`
|
||||
: '';
|
||||
return `${timestamp} [${level}]: ${message}${metaStr}`;
|
||||
})
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ============ ERROR SANITIZER ============
|
||||
/**
|
||||
@ -182,25 +183,25 @@ const sanitizeFormat = winston.format((info) => {
|
||||
if (info.error && typeof info.error === 'object') {
|
||||
info.error = sanitizeError(info.error);
|
||||
}
|
||||
|
||||
|
||||
// If message is an error object, sanitize it
|
||||
if (info.message && typeof info.message === 'object' && (info.message as any).stack) {
|
||||
info.error = sanitizeError(info.message);
|
||||
info.message = (info.message as Error).message;
|
||||
}
|
||||
|
||||
|
||||
// Mask sensitive data in message
|
||||
if (typeof info.message === 'string') {
|
||||
info.message = maskSensitiveData(info.message);
|
||||
}
|
||||
|
||||
|
||||
// Mask sensitive data in all metadata
|
||||
for (const key of Object.keys(info)) {
|
||||
if (key !== 'level' && key !== 'timestamp' && key !== 'service') {
|
||||
info[key] = maskSensitiveData(info[key]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return info;
|
||||
});
|
||||
|
||||
@ -253,7 +254,7 @@ export const logWithContext = (
|
||||
if (sanitizedContext.error) {
|
||||
sanitizedContext.error = sanitizeError(sanitizedContext.error);
|
||||
}
|
||||
|
||||
|
||||
logger.log(level, message, sanitizedContext);
|
||||
};
|
||||
|
||||
@ -342,13 +343,13 @@ export const logAuthEvent = (
|
||||
} = {}
|
||||
) => {
|
||||
const level = event === 'auth_failure' ? 'warn' : 'info';
|
||||
|
||||
|
||||
// Sanitize error if present
|
||||
const sanitizedDetails = { ...details };
|
||||
if (sanitizedDetails.error) {
|
||||
sanitizedDetails.error = sanitizeError(sanitizedDetails.error);
|
||||
}
|
||||
|
||||
|
||||
logger.log(level, `Auth ${event}${userId ? `: ${userId}` : ''}`, {
|
||||
authEvent: event,
|
||||
userId,
|
||||
@ -393,13 +394,13 @@ export const logNotificationEvent = (
|
||||
} = {}
|
||||
) => {
|
||||
const level = event === 'failed' ? 'error' : 'info';
|
||||
|
||||
|
||||
// Sanitize error if present
|
||||
const sanitizedDetails = { ...details };
|
||||
if (sanitizedDetails.error) {
|
||||
sanitizedDetails.error = sanitizeError(sanitizedDetails.error);
|
||||
}
|
||||
|
||||
|
||||
logger.log(level, `Notification ${event}`, {
|
||||
notificationEvent: event,
|
||||
...sanitizedDetails,
|
||||
@ -421,13 +422,13 @@ export const logAIEvent = (
|
||||
} = {}
|
||||
) => {
|
||||
const level = event === 'error' ? 'error' : 'info';
|
||||
|
||||
|
||||
// Sanitize error if present
|
||||
const sanitizedDetails = { ...details };
|
||||
if (sanitizedDetails.error) {
|
||||
sanitizedDetails.error = sanitizeError(sanitizedDetails.error);
|
||||
}
|
||||
|
||||
|
||||
logger.log(level, `AI ${event}`, {
|
||||
aiEvent: event,
|
||||
...sanitizedDetails,
|
||||
|
||||
@ -2,9 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "commonjs",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"lib": ["ES2021"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
@ -23,57 +21,50 @@
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"types": [
|
||||
"node",
|
||||
"jest"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./src/types"
|
||||
],
|
||||
"types": ["node", "jest"],
|
||||
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
],
|
||||
"@controllers/*": [
|
||||
"./controllers/*"
|
||||
],
|
||||
"@middlewares/*": [
|
||||
"./middlewares/*"
|
||||
],
|
||||
"@services/*": [
|
||||
"./services/*"
|
||||
],
|
||||
"@models/*": [
|
||||
"./models/*"
|
||||
],
|
||||
"@routes/*": [
|
||||
"./routes/*"
|
||||
],
|
||||
"@validators/*": [
|
||||
"./validators/*"
|
||||
],
|
||||
"@utils/*": [
|
||||
"./utils/*"
|
||||
],
|
||||
"@types/*": [
|
||||
"./types/*"
|
||||
],
|
||||
"@config/*": [
|
||||
"./config/*"
|
||||
]
|
||||
"@/*": ["./*"],
|
||||
"@controllers/*": ["./controllers/*"],
|
||||
"@middlewares/*": ["./middlewares/*"],
|
||||
"@services/*": ["./services/*"],
|
||||
"@models/*": ["./models/*"],
|
||||
"@routes/*": ["./routes/*"],
|
||||
"@validators/*": ["./validators/*"],
|
||||
"@utils/*": ["./utils/*"],
|
||||
"@types/*": ["./types/*"],
|
||||
"@config/*": ["./config/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"src/app.ts",
|
||||
"src/server.ts",
|
||||
"src/routes/index.ts",
|
||||
"src/routes/auth.routes.ts",
|
||||
"src/controllers/auth.controller.ts",
|
||||
"src/services/auth.service.ts",
|
||||
"src/middlewares/auth.middleware.ts",
|
||||
"src/middlewares/cors.middleware.ts",
|
||||
"src/middlewares/validate.middleware.ts",
|
||||
"src/middlewares/errorHandler.middleware.ts",
|
||||
"src/utils/logger.ts",
|
||||
"src/utils/responseHandler.ts",
|
||||
"src/config/**/*",
|
||||
"src/types/**/*",
|
||||
"src/validators/auth.validator.ts",
|
||||
"src/models/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"tests",
|
||||
"**/*.test.ts",
|
||||
"node_modules",
|
||||
"dist",
|
||||
"tests",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"src/services/enhancedTemplate.service.ts"
|
||||
"src/routes/workflow.routes.ts",
|
||||
"src/controllers/workflow.controller.ts",
|
||||
"src/controllers/approval.controller.ts",
|
||||
"src/services/workflow.service.ts",
|
||||
"src/services/approval.service.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user