enhancd expens and cost items to support gst values addd pwc service file to integrate pwc
This commit is contained in:
parent
2282d29322
commit
17c62d2b45
119
src/migrations/20260209-add-gst-and-pwc-fields.ts
Normal file
119
src/migrations/20260209-add-gst-and-pwc-fields.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { QueryInterface, DataTypes } from 'sequelize';
|
||||
|
||||
/**
|
||||
* Helper function to check if a column exists in a table
|
||||
*/
|
||||
async function columnExists(
|
||||
queryInterface: QueryInterface,
|
||||
tableName: string,
|
||||
columnName: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const tableDescription = await queryInterface.describeTable(tableName);
|
||||
return columnName in tableDescription;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function up(queryInterface: QueryInterface): Promise<void> {
|
||||
// 1. ActivityType
|
||||
const activityCols = {
|
||||
hsn_code: { type: DataTypes.STRING(20), allowNull: true },
|
||||
sac_code: { type: DataTypes.STRING(20), allowNull: true },
|
||||
gst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
gl_code: { type: DataTypes.STRING(20), allowNull: true },
|
||||
credit_nature: { type: DataTypes.STRING(50), allowNull: true }
|
||||
};
|
||||
|
||||
for (const [col, spec] of Object.entries(activityCols)) {
|
||||
if (!(await columnExists(queryInterface, 'activity_types', col))) {
|
||||
await queryInterface.addColumn('activity_types', col, spec);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. GST Fields mapping for Multiple Tables
|
||||
const gstFields = {
|
||||
gst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
gst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cgst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
cgst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
sgst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
sgst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
igst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
igst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
utgst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
utgst_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cess_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true },
|
||||
cess_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
total_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
};
|
||||
|
||||
const tables = ['dealer_proposal_cost_items', 'dealer_completion_expenses', 'claim_credit_notes'];
|
||||
|
||||
for (const table of tables) {
|
||||
for (const [col, spec] of Object.entries(gstFields)) {
|
||||
if (!(await columnExists(queryInterface, table, col))) {
|
||||
await queryInterface.addColumn(table, col, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing expense_date to DealerCompletionExpense
|
||||
if (!(await columnExists(queryInterface, 'dealer_completion_expenses', 'expense_date'))) {
|
||||
await queryInterface.addColumn('dealer_completion_expenses', 'expense_date', { type: DataTypes.DATEONLY, allowNull: true });
|
||||
}
|
||||
|
||||
// 3. ClaimInvoice
|
||||
const invoiceCols = {
|
||||
irn: { type: DataTypes.STRING(500), allowNull: true },
|
||||
ack_no: { type: DataTypes.STRING(255), allowNull: true },
|
||||
ack_date: { type: DataTypes.DATE, allowNull: true },
|
||||
signed_invoice: { type: DataTypes.TEXT, allowNull: true },
|
||||
signed_invoice_url: { type: DataTypes.STRING(500), allowNull: true },
|
||||
dealer_claim_number: { type: DataTypes.STRING(100), allowNull: true },
|
||||
qr_code: { type: DataTypes.TEXT, allowNull: true },
|
||||
qr_image: { type: DataTypes.TEXT, allowNull: true },
|
||||
dealer_claim_date: { type: DataTypes.DATEONLY, allowNull: true },
|
||||
billing_no: { type: DataTypes.STRING(100), allowNull: true },
|
||||
billing_date: { type: DataTypes.DATEONLY, allowNull: true },
|
||||
taxable_value: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cgst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
sgst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
igst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
utgst_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
cess_total: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
tcs_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
round_off_amt: { type: DataTypes.DECIMAL(15, 2), allowNull: true },
|
||||
place_of_supply: { type: DataTypes.STRING(255), allowNull: true },
|
||||
total_value_in_words: { type: DataTypes.STRING(500), allowNull: true },
|
||||
tax_value_in_words: { type: DataTypes.STRING(500), allowNull: true },
|
||||
credit_nature: { type: DataTypes.STRING(100), allowNull: true },
|
||||
consignor_gsin: { type: DataTypes.STRING(255), allowNull: true },
|
||||
gstin_date: { type: DataTypes.DATEONLY, allowNull: true }
|
||||
};
|
||||
|
||||
for (const [col, spec] of Object.entries(invoiceCols)) {
|
||||
if (!(await columnExists(queryInterface, 'claim_invoices', col))) {
|
||||
await queryInterface.addColumn('claim_invoices', col, spec);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure file_path exists as 'file_path'
|
||||
try {
|
||||
if (!(await columnExists(queryInterface, 'claim_invoices', 'file_path'))) {
|
||||
if (await columnExists(queryInterface, 'claim_invoices', 'invoice_file_path')) {
|
||||
await queryInterface.renameColumn('claim_invoices', 'invoice_file_path', 'file_path');
|
||||
} else {
|
||||
await queryInterface.addColumn('claim_invoices', 'file_path', { type: DataTypes.STRING(500), allowNull: true });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently continue
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||
// Note: Best effort rollback (usually not recommended to drop columns in shared dev unless necessary)
|
||||
await queryInterface.removeColumn('dealer_completion_expenses', 'expense_date').catch(() => { });
|
||||
}
|
||||
@ -9,13 +9,18 @@ interface ActivityTypeAttributes {
|
||||
taxationType?: string;
|
||||
sapRefNo?: string;
|
||||
isActive: boolean;
|
||||
hsnCode?: string | null;
|
||||
sacCode?: string | null;
|
||||
gstRate?: number | null;
|
||||
glCode?: string | null;
|
||||
creditNature?: 'Commercial' | 'GST' | null;
|
||||
createdBy: string;
|
||||
updatedBy?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ActivityTypeCreationAttributes extends Optional<ActivityTypeAttributes, 'activityTypeId' | 'itemCode' | 'taxationType' | 'sapRefNo' | 'isActive' | 'updatedBy' | 'createdAt' | 'updatedAt'> {}
|
||||
interface ActivityTypeCreationAttributes extends Optional<ActivityTypeAttributes, 'activityTypeId' | 'itemCode' | 'taxationType' | 'sapRefNo' | 'isActive' | 'updatedBy' | 'createdAt' | 'updatedAt'> { }
|
||||
|
||||
class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAttributes> implements ActivityTypeAttributes {
|
||||
public activityTypeId!: string;
|
||||
@ -24,6 +29,11 @@ class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAtt
|
||||
public taxationType?: string;
|
||||
public sapRefNo?: string;
|
||||
public isActive!: boolean;
|
||||
public hsnCode?: string | null;
|
||||
public sacCode?: string | null;
|
||||
public gstRate?: number | null;
|
||||
public glCode?: string | null;
|
||||
public creditNature?: 'Commercial' | 'GST' | null;
|
||||
public createdBy!: string;
|
||||
public updatedBy?: string;
|
||||
public createdAt!: Date;
|
||||
@ -71,6 +81,31 @@ ActivityType.init(
|
||||
defaultValue: true,
|
||||
field: 'is_active'
|
||||
},
|
||||
hsnCode: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
field: 'hsn_code'
|
||||
},
|
||||
sacCode: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
field: 'sac_code'
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
glCode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'gl_code'
|
||||
},
|
||||
creditNature: {
|
||||
type: DataTypes.ENUM('Commercial', 'GST'),
|
||||
allowNull: true,
|
||||
field: 'credit_nature'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
|
||||
@ -9,7 +9,20 @@ interface ClaimCreditNoteAttributes {
|
||||
invoiceId?: string;
|
||||
creditNoteNumber?: string;
|
||||
creditNoteDate?: Date;
|
||||
creditNoteAmount?: number;
|
||||
creditNoteAmount: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
utgstRate?: number;
|
||||
utgstAmt?: number;
|
||||
cessRate?: number;
|
||||
cessAmt?: number;
|
||||
totalAmt?: number;
|
||||
sapDocumentNumber?: string;
|
||||
creditNoteFilePath?: string;
|
||||
status?: string;
|
||||
@ -22,7 +35,7 @@ interface ClaimCreditNoteAttributes {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ClaimCreditNoteCreationAttributes extends Optional<ClaimCreditNoteAttributes, 'creditNoteId' | 'invoiceId' | 'creditNoteNumber' | 'creditNoteDate' | 'creditNoteAmount' | 'sapDocumentNumber' | 'creditNoteFilePath' | 'status' | 'errorMessage' | 'confirmedBy' | 'confirmedAt' | 'reason' | 'description' | 'createdAt' | 'updatedAt'> {}
|
||||
interface ClaimCreditNoteCreationAttributes extends Optional<ClaimCreditNoteAttributes, 'creditNoteId' | 'invoiceId' | 'creditNoteNumber' | 'creditNoteDate' | 'gstRate' | 'gstAmt' | 'cgstRate' | 'cgstAmt' | 'sgstRate' | 'sgstAmt' | 'igstRate' | 'igstAmt' | 'utgstRate' | 'utgstAmt' | 'cessRate' | 'cessAmt' | 'totalAmt' | 'sapDocumentNumber' | 'creditNoteFilePath' | 'status' | 'errorMessage' | 'confirmedBy' | 'confirmedAt' | 'reason' | 'description' | 'createdAt' | 'updatedAt'> { }
|
||||
|
||||
class ClaimCreditNote extends Model<ClaimCreditNoteAttributes, ClaimCreditNoteCreationAttributes> implements ClaimCreditNoteAttributes {
|
||||
public creditNoteId!: string;
|
||||
@ -30,7 +43,20 @@ class ClaimCreditNote extends Model<ClaimCreditNoteAttributes, ClaimCreditNoteCr
|
||||
public invoiceId?: string;
|
||||
public creditNoteNumber?: string;
|
||||
public creditNoteDate?: Date;
|
||||
public creditNoteAmount?: number;
|
||||
public creditNoteAmount!: number;
|
||||
public gstRate?: number;
|
||||
public gstAmt?: number;
|
||||
public cgstRate?: number;
|
||||
public cgstAmt?: number;
|
||||
public sgstRate?: number;
|
||||
public sgstAmt?: number;
|
||||
public igstRate?: number;
|
||||
public igstAmt?: number;
|
||||
public utgstRate?: number;
|
||||
public utgstAmt?: number;
|
||||
public cessRate?: number;
|
||||
public cessAmt?: number;
|
||||
public totalAmt?: number;
|
||||
public sapDocumentNumber?: string;
|
||||
public creditNoteFilePath?: string;
|
||||
public status?: string;
|
||||
@ -86,8 +112,73 @@ ClaimCreditNote.init(
|
||||
},
|
||||
creditNoteAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
field: 'credit_amount'
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'credit_amount',
|
||||
field: 'gst_rate'
|
||||
},
|
||||
gstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_amt'
|
||||
},
|
||||
cgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_rate'
|
||||
},
|
||||
cgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_amt'
|
||||
},
|
||||
sgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_rate'
|
||||
},
|
||||
sgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_amt'
|
||||
},
|
||||
igstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_rate'
|
||||
},
|
||||
igstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_amt'
|
||||
},
|
||||
utgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_rate'
|
||||
},
|
||||
utgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_amt'
|
||||
},
|
||||
cessRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_rate'
|
||||
},
|
||||
cessAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_amt'
|
||||
},
|
||||
totalAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'total_amt'
|
||||
},
|
||||
sapDocumentNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
|
||||
@ -6,11 +6,36 @@ interface ClaimInvoiceAttributes {
|
||||
invoiceId: string;
|
||||
requestId: string;
|
||||
invoiceNumber?: string;
|
||||
invoiceDate?: Date;
|
||||
amount?: number;
|
||||
dmsNumber?: string;
|
||||
invoiceFilePath?: string;
|
||||
status?: string;
|
||||
invoiceDate?: Date;
|
||||
amount: number;
|
||||
status: string;
|
||||
irn?: string | null;
|
||||
ackNo?: string | null;
|
||||
ackDate?: Date | null;
|
||||
signedInvoice?: string | null;
|
||||
signedInvoiceUrl?: string | null;
|
||||
dealerClaimNumber?: string | null;
|
||||
dealerClaimDate?: Date | null;
|
||||
billingNo?: string | null;
|
||||
billingDate?: Date | null;
|
||||
taxableValue?: number | null;
|
||||
cgstTotal?: number | null;
|
||||
sgstTotal?: number | null;
|
||||
igstTotal?: number | null;
|
||||
utgstTotal?: number | null;
|
||||
cessTotal?: number | null;
|
||||
tcsAmt?: number | null;
|
||||
roundOffAmt?: number | null;
|
||||
placeOfSupply?: string | null;
|
||||
totalValueInWords?: string | null;
|
||||
taxValueInWords?: string | null;
|
||||
creditNature?: string | null;
|
||||
consignorGsin?: string | null;
|
||||
gstinDate?: Date | null;
|
||||
filePath?: string | null;
|
||||
qrCode?: string | null;
|
||||
qrImage?: string | null;
|
||||
errorMessage?: string;
|
||||
generatedAt?: Date;
|
||||
description?: string;
|
||||
@ -18,17 +43,42 @@ interface ClaimInvoiceAttributes {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ClaimInvoiceCreationAttributes extends Optional<ClaimInvoiceAttributes, 'invoiceId' | 'invoiceNumber' | 'invoiceDate' | 'amount' | 'dmsNumber' | 'invoiceFilePath' | 'status' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> {}
|
||||
interface ClaimInvoiceCreationAttributes extends Optional<ClaimInvoiceAttributes, 'invoiceId' | 'invoiceNumber' | 'dmsNumber' | 'invoiceDate' | 'irn' | 'ackNo' | 'ackDate' | 'signedInvoice' | 'signedInvoiceUrl' | 'dealerClaimNumber' | 'dealerClaimDate' | 'billingNo' | 'billingDate' | 'taxableValue' | 'cgstTotal' | 'sgstTotal' | 'igstTotal' | 'utgstTotal' | 'cessTotal' | 'tcsAmt' | 'roundOffAmt' | 'placeOfSupply' | 'totalValueInWords' | 'taxValueInWords' | 'creditNature' | 'consignorGsin' | 'gstinDate' | 'filePath' | 'qrCode' | 'qrImage' | 'errorMessage' | 'generatedAt' | 'description' | 'createdAt' | 'updatedAt'> { }
|
||||
|
||||
class ClaimInvoice extends Model<ClaimInvoiceAttributes, ClaimInvoiceCreationAttributes> implements ClaimInvoiceAttributes {
|
||||
public invoiceId!: string;
|
||||
public requestId!: string;
|
||||
public invoiceNumber?: string;
|
||||
public invoiceDate?: Date;
|
||||
public amount?: number;
|
||||
public dmsNumber?: string;
|
||||
public invoiceFilePath?: string;
|
||||
public status?: string;
|
||||
public invoiceDate?: Date;
|
||||
public amount!: number;
|
||||
public status!: string;
|
||||
public irn?: string | null;
|
||||
public ackNo?: string | null;
|
||||
public ackDate?: Date | null;
|
||||
public signedInvoice?: string | null;
|
||||
public signedInvoiceUrl?: string | null;
|
||||
public dealerClaimNumber?: string | null;
|
||||
public dealerClaimDate?: Date | null;
|
||||
public billingNo?: string | null;
|
||||
public billingDate?: Date | null;
|
||||
public taxableValue?: number | null;
|
||||
public cgstTotal?: number | null;
|
||||
public sgstTotal?: number | null;
|
||||
public igstTotal?: number | null;
|
||||
public utgstTotal?: number | null;
|
||||
public cessTotal?: number | null;
|
||||
public tcsAmt?: number | null;
|
||||
public roundOffAmt?: number | null;
|
||||
public placeOfSupply?: string | null;
|
||||
public totalValueInWords?: string | null;
|
||||
public taxValueInWords?: string | null;
|
||||
public creditNature?: string | null;
|
||||
public consignorGsin?: string | null;
|
||||
public gstinDate?: Date | null;
|
||||
public filePath?: string | null;
|
||||
public qrCode?: string | null;
|
||||
public qrImage?: string | null;
|
||||
public errorMessage?: string;
|
||||
public generatedAt?: Date;
|
||||
public description?: string;
|
||||
@ -61,6 +111,11 @@ ClaimInvoice.init(
|
||||
allowNull: true,
|
||||
field: 'invoice_number',
|
||||
},
|
||||
dmsNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'dms_number',
|
||||
},
|
||||
invoiceDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
@ -68,23 +123,143 @@ ClaimInvoice.init(
|
||||
},
|
||||
amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
allowNull: false,
|
||||
field: 'invoice_amount',
|
||||
},
|
||||
dmsNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'dms_number',
|
||||
},
|
||||
invoiceFilePath: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'invoice_file_path',
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 'PENDING',
|
||||
field: 'generation_status'
|
||||
},
|
||||
irn: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true
|
||||
},
|
||||
ackNo: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'generation_status',
|
||||
field: 'ack_no'
|
||||
},
|
||||
ackDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'ack_date'
|
||||
},
|
||||
signedInvoice: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'signed_invoice'
|
||||
},
|
||||
signedInvoiceUrl: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'signed_invoice_url'
|
||||
},
|
||||
dealerClaimNumber: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'dealer_claim_number'
|
||||
},
|
||||
dealerClaimDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
field: 'dealer_claim_date'
|
||||
},
|
||||
billingNo: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'billing_no'
|
||||
},
|
||||
billingDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
field: 'billing_date'
|
||||
},
|
||||
taxableValue: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'taxable_value'
|
||||
},
|
||||
cgstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_total'
|
||||
},
|
||||
sgstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_total'
|
||||
},
|
||||
igstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_total'
|
||||
},
|
||||
utgstTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_total'
|
||||
},
|
||||
cessTotal: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_total'
|
||||
},
|
||||
tcsAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'tcs_amt'
|
||||
},
|
||||
roundOffAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'round_off_amt'
|
||||
},
|
||||
placeOfSupply: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'place_of_supply'
|
||||
},
|
||||
totalValueInWords: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'total_value_in_words'
|
||||
},
|
||||
taxValueInWords: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'tax_value_in_words'
|
||||
},
|
||||
creditNature: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'credit_nature'
|
||||
},
|
||||
consignorGsin: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'consignor_gsin'
|
||||
},
|
||||
gstinDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
field: 'gstin_date'
|
||||
},
|
||||
filePath: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
field: 'file_path'
|
||||
},
|
||||
qrCode: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'qr_code'
|
||||
},
|
||||
qrImage: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'qr_image'
|
||||
},
|
||||
errorMessage: {
|
||||
type: DataTypes.TEXT,
|
||||
|
||||
@ -9,11 +9,25 @@ interface DealerCompletionExpenseAttributes {
|
||||
completionId?: string | null;
|
||||
description: string;
|
||||
amount: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
utgstRate?: number;
|
||||
utgstAmt?: number;
|
||||
cessRate?: number;
|
||||
cessAmt?: number;
|
||||
totalAmt?: number;
|
||||
expenseDate: Date;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface DealerCompletionExpenseCreationAttributes extends Optional<DealerCompletionExpenseAttributes, 'expenseId' | 'completionId' | 'createdAt' | 'updatedAt'> {}
|
||||
interface DealerCompletionExpenseCreationAttributes extends Optional<DealerCompletionExpenseAttributes, 'expenseId' | 'completionId' | 'createdAt' | 'updatedAt'> { }
|
||||
|
||||
class DealerCompletionExpense extends Model<DealerCompletionExpenseAttributes, DealerCompletionExpenseCreationAttributes> implements DealerCompletionExpenseAttributes {
|
||||
public expenseId!: string;
|
||||
@ -21,6 +35,20 @@ class DealerCompletionExpense extends Model<DealerCompletionExpenseAttributes, D
|
||||
public completionId?: string | null;
|
||||
public description!: string;
|
||||
public amount!: number;
|
||||
public gstRate?: number;
|
||||
public gstAmt?: number;
|
||||
public cgstRate?: number;
|
||||
public cgstAmt?: number;
|
||||
public sgstRate?: number;
|
||||
public sgstAmt?: number;
|
||||
public igstRate?: number;
|
||||
public igstAmt?: number;
|
||||
public utgstRate?: number;
|
||||
public utgstAmt?: number;
|
||||
public cessRate?: number;
|
||||
public cessAmt?: number;
|
||||
public totalAmt?: number;
|
||||
public expenseDate!: Date;
|
||||
public createdAt!: Date;
|
||||
public updatedAt!: Date;
|
||||
}
|
||||
@ -63,6 +91,76 @@ DealerCompletionExpense.init(
|
||||
allowNull: false,
|
||||
field: 'amount',
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
gstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_amt'
|
||||
},
|
||||
cgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_rate'
|
||||
},
|
||||
cgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_amt'
|
||||
},
|
||||
sgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_rate'
|
||||
},
|
||||
sgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_amt'
|
||||
},
|
||||
igstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_rate'
|
||||
},
|
||||
igstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_amt'
|
||||
},
|
||||
utgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_rate'
|
||||
},
|
||||
utgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_amt'
|
||||
},
|
||||
cessRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_rate'
|
||||
},
|
||||
cessAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_amt'
|
||||
},
|
||||
totalAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'total_amt'
|
||||
},
|
||||
expenseDate: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
field: 'expense_date',
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
|
||||
@ -9,12 +9,25 @@ interface DealerProposalCostItemAttributes {
|
||||
requestId: string;
|
||||
itemDescription: string;
|
||||
amount: number;
|
||||
gstRate?: number;
|
||||
gstAmt?: number;
|
||||
cgstRate?: number;
|
||||
cgstAmt?: number;
|
||||
sgstRate?: number;
|
||||
sgstAmt?: number;
|
||||
igstRate?: number;
|
||||
igstAmt?: number;
|
||||
utgstRate?: number;
|
||||
utgstAmt?: number;
|
||||
cessRate?: number;
|
||||
cessAmt?: number;
|
||||
totalAmt?: number;
|
||||
itemOrder: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface DealerProposalCostItemCreationAttributes extends Optional<DealerProposalCostItemAttributes, 'costItemId' | 'itemOrder' | 'createdAt' | 'updatedAt'> {}
|
||||
interface DealerProposalCostItemCreationAttributes extends Optional<DealerProposalCostItemAttributes, 'costItemId' | 'itemOrder' | 'createdAt' | 'updatedAt'> { }
|
||||
|
||||
class DealerProposalCostItem extends Model<DealerProposalCostItemAttributes, DealerProposalCostItemCreationAttributes> implements DealerProposalCostItemAttributes {
|
||||
public costItemId!: string;
|
||||
@ -22,6 +35,19 @@ class DealerProposalCostItem extends Model<DealerProposalCostItemAttributes, Dea
|
||||
public requestId!: string;
|
||||
public itemDescription!: string;
|
||||
public amount!: number;
|
||||
public gstRate?: number;
|
||||
public gstAmt?: number;
|
||||
public cgstRate?: number;
|
||||
public cgstAmt?: number;
|
||||
public sgstRate?: number;
|
||||
public sgstAmt?: number;
|
||||
public igstRate?: number;
|
||||
public igstAmt?: number;
|
||||
public utgstRate?: number;
|
||||
public utgstAmt?: number;
|
||||
public cessRate?: number;
|
||||
public cessAmt?: number;
|
||||
public totalAmt?: number;
|
||||
public itemOrder!: number;
|
||||
public createdAt!: Date;
|
||||
public updatedAt!: Date;
|
||||
@ -66,6 +92,71 @@ DealerProposalCostItem.init(
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false
|
||||
},
|
||||
gstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_rate'
|
||||
},
|
||||
gstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'gst_amt'
|
||||
},
|
||||
cgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_rate'
|
||||
},
|
||||
cgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cgst_amt'
|
||||
},
|
||||
sgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_rate'
|
||||
},
|
||||
sgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'sgst_amt'
|
||||
},
|
||||
igstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_rate'
|
||||
},
|
||||
igstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'igst_amt'
|
||||
},
|
||||
utgstRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_rate'
|
||||
},
|
||||
utgstAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'utgst_amt'
|
||||
},
|
||||
cessRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_rate'
|
||||
},
|
||||
cessAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'cess_amt'
|
||||
},
|
||||
totalAmt: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'total_amt'
|
||||
},
|
||||
itemOrder: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
|
||||
@ -157,6 +157,7 @@ async function runMigrations(): Promise<void> {
|
||||
const m42 = require('../migrations/20250125-create-activity-types');
|
||||
const m43 = require('../migrations/20260113-redesign-dealer-claim-history');
|
||||
const m44 = require('../migrations/20260123-fix-template-id-schema');
|
||||
const m45 = require('../migrations/20260209-add-gst-and-pwc-fields');
|
||||
|
||||
const migrations = [
|
||||
{ name: '2025103000-create-users', module: m0 },
|
||||
@ -206,6 +207,7 @@ async function runMigrations(): Promise<void> {
|
||||
{ name: '20250125-create-activity-types', module: m42 },
|
||||
{ name: '20260113-redesign-dealer-claim-history', module: m43 },
|
||||
{ name: '20260123-fix-template-id-schema', module: m44 },
|
||||
{ name: '20260209-add-gst-and-pwc-fields', module: m45 },
|
||||
];
|
||||
|
||||
// Dynamically import sequelize after secrets are loaded
|
||||
|
||||
@ -47,6 +47,7 @@ import * as m41 from '../migrations/20250120-create-dealers-table';
|
||||
import * as m42 from '../migrations/20250125-create-activity-types';
|
||||
import * as m43 from '../migrations/20260113-redesign-dealer-claim-history';
|
||||
import * as m44 from '../migrations/20260123-fix-template-id-schema';
|
||||
import * as m45 from '../migrations/20260209-add-gst-and-pwc-fields';
|
||||
|
||||
interface Migration {
|
||||
name: string;
|
||||
@ -108,6 +109,7 @@ const migrations: Migration[] = [
|
||||
{ name: '20250125-create-activity-types', module: m42 },
|
||||
{ name: '20260113-redesign-dealer-claim-history', module: m43 },
|
||||
{ name: '20260123-fix-template-id-schema', module: m44 },
|
||||
{ name: '20260209-add-gst-and-pwc-fields', module: m45 }
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@ -247,3 +247,42 @@ export async function searchDealers(searchTerm: string, limit: number = 10): Pro
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find dealer in local table by code or email (tiered lookup)
|
||||
* Used as a fallback until external API is available
|
||||
* @param dealerCode - Optional dealer code (dlrcode)
|
||||
* @param email - Optional email (domainId or dealerPrincipalEmailId)
|
||||
*/
|
||||
export async function findDealerLocally(dealerCode?: string, email?: string): Promise<DealerInfo | null> {
|
||||
try {
|
||||
// 1. Try Lookup by Dealer Code
|
||||
if (dealerCode) {
|
||||
const dealer = await getDealerByCode(dealerCode);
|
||||
if (dealer) return dealer;
|
||||
}
|
||||
|
||||
// 2. Try Lookup by Email (checks both domainId and dealerPrincipalEmailId)
|
||||
if (email) {
|
||||
const dealer = await Dealer.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ domainId: { [Op.iLike]: email.toLowerCase() } as any },
|
||||
{ dealerPrincipalEmailId: { [Op.iLike]: email.toLowerCase() } as any }
|
||||
],
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
|
||||
if (dealer) {
|
||||
// reuse same logic as getDealerByEmail for consistency
|
||||
return getDealerByEmail(dealer.domainId || dealer.dealerPrincipalEmailId || email);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.error('[DealerService] Error in local dealer fallback lookup:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,12 +18,16 @@ import { DealerClaimApprovalService } from './dealerClaimApproval.service';
|
||||
import { generateRequestNumber } from '../utils/helpers';
|
||||
import { Priority, WorkflowStatus, ApprovalStatus, ParticipantType } from '../types/common.types';
|
||||
import { sapIntegrationService } from './sapIntegration.service';
|
||||
import { dmsIntegrationService } from './dmsIntegration.service';
|
||||
import { pwcIntegrationService } from './pwcIntegration.service';
|
||||
import { notificationService } from './notification.service';
|
||||
import { activityService } from './activity.service';
|
||||
import { UserService } from './user.service';
|
||||
import { dmsIntegrationService } from './dmsIntegration.service';
|
||||
import { findDealerLocally } from './dealer.service';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dealer Claim Service
|
||||
* Handles business logic specific to dealer claim management workflow
|
||||
@ -83,6 +87,15 @@ export class DealerClaimService {
|
||||
throw new Error('Initiator not found');
|
||||
}
|
||||
|
||||
// Fallback: Enrichment from local dealer table if data is missing or incomplete
|
||||
const localDealer = await findDealerLocally(claimData.dealerCode, claimData.dealerEmail);
|
||||
if (localDealer) {
|
||||
logger.info(`[DealerClaimService] Enriched claim request with local dealer data: ${localDealer.dealerCode}`);
|
||||
claimData.dealerName = claimData.dealerName || localDealer.dealerName;
|
||||
claimData.dealerEmail = claimData.dealerEmail || localDealer.dealerPrincipalEmailId || localDealer.email;
|
||||
claimData.dealerPhone = claimData.dealerPhone || localDealer.phone;
|
||||
}
|
||||
|
||||
// Validate approvers array is provided
|
||||
if (!claimData.approvers || !Array.isArray(claimData.approvers) || claimData.approvers.length === 0) {
|
||||
throw new Error('Approvers array is required. Please assign approvers for all workflow steps.');
|
||||
@ -1260,6 +1273,19 @@ export class DealerClaimService {
|
||||
requestId,
|
||||
itemDescription: item.description || item.itemDescription || '',
|
||||
amount: Number(item.amount) || 0,
|
||||
gstRate: Number(item.gstRate) || 0,
|
||||
gstAmt: Number(item.gstAmt) || 0,
|
||||
cgstRate: Number(item.cgstRate) || 0,
|
||||
cgstAmt: Number(item.cgstAmt) || 0,
|
||||
sgstRate: Number(item.sgstRate) || 0,
|
||||
sgstAmt: Number(item.sgstAmt) || 0,
|
||||
igstRate: Number(item.igstRate) || 0,
|
||||
igstAmt: Number(item.igstAmt) || 0,
|
||||
utgstRate: Number(item.utgstRate) || 0,
|
||||
utgstAmt: Number(item.utgstAmt) || 0,
|
||||
cessRate: Number(item.cessRate) || 0,
|
||||
cessAmt: Number(item.cessAmt) || 0,
|
||||
totalAmt: Number(item.totalAmt) || Number(item.amount) || 0,
|
||||
itemOrder: index
|
||||
}));
|
||||
|
||||
@ -1397,7 +1423,21 @@ export class DealerClaimService {
|
||||
requestId,
|
||||
completionId,
|
||||
description: item.description,
|
||||
amount: item.amount,
|
||||
amount: Number(item.amount) || 0,
|
||||
gstRate: Number(item.gstRate) || 0,
|
||||
gstAmt: Number(item.gstAmt) || 0,
|
||||
cgstRate: Number(item.cgstRate) || 0,
|
||||
cgstAmt: Number(item.cgstAmt) || 0,
|
||||
sgstRate: Number(item.sgstRate) || 0,
|
||||
sgstAmt: Number(item.sgstAmt) || 0,
|
||||
igstRate: Number(item.igstRate) || 0,
|
||||
igstAmt: Number(item.igstAmt) || 0,
|
||||
utgstRate: Number(item.utgstRate) || 0,
|
||||
utgstAmt: Number(item.utgstAmt) || 0,
|
||||
cessRate: Number(item.cessRate) || 0,
|
||||
cessAmt: Number(item.cessAmt) || 0,
|
||||
totalAmt: Number(item.totalAmt) || Number(item.amount) || 0,
|
||||
expenseDate: item.date instanceof Date ? item.date : (item.date ? new Date(item.date) : (completionData.activityCompletionDate || new Date())),
|
||||
}));
|
||||
await DealerCompletionExpense.bulkCreate(expenseRows);
|
||||
}
|
||||
@ -1874,33 +1914,31 @@ export class DealerClaimService {
|
||||
|| budgetTracking?.initialEstimatedBudget
|
||||
|| 0;
|
||||
|
||||
const invoiceResult = await dmsIntegrationService.generateEInvoice({
|
||||
requestNumber,
|
||||
dealerCode: claimDetails.dealerCode,
|
||||
dealerName: claimDetails.dealerName,
|
||||
amount: invoiceAmount,
|
||||
description: invoiceData?.description || `E-Invoice for claim request ${requestNumber}`,
|
||||
ioNumber: internalOrder?.ioNumber || undefined,
|
||||
});
|
||||
const invoiceResult = await pwcIntegrationService.generateSignedInvoice(requestId);
|
||||
|
||||
if (!invoiceResult.success) {
|
||||
throw new Error(`Failed to generate e-invoice: ${invoiceResult.error}`);
|
||||
throw new Error(`Failed to generate signed e-invoice via PWC: ${invoiceResult.error}`);
|
||||
}
|
||||
|
||||
await ClaimInvoice.upsert({
|
||||
requestId,
|
||||
invoiceNumber: invoiceResult.eInvoiceNumber,
|
||||
invoiceDate: invoiceResult.invoiceDate || new Date(),
|
||||
dmsNumber: invoiceResult.dmsNumber,
|
||||
invoiceNumber: invoiceResult.ackNo, // Using Ack No as primary identifier for now
|
||||
invoiceDate: invoiceResult.ackDate instanceof Date ? invoiceResult.ackDate : (invoiceResult.ackDate ? new Date(invoiceResult.ackDate) : new Date()),
|
||||
irn: invoiceResult.irn,
|
||||
ackNo: invoiceResult.ackNo,
|
||||
ackDate: invoiceResult.ackDate instanceof Date ? invoiceResult.ackDate : (invoiceResult.ackDate ? new Date(invoiceResult.ackDate) : null),
|
||||
signedInvoice: invoiceResult.signedInvoice,
|
||||
qrCode: invoiceResult.qrCode,
|
||||
qrImage: invoiceResult.qrImage,
|
||||
amount: invoiceAmount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
description: invoiceData?.description || `E-Invoice for claim request ${requestNumber}`,
|
||||
description: invoiceData?.description || `PWC Signed Invoice for claim request ${requestNumber}`,
|
||||
});
|
||||
|
||||
logger.info(`[DealerClaimService] E-Invoice generated via DMS for request: ${requestId}`, {
|
||||
eInvoiceNumber: invoiceResult.eInvoiceNumber,
|
||||
dmsNumber: invoiceResult.dmsNumber
|
||||
logger.info(`[DealerClaimService] Signed Invoice generated via PWC for request: ${requestId}`, {
|
||||
ackNo: invoiceResult.ackNo,
|
||||
irn: invoiceResult.irn
|
||||
});
|
||||
} else {
|
||||
// Manual entry - just update the fields
|
||||
@ -1909,7 +1947,7 @@ export class DealerClaimService {
|
||||
invoiceNumber: invoiceData.eInvoiceNumber,
|
||||
invoiceDate: invoiceData.eInvoiceDate || new Date(),
|
||||
dmsNumber: invoiceData.dmsNumber,
|
||||
amount: invoiceData.amount,
|
||||
amount: Number(invoiceData.amount) || 0,
|
||||
status: 'UPDATED',
|
||||
generatedAt: new Date(),
|
||||
description: invoiceData.description,
|
||||
@ -2067,7 +2105,7 @@ export class DealerClaimService {
|
||||
invoiceId: claimInvoice.invoiceId,
|
||||
creditNoteNumber: creditNoteResult.creditNoteNumber,
|
||||
creditNoteDate: creditNoteResult.creditNoteDate || new Date(),
|
||||
creditNoteAmount: creditNoteResult.creditNoteAmount,
|
||||
creditNoteAmount: Number(creditNoteResult.creditNoteAmount) || 0,
|
||||
status: 'GENERATED',
|
||||
confirmedAt: new Date(),
|
||||
reason: creditNoteData?.reason || 'Claim settlement',
|
||||
@ -2100,7 +2138,7 @@ export class DealerClaimService {
|
||||
invoiceId: claimInvoice?.invoiceId || undefined, // Allow undefined if no invoice
|
||||
creditNoteNumber: creditNoteData.creditNoteNumber,
|
||||
creditNoteDate: creditNoteData.creditNoteDate || new Date(),
|
||||
creditNoteAmount: creditNoteData.creditNoteAmount,
|
||||
creditNoteAmount: Number(creditNoteData.creditNoteAmount) || 0,
|
||||
status: 'UPDATED',
|
||||
confirmedAt: new Date(),
|
||||
reason: creditNoteData?.reason,
|
||||
|
||||
@ -119,7 +119,7 @@ export class DMSWebhookService {
|
||||
amount: payload.total_amount || payload.claim_amount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
invoiceFilePath: payload.invoice_file_path || null,
|
||||
filePath: payload.invoice_file_path || null,
|
||||
errorMessage: payload.error_message || null,
|
||||
description: this.buildInvoiceDescription(payload),
|
||||
});
|
||||
@ -137,7 +137,7 @@ export class DMSWebhookService {
|
||||
amount: payload.total_amount || payload.claim_amount,
|
||||
status: 'GENERATED',
|
||||
generatedAt: new Date(),
|
||||
invoiceFilePath: payload.invoice_file_path || null,
|
||||
filePath: payload.invoice_file_path || null,
|
||||
errorMessage: payload.error_message || null,
|
||||
// Store additional DMS data in description or separate fields if needed
|
||||
description: this.buildInvoiceDescription(payload),
|
||||
@ -296,7 +296,7 @@ export class DMSWebhookService {
|
||||
*/
|
||||
private buildInvoiceDescription(payload: any): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
|
||||
if (payload.irn_no) {
|
||||
parts.push(`IRN: ${payload.irn_no}`);
|
||||
}
|
||||
@ -318,7 +318,7 @@ export class DMSWebhookService {
|
||||
*/
|
||||
private buildCreditNoteDescription(payload: any): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
|
||||
if (payload.irn_no) {
|
||||
parts.push(`IRN: ${payload.irn_no}`);
|
||||
}
|
||||
@ -404,7 +404,7 @@ export class DMSWebhookService {
|
||||
attributes: ['userId'],
|
||||
});
|
||||
dealerUserId = dealerUser?.userId || null;
|
||||
|
||||
|
||||
if (dealerUserId) {
|
||||
logger.info('[DMSWebhook] Found dealer user for notification', {
|
||||
requestId,
|
||||
@ -512,9 +512,9 @@ export class DMSWebhookService {
|
||||
const dealerClaimService = new DealerClaimService();
|
||||
const invoice = await ClaimInvoice.findOne({ where: { requestId } });
|
||||
const invoiceNumber = invoice?.invoiceNumber || 'N/A';
|
||||
|
||||
|
||||
await dealerClaimService.logEInvoiceGenerationActivity(requestId, invoiceNumber);
|
||||
|
||||
|
||||
logger.info('[DMSWebhook] E-Invoice Generation activity logged successfully', {
|
||||
requestId,
|
||||
requestNumber,
|
||||
|
||||
171
src/services/pwcIntegration.service.ts
Normal file
171
src/services/pwcIntegration.service.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import axios from 'axios';
|
||||
import logger from '../utils/logger';
|
||||
import { Dealer } from '../models/Dealer';
|
||||
import { ActivityType } from '../models/ActivityType';
|
||||
import { WorkflowRequest } from '../models/WorkflowRequest';
|
||||
import { ClaimInvoice } from '../models/ClaimInvoice';
|
||||
import { InternalOrder } from '../models/InternalOrder';
|
||||
|
||||
/**
|
||||
* PWC E-Invoice Integration Service
|
||||
* Handles communication with PWC API for signed invoice generation
|
||||
*/
|
||||
export class PWCIntegrationService {
|
||||
private apiUrl: string;
|
||||
private appKey: string;
|
||||
private appSecret: string;
|
||||
|
||||
constructor() {
|
||||
this.apiUrl = process.env.PWC_API_URL || 'https://api.qa.einvoice.aw.navigatetax.pwc.co.in';
|
||||
this.appKey = process.env.PWC_APP_KEY || '';
|
||||
this.appSecret = process.env.PWC_APP_SECRET || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve GL Code based on Activity Type and Internal Order
|
||||
*/
|
||||
private async resolveGLCode(activityTypeId: string, ioNumber?: string): Promise<string> {
|
||||
const activity = await ActivityType.findByPk(activityTypeId);
|
||||
if (activity?.glCode) {
|
||||
return activity.glCode;
|
||||
}
|
||||
|
||||
// Default Fallback or IO based logic if required
|
||||
// Based on "IO GL will be changed" comment in user screenshot
|
||||
if (ioNumber) {
|
||||
const io = await InternalOrder.findOne({ where: { ioNumber } });
|
||||
// Logic to derive GL from IO if needed
|
||||
}
|
||||
|
||||
return '610000'; // Default placeholder
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Signed Invoice via PWC API
|
||||
*/
|
||||
async generateSignedInvoice(requestId: string): Promise<{
|
||||
success: boolean;
|
||||
irn?: string;
|
||||
ackNo?: string;
|
||||
ackDate?: Date | string;
|
||||
signedInvoice?: string;
|
||||
qrCode?: string;
|
||||
qrImage?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const request = await WorkflowRequest.findByPk(requestId, {
|
||||
include: ['claimDetails', 'initiator']
|
||||
});
|
||||
|
||||
if (!request) return { success: false, error: 'Request not found' };
|
||||
|
||||
const dealer = await Dealer.findOne({ where: { dlrcode: (request as any).claimDetails?.dealerCode } });
|
||||
const activity = await ActivityType.findOne({ where: { title: (request as any).claimDetails?.activityType } });
|
||||
|
||||
if (!dealer || !activity) {
|
||||
return { success: false, error: 'Dealer or Activity details missing' };
|
||||
}
|
||||
|
||||
// Construct PWC Payload (keeping existing logic for now)
|
||||
const payload = {
|
||||
UserGstin: "33AAACE3882D1ZZ",
|
||||
DocDtls: {
|
||||
Typ: "INV",
|
||||
No: `INV-${Date.now()}`,
|
||||
Dt: new Date().toLocaleDateString('en-GB').replace(/\//g, '-')
|
||||
},
|
||||
SellerDtls: {
|
||||
Gstin: dealer.gst || "33AAACE3882D1ZZ",
|
||||
LglNm: dealer.dealership || 'Dealer',
|
||||
Addr1: dealer.showroomAddress || "Address Line 1",
|
||||
Loc: dealer.location || "Location",
|
||||
Pin: 600001,
|
||||
Stcd: "33"
|
||||
},
|
||||
BuyerDtls: {
|
||||
Gstin: "33AAACE3882D1ZZ",
|
||||
LglNm: "ROYAL ENFIELD (A UNIT OF EICHER MOTORS LTD)",
|
||||
Addr1: "No. 2, Thiruvottiyur High Road",
|
||||
Loc: "Thiruvottiyur",
|
||||
Pin: 600019,
|
||||
Stcd: "33",
|
||||
Pos: "33"
|
||||
},
|
||||
ItemList: [
|
||||
{
|
||||
SlNo: "1",
|
||||
PrdDesc: activity.title,
|
||||
IsServc: "Y",
|
||||
HsnCd: activity.hsnCode || activity.sacCode || "9983",
|
||||
Qty: 1,
|
||||
Unit: "OTH",
|
||||
UnitPrce: (request as any).amount,
|
||||
TotAmt: (request as any).amount,
|
||||
GstRt: activity.gstRate || 18,
|
||||
AssAmt: (request as any).amount,
|
||||
IgstAmt: activity.gstRate === 18 ? ((request as any).amount * 0.18) : 0,
|
||||
TotItemVal: (request as any).amount * 1.18
|
||||
}
|
||||
],
|
||||
ValDtls: {
|
||||
AssVal: (request as any).amount,
|
||||
IgstVal: activity.gstRate === 18 ? ((request as any).amount * 0.18) : 0,
|
||||
TotInvVal: (request as any).amount * 1.18
|
||||
}
|
||||
};
|
||||
|
||||
logger.info(`[PWC] Sending e-invoice request for ${request.requestNumber}`);
|
||||
|
||||
const response = await axios.post(`${this.apiUrl}/generate`, payload, {
|
||||
headers: { 'AppKey': this.appKey, 'AppSecret': this.appSecret }
|
||||
});
|
||||
|
||||
// Parse PWC Response based on provided structure
|
||||
// Sample response is an array: [{ pwc_response, irp_response, qr_b64_encoded }]
|
||||
const responseData = Array.isArray(response.data) ? response.data[0] : response.data;
|
||||
const irpData = responseData?.irp_response?.data;
|
||||
const nicData = irpData?.nic_response_data;
|
||||
const qrB64 = responseData?.qr_b64_encoded;
|
||||
|
||||
let irn = nicData?.Irn;
|
||||
let ackNo = nicData?.AckNo;
|
||||
let ackDate = nicData?.AckDt;
|
||||
let signedInvoice = nicData?.SignedInvoice;
|
||||
let qrCode = nicData?.SignedQRCode;
|
||||
|
||||
// Handle Duplicate IRN Case
|
||||
if (!irn && irpData?.InfoDtls) {
|
||||
const dupInfo = irpData.InfoDtls.find((info: any) => info.InfCd === 'DUPIRN');
|
||||
if (dupInfo?.Desc) {
|
||||
irn = dupInfo.Desc.Irn;
|
||||
ackNo = dupInfo.Desc.AckNo;
|
||||
ackDate = dupInfo.Desc.AckDt;
|
||||
logger.info(`[PWC] Handled duplicate IRN for ${request.requestNumber}: ${irn}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!irn) {
|
||||
const errorMsg = responseData?.irp_response?.message || 'E-Invoice generation failed';
|
||||
logger.error(`[PWC] E-Invoice failed for ${request.requestNumber}: ${errorMsg}`);
|
||||
return { success: false, error: errorMsg };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
irn,
|
||||
ackNo: String(ackNo),
|
||||
ackDate: ackDate ? new Date(ackDate) : undefined,
|
||||
signedInvoice,
|
||||
qrCode,
|
||||
qrImage: qrB64
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[PWC] Error generating e-invoice:', error);
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const pwcIntegrationService = new PWCIntegrationService();
|
||||
Loading…
Reference in New Issue
Block a user