enhancd expens and cost items to support gst values addd pwc service file to integrate pwc

This commit is contained in:
laxmanhalaki 2026-02-09 20:50:17 +05:30
parent 2282d29322
commit 17c62d2b45
12 changed files with 917 additions and 56 deletions

View 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(() => { });
}

View File

@ -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,

View File

@ -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),

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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 }
];
/**

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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,

View 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();