removed suspicious comments

This commit is contained in:
laxmanhalaki 2026-02-10 09:54:24 +05:30
parent 17c62d2b45
commit 9060c39f9c
6 changed files with 156 additions and 273 deletions

View File

@ -49,7 +49,7 @@ router.use('/templates', templateRoutes);
router.use('/dealers', dealerRoutes); router.use('/dealers', dealerRoutes);
router.use('/webhooks/dms', dmsWebhookRoutes); router.use('/webhooks/dms', dmsWebhookRoutes);
// TODO: Add other route modules as they are implemented // Add other route modules as they are implemented
// router.use('/approvals', approvalRoutes); // router.use('/approvals', approvalRoutes);
// router.use('/participants', participantRoutes); // router.use('/participants', participantRoutes);

View File

@ -58,7 +58,7 @@ interface DealerSeedData {
} }
// Sample data based on the provided table // Sample data based on the provided table
// TODO: Replace with your actual dealer data from Excel/CSV // Replace with your actual dealer data from Excel/CSV
const dealersData: DealerSeedData[] = [ const dealersData: DealerSeedData[] = [
{ {
salesCode: '5124', salesCode: '5124',
@ -116,7 +116,7 @@ async function seedDealersTable(): Promise<void> {
for (const dealerData of dealersData) { for (const dealerData of dealersData) {
// Use dlrcode or domainId as unique identifier if available // Use dlrcode or domainId as unique identifier if available
const uniqueIdentifier = dealerData.dlrcode || dealerData.domainId || dealerData.salesCode; const uniqueIdentifier = dealerData.dlrcode || dealerData.domainId || dealerData.salesCode;
if (!uniqueIdentifier) { if (!uniqueIdentifier) {
logger.warn('[Seed Dealers Table] Skipping dealer record without unique identifier'); logger.warn('[Seed Dealers Table] Skipping dealer record without unique identifier');
continue; continue;
@ -130,15 +130,15 @@ async function seedDealersTable(): Promise<void> {
const existingDealer = whereConditions.length > 0 const existingDealer = whereConditions.length > 0
? await Dealer.findOne({ ? await Dealer.findOne({
where: { where: {
[Op.or]: whereConditions [Op.or]: whereConditions
} }
}) })
: null; : null;
if (existingDealer) { if (existingDealer) {
logger.info(`[Seed Dealers Table] Dealer ${uniqueIdentifier} already exists, updating...`); logger.info(`[Seed Dealers Table] Dealer ${uniqueIdentifier} already exists, updating...`);
// Update existing dealer // Update existing dealer
await existingDealer.update({ await existingDealer.update({
...dealerData, ...dealerData,

View File

@ -673,7 +673,7 @@ export class DashboardService {
totalCompleted, totalCompleted,
compliantWorkflows: compliantCount, compliantWorkflows: compliantCount,
changeFromPrevious: { changeFromPrevious: {
compliance: '+5.8%', // TODO: Calculate actual change compliance: '+5.8%', // Calculate actual change
cycleTime: '-0.5h' cycleTime: '-0.5h'
} }
}; };

View File

@ -2215,14 +2215,6 @@ export class DealerClaimService {
dealerName: claimDetails.dealerName, dealerName: claimDetails.dealerName,
}); });
// TODO: Implement email service to send credit note to dealer
// await emailService.sendCreditNoteToDealer({
// dealerEmail: claimDetails.dealerEmail,
// dealerName: claimDetails.dealerName,
// creditNoteNumber: creditNote.creditNoteNumber,
// creditNoteAmount: creditNote.creditNoteAmount,
// requestNumber: requestNumber,
// });
} catch (error) { } catch (error) {
logger.error('[DealerClaimService] Error sending credit note to dealer:', error); logger.error('[DealerClaimService] Error sending credit note to dealer:', error);

View File

@ -64,30 +64,6 @@ export class DMSIntegrationService {
}; };
} }
// TODO: Implement actual DMS API call
// Example:
// const response = await axios.post(`${this.dmsBaseUrl}/api/invoices/generate`, {
// request_number: invoiceData.requestNumber,
// dealer_code: invoiceData.dealerCode,
// dealer_name: invoiceData.dealerName,
// amount: invoiceData.amount,
// description: invoiceData.description,
// io_number: invoiceData.ioNumber,
// tax_details: invoiceData.taxDetails
// }, {
// headers: {
// 'Authorization': `Bearer ${this.dmsApiKey}`,
// 'Content-Type': 'application/json'
// }
// });
//
// return {
// success: response.data.success,
// eInvoiceNumber: response.data.e_invoice_number,
// dmsNumber: response.data.dms_number,
// invoiceDate: new Date(response.data.invoice_date),
// invoiceUrl: response.data.invoice_url
// };
logger.warn('[DMS] DMS e-invoice generation not implemented, generating mock invoice'); logger.warn('[DMS] DMS e-invoice generation not implemented, generating mock invoice');
const mockInvoiceNumber = `EINV-${Date.now()}`; const mockInvoiceNumber = `EINV-${Date.now()}`;
@ -145,31 +121,6 @@ export class DMSIntegrationService {
}; };
} }
// TODO: Implement actual DMS API call
// Example:
// const response = await axios.post(`${this.dmsBaseUrl}/api/credit-notes/generate`, {
// request_number: creditNoteData.requestNumber,
// e_invoice_number: creditNoteData.eInvoiceNumber,
// dealer_code: creditNoteData.dealerCode,
// dealer_name: creditNoteData.dealerName,
// amount: creditNoteData.amount,
// reason: creditNoteData.reason,
// description: creditNoteData.description
// }, {
// headers: {
// 'Authorization': `Bearer ${this.dmsApiKey}`,
// 'Content-Type': 'application/json'
// }
// });
//
// return {
// success: response.data.success,
// creditNoteNumber: response.data.credit_note_number,
// creditNoteDate: new Date(response.data.credit_note_date),
// creditNoteAmount: response.data.credit_note_amount,
// creditNoteUrl: response.data.credit_note_url
// };
logger.warn('[DMS] DMS credit note generation not implemented, generating mock credit note'); logger.warn('[DMS] DMS credit note generation not implemented, generating mock credit note');
const mockCreditNoteNumber = `CN-${Date.now()}`; const mockCreditNoteNumber = `CN-${Date.now()}`;
return { return {
@ -217,23 +168,7 @@ export class DMSIntegrationService {
}; };
} }
// TODO: Implement actual DMS API call ;
// Example:
// const response = await axios.get(`${this.dmsBaseUrl}/api/invoices/${eInvoiceNumber}/status`, {
// headers: {
// 'Authorization': `Bearer ${this.dmsApiKey}`,
// 'Content-Type': 'application/json'
// }
// });
//
// return {
// success: true,
// status: response.data.status,
// invoiceNumber: response.data.invoice_number,
// dmsNumber: response.data.dms_number,
// invoiceDate: new Date(response.data.invoice_date),
// amount: response.data.amount
// };
logger.warn('[DMS] DMS invoice status check not implemented, returning mock status'); logger.warn('[DMS] DMS invoice status check not implemented, returning mock status');
return { return {
@ -277,20 +212,7 @@ export class DMSIntegrationService {
}; };
} }
// TODO: Implement actual DMS API call
// Example:
// const response = await axios.get(`${this.dmsBaseUrl}/api/invoices/${eInvoiceNumber}/download`, {
// headers: {
// 'Authorization': `Bearer ${this.dmsApiKey}`
// },
// responseType: 'arraybuffer'
// });
//
// return {
// success: true,
// documentBuffer: Buffer.from(response.data),
// mimeType: response.headers['content-type'] || 'application/pdf'
// };
logger.warn('[DMS] DMS invoice download not implemented, returning mock URL'); logger.warn('[DMS] DMS invoice download not implemented, returning mock URL');
return { return {

View File

@ -59,10 +59,10 @@ export class SAPIntegrationService {
'sap-client': '200' 'sap-client': '200'
}); });
const fullUrl = `${this.sapBaseUrl}${serviceRootUrl}?${queryParams.toString()}`; const fullUrl = `${this.sapBaseUrl}${serviceRootUrl}?${queryParams.toString()}`;
logger.debug(`[SAP] Fetching CSRF token from service: ${serviceName}`); logger.debug(`[SAP] Fetching CSRF token from service: ${serviceName}`);
logger.debug(`[SAP] CSRF token request URL: ${fullUrl}`); logger.debug(`[SAP] CSRF token request URL: ${fullUrl}`);
// Use standalone axios request with Basic Auth in header // Use standalone axios request with Basic Auth in header
// We need to capture cookies from this response to use in POST request // We need to capture cookies from this response to use in POST request
const response = await axios.get(fullUrl, { const response = await axios.get(fullUrl, {
@ -85,16 +85,16 @@ export class SAPIntegrationService {
}); });
// SAP returns CSRF token in response headers (check multiple case variations) // SAP returns CSRF token in response headers (check multiple case variations)
const csrfToken = response.headers['x-csrf-token'] || const csrfToken = response.headers['x-csrf-token'] ||
response.headers['X-CSRF-Token'] || response.headers['X-CSRF-Token'] ||
response.headers['X-Csrf-Token'] || response.headers['X-Csrf-Token'] ||
response.headers['x-csrf-token']; response.headers['x-csrf-token'];
// Extract cookies from response headers // Extract cookies from response headers
// SAP sets cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext // SAP sets cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
const setCookieHeaders = response.headers['set-cookie'] as string | string[] | undefined; const setCookieHeaders = response.headers['set-cookie'] as string | string[] | undefined;
let cookies = ''; let cookies = '';
if (setCookieHeaders) { if (setCookieHeaders) {
if (Array.isArray(setCookieHeaders)) { if (Array.isArray(setCookieHeaders)) {
// Extract cookie values from Set-Cookie headers // Extract cookie values from Set-Cookie headers
@ -107,36 +107,36 @@ export class SAPIntegrationService {
cookies = setCookieHeaders.split(';')[0].trim(); cookies = setCookieHeaders.split(';')[0].trim();
} }
} }
// Log full GET response for debugging // Log full GET response for debugging
logger.info(`[SAP] GET Response Status: ${response.status} ${response.statusText || ''}`); logger.info(`[SAP] GET Response Status: ${response.status} ${response.statusText || ''}`);
logger.info(`[SAP] GET Response Headers:`, JSON.stringify(response.headers, null, 2)); logger.info(`[SAP] GET Response Headers:`, JSON.stringify(response.headers, null, 2));
logger.info(`[SAP] GET Response Data:`, JSON.stringify(response.data, null, 2)); logger.info(`[SAP] GET Response Data:`, JSON.stringify(response.data, null, 2));
if (csrfToken && typeof csrfToken === 'string' && csrfToken !== 'fetch') { if (csrfToken && typeof csrfToken === 'string' && csrfToken !== 'fetch') {
logger.info(`[SAP] CSRF token obtained successfully (length: ${csrfToken.length})`); logger.info(`[SAP] CSRF token obtained successfully (length: ${csrfToken.length})`);
logger.debug(`[SAP] CSRF token preview: ${csrfToken.substring(0, 20)}...`); logger.debug(`[SAP] CSRF token preview: ${csrfToken.substring(0, 20)}...`);
if (cookies) { if (cookies) {
logger.debug(`[SAP] Session cookies captured: ${cookies.substring(0, 50)}...`); logger.debug(`[SAP] Session cookies captured: ${cookies.substring(0, 50)}...`);
} else { } else {
logger.warn('[SAP] No cookies found in CSRF token response - POST may fail'); logger.warn('[SAP] No cookies found in CSRF token response - POST may fail');
} }
return { csrfToken, cookies }; return { csrfToken, cookies };
} }
logger.warn('[SAP] CSRF token not found in response headers or invalid'); logger.warn('[SAP] CSRF token not found in response headers or invalid');
logger.debug('[SAP] Response status:', response.status); logger.debug('[SAP] Response status:', response.status);
logger.debug('[SAP] Available headers:', Object.keys(response.headers).filter(h => h.toLowerCase().includes('csrf'))); logger.debug('[SAP] Available headers:', Object.keys(response.headers).filter(h => h.toLowerCase().includes('csrf')));
return null; return null;
} catch (error) { } catch (error) {
const axiosError = error as AxiosError; const axiosError = error as AxiosError;
if (axiosError.response) { if (axiosError.response) {
logger.error(`[SAP] Failed to get CSRF token: ${axiosError.response.status} ${axiosError.response.statusText}`); logger.error(`[SAP] Failed to get CSRF token: ${axiosError.response.status} ${axiosError.response.statusText}`);
logger.error(`[SAP] Response data:`, axiosError.response.data); logger.error(`[SAP] Response data:`, axiosError.response.data);
if (axiosError.response.status === 401 || axiosError.response.status === 403) { if (axiosError.response.status === 401 || axiosError.response.status === 403) {
logger.error('[SAP] Authentication failed while fetching CSRF token - check SAP credentials'); logger.error('[SAP] Authentication failed while fetching CSRF token - check SAP credentials');
} else if (axiosError.response.status === 404) { } else if (axiosError.response.status === 404) {
@ -152,7 +152,7 @@ export class SAPIntegrationService {
} else { } else {
logger.error('[SAP] Error setting up CSRF token request:', error instanceof Error ? error.message : 'Unknown error'); logger.error('[SAP] Error setting up CSRF token request:', error instanceof Error ? error.message : 'Unknown error');
} }
return null; return null;
} }
} }
@ -163,7 +163,7 @@ export class SAPIntegrationService {
private createSapClient() { private createSapClient() {
// Check if SSL verification should be disabled (for testing with self-signed certs) // Check if SSL verification should be disabled (for testing with self-signed certs)
const disableSSLVerification = process.env.SAP_DISABLE_SSL_VERIFY === 'true'; const disableSSLVerification = process.env.SAP_DISABLE_SSL_VERIFY === 'true';
const client = axios.create({ const client = axios.create({
baseURL: this.sapBaseUrl, baseURL: this.sapBaseUrl,
timeout: this.sapTimeout, timeout: this.sapTimeout,
@ -265,11 +265,11 @@ export class SAPIntegrationService {
} }
const sapClient = this.createSapClient(); const sapClient = this.createSapClient();
// SAP OData endpoint: GetSenderDataSet with filter on IONumber // SAP OData endpoint: GetSenderDataSet with filter on IONumber
// Service name is configurable via SAP_SERVICE_NAME env variable // Service name is configurable via SAP_SERVICE_NAME env variable
const endpoint = this.buildODataEndpoint('GetSenderDataSet'); const endpoint = this.buildODataEndpoint('GetSenderDataSet');
// Build OData query parameters matching the working URL format // Build OData query parameters matching the working URL format
// $filter: Filter by IO number // $filter: Filter by IO number
// $select: Select specific fields (Sender, ResponseDate, GetIODetailsSet01) // $select: Select specific fields (Sender, ResponseDate, GetIODetailsSet01)
@ -281,18 +281,18 @@ export class SAPIntegrationService {
'$expand': 'GetIODetailsSet01', '$expand': 'GetIODetailsSet01',
'$format': 'json' '$format': 'json'
}); });
const fullUrl = `${endpoint}?${queryParams.toString()}`; const fullUrl = `${endpoint}?${queryParams.toString()}`;
logger.info(`[SAP] Validating IO number: ${ioNumber} using service: ${this.sapServiceName}`); logger.info(`[SAP] Validating IO number: ${ioNumber} using service: ${this.sapServiceName}`);
logger.debug(`[SAP] Request URL: ${this.sapBaseUrl}${fullUrl}`); logger.debug(`[SAP] Request URL: ${this.sapBaseUrl}${fullUrl}`);
const response = await sapClient.get(fullUrl); const response = await sapClient.get(fullUrl);
if (response.status === 200 && response.data) { if (response.status === 200 && response.data) {
// SAP OData response format: { d: { results: [...] } } // SAP OData response format: { d: { results: [...] } }
const results = response.data.d?.results || response.data.results || []; const results = response.data.d?.results || response.data.results || [];
if (results.length === 0) { if (results.length === 0) {
logger.warn(`[SAP] IO number ${ioNumber} not found in SAP`); logger.warn(`[SAP] IO number ${ioNumber} not found in SAP`);
return { return {
@ -308,11 +308,11 @@ export class SAPIntegrationService {
// Get first result (should be only one for a specific IO number) // Get first result (should be only one for a specific IO number)
const senderData = results[0]; const senderData = results[0];
// IO details are in the expanded GetIODetailsSet01 entity set // IO details are in the expanded GetIODetailsSet01 entity set
// Structure: senderData.GetIODetailsSet01.results[0] // Structure: senderData.GetIODetailsSet01.results[0]
const ioDetailsSet = senderData.GetIODetailsSet01; const ioDetailsSet = senderData.GetIODetailsSet01;
if (!ioDetailsSet || !ioDetailsSet.results || !Array.isArray(ioDetailsSet.results) || ioDetailsSet.results.length === 0) { if (!ioDetailsSet || !ioDetailsSet.results || !Array.isArray(ioDetailsSet.results) || ioDetailsSet.results.length === 0) {
logger.warn(`[SAP] No IO details found in expanded GetIODetailsSet01 for IO ${ioNumber}`); logger.warn(`[SAP] No IO details found in expanded GetIODetailsSet01 for IO ${ioNumber}`);
return { return {
@ -325,34 +325,34 @@ export class SAPIntegrationService {
error: 'IO details not found in SAP response' error: 'IO details not found in SAP response'
}; };
} }
// Get the first IO detail from the results array // Get the first IO detail from the results array
const ioDetails = ioDetailsSet.results[0]; const ioDetails = ioDetailsSet.results[0];
// Map SAP response fields to our format based on actual response structure: // Map SAP response fields to our format based on actual response structure:
// - AvailableAmount: string with trailing space (e.g., "14333415.00 ") // - AvailableAmount: string with trailing space (e.g., "14333415.00 ")
// - IODescription: description text // - IODescription: description text
// - IONumber: IO number // - IONumber: IO number
// - BlockedAmount: may not be present in response, default to 0 // - BlockedAmount: may not be present in response, default to 0
// - Currency: may not be present, default to INR // - Currency: may not be present, default to INR
// Parse AvailableAmount - it's a string that may have trailing spaces // Parse AvailableAmount - it's a string that may have trailing spaces
const availableAmountStr = (ioDetails.AvailableAmount || '0').toString().trim(); const availableAmountStr = (ioDetails.AvailableAmount || '0').toString().trim();
const availableBalance = parseFloat(availableAmountStr) || 0; const availableBalance = parseFloat(availableAmountStr) || 0;
// BlockedAmount may not be in the response, default to 0 // BlockedAmount may not be in the response, default to 0
// If it exists, it might also be a string with trailing space // If it exists, it might also be a string with trailing space
const blockedAmountStr = (ioDetails.BlockedAmount || ioDetails.Blocked || '0').toString().trim(); const blockedAmountStr = (ioDetails.BlockedAmount || ioDetails.Blocked || '0').toString().trim();
const blockedAmount = parseFloat(blockedAmountStr) || 0; const blockedAmount = parseFloat(blockedAmountStr) || 0;
const remainingBalance = availableBalance - blockedAmount; const remainingBalance = availableBalance - blockedAmount;
// Currency may not be in response, default to INR // Currency may not be in response, default to INR
const currency = (ioDetails.Currency || ioDetails.CurrencyCode || ioDetails.Curr || 'INR').toString().trim(); const currency = (ioDetails.Currency || ioDetails.CurrencyCode || ioDetails.Curr || 'INR').toString().trim();
// Description from IODescription field // Description from IODescription field
const description = ioDetails.IODescription || ioDetails.Description || ioDetails.Text || ioDetails.ShortText || undefined; const description = ioDetails.IODescription || ioDetails.Description || ioDetails.Text || ioDetails.ShortText || undefined;
// IO Number from the IO details // IO Number from the IO details
const validatedIONumber = ioDetails.IONumber || ioDetails.InternalOrder || ioNumber; const validatedIONumber = ioDetails.IONumber || ioDetails.InternalOrder || ioNumber;
@ -416,7 +416,7 @@ export class SAPIntegrationService {
} }
} catch (error) { } catch (error) {
const axiosError = error as AxiosError; const axiosError = error as AxiosError;
if (axiosError.response) { if (axiosError.response) {
// SAP returned an error response // SAP returned an error response
logger.error(`[SAP] Error validating IO number ${ioNumber}:`, { logger.error(`[SAP] Error validating IO number ${ioNumber}:`, {
@ -424,7 +424,7 @@ export class SAPIntegrationService {
statusText: axiosError.response.statusText, statusText: axiosError.response.statusText,
data: axiosError.response.data data: axiosError.response.data
}); });
return { return {
isValid: false, isValid: false,
ioNumber, ioNumber,
@ -494,16 +494,16 @@ export class SAPIntegrationService {
} }
const sapClient = this.createSapClient(); const sapClient = this.createSapClient();
// SAP OData endpoint for budget blocking // SAP OData endpoint for budget blocking
// Service: ZFI_BUDGET_BLOCK_API_SRV // Service: ZFI_BUDGET_BLOCK_API_SRV
// Entity Set: RequesterInputSet // Entity Set: RequesterInputSet
const endpoint = `/sap/opu/odata/sap/${this.sapBlockServiceName}/RequesterInputSet`; const endpoint = `/sap/opu/odata/sap/${this.sapBlockServiceName}/RequesterInputSet`;
// Format current date/time in ISO format: "2025-08-29T10:51:00" // Format current date/time in ISO format: "2025-08-29T10:51:00"
const now = new Date(); const now = new Date();
const requestDateTime = now.toISOString().replace(/\.\d{3}Z$/, ''); // Remove milliseconds and Z const requestDateTime = now.toISOString().replace(/\.\d{3}Z$/, ''); // Remove milliseconds and Z
// Build request payload matching SAP API structure // Build request payload matching SAP API structure
const requestPayload = { const requestPayload = {
Request_Date_Time: requestDateTime, Request_Date_Time: requestDateTime,
@ -517,26 +517,26 @@ export class SAPIntegrationService {
lt_io_output: [], lt_io_output: [],
ls_response: [] ls_response: []
}; };
logger.info(`[SAP] Blocking budget for IO ${ioNumber}, Amount: ${amount}, Request: ${requestNumber}`); logger.info(`[SAP] Blocking budget for IO ${ioNumber}, Amount: ${amount}, Request: ${requestNumber}`);
logger.debug(`[SAP] Budget block request payload:`, JSON.stringify(requestPayload, null, 2)); logger.debug(`[SAP] Budget block request payload:`, JSON.stringify(requestPayload, null, 2));
// Get CSRF token and cookies for POST request (SAP OData requires both) // Get CSRF token and cookies for POST request (SAP OData requires both)
// SAP sets session cookies during CSRF token fetch that must be included in POST // SAP sets session cookies during CSRF token fetch that must be included in POST
const csrfData = await this.getCsrfToken(this.sapBlockServiceName); const csrfData = await this.getCsrfToken(this.sapBlockServiceName);
if (!csrfData || !csrfData.csrfToken) { if (!csrfData || !csrfData.csrfToken) {
logger.warn('[SAP] CSRF token not available, request may fail with CSRF validation error'); logger.warn('[SAP] CSRF token not available, request may fail with CSRF validation error');
logger.warn('[SAP] This is expected if SAP requires CSRF tokens for POST requests'); logger.warn('[SAP] This is expected if SAP requires CSRF tokens for POST requests');
} }
// Build headers with CSRF token, cookies, and other required headers // Build headers with CSRF token, cookies, and other required headers
// Force JSON format via Accept header (SAP returns XML by default for POST) // Force JSON format via Accept header (SAP returns XML by default for POST)
const headers: Record<string, string> = { const headers: Record<string, string> = {
'Accept': 'application/json, application/atom+xml;q=0.9', // Prefer JSON, fallback to XML 'Accept': 'application/json, application/atom+xml;q=0.9', // Prefer JSON, fallback to XML
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}; };
// Add CSRF token if available (required by SAP for POST/PUT/DELETE) // Add CSRF token if available (required by SAP for POST/PUT/DELETE)
// Use lowercase 'x-csrf-token' as per SAP requirement // Use lowercase 'x-csrf-token' as per SAP requirement
if (csrfData?.csrfToken) { if (csrfData?.csrfToken) {
@ -545,7 +545,7 @@ export class SAPIntegrationService {
} else { } else {
logger.warn('[SAP] CSRF token not available - request may fail with CSRF validation error'); logger.warn('[SAP] CSRF token not available - request may fail with CSRF validation error');
} }
// Add cookies if available (SAP session cookies required for POST) // Add cookies if available (SAP session cookies required for POST)
// Cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext // Cookies like: SAP_SESSIONID_DRE_200 and sap-usercontext
if (csrfData?.cookies) { if (csrfData?.cookies) {
@ -554,24 +554,24 @@ export class SAPIntegrationService {
} else { } else {
logger.warn('[SAP] No session cookies available - request may fail'); logger.warn('[SAP] No session cookies available - request may fail');
} }
// Some SAP systems also require these headers // Some SAP systems also require these headers
headers['X-Requested-With'] = 'XMLHttpRequest'; headers['X-Requested-With'] = 'XMLHttpRequest';
// NOTE: Do NOT add query parameters ($format, sap-client) to POST requests // NOTE: Do NOT add query parameters ($format, sap-client) to POST requests
// SAP OData does not allow SystemQueryOptions in POST requests // SAP OData does not allow SystemQueryOptions in POST requests
// Query parameters are only allowed for GET requests // Query parameters are only allowed for GET requests
// Use the endpoint directly without query parameters // Use the endpoint directly without query parameters
const urlWithParams = endpoint; const urlWithParams = endpoint;
logger.debug(`[SAP] POST request URL: ${urlWithParams}`); logger.debug(`[SAP] POST request URL: ${urlWithParams}`);
logger.debug(`[SAP] Request headers (CSRF token and cookies masked):`, { logger.debug(`[SAP] Request headers (CSRF token and cookies masked):`, {
...headers, ...headers,
'x-csrf-token': csrfData?.csrfToken ? `${csrfData.csrfToken.substring(0, 10)}...` : 'not set', 'x-csrf-token': csrfData?.csrfToken ? `${csrfData.csrfToken.substring(0, 10)}...` : 'not set',
'Cookie': csrfData?.cookies ? `${csrfData.cookies.substring(0, 30)}...` : 'not set' 'Cookie': csrfData?.cookies ? `${csrfData.cookies.substring(0, 30)}...` : 'not set'
}); });
logger.debug(`[SAP] Using username: ${this.sapUsername}`); logger.debug(`[SAP] Using username: ${this.sapUsername}`);
// Ensure auth is explicitly included in POST request config // Ensure auth is explicitly included in POST request config
// The axios instance has auth configured, but we'll include it explicitly to be safe // The axios instance has auth configured, but we'll include it explicitly to be safe
// This ensures auth is sent even if the instance config is overridden // This ensures auth is sent even if the instance config is overridden
@ -587,21 +587,21 @@ export class SAPIntegrationService {
}) : undefined, }) : undefined,
validateStatus: (status: number) => status < 500 // Don't throw on 4xx validateStatus: (status: number) => status < 500 // Don't throw on 4xx
}; };
logger.debug(`[SAP] POST request config prepared (auth included)`); logger.debug(`[SAP] POST request config prepared (auth included)`);
const response = await sapClient.post(urlWithParams, requestPayload, postConfig); const response = await sapClient.post(urlWithParams, requestPayload, postConfig);
// Log full response for debugging // Log full response for debugging
logger.info(`[SAP] POST Response Status: ${response.status} ${response.statusText || ''}`); logger.info(`[SAP] POST Response Status: ${response.status} ${response.statusText || ''}`);
logger.info(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2)); logger.info(`[SAP] POST Response Headers:`, JSON.stringify(response.headers, null, 2));
// Check if response is XML (SAP returns XML/Atom by default for POST) // Check if response is XML (SAP returns XML/Atom by default for POST)
const contentType = response.headers['content-type'] || ''; const contentType = response.headers['content-type'] || '';
const isXML = contentType.includes('xml') || contentType.includes('atom') || const isXML = contentType.includes('xml') || contentType.includes('atom') ||
(typeof response.data === 'string' && response.data.trim().startsWith('<')); (typeof response.data === 'string' && response.data.trim().startsWith('<'));
let responseData: any = response.data; let responseData: any = response.data;
// Parse XML if needed // Parse XML if needed
if (isXML && typeof response.data === 'string') { if (isXML && typeof response.data === 'string') {
logger.info(`[SAP] Response is XML, parsing to JSON...`); logger.info(`[SAP] Response is XML, parsing to JSON...`);
@ -624,7 +624,7 @@ export class SAPIntegrationService {
// Continue with original data // Continue with original data
} }
} }
// Log response data summary // Log response data summary
if (responseData) { if (responseData) {
if (responseData.entry) { if (responseData.entry) {
@ -633,11 +633,11 @@ export class SAPIntegrationService {
logger.info(`[SAP] Response has OData 'd' wrapper`); logger.info(`[SAP] Response has OData 'd' wrapper`);
} }
} }
// Also log the request that was sent // Also log the request that was sent
logger.info(`[SAP] POST Request URL: ${urlWithParams}`); logger.info(`[SAP] POST Request URL: ${urlWithParams}`);
logger.info(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2)); logger.info(`[SAP] POST Request Payload:`, JSON.stringify(requestPayload, null, 2));
if (response.status === 200 || response.status === 201) { if (response.status === 200 || response.status === 201) {
// Parse SAP response // Parse SAP response
// Response structure may vary, but typically contains: // Response structure may vary, but typically contains:
@ -645,7 +645,7 @@ export class SAPIntegrationService {
// - Blocked amount confirmation // - Blocked amount confirmation
// - Remaining balance (in lt_io_output[0].Available_Amount for XML) // - Remaining balance (in lt_io_output[0].Available_Amount for XML)
// - Block ID or reference number // - Block ID or reference number
// Helper function to extract remaining balance from various field names // Helper function to extract remaining balance from various field names
// For XML: Available_Amount in lt_io_output[0] (may be prefixed with 'd:' namespace) // For XML: Available_Amount in lt_io_output[0] (may be prefixed with 'd:' namespace)
// For JSON: RemainingBalance, Remaining, Available_Amount, etc. // For JSON: RemainingBalance, Remaining, Available_Amount, etc.
@ -654,43 +654,43 @@ export class SAPIntegrationService {
logger.debug(`[SAP] extractRemainingBalance: obj is null/undefined`); logger.debug(`[SAP] extractRemainingBalance: obj is null/undefined`);
return 0; return 0;
} }
// Helper to extract value from field (handles both direct values and nested #text nodes) // Helper to extract value from field (handles both direct values and nested #text nodes)
const getFieldValue = (fieldName: string): any => { const getFieldValue = (fieldName: string): any => {
const field = obj[fieldName]; const field = obj[fieldName];
if (field === undefined || field === null) return null; if (field === undefined || field === null) return null;
// If it's an object with #text property (XML parser sometimes does this) // If it's an object with #text property (XML parser sometimes does this)
if (typeof field === 'object' && field['#text'] !== undefined) { if (typeof field === 'object' && field['#text'] !== undefined) {
return field['#text']; return field['#text'];
} }
// Direct value // Direct value
return field; return field;
}; };
// Try various field name variations (both JSON and XML formats) // Try various field name variations (both JSON and XML formats)
// XML namespace prefixes: 'd:Available_Amount', 'd:RemainingBalance', etc. // XML namespace prefixes: 'd:Available_Amount', 'd:RemainingBalance', etc.
// IMPORTANT: Check 'd:Available_Amount' first as that's what SAP returns in XML // IMPORTANT: Check 'd:Available_Amount' first as that's what SAP returns in XML
// Also check without namespace prefix as parser might strip it // Also check without namespace prefix as parser might strip it
const value = getFieldValue('d:Available_Amount') ?? // XML format with namespace prefix (PRIORITY) const value = getFieldValue('d:Available_Amount') ?? // XML format with namespace prefix (PRIORITY)
getFieldValue('Available_Amount') ?? // XML format without prefix (parser might strip 'd:') getFieldValue('Available_Amount') ?? // XML format without prefix (parser might strip 'd:')
getFieldValue('d:AvailableAmount') ?? // CamelCase variation with prefix getFieldValue('d:AvailableAmount') ?? // CamelCase variation with prefix
getFieldValue('AvailableAmount') ?? // CamelCase variation without prefix getFieldValue('AvailableAmount') ?? // CamelCase variation without prefix
getFieldValue('d:RemainingBalance') ?? getFieldValue('d:RemainingBalance') ??
getFieldValue('RemainingBalance') ?? getFieldValue('RemainingBalance') ??
getFieldValue('RemainingAmount') ?? getFieldValue('RemainingAmount') ??
getFieldValue('Remaining') ?? getFieldValue('Remaining') ??
getFieldValue('AvailableBalance') ?? getFieldValue('AvailableBalance') ??
getFieldValue('Balance') ?? getFieldValue('Balance') ??
getFieldValue('Available') ?? getFieldValue('Available') ??
null; null;
if (value === null || value === undefined) { if (value === null || value === undefined) {
logger.debug(`[SAP] extractRemainingBalance: No value found. Object keys:`, Object.keys(obj)); logger.debug(`[SAP] extractRemainingBalance: No value found. Object keys:`, Object.keys(obj));
// Log all keys that might be relevant // Log all keys that might be relevant
const relevantKeys = Object.keys(obj).filter(k => const relevantKeys = Object.keys(obj).filter(k =>
k.toLowerCase().includes('available') || k.toLowerCase().includes('available') ||
k.toLowerCase().includes('amount') || k.toLowerCase().includes('amount') ||
k.toLowerCase().includes('remaining') || k.toLowerCase().includes('remaining') ||
k.toLowerCase().includes('balance') k.toLowerCase().includes('balance')
@ -700,47 +700,47 @@ export class SAPIntegrationService {
} }
return 0; return 0;
} }
// Convert to string first, then parse (handles both string "14291525.00" and number) // Convert to string first, then parse (handles both string "14291525.00" and number)
const valueStr = value?.toString().trim() || '0'; const valueStr = value?.toString().trim() || '0';
const parsed = parseFloat(valueStr); const parsed = parseFloat(valueStr);
if (isNaN(parsed)) { if (isNaN(parsed)) {
logger.warn(`[SAP] extractRemainingBalance: Failed to parse value "${valueStr}" as number`); logger.warn(`[SAP] extractRemainingBalance: Failed to parse value "${valueStr}" as number`);
return 0; return 0;
} }
logger.debug(`[SAP] extractRemainingBalance: Extracted value "${valueStr}" -> ${parsed}`); logger.debug(`[SAP] extractRemainingBalance: Extracted value "${valueStr}" -> ${parsed}`);
return parsed; return parsed;
}; };
// Helper function to extract blocked amount // Helper function to extract blocked amount
const extractBlockedAmount = (obj: any): number => { const extractBlockedAmount = (obj: any): number => {
if (!obj) return amount; if (!obj) return amount;
const value = obj.BlockedAmount || const value = obj.BlockedAmount ||
obj.Amount || obj.Amount ||
obj.Blocked || obj.Blocked ||
amount.toString(); amount.toString();
const parsed = parseFloat(value?.toString() || amount.toString()); const parsed = parseFloat(value?.toString() || amount.toString());
return isNaN(parsed) ? amount : parsed; return isNaN(parsed) ? amount : parsed;
}; };
// Handle different possible response structures // Handle different possible response structures
let success = false; let success = false;
let blockedAmount = amount; let blockedAmount = amount;
let remainingBalance = 0; let remainingBalance = 0;
let blockId: string | undefined; let blockId: string | undefined;
// Parse XML structure: entry -> link[@rel='lt_io_output'] -> inline -> feed -> entry -> content -> properties // Parse XML structure: entry -> link[@rel='lt_io_output'] -> inline -> feed -> entry -> content -> properties
// Or JSON structure: { d: {...} } or { lt_io_output: [...] } // Or JSON structure: { d: {...} } or { lt_io_output: [...] }
// Check for XML structure first (parsed XML from fast-xml-parser) // Check for XML structure first (parsed XML from fast-xml-parser)
let ioOutputData: any = null; let ioOutputData: any = null;
let message = ''; let message = '';
let mainEntryProperties: any = null; let mainEntryProperties: any = null;
// XML structure: entry.link (array) -> find link with @rel='lt_io_output' -> inline.feed.entry // XML structure: entry.link (array) -> find link with @rel='lt_io_output' -> inline.feed.entry
// OR JSON OData format: { d: { lt_io_output: { results: [...] } } } // OR JSON OData format: { d: { lt_io_output: { results: [...] } } }
if (responseData.d) { if (responseData.d) {
@ -773,7 +773,7 @@ export class SAPIntegrationService {
responseData = responseData.d; responseData = responseData.d;
} }
} }
// Sometimes XML parser might create the root element with a different name // Sometimes XML parser might create the root element with a different name
// Check if responseData itself IS the entry (if root element was <entry>) // Check if responseData itself IS the entry (if root element was <entry>)
let actualEntry = responseData.entry; let actualEntry = responseData.entry;
@ -783,25 +783,25 @@ export class SAPIntegrationService {
actualEntry = responseData; actualEntry = responseData;
} }
} }
// Check if responseData might be an array (sometimes XML parser returns arrays) // Check if responseData might be an array (sometimes XML parser returns arrays)
if (Array.isArray(responseData) && responseData.length > 0 && responseData[0]?.entry) { if (Array.isArray(responseData) && responseData.length > 0 && responseData[0]?.entry) {
responseData = responseData[0]; responseData = responseData[0];
} }
// Use actualEntry if we found it, otherwise try responseData.entry // Use actualEntry if we found it, otherwise try responseData.entry
const entry = actualEntry || responseData.entry; const entry = actualEntry || responseData.entry;
if (entry && !ioOutputData) { if (entry && !ioOutputData) {
// Also check main entry properties (sometimes Available_Amount is here) // Also check main entry properties (sometimes Available_Amount is here)
const mainContent = entry.content || {}; const mainContent = entry.content || {};
mainEntryProperties = mainContent['m:properties'] || mainContent.properties || (mainContent['@_type'] === 'application/xml' ? mainContent : null); mainEntryProperties = mainContent['m:properties'] || mainContent.properties || (mainContent['@_type'] === 'application/xml' ? mainContent : null);
if (mainEntryProperties) { if (mainEntryProperties) {
logger.info(`[SAP] Found main entry properties, keys:`, Object.keys(mainEntryProperties)); logger.info(`[SAP] Found main entry properties, keys:`, Object.keys(mainEntryProperties));
} }
// Find lt_io_output link in XML structure // Find lt_io_output link in XML structure
const links = Array.isArray(entry.link) ? entry.link : (entry.link ? [entry.link] : []); const links = Array.isArray(entry.link) ? entry.link : (entry.link ? [entry.link] : []);
const ioOutputLink = links.find((link: any) => { const ioOutputLink = links.find((link: any) => {
@ -809,15 +809,15 @@ export class SAPIntegrationService {
const title = link['@_title'] || link.title || ''; const title = link['@_title'] || link.title || '';
return rel.includes('lt_io_output') || title === 'IOOutputSet' || title === 'lt_io_output'; return rel.includes('lt_io_output') || title === 'IOOutputSet' || title === 'lt_io_output';
}); });
if (ioOutputLink?.inline?.feed?.entry) { if (ioOutputLink?.inline?.feed?.entry) {
const ioEntry = Array.isArray(ioOutputLink.inline.feed.entry) const ioEntry = Array.isArray(ioOutputLink.inline.feed.entry)
? ioOutputLink.inline.feed.entry[0] ? ioOutputLink.inline.feed.entry[0]
: ioOutputLink.inline.feed.entry; : ioOutputLink.inline.feed.entry;
const content = ioEntry.content || {}; const content = ioEntry.content || {};
const properties = content['m:properties'] || content.properties || (content['@_type'] === 'application/xml' ? content : null); const properties = content['m:properties'] || content.properties || (content['@_type'] === 'application/xml' ? content : null);
if (properties) { if (properties) {
ioOutputData = properties; ioOutputData = properties;
message = ioOutputData['d:Message'] || ioOutputData.Message || ioOutputData['#text'] || ''; message = ioOutputData['d:Message'] || ioOutputData.Message || ioOutputData['#text'] || '';
@ -825,7 +825,7 @@ export class SAPIntegrationService {
} }
} }
} }
// Extract data from ioOutputData (already extracted above for both XML and JSON formats) // Extract data from ioOutputData (already extracted above for both XML and JSON formats)
if (ioOutputData) { if (ioOutputData) {
// XML parsed structure - extract from lt_io_output properties // XML parsed structure - extract from lt_io_output properties
@ -833,7 +833,7 @@ export class SAPIntegrationService {
success = message.includes('Successful') || message.includes('Success') || !message.includes('Error'); success = message.includes('Successful') || message.includes('Success') || !message.includes('Error');
blockedAmount = amount; // Use the amount we sent (from lt_io_input) blockedAmount = amount; // Use the amount we sent (from lt_io_input)
remainingBalance = extractRemainingBalance(ioOutputData); // Available_Amount from XML remainingBalance = extractRemainingBalance(ioOutputData); // Available_Amount from XML
// If not found in lt_io_output, try main entry properties // If not found in lt_io_output, try main entry properties
if (remainingBalance === 0 && mainEntryProperties) { if (remainingBalance === 0 && mainEntryProperties) {
logger.info(`[SAP] Available_Amount not found in lt_io_output, trying main entry properties`); logger.info(`[SAP] Available_Amount not found in lt_io_output, trying main entry properties`);
@ -842,51 +842,51 @@ export class SAPIntegrationService {
logger.info(`[SAP] Found Available_Amount in main entry properties: ${remainingBalance}`); logger.info(`[SAP] Found Available_Amount in main entry properties: ${remainingBalance}`);
} }
} }
// Helper function to extract SAP reference number (similar to extractRemainingBalance) // Helper function to extract SAP reference number (similar to extractRemainingBalance)
const extractSapReference = (obj: any): string | undefined => { const extractSapReference = (obj: any): string | undefined => {
if (!obj) return undefined; if (!obj) return undefined;
const getFieldValue = (fieldName: string): any => { const getFieldValue = (fieldName: string): any => {
const field = obj[fieldName]; const field = obj[fieldName];
if (field === undefined || field === null) return null; if (field === undefined || field === null) return null;
// If it's an object with #text property (XML parser sometimes does this) // If it's an object with #text property (XML parser sometimes does this)
if (typeof field === 'object' && field['#text'] !== undefined) { if (typeof field === 'object' && field['#text'] !== undefined) {
return field['#text']; return field['#text'];
} }
// Direct value // Direct value
return field; return field;
}; };
// Try various field name variations for SAP reference number // Try various field name variations for SAP reference number
const value = getFieldValue('d:Sap_Reference_no') ?? // XML format with namespace prefix (PRIORITY) const value = getFieldValue('d:Sap_Reference_no') ?? // XML format with namespace prefix (PRIORITY)
getFieldValue('Sap_Reference_no') ?? // XML format without prefix getFieldValue('Sap_Reference_no') ?? // XML format without prefix
getFieldValue('d:SapReferenceNo') ?? getFieldValue('d:SapReferenceNo') ??
getFieldValue('SapReferenceNo') ?? getFieldValue('SapReferenceNo') ??
getFieldValue('d:Reference') ?? getFieldValue('d:Reference') ??
getFieldValue('Reference') ?? getFieldValue('Reference') ??
getFieldValue('d:BlockId') ?? getFieldValue('d:BlockId') ??
getFieldValue('BlockId') ?? getFieldValue('BlockId') ??
getFieldValue('d:DocumentNumber') ?? getFieldValue('d:DocumentNumber') ??
getFieldValue('DocumentNumber') ?? getFieldValue('DocumentNumber') ??
null; null;
if (value === null || value === undefined) { if (value === null || value === undefined) {
logger.debug(`[SAP] extractSapReference: No value found. Object keys:`, Object.keys(obj)); logger.debug(`[SAP] extractSapReference: No value found. Object keys:`, Object.keys(obj));
return undefined; return undefined;
} }
// Convert to string and trim // Convert to string and trim
const valueStr = String(value).trim(); const valueStr = String(value).trim();
logger.debug(`[SAP] extractSapReference: Extracted value "${valueStr}"`); logger.debug(`[SAP] extractSapReference: Extracted value "${valueStr}"`);
return valueStr || undefined; return valueStr || undefined;
}; };
// Extract SAP reference number using helper function // Extract SAP reference number using helper function
blockId = extractSapReference(ioOutputData) || extractSapReference(mainEntryProperties) || undefined; blockId = extractSapReference(ioOutputData) || extractSapReference(mainEntryProperties) || undefined;
// Log detailed information for debugging // Log detailed information for debugging
logger.info(`[SAP] Extracted from XML lt_io_output:`, { logger.info(`[SAP] Extracted from XML lt_io_output:`, {
message, message,
@ -896,10 +896,10 @@ export class SAPIntegrationService {
sampleKeys: Object.keys(ioOutputData).slice(0, 10), // First 10 keys for debugging sampleKeys: Object.keys(ioOutputData).slice(0, 10), // First 10 keys for debugging
foundInMainEntry: remainingBalance > 0 && mainEntryProperties ? true : false, foundInMainEntry: remainingBalance > 0 && mainEntryProperties ? true : false,
ioOutputDataSample: Object.keys(ioOutputData).reduce((acc: any, key: string) => { ioOutputDataSample: Object.keys(ioOutputData).reduce((acc: any, key: string) => {
if (key.toLowerCase().includes('available') || if (key.toLowerCase().includes('available') ||
key.toLowerCase().includes('amount') || key.toLowerCase().includes('amount') ||
key.toLowerCase().includes('reference') || key.toLowerCase().includes('reference') ||
key.toLowerCase().includes('sap')) { key.toLowerCase().includes('sap')) {
acc[key] = ioOutputData[key]; acc[key] = ioOutputData[key];
} }
return acc; return acc;
@ -933,7 +933,7 @@ export class SAPIntegrationService {
logger.warn('[SAP] Budget block response structure unclear, assuming success'); logger.warn('[SAP] Budget block response structure unclear, assuming success');
logger.warn('[SAP] Response data keys:', Object.keys(responseData || {})); logger.warn('[SAP] Response data keys:', Object.keys(responseData || {}));
} }
// Log what we extracted // Log what we extracted
logger.info(`[SAP] Extracted from response:`, { logger.info(`[SAP] Extracted from response:`, {
success, success,
@ -947,7 +947,7 @@ export class SAPIntegrationService {
hasMainEntryProperties: !!mainEntryProperties, hasMainEntryProperties: !!mainEntryProperties,
mainEntryPropertiesKeys: mainEntryProperties ? Object.keys(mainEntryProperties) : null mainEntryPropertiesKeys: mainEntryProperties ? Object.keys(mainEntryProperties) : null
}); });
// If ioOutputData exists but we didn't extract values, log detailed info // If ioOutputData exists but we didn't extract values, log detailed info
if (ioOutputData && (remainingBalance === 0 || !blockId)) { if (ioOutputData && (remainingBalance === 0 || !blockId)) {
logger.warn(`[SAP] ⚠️ ioOutputData exists but extraction failed. Full ioOutputData:`, JSON.stringify(ioOutputData, null, 2)); logger.warn(`[SAP] ⚠️ ioOutputData exists but extraction failed. Full ioOutputData:`, JSON.stringify(ioOutputData, null, 2));
@ -957,7 +957,7 @@ export class SAPIntegrationService {
return acc; return acc;
}, {})); }, {}));
} }
// If remaining balance is 0, log the full response structure for debugging // If remaining balance is 0, log the full response structure for debugging
if (remainingBalance === 0 && response.status === 200 || response.status === 201) { if (remainingBalance === 0 && response.status === 200 || response.status === 201) {
logger.warn(`[SAP] ⚠️ Remaining balance is 0, but request was successful. Full response structure:`, JSON.stringify(responseData, null, 2)); logger.warn(`[SAP] ⚠️ Remaining balance is 0, but request was successful. Full response structure:`, JSON.stringify(responseData, null, 2));
@ -965,7 +965,7 @@ export class SAPIntegrationService {
if (success) { if (success) {
logger.info(`[SAP] Budget blocked successfully for IO ${ioNumber}. Blocked: ${blockedAmount}, Remaining: ${remainingBalance}`); logger.info(`[SAP] Budget blocked successfully for IO ${ioNumber}. Blocked: ${blockedAmount}, Remaining: ${remainingBalance}`);
// Only return blockId if SAP provided a reference number // Only return blockId if SAP provided a reference number
// Don't generate a fallback - we want the actual SAP document number // Don't generate a fallback - we want the actual SAP document number
if (blockId) { if (blockId) {
@ -973,7 +973,7 @@ export class SAPIntegrationService {
} else { } else {
logger.warn(`[SAP] ⚠️ No SAP Reference Number (Sap_Reference_no) found in response`); logger.warn(`[SAP] ⚠️ No SAP Reference Number (Sap_Reference_no) found in response`);
} }
return { return {
success: true, success: true,
blockId: blockId || undefined, // Only return actual SAP reference number, no fallback blockId: blockId || undefined, // Only return actual SAP reference number, no fallback
@ -994,7 +994,7 @@ export class SAPIntegrationService {
logger.error(`[SAP] Authentication failed during budget blocking (Status: ${response.status}) - check SAP credentials`); logger.error(`[SAP] Authentication failed during budget blocking (Status: ${response.status}) - check SAP credentials`);
logger.error(`[SAP] Response data:`, response.data); logger.error(`[SAP] Response data:`, response.data);
logger.error(`[SAP] Response headers:`, response.headers); logger.error(`[SAP] Response headers:`, response.headers);
// Check if it's actually a CSRF error disguised as auth error // Check if it's actually a CSRF error disguised as auth error
const responseText = JSON.stringify(response.data || {}); const responseText = JSON.stringify(response.data || {});
if (responseText.includes('CSRF') || responseText.includes('csrf') || responseText.includes('token')) { if (responseText.includes('CSRF') || responseText.includes('csrf') || responseText.includes('token')) {
@ -1006,7 +1006,7 @@ export class SAPIntegrationService {
error: 'SAP CSRF token validation failed - token may have expired or be invalid' error: 'SAP CSRF token validation failed - token may have expired or be invalid'
}; };
} }
return { return {
success: false, success: false,
blockedAmount: 0, blockedAmount: 0,
@ -1016,15 +1016,15 @@ export class SAPIntegrationService {
} else { } else {
// Handle 400 Bad Request - usually means invalid request format // Handle 400 Bad Request - usually means invalid request format
let errorMessage = `SAP API returned status ${response.status}`; let errorMessage = `SAP API returned status ${response.status}`;
if (response.status === 400) { if (response.status === 400) {
errorMessage = 'SAP API returned 400 Bad Request - check request payload format'; errorMessage = 'SAP API returned 400 Bad Request - check request payload format';
// Try to extract error message from response // Try to extract error message from response
if (response.data) { if (response.data) {
try { try {
const errorData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data; const errorData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
// Check for common SAP error fields // Check for common SAP error fields
if (errorData.error) { if (errorData.error) {
errorMessage = errorData.error.message?.value || errorData.error.message || errorMessage; errorMessage = errorData.error.message?.value || errorData.error.message || errorMessage;
@ -1035,14 +1035,14 @@ export class SAPIntegrationService {
} else if (errorData.d?.error) { } else if (errorData.d?.error) {
errorMessage = errorData.d.error.message?.value || errorData.d.error.message || errorMessage; errorMessage = errorData.d.error.message?.value || errorData.d.error.message || errorMessage;
} }
logger.error(`[SAP] SAP Error Details:`, JSON.stringify(errorData, null, 2)); logger.error(`[SAP] SAP Error Details:`, JSON.stringify(errorData, null, 2));
} catch (e) { } catch (e) {
logger.error(`[SAP] Error parsing response data:`, response.data); logger.error(`[SAP] Error parsing response data:`, response.data);
} }
} }
} }
logger.error(`[SAP] Unexpected response status during budget blocking: ${response.status}`); logger.error(`[SAP] Unexpected response status during budget blocking: ${response.status}`);
logger.error(`[SAP] Response data:`, response.data); logger.error(`[SAP] Response data:`, response.data);
return { return {
@ -1054,7 +1054,7 @@ export class SAPIntegrationService {
} }
} catch (error) { } catch (error) {
const axiosError = error as AxiosError; const axiosError = error as AxiosError;
if (axiosError.response) { if (axiosError.response) {
// SAP returned an error response // SAP returned an error response
logger.error(`[SAP] Error blocking budget for IO ${ioNumber}:`, { logger.error(`[SAP] Error blocking budget for IO ${ioNumber}:`, {
@ -1062,7 +1062,7 @@ export class SAPIntegrationService {
statusText: axiosError.response.statusText, statusText: axiosError.response.statusText,
data: axiosError.response.data data: axiosError.response.data
}); });
return { return {
success: false, success: false,
blockedAmount: 0, blockedAmount: 0,
@ -1117,22 +1117,7 @@ export class SAPIntegrationService {
}; };
} }
// TODO: Implement actual SAP API call to release budget
// Example:
// const response = await axios.post(`${this.sapBaseUrl}/api/io/${ioNumber}/release`, {
// block_id: blockId,
// reference: requestNumber
// }, {
// headers: {
// 'Authorization': `Bearer ${this.sapApiKey}`,
// 'Content-Type': 'application/json'
// }
// });
//
// return {
// success: response.data.success,
// releasedAmount: response.data.released_amount
// };
logger.warn('[SAP] SAP budget release not implemented, simulating release'); logger.warn('[SAP] SAP budget release not implemented, simulating release');
return { return {
@ -1177,23 +1162,7 @@ export class SAPIntegrationService {
}; };
} }
// TODO: Implement actual SAP API call to get dealer info
// Example:
// const response = await axios.get(`${this.sapBaseUrl}/api/dealers/${dealerCode}`, {
// headers: {
// 'Authorization': `Bearer ${this.sapApiKey}`,
// 'Content-Type': 'application/json'
// }
// });
//
// return {
// isValid: response.data.valid,
// dealerCode: response.data.dealer_code,
// dealerName: response.data.dealer_name,
// dealerEmail: response.data.dealer_email,
// dealerPhone: response.data.dealer_phone,
// dealerAddress: response.data.dealer_address
// };
logger.warn('[SAP] SAP dealer lookup not implemented, returning mock data'); logger.warn('[SAP] SAP dealer lookup not implemented, returning mock data');
return { return {