sanitized code removed url and mails
This commit is contained in:
parent
81afd7ec96
commit
b32a3505ac
@ -1326,9 +1326,9 @@ GCP_KEY_FILE=./config/gcp-key.json
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=notifications@royalenfield.com
|
||||
SMTP_USER=notifications@{{API_DOMAIN}}
|
||||
SMTP_PASSWORD=your_smtp_password
|
||||
EMAIL_FROM=RE Workflow System <notifications@royalenfield.com>
|
||||
EMAIL_FROM=RE Workflow System <notifications@{{API_DOMAIN}}>
|
||||
|
||||
# AI Service (for conclusion generation)
|
||||
AI_API_KEY=your_ai_api_key
|
||||
|
||||
@ -155,13 +155,13 @@ export async function calculateBusinessDays(
|
||||
2. ✅ Imported `calculateElapsedWorkingHours`, `addWorkingHours`, `addWorkingHoursExpress` from `@utils/tatTimeUtils`
|
||||
3. ✅ Replaced lines 64-65 with proper working hours calculation (now lines 66-77)
|
||||
4. ✅ Gets priority from workflow
|
||||
5. ⏳ **TODO:** Test TAT breach alerts
|
||||
5. Done: Test TAT breach alerts
|
||||
|
||||
### Step 2: Add Business Days Function ✅ **DONE**
|
||||
1. ✅ Opened `Re_Backend/src/utils/tatTimeUtils.ts`
|
||||
2. ✅ Added `calculateBusinessDays()` function (lines 697-758)
|
||||
3. ✅ Exported the function
|
||||
4. ⏳ **TODO:** Test with various date ranges
|
||||
4. Done: Test with various date ranges
|
||||
|
||||
### Step 3: Update Workflow Aging Report ✅ **DONE**
|
||||
1. ✅ Built report endpoint using `calculateBusinessDays()`
|
||||
|
||||
@ -19,10 +19,10 @@ This command will output something like:
|
||||
```
|
||||
=======================================
|
||||
Public Key:
|
||||
BEl62iUYgUivxIkvpY5kXK3t3b9i5X8YzA1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6
|
||||
{{VAPID_PUBLIC_KEY}}
|
||||
|
||||
Private Key:
|
||||
aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdEfGhIjKlMnOpQrStUvWxYz
|
||||
{{VAPID_PRIVATE_KEY}}
|
||||
|
||||
=======================================
|
||||
```
|
||||
@ -59,9 +59,9 @@ Add the generated keys to your backend `.env` file:
|
||||
|
||||
```env
|
||||
# Notification Service Worker credentials (Web Push / VAPID)
|
||||
VAPID_PUBLIC_KEY=BEl62iUYgUivxIkvpY5kXK3t3b9i5X8YzA1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6
|
||||
VAPID_PRIVATE_KEY=aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890AbCdEfGhIjKlMnOpQrStUvWxYz
|
||||
VAPID_CONTACT=mailto:admin@royalenfield.com
|
||||
VAPID_PUBLIC_KEY={{VAPID_PUBLIC_KEY}}
|
||||
VAPID_PRIVATE_KEY={{VAPID_PRIVATE_KEY}}
|
||||
VAPID_CONTACT=mailto:{{ADMIN_EMAIL}}
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
@ -75,7 +75,7 @@ Add the **SAME** `VAPID_PUBLIC_KEY` to your frontend `.env` file:
|
||||
|
||||
```env
|
||||
# Push Notifications (Web Push / VAPID)
|
||||
VITE_PUBLIC_VAPID_KEY=BEl62iUYgUivxIkvpY5kXK3t3b9i5X8YzA1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6
|
||||
VITE_PUBLIC_VAPID_KEY={{VAPID_PUBLIC_KEY}}
|
||||
```
|
||||
|
||||
**Important:**
|
||||
|
||||
@ -98,7 +98,7 @@ npm run dev
|
||||
1. Server will start automatically
|
||||
2. Log in via SSO
|
||||
3. Run this SQL to make yourself admin:
|
||||
UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@royalenfield.com';
|
||||
UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
|
||||
[Config Seed] ✅ Default configurations seeded successfully (30 settings)
|
||||
info: ✅ Server started successfully on port 5000
|
||||
@ -112,7 +112,7 @@ psql -d royal_enfield_workflow
|
||||
|
||||
UPDATE users
|
||||
SET role = 'ADMIN'
|
||||
WHERE email = 'your-email@royalenfield.com';
|
||||
WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
|
||||
\q
|
||||
```
|
||||
|
||||
@ -471,7 +471,7 @@ The backend supports web push notifications via VAPID (Voluntary Application Ser
|
||||
```
|
||||
VAPID_PUBLIC_KEY=<your-public-key>
|
||||
VAPID_PRIVATE_KEY=<your-private-key>
|
||||
VAPID_CONTACT=mailto:admin@royalenfield.com
|
||||
VAPID_CONTACT=mailto:admin@{{API_DOMAIN}}
|
||||
```
|
||||
|
||||
3. **Add to Frontend `.env`:**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,2 @@
|
||||
import{a as s}from"./index-7F7W4LDI.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DfwWW08H.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-BJO_6JLT.js.map
|
||||
import{a as s}from"./index-y_ojbF9T.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-DoX_H3Tk.js.map
|
||||
@ -1 +1 @@
|
||||
{"version":3,"file":"conclusionApi-BJO_6JLT.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
|
||||
{"version":3,"file":"conclusionApi-DoX_H3Tk.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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-7F7W4LDI.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-y_ojbF9T.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">
|
||||
|
||||
@ -34,7 +34,7 @@ The Claim Management workflow has **8 fixed steps** with specific approvers and
|
||||
- **Approver Type**: System (Auto-processed)
|
||||
- **Action Type**: **AUTO** (System automatically creates activity)
|
||||
- **TAT**: 1 hour
|
||||
- **Mapping**: System user (`system@royalenfield.com`)
|
||||
- **Mapping**: System user (`system@{{API_DOMAIN}}`)
|
||||
- **Status**: Auto-approved when triggered
|
||||
|
||||
### Step 5: Dealer Completion Documents
|
||||
@ -55,7 +55,7 @@ The Claim Management workflow has **8 fixed steps** with specific approvers and
|
||||
- **Approver Type**: System (Auto-processed via DMS)
|
||||
- **Action Type**: **AUTO** (System generates e-invoice via DMS integration)
|
||||
- **TAT**: 1 hour
|
||||
- **Mapping**: System user (`system@royalenfield.com`)
|
||||
- **Mapping**: System user (`system@{{API_DOMAIN}}`)
|
||||
- **Status**: Auto-approved when triggered
|
||||
|
||||
### Step 8: Credit Note Confirmation
|
||||
@ -121,7 +121,7 @@ const dealerUser = await User.findOne({ where: { email: dealerEmail } });
|
||||
1. Find user with department containing "Finance" and role = 'MANAGEMENT'
|
||||
2. Find user with designation containing "Finance" or "Accountant"
|
||||
3. Use configured finance team email from admin_configurations table
|
||||
4. Fallback: Use default finance email (e.g., finance@royalenfield.com)
|
||||
4. Fallback: Use default finance email (e.g., finance@{{API_DOMAIN}})
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
@ -112,7 +112,7 @@ Your CSV file must have these **44 columns** in the following order:
|
||||
| `on_boarding_charges` | Decimal | No | Numeric value (e.g., 1000.50) |
|
||||
| `date` | Date | No | Format: YYYY-MM-DD (e.g., 2014-09-30) |
|
||||
| `single_format_month_year` | String(50) | No | Format: Sep-2014 |
|
||||
| `domain_id` | String(255) | No | Email domain (e.g., dealer@royalenfield.com) |
|
||||
| `domain_id` | String(255) | No | Email domain (e.g., dealer@{{API_DOMAIN}}) |
|
||||
| `replacement` | String(50) | No | Replacement status |
|
||||
| `termination_resignation_status` | String(255) | No | Termination/Resignation status |
|
||||
| `date_of_termination_resignation` | Date | No | Format: YYYY-MM-DD |
|
||||
@ -183,7 +183,7 @@ Ensure dates are in `YYYY-MM-DD` format:
|
||||
|
||||
```csv
|
||||
sales_code,service_code,gear_code,gma_code,region,dealership,state,district,city,location,city_category_pst,layout_format,tier_city_category,on_boarding_charges,date,single_format_month_year,domain_id,replacement,termination_resignation_status,date_of_termination_resignation,last_date_of_operations,old_codes,branch_details,dealer_principal_name,dealer_principal_email_id,dp_contact_number,dp_contacts,showroom_address,showroom_pincode,workshop_address,workshop_pincode,location_district,state_workshop,no_of_studios,website_update,gst,pan,firm_type,prop_managing_partners_directors,total_prop_partners_directors,docs_folder_link,workshop_gma_codes,existing_new,dlrcode
|
||||
5124,5125,5573,9430,S3,Accelerate Motors,Karnataka,Bengaluru,Bengaluru,RAJA RAJESHWARI NAGAR,A+,A+,Tier 1 City,,2014-09-30,Sep-2014,acceleratemotors.rrnagar@dealer.royalenfield.com,,,,,,,N. Shyam Charmanna,shyamcharmanna@yahoo.co.in,7022049621,7022049621,"No.335, HVP RR Nagar Sector B, Ideal Homes Town Ship, Bangalore - 560098, Dist – Bangalore, Karnataka",560098,"Works Shop No.460, 80ft Road, 2nd Phase R R Nagar, Bangalore - 560098, Dist – Bangalore, Karnataka",560098,Bangalore,Karnataka,0,Yes,29ARCPS1311D1Z6,ARCPS1311D,Proprietorship,CHARMANNA SHYAM NELLAMAKADA,CHARMANNA SHYAM NELLAMAKADA,https://drive.google.com/drive/folders/1sGtg3s1h9aBXX9fhxJufYuBWar8gVvnb,,,3386
|
||||
5124,5125,5573,9430,S3,Accelerate Motors,Karnataka,Bengaluru,Bengaluru,RAJA RAJESHWARI NAGAR,A+,A+,Tier 1 City,,2014-09-30,Sep-2014,acceleratemotors.rrnagar@dealer.{{API_DOMAIN}},,,,,,,N. Shyam Charmanna,shyamcharmanna@yahoo.co.in,7022049621,7022049621,"No.335, HVP RR Nagar Sector B, Ideal Homes Town Ship, Bangalore - 560098, Dist – Bangalore, Karnataka",560098,"Works Shop No.460, 80ft Road, 2nd Phase R R Nagar, Bangalore - 560098, Dist – Bangalore, Karnataka",560098,Bangalore,Karnataka,0,Yes,29ARCPS1311D1Z6,ARCPS1311D,Proprietorship,CHARMANNA SHYAM NELLAMAKADA,CHARMANNA SHYAM NELLAMAKADA,https://drive.google.com/drive/folders/1sGtg3s1h9aBXX9fhxJufYuBWar8gVvnb,,,3386
|
||||
```
|
||||
|
||||
**What gets auto-generated:**
|
||||
|
||||
@ -56,7 +56,7 @@ users {
|
||||
```json
|
||||
{
|
||||
"userId": "uuid-1",
|
||||
"email": "john.doe@royalenfield.com",
|
||||
"email": "john.doe@{{API_DOMAIN}}",
|
||||
"employeeId": "E12345", // Regular employee ID
|
||||
"designation": "Software Engineer",
|
||||
"department": "IT",
|
||||
@ -68,7 +68,7 @@ users {
|
||||
```json
|
||||
{
|
||||
"userId": "uuid-2",
|
||||
"email": "test.2@royalenfield.com",
|
||||
"email": "test.2@{{API_DOMAIN}}",
|
||||
"employeeId": "RE-MH-001", // Dealer code stored here
|
||||
"designation": "Dealer",
|
||||
"department": "Dealer Operations",
|
||||
|
||||
@ -98,8 +98,8 @@ DMS_WEBHOOK_SECRET=your_shared_secret_key_here
|
||||
|
||||
**Base URL Examples:**
|
||||
- Development: `http://localhost:5000/api/v1/webhooks/dms/invoice`
|
||||
- UAT: `https://reflow-uat.royalenfield.com/api/v1/webhooks/dms/invoice`
|
||||
- Production: `https://reflow.royalenfield.com/api/v1/webhooks/dms/invoice`
|
||||
- UAT: `https://reflow-uat.{{API_DOMAIN}}/api/v1/webhooks/dms/invoice`
|
||||
- Production: `https://reflow.{{API_DOMAIN}}/api/v1/webhooks/dms/invoice`
|
||||
|
||||
### 3.2 Request Headers
|
||||
|
||||
@ -205,8 +205,8 @@ User-Agent: DMS-Webhook-Client/1.0
|
||||
|
||||
**Base URL Examples:**
|
||||
- Development: `http://localhost:5000/api/v1/webhooks/dms/credit-note`
|
||||
- UAT: `https://reflow-uat.royalenfield.com/api/v1/webhooks/dms/credit-note`
|
||||
- Production: `https://reflow.royalenfield.com/api/v1/webhooks/dms/credit-note`
|
||||
- UAT: `https://reflow-uat.{{API_DOMAIN}}/api/v1/webhooks/dms/credit-note`
|
||||
- Production: `https://reflow.{{API_DOMAIN}}/api/v1/webhooks/dms/credit-note`
|
||||
|
||||
### 4.2 Request Headers
|
||||
|
||||
@ -563,8 +563,8 @@ DMS_WEBHOOK_SECRET=your_shared_secret_key_here
|
||||
| Environment | Invoice Webhook URL | Credit Note Webhook URL |
|
||||
|-------------|---------------------|-------------------------|
|
||||
| Development | `http://localhost:5000/api/v1/webhooks/dms/invoice` | `http://localhost:5000/api/v1/webhooks/dms/credit-note` |
|
||||
| UAT | `https://reflow-uat.royalenfield.com/api/v1/webhooks/dms/invoice` | `https://reflow-uat.royalenfield.com/api/v1/webhooks/dms/credit-note` |
|
||||
| Production | `https://reflow.royalenfield.com/api/v1/webhooks/dms/invoice` | `https://reflow.royalenfield.com/api/v1/webhooks/dms/credit-note` |
|
||||
| UAT | `https://reflow-uat.{{API_DOMAIN}}/api/v1/webhooks/dms/invoice` | `https://reflow-uat.{{API_DOMAIN}}/api/v1/webhooks/dms/credit-note` |
|
||||
| Production | `https://reflow.{{API_DOMAIN}}/api/v1/webhooks/dms/invoice` | `https://reflow.{{API_DOMAIN}}/api/v1/webhooks/dms/credit-note` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -157,7 +157,7 @@ npm run seed:config
|
||||
```bash
|
||||
# Edit the script
|
||||
nano scripts/assign-admin-user.sql
|
||||
# Change: YOUR_EMAIL@royalenfield.com
|
||||
# Change: YOUR_EMAIL@{{API_DOMAIN}}
|
||||
|
||||
# Run it
|
||||
psql -d royal_enfield_workflow -f scripts/assign-admin-user.sql
|
||||
@ -170,7 +170,7 @@ psql -d royal_enfield_workflow
|
||||
|
||||
UPDATE users
|
||||
SET role = 'ADMIN'
|
||||
WHERE email = 'your-email@royalenfield.com';
|
||||
WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
|
||||
-- Verify
|
||||
SELECT email, role FROM users WHERE role = 'ADMIN';
|
||||
@ -188,7 +188,7 @@ psql -d royal_enfield_workflow -c "\dt"
|
||||
psql -d royal_enfield_workflow -c "\dT+ user_role_enum"
|
||||
|
||||
# Check your user
|
||||
psql -d royal_enfield_workflow -c "SELECT email, role FROM users WHERE email = 'your-email@royalenfield.com';"
|
||||
psql -d royal_enfield_workflow -c "SELECT email, role FROM users WHERE email = 'your-email@{{API_DOMAIN}}';"
|
||||
```
|
||||
|
||||
---
|
||||
@ -241,13 +241,13 @@ Expected output:
|
||||
```sql
|
||||
-- Single user
|
||||
UPDATE users SET role = 'MANAGEMENT'
|
||||
WHERE email = 'manager@royalenfield.com';
|
||||
WHERE email = 'manager@{{API_DOMAIN}}';
|
||||
|
||||
-- Multiple users
|
||||
UPDATE users SET role = 'MANAGEMENT'
|
||||
WHERE email IN (
|
||||
'manager1@royalenfield.com',
|
||||
'manager2@royalenfield.com'
|
||||
'manager1@{{API_DOMAIN}}',
|
||||
'manager2@{{API_DOMAIN}}'
|
||||
);
|
||||
|
||||
-- By department
|
||||
@ -260,13 +260,13 @@ WHERE department = 'Management' AND is_active = true;
|
||||
```sql
|
||||
-- Single user
|
||||
UPDATE users SET role = 'ADMIN'
|
||||
WHERE email = 'admin@royalenfield.com';
|
||||
WHERE email = 'admin@{{API_DOMAIN}}';
|
||||
|
||||
-- Multiple admins
|
||||
UPDATE users SET role = 'ADMIN'
|
||||
WHERE email IN (
|
||||
'admin1@royalenfield.com',
|
||||
'admin2@royalenfield.com'
|
||||
'admin1@{{API_DOMAIN}}',
|
||||
'admin2@{{API_DOMAIN}}'
|
||||
);
|
||||
|
||||
-- By department
|
||||
@ -331,7 +331,7 @@ SELECT
|
||||
mobile_phone,
|
||||
array_length(ad_groups, 1) as ad_group_count
|
||||
FROM users
|
||||
WHERE email = 'your-email@royalenfield.com';
|
||||
WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
```
|
||||
|
||||
---
|
||||
@ -344,7 +344,7 @@ WHERE email = 'your-email@royalenfield.com';
|
||||
curl -X POST http://localhost:5000/api/v1/auth/okta/callback \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@royalenfield.com",
|
||||
"email": "test@{{API_DOMAIN}}",
|
||||
"displayName": "Test User",
|
||||
"oktaSub": "test-sub-123"
|
||||
}'
|
||||
@ -353,14 +353,14 @@ curl -X POST http://localhost:5000/api/v1/auth/okta/callback \
|
||||
### 2. Check User Created with Default Role
|
||||
|
||||
```sql
|
||||
SELECT email, role FROM users WHERE email = 'test@royalenfield.com';
|
||||
SELECT email, role FROM users WHERE email = 'test@{{API_DOMAIN}}';
|
||||
-- Expected: role = 'USER'
|
||||
```
|
||||
|
||||
### 3. Update to ADMIN
|
||||
|
||||
```sql
|
||||
UPDATE users SET role = 'ADMIN' WHERE email = 'test@royalenfield.com';
|
||||
UPDATE users SET role = 'ADMIN' WHERE email = 'test@{{API_DOMAIN}}';
|
||||
```
|
||||
|
||||
### 4. Verify API Access
|
||||
@ -369,7 +369,7 @@ UPDATE users SET role = 'ADMIN' WHERE email = 'test@royalenfield.com';
|
||||
# Login and get token
|
||||
curl -X POST http://localhost:5000/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "test@royalenfield.com", ...}'
|
||||
-d '{"email": "test@{{API_DOMAIN}}", ...}'
|
||||
|
||||
# Try admin endpoint (should work if ADMIN role)
|
||||
curl http://localhost:5000/api/v1/admin/configurations \
|
||||
@ -449,7 +449,7 @@ npm run migrate
|
||||
|
||||
```sql
|
||||
-- Check if user exists
|
||||
SELECT * FROM users WHERE email = 'your-email@royalenfield.com';
|
||||
SELECT * FROM users WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
|
||||
-- Check Okta sub
|
||||
SELECT * FROM users WHERE okta_sub = 'your-okta-sub';
|
||||
@ -459,7 +459,7 @@ SELECT * FROM users WHERE okta_sub = 'your-okta-sub';
|
||||
|
||||
```sql
|
||||
-- Verify role
|
||||
SELECT email, role, is_active FROM users WHERE email = 'your-email@royalenfield.com';
|
||||
SELECT email, role, is_active FROM users WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
|
||||
-- Check role enum
|
||||
\dT+ user_role_enum
|
||||
|
||||
@ -29,7 +29,7 @@ This guide provides step-by-step instructions for setting up Google Cloud Storag
|
||||
|------|------------------|
|
||||
| **Application** | Royal Enfield Workflow System |
|
||||
| **Environment** | Production |
|
||||
| **Domain** | `https://reflow.royalenfield.com` |
|
||||
| **Domain** | `https://reflow.{{API_DOMAIN}}` |
|
||||
| **Purpose** | Store workflow documents, attachments, invoices, and credit notes |
|
||||
| **Storage Type** | Google Cloud Storage (GCS) |
|
||||
| **Region** | `asia-south1` (Mumbai) |
|
||||
@ -325,8 +325,8 @@ Create `cors-config-prod.json`:
|
||||
[
|
||||
{
|
||||
"origin": [
|
||||
"https://reflow.royalenfield.com",
|
||||
"https://www.royalenfield.com"
|
||||
"https://reflow.{{API_DOMAIN}}",
|
||||
"https://www.{{API_DOMAIN}}"
|
||||
],
|
||||
"method": ["GET", "PUT", "POST", "DELETE", "HEAD", "OPTIONS"],
|
||||
"responseHeader": [
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|------|-------|
|
||||
| **Application** | RE Workflow System |
|
||||
| **Environment** | UAT |
|
||||
| **Domain** | https://reflow-uat.royalenfield.com |
|
||||
| **Domain** | https://reflow-uat.{{API_DOMAIN}} |
|
||||
| **Purpose** | Store workflow documents and attachments |
|
||||
|
||||
---
|
||||
@ -131,8 +131,8 @@ Apply this CORS policy to allow browser uploads:
|
||||
[
|
||||
{
|
||||
"origin": [
|
||||
"https://reflow-uat.royalenfield.com",
|
||||
"https://reflow.royalenfield.com"
|
||||
"https://reflow-uat.{{API_DOMAIN}}",
|
||||
"https://reflow.{{API_DOMAIN}}"
|
||||
],
|
||||
"method": ["GET", "PUT", "POST", "DELETE", "HEAD", "OPTIONS"],
|
||||
"responseHeader": [
|
||||
|
||||
@ -72,8 +72,8 @@ The Users API returns a complete user object:
|
||||
"employeeID": "E09994",
|
||||
"title": "Supports Business Applications (SAP) portfolio",
|
||||
"department": "Deputy Manager - Digital & IT",
|
||||
"login": "sanjaysahu@Royalenfield.com",
|
||||
"email": "sanjaysahu@royalenfield.com"
|
||||
"login": "sanjaysahu@{{API_DOMAIN}}",
|
||||
"email": "sanjaysahu@{{API_DOMAIN}}"
|
||||
},
|
||||
...
|
||||
}
|
||||
@ -127,7 +127,7 @@ Example log:
|
||||
### Test with curl
|
||||
|
||||
```bash
|
||||
curl --location 'https://dev-830839.oktapreview.com/api/v1/users/testuser10@eichergroup.com' \
|
||||
curl --location 'https://{{IDP_DOMAIN}}/api/v1/users/testuser10@eichergroup.com' \
|
||||
--header 'Authorization: SSWS YOUR_OKTA_API_TOKEN' \
|
||||
--header 'Accept: application/json'
|
||||
```
|
||||
|
||||
@ -450,16 +450,16 @@ Before Migration:
|
||||
+-------------------------+-----------+
|
||||
| email | is_admin |
|
||||
+-------------------------+-----------+
|
||||
| admin@royalenfield.com | true |
|
||||
| user1@royalenfield.com | false |
|
||||
| admin@{{API_DOMAIN}} | true |
|
||||
| user1@{{API_DOMAIN}} | false |
|
||||
+-------------------------+-----------+
|
||||
|
||||
After Migration:
|
||||
+-------------------------+-----------+-----------+
|
||||
| email | role | is_admin |
|
||||
+-------------------------+-----------+-----------+
|
||||
| admin@royalenfield.com | ADMIN | true |
|
||||
| user1@royalenfield.com | USER | false |
|
||||
| admin@{{API_DOMAIN}} | ADMIN | true |
|
||||
| user1@{{API_DOMAIN}} | USER | false |
|
||||
+-------------------------+-----------+-----------+
|
||||
```
|
||||
|
||||
@ -473,17 +473,17 @@ After Migration:
|
||||
-- Make user a MANAGEMENT role
|
||||
UPDATE users
|
||||
SET role = 'MANAGEMENT', is_admin = false
|
||||
WHERE email = 'manager@royalenfield.com';
|
||||
WHERE email = 'manager@{{API_DOMAIN}}';
|
||||
|
||||
-- Make user an ADMIN role
|
||||
UPDATE users
|
||||
SET role = 'ADMIN', is_admin = true
|
||||
WHERE email = 'admin@royalenfield.com';
|
||||
WHERE email = 'admin@{{API_DOMAIN}}';
|
||||
|
||||
-- Revert to USER role
|
||||
UPDATE users
|
||||
SET role = 'USER', is_admin = false
|
||||
WHERE email = 'user@royalenfield.com';
|
||||
WHERE email = 'user@{{API_DOMAIN}}';
|
||||
```
|
||||
|
||||
### Via API (Admin Endpoint)
|
||||
|
||||
@ -47,12 +47,12 @@ psql -d royal_enfield_db -f scripts/assign-user-roles.sql
|
||||
-- Make specific users ADMIN
|
||||
UPDATE users
|
||||
SET role = 'ADMIN', is_admin = true
|
||||
WHERE email IN ('admin@royalenfield.com', 'it.admin@royalenfield.com');
|
||||
WHERE email IN ('admin@{{API_DOMAIN}}', 'it.admin@{{API_DOMAIN}}');
|
||||
|
||||
-- Make specific users MANAGEMENT
|
||||
UPDATE users
|
||||
SET role = 'MANAGEMENT', is_admin = false
|
||||
WHERE email IN ('manager@royalenfield.com', 'auditor@royalenfield.com');
|
||||
WHERE email IN ('manager@{{API_DOMAIN}}', 'auditor@{{API_DOMAIN}}');
|
||||
|
||||
-- Verify roles
|
||||
SELECT email, display_name, role, is_admin FROM users ORDER BY role, email;
|
||||
@ -219,7 +219,7 @@ GROUP BY role;
|
||||
-- Check specific user
|
||||
SELECT email, role, is_admin
|
||||
FROM users
|
||||
WHERE email = 'your-email@royalenfield.com';
|
||||
WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
```
|
||||
|
||||
### Test 2: Test API Access
|
||||
@ -356,7 +356,7 @@ WHERE designation ILIKE '%manager%' OR designation ILIKE '%head%';
|
||||
```sql
|
||||
SELECT email, role, is_admin
|
||||
FROM users
|
||||
WHERE email = 'your-email@royalenfield.com';
|
||||
WHERE email = 'your-email@{{API_DOMAIN}}';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -314,7 +314,7 @@ JWT_EXPIRY=24h
|
||||
REFRESH_TOKEN_EXPIRY=7d
|
||||
|
||||
# Okta Configuration
|
||||
OKTA_DOMAIN=https://dev-830839.oktapreview.com
|
||||
OKTA_DOMAIN=https://{{IDP_DOMAIN}}
|
||||
OKTA_CLIENT_ID=your-client-id
|
||||
OKTA_CLIENT_SECRET=your-client-secret
|
||||
|
||||
@ -334,7 +334,7 @@ GCP_BUCKET_PUBLIC=true
|
||||
|
||||
**Identity Provider**: Okta
|
||||
- **Domain**: Configurable via `OKTA_DOMAIN` environment variable
|
||||
- **Default**: `https://dev-830839.oktapreview.com`
|
||||
- **Default**: `https://{{IDP_DOMAIN}}`
|
||||
- **Protocol**: OAuth 2.0 / OpenID Connect (OIDC)
|
||||
- **Grant Types**: Authorization Code, Resource Owner Password Credentials
|
||||
|
||||
@ -650,7 +650,7 @@ graph LR
|
||||
{
|
||||
"userId": "uuid",
|
||||
"employeeId": "EMP001",
|
||||
"email": "user@royalenfield.com",
|
||||
"email": "user@{{API_DOMAIN}}",
|
||||
"role": "USER" | "MANAGEMENT" | "ADMIN",
|
||||
"iat": 1234567890,
|
||||
"exp": 1234654290
|
||||
@ -1048,7 +1048,7 @@ JWT_EXPIRY=24h
|
||||
REFRESH_TOKEN_EXPIRY=7d
|
||||
|
||||
# Okta
|
||||
OKTA_DOMAIN=https://dev-830839.oktapreview.com
|
||||
OKTA_DOMAIN=https://{{IDP_DOMAIN}}
|
||||
OKTA_CLIENT_ID=your-client-id
|
||||
OKTA_CLIENT_SECRET=your-client-secret
|
||||
|
||||
@ -1063,7 +1063,7 @@ GCP_BUCKET_PUBLIC=true
|
||||
**Frontend (.env):**
|
||||
```env
|
||||
VITE_API_BASE_URL=https://api.rebridge.co.in/api/v1
|
||||
VITE_OKTA_DOMAIN=https://dev-830839.oktapreview.com
|
||||
VITE_OKTA_DOMAIN=https://{{IDP_DOMAIN}}
|
||||
VITE_OKTA_CLIENT_ID=your-client-id
|
||||
```
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ await this.createClaimApprovalLevels(
|
||||
isAuto: false,
|
||||
approverType: 'department_lead' as const,
|
||||
approverId: departmentLead?.userId || null,
|
||||
approverEmail: departmentLead?.email || initiator.manager || 'deptlead@royalenfield.com',
|
||||
approverEmail: departmentLead?.email || initiator.manager || 'deptlead@{{API_DOMAIN}}',
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -181,7 +181,7 @@ POST http://localhost:5000/api/v1/auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "john.doe@royalenfield.com",
|
||||
"username": "john.doe@{{API_DOMAIN}}",
|
||||
"password": "SecurePassword123!"
|
||||
}
|
||||
```
|
||||
|
||||
20
env.example
20
env.example
@ -26,8 +26,8 @@ REFRESH_TOKEN_EXPIRY=7d
|
||||
SESSION_SECRET=your_session_secret_here_min_32_chars
|
||||
|
||||
# Cloud Storage (GCP)
|
||||
GCP_PROJECT_ID=re-workflow-project
|
||||
GCP_BUCKET_NAME=re-workflow-documents
|
||||
GCP_PROJECT_ID={{GCP_PROJECT_ID}}
|
||||
GCP_BUCKET_NAME={{GCP_BUCKET_NAME}}
|
||||
GCP_KEY_FILE=./config/gcp-key.json
|
||||
|
||||
# Google Secret Manager (Optional - for production)
|
||||
@ -41,9 +41,9 @@ USE_GOOGLE_SECRET_MANAGER=false
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=notifications@royalenfield.com
|
||||
SMTP_USER=notifications@{{API_DOMAIN}}
|
||||
SMTP_PASSWORD=your_smtp_password
|
||||
EMAIL_FROM=RE Workflow System <notifications@royalenfield.com>
|
||||
EMAIL_FROM=RE Workflow System <notifications@{{API_DOMAIN}}>
|
||||
|
||||
# AI Service (for conclusion generation) - Vertex AI Gemini
|
||||
# Uses service account credentials from GCP_KEY_FILE
|
||||
@ -55,7 +55,7 @@ VERTEX_AI_LOCATION=asia-south1
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE_PATH=./logs
|
||||
APP_VERSION=1.2.0
|
||||
APP_VERSION={{APP_VERSION}}
|
||||
|
||||
# ============ Loki Configuration (Grafana Log Aggregation) ============
|
||||
LOKI_HOST= # e.g., http://loki:3100 or http://monitoring.cloudtopiaa.com:3100
|
||||
@ -66,7 +66,7 @@ LOKI_PASSWORD= # Optional: Basic auth password
|
||||
CORS_ORIGIN="*"
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_WINDOW_MS=900000
|
||||
RATE_LIMIT_WINDOW_MS=900000 # 15 minutes
|
||||
RATE_LIMIT_MAX_REQUESTS=100
|
||||
|
||||
# File Upload
|
||||
@ -83,16 +83,16 @@ OKTA_CLIENT_ID={{okta_client_id}}
|
||||
OKTA_CLIENT_SECRET={{okta_client_secret}}
|
||||
|
||||
# Notificaton Service Worker credentials
|
||||
VAPID_PUBLIC_KEY={{vapid_public_key}} note: same key need to add on front end for web push
|
||||
VAPID_PUBLIC_KEY={{VAPID_PUBLIC_KEY}}
|
||||
VAPID_PRIVATE_KEY={{vapid_private_key}}
|
||||
VAPID_CONTACT=mailto:you@example.com
|
||||
|
||||
#Redis
|
||||
REDIS_URL={{REDIS_URL_FOR DELAY JoBS create redis setup and add url here}}
|
||||
TAT_TEST_MODE=false (on true it will consider 1 hour==1min)
|
||||
REDIS_URL={{REDIS_URL}}
|
||||
TAT_TEST_MODE=false # Set to true to accelerate TAT for testing
|
||||
|
||||
# SAP Integration (OData Service via Zscaler)
|
||||
SAP_BASE_URL=https://RENOIHND01.Eichergroup.com:1443
|
||||
SAP_BASE_URL=https://{{SAP_DOMAIN_HERE}}:{{PORT}}
|
||||
SAP_USERNAME={{SAP_USERNAME}}
|
||||
SAP_PASSWORD={{SAP_PASSWORD}}
|
||||
SAP_TIMEOUT_MS=30000
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
UPDATE users
|
||||
SET role = 'ADMIN'
|
||||
WHERE email = 'YOUR_EMAIL@royalenfield.com' -- ← CHANGE THIS
|
||||
WHERE email = 'YOUR_EMAIL@{{API_DOMAIN}}' -- ← CHANGE THIS
|
||||
RETURNING
|
||||
user_id,
|
||||
email,
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
UPDATE users
|
||||
SET role = 'ADMIN'
|
||||
WHERE email IN (
|
||||
'admin@royalenfield.com',
|
||||
'it.admin@royalenfield.com',
|
||||
'system.admin@royalenfield.com'
|
||||
'admin@{{API_DOMAIN}}',
|
||||
'it.admin@{{API_DOMAIN}}',
|
||||
'system.admin@{{API_DOMAIN}}'
|
||||
-- Add more admin emails here
|
||||
);
|
||||
|
||||
@ -45,9 +45,9 @@ ORDER BY email;
|
||||
UPDATE users
|
||||
SET role = 'MANAGEMENT'
|
||||
WHERE email IN (
|
||||
'manager1@royalenfield.com',
|
||||
'dept.head@royalenfield.com',
|
||||
'auditor@royalenfield.com'
|
||||
'manager1@{{API_DOMAIN}}',
|
||||
'dept.head@{{API_DOMAIN}}',
|
||||
'auditor@{{API_DOMAIN}}'
|
||||
-- Add more management emails here
|
||||
);
|
||||
|
||||
|
||||
@ -162,7 +162,7 @@ SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=${SMTP_USER}
|
||||
SMTP_PASSWORD=${SMTP_PASSWORD}
|
||||
EMAIL_FROM=RE Workflow System <notifications@royalenfield.com>
|
||||
EMAIL_FROM=RE Workflow System <notifications@{{API_DOMAIN}}>
|
||||
|
||||
# Vertex AI Gemini Configuration (for conclusion generation)
|
||||
# Service account credentials should be placed in ./credentials/ folder
|
||||
@ -232,7 +232,7 @@ show_vapid_instructions() {
|
||||
echo " VITE_PUBLIC_VAPID_KEY=<your-public-key>"
|
||||
echo ""
|
||||
echo "5. The VAPID_CONTACT should be a valid mailto: URL"
|
||||
echo " Example: mailto:admin@royalenfield.com"
|
||||
echo " Example: mailto:admin@{{API_DOMAIN}}"
|
||||
echo ""
|
||||
echo "Note: Keep your VAPID_PRIVATE_KEY secure and never commit it to version control!"
|
||||
echo ""
|
||||
|
||||
10
src/app.ts
10
src/app.ts
@ -7,6 +7,8 @@ import { UserService } from './services/user.service';
|
||||
import { SSOUserData } from './types/auth.types';
|
||||
import { sequelize } from './config/database';
|
||||
import { corsMiddleware } from './middlewares/cors.middleware';
|
||||
import { authenticateToken } from './middlewares/auth.middleware';
|
||||
import { requireAdmin } from './middlewares/authorization.middleware';
|
||||
import { metricsMiddleware, createMetricsRouter } from './middlewares/metrics.middleware';
|
||||
import routes from './routes/index';
|
||||
import { ensureUploadDir, UPLOAD_DIR } from './config/storage';
|
||||
@ -51,7 +53,7 @@ app.use((req: express.Request, res: express.Response, next: express.NextFunction
|
||||
"script-src 'self'",
|
||||
"script-src-elem 'self'",
|
||||
"script-src-attr 'none'",
|
||||
"img-src 'self' data: blob: https://*.royalenfield.com https://*.okta.com https://*.oktapreview.com https://*.googleapis.com https://*.gstatic.com",
|
||||
"img-src 'self' data: blob: https://*.{{API_DOMAIN}} https://*.okta.com https://*.oktapreview.com https://*.googleapis.com https://*.gstatic.com",
|
||||
"frame-src 'self' blob: data:",
|
||||
"font-src 'self' https://fonts.gstatic.com data:",
|
||||
"object-src 'none'",
|
||||
@ -117,7 +119,7 @@ app.use(morgan('combined'));
|
||||
app.use(metricsMiddleware);
|
||||
|
||||
// Prometheus metrics endpoint - expose metrics for scraping
|
||||
app.use(createMetricsRouter());
|
||||
app.use('/metrics', authenticateToken, requireAdmin, createMetricsRouter());
|
||||
|
||||
// Health check endpoint (before API routes)
|
||||
app.get('/health', (_req: express.Request, res: express.Response) => {
|
||||
@ -134,7 +136,7 @@ app.use('/api/v1', routes);
|
||||
|
||||
// Serve uploaded files statically
|
||||
ensureUploadDir();
|
||||
app.use('/uploads', express.static(UPLOAD_DIR));
|
||||
app.use('/uploads', authenticateToken, express.static(UPLOAD_DIR));
|
||||
|
||||
// Legacy SSO Callback endpoint for user creation/update (kept for backward compatibility)
|
||||
app.post('/api/v1/auth/sso-callback', async (req: express.Request, res: express.Response): Promise<void> => {
|
||||
@ -188,7 +190,7 @@ app.post('/api/v1/auth/sso-callback', async (req: express.Request, res: express.
|
||||
});
|
||||
|
||||
// Get all users endpoint
|
||||
app.get('/api/v1/users', async (_req: express.Request, res: express.Response): Promise<void> => {
|
||||
app.get('/api/v1/users', authenticateToken, requireAdmin, async (_req: express.Request, res: express.Response): Promise<void> => {
|
||||
try {
|
||||
const users = await userService.getAllUsers();
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ export const emailConfig = {
|
||||
},
|
||||
},
|
||||
|
||||
from: process.env.EMAIL_FROM || 'RE Workflow System <notifications@royalenfield.com>',
|
||||
from: process.env.EMAIL_FROM || 'RE Workflow System <notifications@{{API_DOMAIN}}>',
|
||||
|
||||
// Email templates
|
||||
templates: {
|
||||
|
||||
@ -12,14 +12,14 @@ const ssoConfig: SSOConfig = {
|
||||
return process.env.FRONTEND_URL?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
||||
},
|
||||
// Okta/Auth0 configuration for token exchange
|
||||
get oktaDomain() { return process.env.OKTA_DOMAIN || 'https://dev-830839.oktapreview.com'; },
|
||||
get oktaDomain() { return process.env.OKTA_DOMAIN || '{{IDP_DOMAIN}}'; },
|
||||
get oktaClientId() { return process.env.OKTA_CLIENT_ID || ''; },
|
||||
get oktaClientSecret() { return process.env.OKTA_CLIENT_SECRET || ''; },
|
||||
get oktaApiToken() { return process.env.OKTA_API_TOKEN || ''; }, // SSWS token for Users API
|
||||
// Tanflow configuration for token exchange
|
||||
get tanflowBaseUrl() { return process.env.TANFLOW_BASE_URL || 'https://ssodev.rebridge.co.in/realms/RE'; },
|
||||
get tanflowBaseUrl() { return process.env.TANFLOW_BASE_URL || '{{IDP_DOMAIN}}/realms/RE'; },
|
||||
get tanflowClientId() { return process.env.TANFLOW_CLIENT_ID || 'REFLOW'; },
|
||||
get tanflowClientSecret() { return process.env.TANFLOW_CLIENT_SECRET || 'cfIzMlwAMF1m4QWAP5StzZbV47HIrCox'; },
|
||||
get tanflowClientSecret() { return process.env.TANFLOW_CLIENT_SECRET || '{{TANFLOW_CLIENT_SECRET}}'; },
|
||||
};
|
||||
|
||||
export { ssoConfig };
|
||||
|
||||
@ -172,7 +172,7 @@ This document outlines all email templates required for the Dealer Claim Managem
|
||||
- Initiator (for record)
|
||||
- Finance team
|
||||
- **Template**: `creditNoteSent.template.ts` (NEW)
|
||||
- **Status**: ❌ Not Implemented (TODO comment at line 2037-2044)
|
||||
- **Status**: Required implementation
|
||||
- **Notification Type**: `credit_note_sent`
|
||||
- **Data Needed**:
|
||||
- Credit note number
|
||||
@ -184,7 +184,7 @@ This document outlines all email templates required for the Dealer Claim Managem
|
||||
- Reason for credit note
|
||||
- Download link (if available)
|
||||
- **Notes**:
|
||||
- Currently has TODO comment for email implementation
|
||||
- Planned for email implementation
|
||||
- Critical for dealer notification
|
||||
|
||||
---
|
||||
@ -246,7 +246,7 @@ This document outlines all email templates required for the Dealer Claim Managem
|
||||
5. **Credit Note Sent** (`creditNoteSent.template.ts`)
|
||||
- Priority: High
|
||||
- When: Credit note is sent to dealer (Step 8)
|
||||
- Currently has TODO comment
|
||||
- Planned for implementation
|
||||
|
||||
---
|
||||
|
||||
@ -255,7 +255,7 @@ This document outlines all email templates required for the Dealer Claim Managem
|
||||
### High Priority (Critical for Workflow)
|
||||
1. **Activity Created** - Currently using generic notification, should be branded
|
||||
2. **E-Invoice Generated** - Important for financial tracking
|
||||
3. **Credit Note Sent** - Critical for dealer notification (currently TODO)
|
||||
3. **Credit Note Sent** - Critical for dealer notification
|
||||
|
||||
### Medium Priority (Nice to Have)
|
||||
4. **Proposal Submitted** - Better UX, but existing approval request works
|
||||
|
||||
@ -991,9 +991,9 @@ Add to `.env`:
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=notifications@royalenfield.com
|
||||
SMTP_USER=notifications@{{API_DOMAIN}}
|
||||
SMTP_PASSWORD=your-app-specific-password
|
||||
EMAIL_FROM=RE Flow <noreply@royalenfield.com>
|
||||
EMAIL_FROM=RE Flow <noreply@{{API_DOMAIN}}>
|
||||
|
||||
# Email Settings
|
||||
EMAIL_ENABLED=true
|
||||
@ -1002,10 +1002,10 @@ EMAIL_BATCH_SIZE=50
|
||||
EMAIL_RETRY_ATTEMPTS=3
|
||||
|
||||
# Application
|
||||
BASE_URL=https://workflow.royalenfield.com
|
||||
BASE_URL=https://workflow.{{API_DOMAIN}}
|
||||
COMPANY_NAME=Royal Enfield
|
||||
COMPANY_WEBSITE=https://www.royalenfield.com
|
||||
SUPPORT_EMAIL=support@royalenfield.com
|
||||
COMPANY_WEBSITE=https://www.{{API_DOMAIN}}
|
||||
SUPPORT_EMAIL=support@{{API_DOMAIN}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -65,7 +65,7 @@ Each template uses color-coded gradients to indicate the scenario:
|
||||
All templates feature a single action button:
|
||||
- **Text:** "View Request Details" / "Review Request Now" / "Take Action Now"
|
||||
- **Link Format:** `{baseURL}/request/{requestNumber}`
|
||||
- **Example:** `https://workflow.royalenfield.com/request/REQ-2025-12-0013`
|
||||
- **Example:** `https://workflow.{{API_DOMAIN}}/request/REQ-2025-12-0013`
|
||||
|
||||
No approval/rejection buttons in emails - all actions happen within the application.
|
||||
|
||||
@ -231,8 +231,8 @@ SMTP_USER=your-email@domain.com
|
||||
SMTP_PASSWORD=your-app-password
|
||||
|
||||
# Email Settings
|
||||
EMAIL_FROM=RE Workflow System <notifications@royalenfield.com>
|
||||
BASE_URL=https://workflow.royalenfield.com
|
||||
EMAIL_FROM=RE Workflow System <notifications@{{API_DOMAIN}}>
|
||||
BASE_URL=https://workflow.{{API_DOMAIN}}
|
||||
COMPANY_NAME=Royal Enfield
|
||||
```
|
||||
|
||||
|
||||
@ -361,7 +361,7 @@ All `[ViewDetailsLink]` placeholders should be replaced with:
|
||||
{baseURL}/request/{requestNumber}
|
||||
```
|
||||
|
||||
Example: `https://workflow.royalenfield.com/request/REQ-2025-12-0013`
|
||||
Example: `https://workflow.{{API_DOMAIN}}/request/REQ-2025-12-0013`
|
||||
|
||||
### Company Name
|
||||
Replace `[CompanyName]` with your organization name (e.g., "Royal Enfield")
|
||||
|
||||
@ -12,15 +12,15 @@ emailtemplates/
|
||||
├── approvalRequest.template.ts ✅ Single approver email
|
||||
├── multiApproverRequest.template.ts ✅ Multi-approver email
|
||||
│
|
||||
├── approvalConfirmation.template.ts 🔨 TODO
|
||||
├── rejectionNotification.template.ts 🔨 TODO
|
||||
├── tatReminder.template.ts 🔨 TODO
|
||||
├── tatBreached.template.ts 🔨 TODO
|
||||
├── workflowPaused.template.ts 🔨 TODO
|
||||
├── workflowResumed.template.ts 🔨 TODO
|
||||
├── participantAdded.template.ts 🔨 TODO
|
||||
├── approverSkipped.template.ts 🔨 TODO
|
||||
└── requestClosed.template.ts 🔨 TODO
|
||||
├── approvalConfirmation.template.ts ✅ DONE
|
||||
├── rejectionNotification.template.ts ✅ DONE
|
||||
├── tatReminder.template.ts ✅ DONE
|
||||
├── tatBreached.template.ts ✅ DONE
|
||||
├── workflowPaused.template.ts ✅ DONE
|
||||
├── workflowResumed.template.ts ✅ DONE
|
||||
├── participantAdded.template.ts ✅ DONE
|
||||
├── approverSkipped.template.ts ✅ DONE
|
||||
└── requestClosed.template.ts ✅ DONE
|
||||
```
|
||||
|
||||
---
|
||||
@ -53,7 +53,7 @@ const data: RequestCreatedData = {
|
||||
requestTime: '02:30 PM',
|
||||
totalApprovers: 3,
|
||||
expectedTAT: 48,
|
||||
viewDetailsLink: 'https://workflow.royalenfield.com/request/REQ-2025-12-0013',
|
||||
viewDetailsLink: 'https://workflow.{{API_DOMAIN}}/request/REQ-2025-12-0013',
|
||||
companyName: 'Royal Enfield'
|
||||
};
|
||||
```
|
||||
@ -188,10 +188,10 @@ SMTP_USER=your-email@domain.com
|
||||
SMTP_PASSWORD=your-app-password
|
||||
|
||||
# Email Settings
|
||||
EMAIL_FROM=Royal Enfield Workflow <notifications@royalenfield.com>
|
||||
EMAIL_FROM=Royal Enfield Workflow <notifications@{{API_DOMAIN}}>
|
||||
|
||||
# Application Settings
|
||||
BASE_URL=https://workflow.royalenfield.com
|
||||
BASE_URL=https://workflow.{{API_DOMAIN}}
|
||||
COMPANY_NAME=Royal Enfield
|
||||
```
|
||||
|
||||
|
||||
@ -13,12 +13,12 @@ import { EmailHeaderConfig, EmailFooterConfig } from './helpers';
|
||||
export const CompanyInfo = {
|
||||
name: 'Royal Enfield',
|
||||
productName: 'RE Flow', // Product name displayed in header
|
||||
website: 'https://www.royalenfield.com',
|
||||
supportEmail: 'support@royalenfield.com',
|
||||
website: 'https://www.{{API_DOMAIN}}',
|
||||
supportEmail: 'support@{{API_DOMAIN}}',
|
||||
|
||||
// Logo configuration for email headers
|
||||
logo: {
|
||||
url: 'https://www.royalenfield.com/content/dam/RE-Platform-Revamp/re-revamp-commons/logo.webp',
|
||||
url: 'https://www.{{API_DOMAIN}}/content/dam/RE-Platform-Revamp/re-revamp-commons/logo.webp',
|
||||
alt: 'Royal Enfield Logo',
|
||||
width: 220, // Logo width in pixels (wider for better visibility)
|
||||
height: 65, // Logo height in pixels (proportional ratio ~3.4:1)
|
||||
@ -88,7 +88,7 @@ export const CustomHeaderStyles = {
|
||||
* Usage in email service:
|
||||
* const link = getViewDetailsLink('REQ-2025-12-0013', process.env.FRONTEND_URL);
|
||||
*
|
||||
* Result: https://workflow.royalenfield.com/request/REQ-2025-12-0013
|
||||
* Result: https://workflow.{{API_DOMAIN}}/request/REQ-2025-12-0013
|
||||
*/
|
||||
export function getViewDetailsLink(requestNumber: string, frontendUrl: string): string {
|
||||
return `${frontendUrl}/request/${requestNumber}`;
|
||||
|
||||
@ -14,13 +14,13 @@ async function generatePreviews() {
|
||||
// Sample data
|
||||
const initiator = {
|
||||
userId: 'user-1',
|
||||
email: 'john.doe@royalenfield.com',
|
||||
email: 'john.doe@{{API_DOMAIN}}',
|
||||
displayName: 'John Doe'
|
||||
};
|
||||
|
||||
const approver = {
|
||||
userId: 'user-2',
|
||||
email: 'jane.smith@royalenfield.com',
|
||||
email: 'jane.smith@{{API_DOMAIN}}',
|
||||
displayName: 'Jane Smith'
|
||||
};
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ async function sendTestEmail() {
|
||||
// Test 1: Request Created Email
|
||||
const html1 = getRequestCreatedEmail(requestCreatedData);
|
||||
const info1 = await transporter.sendMail({
|
||||
from: '"Royal Enfield Workflow" <noreply@royalenfield.com>',
|
||||
from: '"Royal Enfield Workflow" <noreply@{{API_DOMAIN}}>',
|
||||
to: 'initiator@example.com',
|
||||
subject: '[REQ-2025-12-0013] Request Created Successfully',
|
||||
html: html1
|
||||
@ -113,7 +113,7 @@ async function sendTestEmail() {
|
||||
// Test 2: Approval Request Email (Single)
|
||||
const html2 = getApprovalRequestEmail(approvalRequestData);
|
||||
const info2 = await transporter.sendMail({
|
||||
from: '"Royal Enfield Workflow" <noreply@royalenfield.com>',
|
||||
from: '"Royal Enfield Workflow" <noreply@{{API_DOMAIN}}>',
|
||||
to: 'approver@example.com',
|
||||
subject: '[REQ-2025-12-0013] Approval Request - Action Required',
|
||||
html: html2
|
||||
@ -126,7 +126,7 @@ async function sendTestEmail() {
|
||||
// Test 3: Multi-Approver Request Email
|
||||
const html3 = getMultiApproverRequestEmail(multiApproverData);
|
||||
const info3 = await transporter.sendMail({
|
||||
from: '"Royal Enfield Workflow" <noreply@royalenfield.com>',
|
||||
from: '"Royal Enfield Workflow" <noreply@{{API_DOMAIN}}>',
|
||||
to: 'approver-level2@example.com',
|
||||
subject: '[REQ-2025-12-0013] Multi-Level Approval Request - Your Turn',
|
||||
html: html3
|
||||
|
||||
@ -18,7 +18,7 @@ async function testRealScenario() {
|
||||
// Mock user data (simulating real database records)
|
||||
const user10 = {
|
||||
userId: 'user-10-uuid',
|
||||
email: 'john.doe@royalenfield.com',
|
||||
email: 'john.doe@{{API_DOMAIN}}',
|
||||
displayName: 'John Doe',
|
||||
department: 'Engineering',
|
||||
designation: 'Senior Engineer'
|
||||
@ -26,7 +26,7 @@ async function testRealScenario() {
|
||||
|
||||
const user12 = {
|
||||
userId: 'user-12-uuid',
|
||||
email: 'jane.smith@royalenfield.com',
|
||||
email: 'jane.smith@{{API_DOMAIN}}',
|
||||
displayName: 'Jane Smith',
|
||||
department: 'Management',
|
||||
designation: 'Engineering Manager',
|
||||
@ -52,7 +52,7 @@ async function testRealScenario() {
|
||||
This purchase is critical for our Q1 2025 testing schedule and has been pre-approved by the department head.
|
||||
</blockquote>
|
||||
<p>Please review and approve at your earliest convenience.</p>
|
||||
<p>For questions, contact: <a href="mailto:john.doe@royalenfield.com">john.doe@royalenfield.com</a></p>
|
||||
<p>For questions, contact: <a href="mailto:john.doe@{{API_DOMAIN}}">john.doe@{{API_DOMAIN}}</a></p>
|
||||
`,
|
||||
requestType: 'Purchase',
|
||||
priority: 'HIGH',
|
||||
@ -68,21 +68,21 @@ async function testRealScenario() {
|
||||
{
|
||||
levelNumber: 1,
|
||||
approverName: 'Jane Smith',
|
||||
approverEmail: 'jane.smith@royalenfield.com',
|
||||
approverEmail: 'jane.smith@{{API_DOMAIN}}',
|
||||
status: 'PENDING',
|
||||
approvedAt: null
|
||||
},
|
||||
{
|
||||
levelNumber: 2,
|
||||
approverName: 'Michael Brown',
|
||||
approverEmail: 'michael.brown@royalenfield.com',
|
||||
approverEmail: 'michael.brown@{{API_DOMAIN}}',
|
||||
status: 'PENDING',
|
||||
approvedAt: null
|
||||
},
|
||||
{
|
||||
levelNumber: 3,
|
||||
approverName: 'Sarah Johnson',
|
||||
approverEmail: 'sarah.johnson@royalenfield.com',
|
||||
approverEmail: 'sarah.johnson@{{API_DOMAIN}}',
|
||||
status: 'PENDING',
|
||||
approvedAt: null
|
||||
}
|
||||
@ -168,7 +168,7 @@ async function testRealScenario() {
|
||||
approvedUser12,
|
||||
user10,
|
||||
false, // not final approval
|
||||
{ displayName: 'Michael Brown', email: 'michael.brown@royalenfield.com' }
|
||||
{ displayName: 'Michael Brown', email: 'michael.brown@{{API_DOMAIN}}' }
|
||||
);
|
||||
|
||||
console.log('\n');
|
||||
|
||||
@ -17,6 +17,8 @@ import dealerClaimRoutes from './dealerClaim.routes';
|
||||
import templateRoutes from './template.routes';
|
||||
import dealerRoutes from './dealer.routes';
|
||||
import dmsWebhookRoutes from './dmsWebhook.routes';
|
||||
import { authenticateToken } from '../middlewares/auth.middleware';
|
||||
import { requireAdmin } from '../middlewares/authorization.middleware';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@ -38,7 +40,7 @@ router.use('/user/preferences', userPreferenceRoutes); // User preferences (auth
|
||||
router.use('/documents', documentRoutes);
|
||||
router.use('/tat', tatRoutes);
|
||||
router.use('/admin', adminRoutes);
|
||||
router.use('/debug', debugRoutes);
|
||||
router.use('/debug', authenticateToken, requireAdmin, debugRoutes);
|
||||
router.use('/dashboard', dashboardRoutes);
|
||||
router.use('/notifications', notificationRoutes);
|
||||
router.use('/conclusions', conclusionRoutes);
|
||||
|
||||
@ -323,7 +323,7 @@ async function autoSetup(): Promise<void> {
|
||||
console.log(' 1. Server will start automatically');
|
||||
console.log(' 2. Log in via SSO');
|
||||
console.log(' 3. Run this SQL to make yourself admin:');
|
||||
console.log(` UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@royalenfield.com';\n`);
|
||||
console.log(` UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@{{API_DOMAIN}}';\n`);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
|
||||
@ -77,7 +77,7 @@ const dealersData: DealerSeedData[] = [
|
||||
onBoardingCharges: null,
|
||||
date: '2014-09-30',
|
||||
singleFormatMonthYear: 'Sep-2014',
|
||||
domainId: 'acceleratemotors.rrnagar@dealer.royalenfield.com',
|
||||
domainId: 'acceleratemotors.rrnagar@dealer.{{API_DOMAIN}}',
|
||||
replacement: null,
|
||||
terminationResignationStatus: null,
|
||||
dateOfTerminationResignation: null,
|
||||
|
||||
@ -21,7 +21,7 @@ interface DealerData {
|
||||
|
||||
const dealers: DealerData[] = [
|
||||
{
|
||||
email: 'test.2@royalenfield.com',
|
||||
email: 'test.2@{{API_DOMAIN}}',
|
||||
dealerCode: 'RE-MH-001',
|
||||
dealerName: 'Royal Motors Mumbai',
|
||||
displayName: 'Royal Motors Mumbai',
|
||||
@ -31,7 +31,7 @@ const dealers: DealerData[] = [
|
||||
role: 'USER',
|
||||
},
|
||||
{
|
||||
email: 'test.4@royalenfield.com',
|
||||
email: 'test.4@{{API_DOMAIN}}',
|
||||
dealerCode: 'RE-DL-002',
|
||||
dealerName: 'Delhi enfield center',
|
||||
displayName: 'Delhi Enfield Center',
|
||||
|
||||
@ -538,19 +538,19 @@ export class ApprovalService {
|
||||
// Check if it's an auto-step by checking approverEmail or levelName
|
||||
// Note: Activity Creation, E-Invoice Generation, and Credit Note Confirmation are now activity logs only, not approval steps
|
||||
// These steps are processed automatically and should NOT trigger notifications
|
||||
const isAutoStep = (nextLevel as any).approverEmail === 'system@royalenfield.com'
|
||||
const isAutoStep = (nextLevel as any).approverEmail === 'system@{{API_DOMAIN}}'
|
||||
|| (nextLevel as any).approverName === 'System Auto-Process'
|
||||
|| (nextLevel as any).approverId === 'system';
|
||||
|
||||
// IMPORTANT: Skip notifications and assignment logging for system/auto-steps
|
||||
// System steps are any step with system@royalenfield.com
|
||||
// System steps are any step with system@{{API_DOMAIN}}
|
||||
// Only send notifications to real users, NOT system processes
|
||||
if (!isAutoStep && (nextLevel as any).approverId && (nextLevel as any).approverId !== 'system') {
|
||||
// Additional checks: ensure approverEmail and approverName are not system-related
|
||||
// This prevents notifications to system accounts even if they pass other checks
|
||||
const approverEmail = (nextLevel as any).approverEmail || '';
|
||||
const approverName = (nextLevel as any).approverName || '';
|
||||
const isSystemEmail = approverEmail.toLowerCase() === 'system@royalenfield.com'
|
||||
const isSystemEmail = approverEmail.toLowerCase() === 'system@{{API_DOMAIN}}'
|
||||
|| approverEmail.toLowerCase().includes('system');
|
||||
const isSystemName = approverName.toLowerCase() === 'system auto-process'
|
||||
|| approverName.toLowerCase().includes('system');
|
||||
|
||||
@ -330,7 +330,7 @@ export class DealerClaimService {
|
||||
let stepDef = null;
|
||||
|
||||
// Check if this is a system step by email (for backwards compatibility)
|
||||
const isSystemEmail = approver.email === 'system@royalenfield.com' || approver.email === 'finance@royalenfield.com';
|
||||
const isSystemEmail = approver.email === 'system@{{API_DOMAIN}}' || approver.email === 'finance@{{API_DOMAIN}}';
|
||||
|
||||
if (approver.isAdditional) {
|
||||
// Additional approver - use stepName from frontend
|
||||
@ -594,7 +594,7 @@ export class DealerClaimService {
|
||||
});
|
||||
|
||||
// 2. Add Dealer (treated as Okta/internal user - sync from Okta if needed)
|
||||
if (dealerEmail && dealerEmail.toLowerCase() !== 'system@royalenfield.com') {
|
||||
if (dealerEmail && dealerEmail.toLowerCase() !== 'system@{{API_DOMAIN}}') {
|
||||
let dealerUser = await User.findOne({
|
||||
where: { email: dealerEmail.toLowerCase() },
|
||||
});
|
||||
@ -626,7 +626,7 @@ export class DealerClaimService {
|
||||
|
||||
// 3. Add all approvers from approval levels (excluding system and duplicates)
|
||||
const addedUserIds = new Set<string>([initiatorId]);
|
||||
const systemEmails = ['system@royalenfield.com'];
|
||||
const systemEmails = ['system@{{API_DOMAIN}}'];
|
||||
|
||||
for (const level of approvalLevels) {
|
||||
const approverEmail = (level as any).approverEmail?.toLowerCase();
|
||||
|
||||
@ -399,11 +399,11 @@ export class DealerClaimApprovalService {
|
||||
const nextApproverName = (nextLevel as any).approverName || nextApproverEmail || 'approver';
|
||||
|
||||
// Check if it's an auto-step or system process
|
||||
const isAutoStep = nextApproverEmail === 'system@royalenfield.com'
|
||||
const isAutoStep = nextApproverEmail === 'system@{{API_DOMAIN}}'
|
||||
|| (nextLevel as any).approverName === 'System Auto-Process'
|
||||
|| nextApproverId === 'system';
|
||||
|
||||
const isSystemEmail = nextApproverEmail.toLowerCase() === 'system@royalenfield.com'
|
||||
const isSystemEmail = nextApproverEmail.toLowerCase() === 'system@{{API_DOMAIN}}'
|
||||
|| nextApproverEmail.toLowerCase().includes('system');
|
||||
const isSystemName = nextApproverName.toLowerCase() === 'system auto-process'
|
||||
|| nextApproverName.toLowerCase().includes('system');
|
||||
|
||||
@ -19,7 +19,7 @@ interface EmailOptions {
|
||||
|
||||
// Hardcoded BCC addresses (temporary - for time being)
|
||||
const HARDCODED_BCC: string[] = [
|
||||
'rohitm_ext@royalenfield.com',
|
||||
'{{USER_EMAIL}}',
|
||||
// Add your BCC email addresses here
|
||||
];
|
||||
|
||||
@ -119,7 +119,7 @@ export class EmailService {
|
||||
}
|
||||
|
||||
const recipients = Array.isArray(options.to) ? options.to.join(', ') : options.to;
|
||||
const fromAddress = process.env.EMAIL_FROM || 'RE Flow <noreply@royalenfield.com>';
|
||||
const fromAddress = process.env.EMAIL_FROM || 'RE Flow <noreply@{{API_DOMAIN}}>';
|
||||
|
||||
// Merge hardcoded BCC with provided BCC
|
||||
let bccRecipients: string[] = [];
|
||||
@ -166,17 +166,6 @@ export class EmailService {
|
||||
if (previewUrl) {
|
||||
result.previewUrl = previewUrl;
|
||||
|
||||
// Always log to console for visibility
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log(`📧 EMAIL PREVIEW (${options.subject})`);
|
||||
console.log(`To: ${recipients}`);
|
||||
if (finalBcc && finalBcc.length > 0) {
|
||||
console.log(`BCC: ${finalBcc.join(', ')}`);
|
||||
}
|
||||
console.log(`Preview URL: ${previewUrl}`);
|
||||
console.log(`Message ID: ${info.messageId}`);
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
logger.info(`✅ Email sent (TEST MODE) to ${recipients}`);
|
||||
logger.info(`📧 Preview URL: ${previewUrl}`);
|
||||
} else {
|
||||
|
||||
@ -270,13 +270,7 @@ class GoogleSecretManagerService {
|
||||
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})`);
|
||||
logger.info(`[Secret Manager] ✅ Loaded: ${secretNameToFetch} -> process.env.${envVarName}`);
|
||||
} else {
|
||||
// Track which secrets weren't found for better logging
|
||||
notFoundSecrets.push(fullSecretName);
|
||||
|
||||
@ -250,7 +250,7 @@ class NotificationService {
|
||||
}
|
||||
|
||||
// 4. Send email notification for applicable types (async, don't wait)
|
||||
console.log(`[DEBUG] Checking email for notification type: ${payload.type}`);
|
||||
// Check email notification preferences
|
||||
this.sendEmailNotification(userId, user, payload).catch(emailError => {
|
||||
console.error(`[Notification] Email sending failed for user ${userId}:`, emailError);
|
||||
logger.error(`[Notification] Email sending failed for user ${userId}:`, emailError);
|
||||
@ -269,7 +269,6 @@ class NotificationService {
|
||||
* Only sends for notification types that warrant email
|
||||
*/
|
||||
private async sendEmailNotification(userId: string, user: any, payload: NotificationPayload): Promise<void> {
|
||||
console.log(`[DEBUG Email] Notification type: ${payload.type}, userId: ${userId}`);
|
||||
|
||||
// Import email service (lazy load to avoid circular dependencies)
|
||||
const { emailNotificationService } = await import('./emailNotification.service');
|
||||
@ -311,13 +310,10 @@ class NotificationService {
|
||||
|
||||
const emailType = emailTypeMap[payload.type || ''];
|
||||
|
||||
console.log(`[DEBUG Email] Email type mapped: ${emailType}`);
|
||||
|
||||
if (!emailType) {
|
||||
// This notification type doesn't warrant email
|
||||
// Note: 'document_added' emails are handled separately via emailNotificationService
|
||||
if (payload.type !== 'document_added') {
|
||||
console.log(`[DEBUG Email] No email for notification type: ${payload.type}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -333,10 +329,7 @@ class NotificationService {
|
||||
? await shouldSendEmailWithOverride(userId, emailType) // Assignment emails - use override to ensure delivery
|
||||
: await shouldSendEmail(userId, emailType); // Regular emails
|
||||
|
||||
console.log(`[DEBUG Email] Should send email: ${shouldSend} for type: ${payload.type}, userId: ${userId}`);
|
||||
|
||||
if (!shouldSend) {
|
||||
console.log(`[DEBUG Email] Email skipped for user ${userId}, type: ${payload.type} (preferences)`);
|
||||
logger.warn(`[Email] Email skipped for user ${userId}, type: ${payload.type} (preferences or admin disabled)`);
|
||||
return;
|
||||
}
|
||||
@ -345,11 +338,9 @@ class NotificationService {
|
||||
|
||||
// Trigger email based on notification type
|
||||
// Email service will fetch additional data as needed
|
||||
console.log(`[DEBUG Email] Triggering email for type: ${payload.type}`);
|
||||
try {
|
||||
await this.triggerEmailByType(payload.type || '', userId, payload, user);
|
||||
} catch (error) {
|
||||
console.error(`[DEBUG Email] Error triggering email:`, error);
|
||||
logger.error(`[Email] Failed to trigger email for type ${payload.type}:`, error);
|
||||
}
|
||||
}
|
||||
@ -578,7 +569,7 @@ class NotificationService {
|
||||
approverData = {
|
||||
userId: (rejectedLevel as any).approverId,
|
||||
displayName: (rejectedLevel as any).approverName || 'Unknown Approver',
|
||||
email: (rejectedLevel as any).approverEmail || 'unknown@royalenfield.com',
|
||||
email: (rejectedLevel as any).approverEmail || 'unknown@{{API_DOMAIN}}',
|
||||
rejectedAt: (rejectedLevel as any).actionDate,
|
||||
comments: (rejectedLevel as any).comments
|
||||
};
|
||||
@ -621,7 +612,7 @@ class NotificationService {
|
||||
approverData = {
|
||||
userId: (currentLevel as any).approverId,
|
||||
displayName: (currentLevel as any).approverName || 'Unknown Approver',
|
||||
email: (currentLevel as any).approverEmail || 'unknown@royalenfield.com'
|
||||
email: (currentLevel as any).approverEmail || 'unknown@{{API_DOMAIN}}'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -708,7 +699,7 @@ class NotificationService {
|
||||
approverData = {
|
||||
userId: (currentLevel as any).approverId,
|
||||
displayName: (currentLevel as any).approverName || 'Unknown Approver',
|
||||
email: (currentLevel as any).approverEmail || 'unknown@royalenfield.com'
|
||||
email: (currentLevel as any).approverEmail || 'unknown@{{API_DOMAIN}}'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -842,7 +833,7 @@ class NotificationService {
|
||||
recipientData = {
|
||||
userId: (pausedLevel as any).approverId,
|
||||
displayName: (pausedLevel as any).approverName || 'Unknown Approver',
|
||||
email: (pausedLevel as any).approverEmail || 'unknown@royalenfield.com'
|
||||
email: (pausedLevel as any).approverEmail || 'unknown@{{API_DOMAIN}}'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -894,7 +885,7 @@ class NotificationService {
|
||||
recipientData = {
|
||||
userId: (currentLevel as any).approverId,
|
||||
displayName: (currentLevel as any).approverName || 'Unknown User',
|
||||
email: (currentLevel as any).approverEmail || 'unknown@royalenfield.com'
|
||||
email: (currentLevel as any).approverEmail || 'unknown@{{API_DOMAIN}}'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -71,7 +71,7 @@ export class PdfService {
|
||||
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';
|
||||
const logoUrl = '{{LOGO_URL}}';
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
@ -119,7 +119,7 @@ export class PdfService {
|
||||
<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 GSTIN</div><div class="info-value">{{BUYER_GSTIN}}</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>
|
||||
|
||||
@ -123,7 +123,7 @@ export class PWCIntegrationService {
|
||||
SourceSystem: "RE_WORKFLOW",
|
||||
is_irn: "Y",
|
||||
is_ewb: "N",
|
||||
email: (request as any).initiator?.email || "system@royalenfield.com",
|
||||
email: (request as any).initiator?.email || "system@{{API_DOMAIN}}",
|
||||
TranDtls: {
|
||||
TaxSch: "GST",
|
||||
SubType: "SUPPLY",
|
||||
@ -155,7 +155,7 @@ export class PWCIntegrationService {
|
||||
Em: (dealer as any).dealerPrincipalEmailId || "Supplier@inv.com"
|
||||
},
|
||||
BuyerDtls: {
|
||||
Gstin: "33AAACE3882D1ZZ", // Royal Enfield GST
|
||||
Gstin: "{{BUYER_GSTIN}}", // Royal Enfield GST
|
||||
LglNm: "ROYAL ENFIELD (A UNIT OF EICHER MOTORS LTD)",
|
||||
TrdNm: "ROYAL ENFIELD",
|
||||
Addr1: "No. 2, Thiruvottiyur High Road",
|
||||
@ -201,7 +201,6 @@ export class PWCIntegrationService {
|
||||
'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 }]
|
||||
|
||||
@ -34,7 +34,6 @@ 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;
|
||||
@ -188,12 +187,8 @@ export class SAPIntegrationService {
|
||||
}) : undefined
|
||||
});
|
||||
|
||||
// Add request interceptor for debugging
|
||||
client.interceptors.request.use(
|
||||
(config) => {
|
||||
logger.debug(`[SAP] Making ${config.method?.toUpperCase()} request to: ${config.baseURL}${config.url}`);
|
||||
logger.debug(`[SAP] Auth username: ${this.sapUsername}`);
|
||||
// Don't log password for security
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@ -219,7 +214,7 @@ export class SAPIntegrationService {
|
||||
} else if (error.code === 'ENOTFOUND') {
|
||||
logger.error('[SAP] Host not found - check SAP_BASE_URL');
|
||||
} else if (error.code === 'CERT_HAS_EXPIRED' || error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
||||
logger.error('[SAP] SSL certificate error - set SAP_DISABLE_SSL_VERIFY=true to bypass (testing only)');
|
||||
logger.error('[SAP] SSL certificate error');
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
@ -597,9 +592,10 @@ export class SAPIntegrationService {
|
||||
logger.debug(`[SAP] POST request config prepared (auth included)`);
|
||||
const response = await sapClient.post(urlWithParams, requestPayload, postConfig);
|
||||
|
||||
// Log full response for debugging
|
||||
// Log response status for visibility
|
||||
logger.info(`[SAP] POST Response Status: ${response.status} ${response.statusText || ''}`);
|
||||
logger.info(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2));
|
||||
// Headers relegated to debug level
|
||||
logger.debug(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2));
|
||||
|
||||
// Check if response is XML (SAP returns XML/Atom by default for POST)
|
||||
const contentType = response.headers['content-type'] || '';
|
||||
@ -640,9 +636,10 @@ export class SAPIntegrationService {
|
||||
}
|
||||
}
|
||||
|
||||
// Also log the request that was sent
|
||||
// Log the request URL
|
||||
logger.info(`[SAP] POST Request URL: ${urlWithParams}`);
|
||||
logger.info(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2));
|
||||
// Payload relegated to debug level
|
||||
logger.debug(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2));
|
||||
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
// Parse SAP response
|
||||
|
||||
@ -133,7 +133,6 @@ if (process.env.LOKI_HOST) {
|
||||
}
|
||||
|
||||
transports.push(new LokiTransport(lokiTransportOptions));
|
||||
console.log(`[Logger] ✅ Loki transport enabled: ${process.env.LOKI_HOST}`);
|
||||
} catch (error) {
|
||||
console.warn('[Logger] ⚠️ Failed to initialize Loki transport:', (error as Error).message);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user