SAP team asked to map credit posting on based on the item group Vehicle/Spares have been implemented also points like delare code pading and nomenclature changes added
This commit is contained in:
parent
64e8c2237a
commit
0f99fe68d5
@ -1 +1 @@
|
|||||||
import{a as s}from"./index-DsQZmIYq.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-CxsBWvVP.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-BATWUvr6.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
import{a as s}from"./index-BgKXDGEk.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-BTBPSQfW.js";import"./ui-vendor-CxsBWvVP.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-BATWUvr6.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
build/assets/index-D2NzWWdB.css
Normal file
1
build/assets/index-D2NzWWdB.css
Normal file
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@
|
|||||||
<!-- Preload essential fonts and icons -->
|
<!-- Preload essential fonts and icons -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<script type="module" crossorigin src="/assets/index-DsQZmIYq.js"></script>
|
<script type="module" crossorigin src="/assets/index-BgKXDGEk.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-BVfwAPj-.js">
|
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-BVfwAPj-.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CYvDqP9X.js">
|
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CYvDqP9X.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-BTBPSQfW.js">
|
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-BTBPSQfW.js">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/router-vendor-BATWUvr6.js">
|
<link rel="modulepreload" crossorigin href="/assets/router-vendor-BATWUvr6.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Bgoo5ePN.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D2NzWWdB.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -1189,6 +1189,7 @@ export const createActivityType = async (req: Request, res: Response): Promise<v
|
|||||||
itemCode: itemCode || null,
|
itemCode: itemCode || null,
|
||||||
taxationType: taxationType || null,
|
taxationType: taxationType || null,
|
||||||
sapRefNo: sapRefNo || null,
|
sapRefNo: sapRefNo || null,
|
||||||
|
creditPostingOn: req.body.creditPostingOn || null,
|
||||||
createdBy: userId
|
createdBy: userId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { ClaimInvoiceItem } from '../models/ClaimInvoiceItem';
|
|||||||
import { ActivityType } from '../models/ActivityType';
|
import { ActivityType } from '../models/ActivityType';
|
||||||
import { Participant } from '../models/Participant';
|
import { Participant } from '../models/Participant';
|
||||||
import { sanitizeObject, sanitizePermissive } from '../utils/sanitizer';
|
import { sanitizeObject, sanitizePermissive } from '../utils/sanitizer';
|
||||||
|
import { padDealerCode } from '../utils/helpers';
|
||||||
|
|
||||||
export class DealerClaimController {
|
export class DealerClaimController {
|
||||||
private dealerClaimService = new DealerClaimService();
|
private dealerClaimService = new DealerClaimService();
|
||||||
@ -1109,7 +1110,7 @@ export class DealerClaimController {
|
|||||||
const trnsUniqNo = item.transactionCode || '';
|
const trnsUniqNo = item.transactionCode || '';
|
||||||
const claimNumber = requestNumber;
|
const claimNumber = requestNumber;
|
||||||
const invNumber = invoice?.invoiceNumber || '';
|
const invNumber = invoice?.invoiceNumber || '';
|
||||||
const dealerCode = claimDetails?.dealerCode || '';
|
const dealerCode = padDealerCode(claimDetails?.dealerCode || '');
|
||||||
const ioNumber = internalOrder?.ioNumber || '';
|
const ioNumber = internalOrder?.ioNumber || '';
|
||||||
const claimDocTyp = sapRefNo;
|
const claimDocTyp = sapRefNo;
|
||||||
const claimType = claimDetails?.activityType || '';
|
const claimType = claimDetails?.activityType || '';
|
||||||
|
|||||||
86
src/migrations/20260317-refactor-activity-types-columns.ts
Normal file
86
src/migrations/20260317-refactor-activity-types-columns.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { QueryInterface, DataTypes } from 'sequelize';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: returns true if the column exists in the table
|
||||||
|
*/
|
||||||
|
async function columnExists(
|
||||||
|
queryInterface: QueryInterface,
|
||||||
|
tableName: string,
|
||||||
|
columnName: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const description = await queryInterface.describeTable(tableName);
|
||||||
|
return columnName in description;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration: Refactor activity_types table
|
||||||
|
*
|
||||||
|
* Drops deprecated columns that will not be used going forward:
|
||||||
|
* hsn_code, sac_code, gst_rate, gl_code, credit_nature
|
||||||
|
*
|
||||||
|
* Adds new column:
|
||||||
|
* credit_posting_on VARCHAR(50) – indicates posting target (e.g. 'Spares', 'Vehicle')
|
||||||
|
*
|
||||||
|
* All drops are guarded so this migration is safe to run on a fresh database
|
||||||
|
* where these columns were never added.
|
||||||
|
*/
|
||||||
|
export async function up(queryInterface: QueryInterface): Promise<void> {
|
||||||
|
const TABLE = 'activity_types';
|
||||||
|
|
||||||
|
// ── Drop deprecated columns (safe: only if they exist) ──────────────────────
|
||||||
|
const columnsToDrop = ['hsn_code', 'sac_code', 'gst_rate', 'gl_code', 'credit_nature'];
|
||||||
|
|
||||||
|
for (const col of columnsToDrop) {
|
||||||
|
if (await columnExists(queryInterface, TABLE, col)) {
|
||||||
|
await queryInterface.removeColumn(TABLE, col);
|
||||||
|
console.log(`[Migration] Dropped column ${TABLE}.${col}`);
|
||||||
|
} else {
|
||||||
|
console.log(`[Migration] Column ${TABLE}.${col} does not exist – skipping drop`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Add new column ────────────────────────────────────────────────────────────
|
||||||
|
if (!(await columnExists(queryInterface, TABLE, 'credit_posting_on'))) {
|
||||||
|
await queryInterface.addColumn(TABLE, 'credit_posting_on', {
|
||||||
|
type: DataTypes.STRING(50),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
comment: 'Indicates what the credit note is posted against (e.g. "Spares", "Vehicle")'
|
||||||
|
});
|
||||||
|
console.log(`[Migration] Added column ${TABLE}.credit_posting_on`);
|
||||||
|
} else {
|
||||||
|
console.log(`[Migration] Column ${TABLE}.credit_posting_on already exists – skipping add`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback: re-add the dropped columns and remove credit_posting_on.
|
||||||
|
* Columns are restored as nullable so existing rows are unaffected.
|
||||||
|
*/
|
||||||
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||||
|
const TABLE = 'activity_types';
|
||||||
|
|
||||||
|
// Remove the newly added column
|
||||||
|
if (await columnExists(queryInterface, TABLE, 'credit_posting_on')) {
|
||||||
|
await queryInterface.removeColumn(TABLE, 'credit_posting_on');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore dropped columns
|
||||||
|
const columnsToRestore: Record<string, any> = {
|
||||||
|
hsn_code: { type: DataTypes.STRING(20), allowNull: true, defaultValue: null },
|
||||||
|
sac_code: { type: DataTypes.STRING(20), allowNull: true, defaultValue: null },
|
||||||
|
gst_rate: { type: DataTypes.DECIMAL(5, 2), allowNull: true, defaultValue: null },
|
||||||
|
gl_code: { type: DataTypes.STRING(20), allowNull: true, defaultValue: null },
|
||||||
|
credit_nature: { type: DataTypes.STRING(50), allowNull: true, defaultValue: null }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [col, spec] of Object.entries(columnsToRestore)) {
|
||||||
|
if (!(await columnExists(queryInterface, TABLE, col))) {
|
||||||
|
await queryInterface.addColumn(TABLE, col, spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,18 +9,14 @@ interface ActivityTypeAttributes {
|
|||||||
taxationType?: string;
|
taxationType?: string;
|
||||||
sapRefNo?: string;
|
sapRefNo?: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
hsnCode?: string | null;
|
creditPostingOn?: string | null;
|
||||||
sacCode?: string | null;
|
|
||||||
gstRate?: number | null;
|
|
||||||
glCode?: string | null;
|
|
||||||
creditNature?: 'Commercial' | 'GST' | null;
|
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
updatedBy?: string;
|
updatedBy?: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: 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' | 'creditPostingOn' | 'updatedBy' | 'createdAt' | 'updatedAt'> { }
|
||||||
|
|
||||||
class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAttributes> implements ActivityTypeAttributes {
|
class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAttributes> implements ActivityTypeAttributes {
|
||||||
public activityTypeId!: string;
|
public activityTypeId!: string;
|
||||||
@ -29,11 +25,7 @@ class ActivityType extends Model<ActivityTypeAttributes, ActivityTypeCreationAtt
|
|||||||
public taxationType?: string;
|
public taxationType?: string;
|
||||||
public sapRefNo?: string;
|
public sapRefNo?: string;
|
||||||
public isActive!: boolean;
|
public isActive!: boolean;
|
||||||
public hsnCode?: string | null;
|
public creditPostingOn?: string | null;
|
||||||
public sacCode?: string | null;
|
|
||||||
public gstRate?: number | null;
|
|
||||||
public glCode?: string | null;
|
|
||||||
public creditNature?: 'Commercial' | 'GST' | null;
|
|
||||||
public createdBy!: string;
|
public createdBy!: string;
|
||||||
public updatedBy?: string;
|
public updatedBy?: string;
|
||||||
public createdAt!: Date;
|
public createdAt!: Date;
|
||||||
@ -81,30 +73,12 @@ ActivityType.init(
|
|||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
field: 'is_active'
|
field: 'is_active'
|
||||||
},
|
},
|
||||||
hsnCode: {
|
creditPostingOn: {
|
||||||
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),
|
type: DataTypes.STRING(50),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
field: 'gl_code'
|
defaultValue: null,
|
||||||
},
|
field: 'credit_posting_on',
|
||||||
creditNature: {
|
comment: 'Indicates what the credit note is posted against (e.g. "Spares", "Vehicle")'
|
||||||
type: DataTypes.ENUM('Commercial', 'GST'),
|
|
||||||
allowNull: true,
|
|
||||||
field: 'credit_nature'
|
|
||||||
},
|
},
|
||||||
createdBy: {
|
createdBy: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
|
|||||||
@ -42,7 +42,8 @@ router.get('/activity-types',
|
|||||||
title: at.title,
|
title: at.title,
|
||||||
itemCode: at.itemCode,
|
itemCode: at.itemCode,
|
||||||
taxationType: at.taxationType,
|
taxationType: at.taxationType,
|
||||||
sapRefNo: at.sapRefNo
|
sapRefNo: at.sapRefNo,
|
||||||
|
creditPostingOn: at.creditPostingOn
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -175,6 +175,7 @@ async function runMigrations(): Promise<void> {
|
|||||||
const m58 = require('../migrations/20260303100001-drop-form16a-number-unique');
|
const m58 = require('../migrations/20260303100001-drop-form16a-number-unique');
|
||||||
const m59 = require('../migrations/20260309-add-wfm-push-fields');
|
const m59 = require('../migrations/20260309-add-wfm-push-fields');
|
||||||
const m60 = require('../migrations/20260316-update-holiday-type-enum');
|
const m60 = require('../migrations/20260316-update-holiday-type-enum');
|
||||||
|
const m61 = require('../migrations/20260317-refactor-activity-types-columns');
|
||||||
|
|
||||||
const migrations = [
|
const migrations = [
|
||||||
{ name: '2025103000-create-users', module: m0 },
|
{ name: '2025103000-create-users', module: m0 },
|
||||||
@ -242,6 +243,7 @@ async function runMigrations(): Promise<void> {
|
|||||||
{ name: '20260303100001-drop-form16a-number-unique', module: m58 },
|
{ name: '20260303100001-drop-form16a-number-unique', module: m58 },
|
||||||
{ name: '20260309-add-wfm-push-fields', module: m59 },
|
{ name: '20260309-add-wfm-push-fields', module: m59 },
|
||||||
{ name: '20260316-update-holiday-type-enum', module: m60 },
|
{ name: '20260316-update-holiday-type-enum', module: m60 },
|
||||||
|
{ name: '20260317-refactor-activity-types-columns', module: m61 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Dynamically import sequelize after secrets are loaded
|
// Dynamically import sequelize after secrets are loaded
|
||||||
|
|||||||
@ -65,6 +65,7 @@ import * as m57 from '../migrations/20260225100001-add-form16-archived-at';
|
|||||||
import * as m58 from '../migrations/20260303100001-drop-form16a-number-unique';
|
import * as m58 from '../migrations/20260303100001-drop-form16a-number-unique';
|
||||||
import * as m59 from '../migrations/20260309-add-wfm-push-fields';
|
import * as m59 from '../migrations/20260309-add-wfm-push-fields';
|
||||||
import * as m60 from '../migrations/20260316-update-holiday-type-enum';
|
import * as m60 from '../migrations/20260316-update-holiday-type-enum';
|
||||||
|
import * as m61 from '../migrations/20260317-refactor-activity-types-columns';
|
||||||
|
|
||||||
interface Migration {
|
interface Migration {
|
||||||
name: string;
|
name: string;
|
||||||
@ -137,6 +138,7 @@ const migrations: Migration[] = [
|
|||||||
{ name: '20260303100001-drop-form16a-number-unique', module: m58 },
|
{ name: '20260303100001-drop-form16a-number-unique', module: m58 },
|
||||||
{ name: '20260309-add-wfm-push-fields', module: m59 },
|
{ name: '20260309-add-wfm-push-fields', module: m59 },
|
||||||
{ name: '20260316-update-holiday-type-enum', module: m60 },
|
{ name: '20260316-update-holiday-type-enum', module: m60 },
|
||||||
|
{ name: '20260317-refactor-activity-types-columns', module: m61 },
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -60,14 +60,12 @@ export class ActivityTypeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new activity type
|
|
||||||
*/
|
|
||||||
async createActivityType(activityTypeData: {
|
async createActivityType(activityTypeData: {
|
||||||
title: string;
|
title: string;
|
||||||
itemCode?: string;
|
itemCode?: string;
|
||||||
taxationType?: string;
|
taxationType?: string;
|
||||||
sapRefNo?: string;
|
sapRefNo?: string;
|
||||||
|
creditPostingOn?: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
}): Promise<ActivityType> {
|
}): Promise<ActivityType> {
|
||||||
try {
|
try {
|
||||||
@ -104,6 +102,7 @@ export class ActivityTypeService {
|
|||||||
itemCode?: string;
|
itemCode?: string;
|
||||||
taxationType?: string;
|
taxationType?: string;
|
||||||
sapRefNo?: string;
|
sapRefNo?: string;
|
||||||
|
creditPostingOn?: string;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}, updatedBy: string): Promise<ActivityType | null> {
|
}, updatedBy: string): Promise<ActivityType | null> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -8,19 +8,20 @@ import { ActivityType } from '@models/ActivityType';
|
|||||||
* These will be seeded into the database with default item_code values (1-13)
|
* These will be seeded into the database with default item_code values (1-13)
|
||||||
*/
|
*/
|
||||||
const DEFAULT_ACTIVITY_TYPES = [
|
const DEFAULT_ACTIVITY_TYPES = [
|
||||||
{ title: 'Riders Mania Claims', itemCode: '1', taxationType: 'Non GST', sapRefNo: 'ZRDM' },
|
{ title: 'Riders Mania Claims', itemCode: '1', taxationType: 'Non GST', sapRefNo: 'ZRDM', creditPostingOn: 'Spares' },
|
||||||
{ title: 'Marketing Cost – Bike to Vendor', itemCode: '2', taxationType: 'Non GST', sapRefNo: 'ZMBV' },
|
{ title: 'Marketing Cost – Bike to Vendor', itemCode: '2', taxationType: 'Non GST', sapRefNo: 'ZMBV', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Media Bike Service', itemCode: '3', taxationType: 'GST', sapRefNo: 'ZMBS' },
|
{ title: 'Media Bike Service', itemCode: '3', taxationType: 'GST', sapRefNo: 'ZMBS', creditPostingOn: 'Spares' },
|
||||||
{ title: 'ARAI Motorcycle Liquidation', itemCode: '4', taxationType: 'GST', sapRefNo: 'ZAML' },
|
{ title: 'ARAI Motorcycle Liquidation', itemCode: '4', taxationType: 'GST', sapRefNo: 'ZAML', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'ARAI Certification – STA Approval CNR', itemCode: '5', taxationType: 'Non GST', sapRefNo: 'ZACS' },
|
{ title: 'ARAI Certification – STA Approval CNR', itemCode: '5', taxationType: 'Non GST', sapRefNo: 'ZACS', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Procurement of Spares/Apparel/GMA for Events', itemCode: '6', taxationType: 'GST', sapRefNo: 'ZPPE' },
|
{ title: 'Procurement of Spares/Apparel/GMA for Events', itemCode: '6', taxationType: 'GST', sapRefNo: 'ZPPE', creditPostingOn: 'Spares' },
|
||||||
{ title: 'Fuel for Media Bike Used for Event', itemCode: '7', taxationType: 'Non GST', sapRefNo: 'ZFMB' },
|
{ title: 'Fuel for Media Bike Used for Event', itemCode: '7', taxationType: 'Non GST', sapRefNo: 'ZFMB', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Motorcycle Buyback and Goodwill Support', itemCode: '8', taxationType: 'Non GST', sapRefNo: 'ZMBG' },
|
{ title: 'Motorcycle Buyback and Goodwill Support', itemCode: '8', taxationType: 'Non GST', sapRefNo: 'ZMBG', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Liquidation of Used Motorcycle', itemCode: '9', taxationType: 'GST', sapRefNo: 'ZLUM' },
|
{ title: 'Liquidation of Used Motorcycle', itemCode: '9', taxationType: 'GST', sapRefNo: 'ZLUM', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Motorcycle Registration CNR (Owned or Gifted by RE)', itemCode: '10', taxationType: 'GST', sapRefNo: 'ZMRC' },
|
{ title: 'Motorcycle Registration CNR (Owned or Gifted by RE)', itemCode: '10', taxationType: 'GST', sapRefNo: 'ZMRC', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Legal Claims Reimbursement', itemCode: '11', taxationType: 'Non GST', sapRefNo: 'ZLCR' },
|
{ title: 'Legal Claims Reimbursement', itemCode: '11', taxationType: 'Non GST', sapRefNo: 'ZLCR', creditPostingOn: 'Vehicle' },
|
||||||
{ title: 'Service Camp Claims', itemCode: '12', taxationType: 'Non GST', sapRefNo: 'ZSCC' },
|
{ title: 'Service Camp Claims', itemCode: '12', taxationType: 'Non GST', sapRefNo: 'ZSCC', creditPostingOn: 'Spares' },
|
||||||
{ title: 'Corporate Claims – Institutional Sales PDI', itemCode: '13', taxationType: 'Non GST', sapRefNo: 'ZCCN' }
|
{ title: 'Corporate Claims – Institutional Sales PD', itemCode: '13', taxationType: 'Non GST', sapRefNo: 'ZCCN', creditPostingOn: 'Vehicle' },
|
||||||
|
{ title: 'Corporate Claims – Institutional Sales PD', itemCode: '14', taxationType: 'GST', sapRefNo: 'ZCCG', creditPostingOn: 'Vehicle' }
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,19 +71,20 @@ export async function seedDefaultActivityTypes(): Promise<void> {
|
|||||||
let skippedCount = 0;
|
let skippedCount = 0;
|
||||||
|
|
||||||
for (const activityType of DEFAULT_ACTIVITY_TYPES) {
|
for (const activityType of DEFAULT_ACTIVITY_TYPES) {
|
||||||
const { title, itemCode, taxationType, sapRefNo } = activityType;
|
const { title, itemCode, taxationType, sapRefNo, creditPostingOn } = activityType;
|
||||||
try {
|
try {
|
||||||
// Check if activity type already exists (active or inactive)
|
// Check if activity type already exists (active or inactive)
|
||||||
const existing = await ActivityType.findOne({
|
const existing = await ActivityType.findOne({
|
||||||
where: { title }
|
where: { title, sapRefNo } // Match on both title and sapRefNo since title "Corporate Claims..." is duplicated
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
// Identify fields to update (only need systemUserId for updatedBy)
|
// Identify fields to update (only need systemUserId for updatedBy)
|
||||||
const updates: any = {};
|
const updates: any = {};
|
||||||
if (!existing.itemCode && itemCode) updates.itemCode = itemCode;
|
if (existing.itemCode !== itemCode) updates.itemCode = itemCode;
|
||||||
if (!existing.taxationType && taxationType) updates.taxationType = taxationType;
|
if (existing.taxationType !== taxationType) updates.taxationType = taxationType;
|
||||||
if (!existing.sapRefNo && sapRefNo) updates.sapRefNo = sapRefNo;
|
if (existing.sapRefNo !== sapRefNo) updates.sapRefNo = sapRefNo;
|
||||||
|
if (existing.creditPostingOn !== creditPostingOn) updates.creditPostingOn = creditPostingOn;
|
||||||
if (systemUserId) updates.updatedBy = systemUserId;
|
if (systemUserId) updates.updatedBy = systemUserId;
|
||||||
|
|
||||||
if (!existing.isActive) {
|
if (!existing.isActive) {
|
||||||
@ -94,6 +96,7 @@ export async function seedDefaultActivityTypes(): Promise<void> {
|
|||||||
if (Object.keys(updates).length > 0) {
|
if (Object.keys(updates).length > 0) {
|
||||||
await existing.update(updates);
|
await existing.update(updates);
|
||||||
logger.debug(`[ActivityType Seed] Updated fields for existing activity type: ${title} (updates: ${JSON.stringify(updates)})`);
|
logger.debug(`[ActivityType Seed] Updated fields for existing activity type: ${title} (updates: ${JSON.stringify(updates)})`);
|
||||||
|
updatedCount++;
|
||||||
} else {
|
} else {
|
||||||
skippedCount++;
|
skippedCount++;
|
||||||
logger.debug(`[ActivityType Seed] Activity type already exists and active: ${title}`);
|
logger.debug(`[ActivityType Seed] Activity type already exists and active: ${title}`);
|
||||||
@ -111,6 +114,7 @@ export async function seedDefaultActivityTypes(): Promise<void> {
|
|||||||
itemCode: itemCode,
|
itemCode: itemCode,
|
||||||
taxationType: taxationType,
|
taxationType: taxationType,
|
||||||
sapRefNo: sapRefNo,
|
sapRefNo: sapRefNo,
|
||||||
|
creditPostingOn: creditPostingOn,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
createdBy: systemUserId!
|
createdBy: systemUserId!
|
||||||
} as any);
|
} as any);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export interface DealerInfo {
|
|||||||
dealerPrincipalEmailId?: string | null;
|
dealerPrincipalEmailId?: string | null;
|
||||||
gstin?: string | null;
|
gstin?: string | null;
|
||||||
pincode?: string | null;
|
pincode?: string | null;
|
||||||
|
itemGroup?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,6 +111,7 @@ export async function getAllDealers(searchTerm?: string, limit: number = 10): Pr
|
|||||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||||
gstin: dealer.gst || null,
|
gstin: dealer.gst || null,
|
||||||
pincode: dealer.showroomPincode || null,
|
pincode: dealer.showroomPincode || null,
|
||||||
|
itemGroup: null, // Local dealer table doesn't have item group yet
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -162,6 +164,7 @@ export async function getDealerByCode(dealerCode: string): Promise<DealerInfo |
|
|||||||
city: externalData?.['re city'] || null,
|
city: externalData?.['re city'] || null,
|
||||||
state: externalData?.['re state code'] || null,
|
state: externalData?.['re state code'] || null,
|
||||||
pincode: externalData?.pincode || null,
|
pincode: externalData?.pincode || null,
|
||||||
|
itemGroup: externalData?.['item group'] || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +192,7 @@ export async function getDealerByCode(dealerCode: string): Promise<DealerInfo |
|
|||||||
city: externalData['re city'],
|
city: externalData['re city'],
|
||||||
state: externalData['re state code'],
|
state: externalData['re state code'],
|
||||||
pincode: externalData.pincode,
|
pincode: externalData.pincode,
|
||||||
|
itemGroup: externalData['item group'] || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
logger.warn(`[DealerService] Dealer not found in any source: ${dealerCode}`);
|
logger.warn(`[DealerService] Dealer not found in any source: ${dealerCode}`);
|
||||||
@ -217,6 +221,7 @@ export async function getDealerByCode(dealerCode: string): Promise<DealerInfo |
|
|||||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||||
gstin: externalData?.gstin || dealer.gst || null,
|
gstin: externalData?.gstin || dealer.gst || null,
|
||||||
pincode: externalData?.pincode || dealer.showroomPincode || null,
|
pincode: externalData?.pincode || dealer.showroomPincode || null,
|
||||||
|
itemGroup: externalData?.['item group'] || null,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[DealerService] Error fetching dealer by code:', error);
|
logger.error('[DealerService] Error fetching dealer by code:', error);
|
||||||
@ -278,6 +283,7 @@ export async function getDealerByEmail(email: string): Promise<DealerInfo | null
|
|||||||
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
dealerPrincipalEmailId: dealer.dealerPrincipalEmailId || null,
|
||||||
gstin: dealer.gst || null,
|
gstin: dealer.gst || null,
|
||||||
pincode: dealer.showroomPincode || null,
|
pincode: dealer.showroomPincode || null,
|
||||||
|
itemGroup: null,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[DealerService] Error fetching dealer by email:', error);
|
logger.error('[DealerService] Error fetching dealer by email:', error);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { Document } from '../models/Document';
|
|||||||
import { Dealer } from '../models/Dealer';
|
import { Dealer } from '../models/Dealer';
|
||||||
import { WorkflowService } from './workflow.service';
|
import { WorkflowService } from './workflow.service';
|
||||||
import { DealerClaimApprovalService } from './dealerClaimApproval.service';
|
import { DealerClaimApprovalService } from './dealerClaimApproval.service';
|
||||||
import { generateRequestNumber } from '../utils/helpers';
|
import { generateRequestNumber, padDealerCode } from '../utils/helpers';
|
||||||
import { Priority, WorkflowStatus, ApprovalStatus, ParticipantType } from '../types/common.types';
|
import { Priority, WorkflowStatus, ApprovalStatus, ParticipantType } from '../types/common.types';
|
||||||
import { sapIntegrationService } from './sapIntegration.service';
|
import { sapIntegrationService } from './sapIntegration.service';
|
||||||
import { pwcIntegrationService } from './pwcIntegration.service';
|
import { pwcIntegrationService } from './pwcIntegration.service';
|
||||||
@ -125,6 +125,38 @@ export class DealerClaimService {
|
|||||||
logger.info(`[DealerClaimService] Validating dealer for code: ${claimData.dealerCode}`);
|
logger.info(`[DealerClaimService] Validating dealer for code: ${claimData.dealerCode}`);
|
||||||
const dealerUser = await validateDealerUser(claimData.dealerCode);
|
const dealerUser = await validateDealerUser(claimData.dealerCode);
|
||||||
|
|
||||||
|
// 0a. Validate Dealer Item Group against Activity Credit Posting
|
||||||
|
const activityType = await ActivityType.findOne({ where: { title: claimData.activityType } });
|
||||||
|
if (activityType && activityType.creditPostingOn) {
|
||||||
|
// Fetch full dealer info (including external API data like itemGroup)
|
||||||
|
const fullDealerInfo = await findDealerLocally(dealerCode);
|
||||||
|
|
||||||
|
if (fullDealerInfo && fullDealerInfo.itemGroup) {
|
||||||
|
const creditPostingOn = activityType.creditPostingOn.toLowerCase();
|
||||||
|
const itemGroup = fullDealerInfo.itemGroup.toLowerCase();
|
||||||
|
|
||||||
|
let isMatch = false;
|
||||||
|
if (creditPostingOn === 'vehicle' && itemGroup === 'vehicle') {
|
||||||
|
isMatch = true;
|
||||||
|
} else if (creditPostingOn === 'spares' && itemGroup === 'spares') {
|
||||||
|
isMatch = true;
|
||||||
|
} else if (creditPostingOn === 'gma' && itemGroup === 'gma') {
|
||||||
|
isMatch = true;
|
||||||
|
} else if (creditPostingOn === 'apparel' && itemGroup === 'apparel') {
|
||||||
|
isMatch = true;
|
||||||
|
} else if (creditPostingOn === itemGroup) {
|
||||||
|
isMatch = true; // Fallback for direct match
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMatch) {
|
||||||
|
logger.warn(`[DealerClaimService] Validation failed: Activity ${claimData.activityType} (${creditPostingOn}) vs Dealer ${dealerCode} (${itemGroup})`);
|
||||||
|
throw new Error('incorrect Delercode for selected service group');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[DealerClaimService] Validation successful: Activity ${creditPostingOn} matched Dealer group ${itemGroup}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update claim data with validated dealer user details if not provided
|
// Update claim data with validated dealer user details if not provided
|
||||||
claimData.dealerName = dealerUser.displayName || claimData.dealerName;
|
claimData.dealerName = dealerUser.displayName || claimData.dealerName;
|
||||||
claimData.dealerEmail = dealerUser.email || claimData.dealerEmail;
|
claimData.dealerEmail = dealerUser.email || claimData.dealerEmail;
|
||||||
@ -1148,14 +1180,15 @@ export class DealerClaimService {
|
|||||||
|
|
||||||
// Fallback 2: Handle cases where activity is found but taxationType is missing, or activity not found
|
// Fallback 2: Handle cases where activity is found but taxationType is missing, or activity not found
|
||||||
if (activity && activity.taxationType) {
|
if (activity && activity.taxationType) {
|
||||||
serializedClaimDetails.defaultGstRate = Number(activity.gstRate) || 18;
|
const isNonGstStatus = activity.taxationType.toLowerCase().includes('non');
|
||||||
|
serializedClaimDetails.defaultGstRate = isNonGstStatus ? 0 : 18;
|
||||||
serializedClaimDetails.taxationType = activity.taxationType;
|
serializedClaimDetails.taxationType = activity.taxationType;
|
||||||
logger.info(`[DealerClaimService] Resolved from ActivityType record: ${activity.taxationType}`);
|
logger.info(`[DealerClaimService] Resolved from ActivityType record: ${activity.taxationType}`);
|
||||||
} else {
|
} else {
|
||||||
// Infer from title if record is missing or incomplete
|
// Infer from title if record is missing or incomplete
|
||||||
const isNonGst = activityTypeTitle.toLowerCase().includes('non');
|
const isNonGst = activityTypeTitle.toLowerCase().includes('non');
|
||||||
serializedClaimDetails.taxationType = isNonGst ? 'Non GST' : 'GST';
|
serializedClaimDetails.taxationType = isNonGst ? 'Non GST' : 'GST';
|
||||||
serializedClaimDetails.defaultGstRate = isNonGst ? 0 : (activity ? (Number(activity.gstRate) || 18) : 18);
|
serializedClaimDetails.defaultGstRate = isNonGst ? 0 : 18;
|
||||||
|
|
||||||
logger.info(`[DealerClaimService] Inferred taxationType from title: ${serializedClaimDetails.taxationType} (Activity record ${activity ? 'found but missing taxationType' : 'not found'})`);
|
logger.info(`[DealerClaimService] Inferred taxationType from title: ${serializedClaimDetails.taxationType} (Activity record ${activity ? 'found but missing taxationType' : 'not found'})`);
|
||||||
}
|
}
|
||||||
@ -3638,7 +3671,7 @@ export class DealerClaimService {
|
|||||||
TRNS_UNIQ_NO: item.transactionCode || '',
|
TRNS_UNIQ_NO: item.transactionCode || '',
|
||||||
CLAIM_NUMBER: requestNumber,
|
CLAIM_NUMBER: requestNumber,
|
||||||
INV_NUMBER: invoice.invoiceNumber || '',
|
INV_NUMBER: invoice.invoiceNumber || '',
|
||||||
DEALER_CODE: claimDetails.dealerCode,
|
DEALER_CODE: padDealerCode(claimDetails.dealerCode),
|
||||||
IO_NUMBER: internalOrder?.ioNumber || '',
|
IO_NUMBER: internalOrder?.ioNumber || '',
|
||||||
CLAIM_DOC_TYP: sapRefNo,
|
CLAIM_DOC_TYP: sapRefNo,
|
||||||
CLAIM_TYPE: claimDetails.activityType,
|
CLAIM_TYPE: claimDetails.activityType,
|
||||||
@ -3655,7 +3688,7 @@ export class DealerClaimService {
|
|||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
await wfmFileService.generateIncomingClaimCSV(csvData, `CN_${claimDetails.dealerCode}_${requestNumber}.csv`, isNonGst);
|
await wfmFileService.generateIncomingClaimCSV(csvData, `CN_${padDealerCode(claimDetails.dealerCode)}_${requestNumber}.csv`, isNonGst);
|
||||||
|
|
||||||
await invoice.update({
|
await invoice.update({
|
||||||
wfmPushStatus: 'SUCCESS',
|
wfmPushStatus: 'SUCCESS',
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { Form16NonSubmittedNotification } from '../models/Form16NonSubmittedNoti
|
|||||||
import { Dealer } from '../models/Dealer';
|
import { Dealer } from '../models/Dealer';
|
||||||
import { User } from '../models/User';
|
import { User } from '../models/User';
|
||||||
import { Priority, WorkflowStatus } from '../types/common.types';
|
import { Priority, WorkflowStatus } from '../types/common.types';
|
||||||
import { generateRequestNumber } from '../utils/helpers';
|
import { generateRequestNumber, padDealerCode } from '../utils/helpers';
|
||||||
import { gcsStorageService } from './gcsStorage.service';
|
import { gcsStorageService } from './gcsStorage.service';
|
||||||
import { activityService } from './activity.service';
|
import { activityService } from './activity.service';
|
||||||
import { wfmFileService } from './wfmFile.service';
|
import { wfmFileService } from './wfmFile.service';
|
||||||
@ -415,6 +415,44 @@ function form16FyCompact(financialYear: string): string {
|
|||||||
return fy;
|
return fy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** FY Short for 16-char ID: "2025-26" -> "26" */
|
||||||
|
function form16FyShort(financialYear: string): string {
|
||||||
|
const fy = normalizeFinancialYear(financialYear) || (financialYear || '').trim();
|
||||||
|
const m = fy.match(/-(\d{2})$/);
|
||||||
|
return m ? m[1] : (fy.length >= 2 ? fy.slice(-2) : 'XX');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get next sequence for Form 16 credit note (4 digits) */
|
||||||
|
async function getNextForm16NoteSequence(
|
||||||
|
dealerCode: string,
|
||||||
|
financialYear: string,
|
||||||
|
quarter: string
|
||||||
|
): Promise<string> {
|
||||||
|
const dc = padDealerCode(dealerCode);
|
||||||
|
const fy = form16FyShort(financialYear);
|
||||||
|
const q = normalizeQuarter(quarter);
|
||||||
|
const prefix = `CN${dc}${fy}${q}`;
|
||||||
|
|
||||||
|
const lastNote = (await Form16CreditNote.findOne({
|
||||||
|
where: {
|
||||||
|
creditNoteNumber: { [Op.like]: `${prefix}%` },
|
||||||
|
},
|
||||||
|
order: [['creditNoteNumber', 'DESC']],
|
||||||
|
attributes: ['creditNoteNumber'],
|
||||||
|
})) as any;
|
||||||
|
|
||||||
|
let seq = 1;
|
||||||
|
if (lastNote?.creditNoteNumber) {
|
||||||
|
const lastSeqStr = lastNote.creditNoteNumber.slice(-4);
|
||||||
|
const lastSeq = parseInt(lastSeqStr, 10);
|
||||||
|
if (!isNaN(lastSeq)) {
|
||||||
|
seq = lastSeq + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return seq.toString().padStart(4, '0');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize certificate number for use in note numbers (alphanumeric and single hyphens only).
|
* Sanitize certificate number for use in note numbers (alphanumeric and single hyphens only).
|
||||||
*/
|
*/
|
||||||
@ -424,41 +462,32 @@ function sanitizeCertificateNumber(raw: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form 16 credit note number: CN-F-16-{certificateNumber}-{dealerCode}-{FY}-{quarter}-V{version}
|
* Form 16 credit note number (16 chars): CN{dc}{fy}{q}{seq}
|
||||||
* Supports revised 26AS / Form 16 resubmission versioning.
|
* Example: CN00628226Q20001
|
||||||
*/
|
*/
|
||||||
export function formatForm16CreditNoteNumber(
|
export async function formatForm16CreditNoteNumber(
|
||||||
dealerCode: string,
|
dealerCode: string,
|
||||||
financialYear: string,
|
financialYear: string,
|
||||||
quarter: string,
|
quarter: string
|
||||||
certificateNumber: string,
|
): Promise<string> {
|
||||||
version: number = 1
|
const dc = padDealerCode(dealerCode);
|
||||||
): string {
|
const fy = form16FyShort(financialYear);
|
||||||
const cert = sanitizeCertificateNumber(certificateNumber);
|
const q = normalizeQuarter(quarter);
|
||||||
const dc = (dealerCode || '').trim().replace(/\s+/g, '-') || 'XX';
|
const seq = await getNextForm16NoteSequence(dealerCode, financialYear, quarter);
|
||||||
const fy = form16FyCompact(financialYear) || 'XX';
|
return `CN${dc}${fy}${q}${seq}`;
|
||||||
const q = normalizeQuarter(quarter) || 'X';
|
|
||||||
const v = Math.max(1, Math.floor(version));
|
|
||||||
return `CN-F-16-${cert}-${dc}-${fy}-${q}-V${v}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form 16 debit note number: DN-F-16-{creditNoteCertificateNumber}-{dc}-{fy}-{q}-V{version}
|
* Form 16 debit note number (16 chars): DN{dc}{fy}{q}{seq}
|
||||||
* Uses the certificate number of the credit note being reversed (same Form 16A certificate that led to that credit note).
|
* Usually matches the sequence of the credit note being reversed.
|
||||||
*/
|
*/
|
||||||
export function formatForm16DebitNoteNumber(
|
export function formatForm16DebitNoteNumber(
|
||||||
dealerCode: string,
|
creditNoteNumber: string
|
||||||
financialYear: string,
|
|
||||||
quarter: string,
|
|
||||||
version: number = 1,
|
|
||||||
creditNoteCertificateNumber: string = ''
|
|
||||||
): string {
|
): string {
|
||||||
const cert = sanitizeCertificateNumber(creditNoteCertificateNumber) || 'XX';
|
if (creditNoteNumber.startsWith('CN')) {
|
||||||
const dc = (dealerCode || '').trim().replace(/\s+/g, '-') || 'XX';
|
return creditNoteNumber.replace(/^CN/, 'DN');
|
||||||
const fy = form16FyCompact(financialYear) || 'XX';
|
}
|
||||||
const q = normalizeQuarter(quarter) || 'X';
|
return creditNoteNumber.replace(/^.{2}/, 'DN'); // fallback
|
||||||
const v = Math.max(1, Math.floor(version));
|
|
||||||
return `DN-F-16-${cert}-${dc}-${fy}-${q}-V${v}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -550,7 +579,7 @@ async function run26asMatchAndCreditNote(submission: Form16aSubmission): Promise
|
|||||||
const dealerCode = (sub.dealerCode || '').toString().trim();
|
const dealerCode = (sub.dealerCode || '').toString().trim();
|
||||||
const certificateNumber = (sub.form16aNumber || '').toString().trim();
|
const certificateNumber = (sub.form16aNumber || '').toString().trim();
|
||||||
const version = typeof sub.version === 'number' && sub.version >= 1 ? sub.version : 1;
|
const version = typeof sub.version === 'number' && sub.version >= 1 ? sub.version : 1;
|
||||||
const cnNumber = formatForm16CreditNoteNumber(dealerCode, financialYear, quarter, certificateNumber, version);
|
const cnNumber = await formatForm16CreditNoteNumber(dealerCode, financialYear, quarter);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const creditNote = await Form16CreditNote.create({
|
const creditNote = await Form16CreditNote.create({
|
||||||
submissionId: submission.id,
|
submissionId: submission.id,
|
||||||
@ -588,7 +617,7 @@ async function run26asMatchAndCreditNote(submission: Form16aSubmission): Promise
|
|||||||
const csvRow: Record<string, string | number> = {
|
const csvRow: Record<string, string | number> = {
|
||||||
TRNS_UNIQ_NO: trnsUniqNo,
|
TRNS_UNIQ_NO: trnsUniqNo,
|
||||||
TDS_TRNS_ID: cnNumber,
|
TDS_TRNS_ID: cnNumber,
|
||||||
DEALER_CODE: dealerCode,
|
DEALER_CODE: padDealerCode(dealerCode),
|
||||||
TDS_TRNS_DOC_TYP: 'ZTDS',
|
TDS_TRNS_DOC_TYP: 'ZTDS',
|
||||||
DLR_TAN_NO: tanNumber,
|
DLR_TAN_NO: tanNumber,
|
||||||
'FIN_YEAR & QUARTER': finYearAndQuarter,
|
'FIN_YEAR & QUARTER': finYearAndQuarter,
|
||||||
@ -2005,7 +2034,8 @@ export async function process26asUploadAggregation(uploadLogId: number): Promise
|
|||||||
const creditNoteCertNumber = submission ? ((submission as any).form16aNumber || '').toString().trim() : '';
|
const creditNoteCertNumber = submission ? ((submission as any).form16aNumber || '').toString().trim() : '';
|
||||||
const cnFy = (creditNote as any).financialYear || fy;
|
const cnFy = (creditNote as any).financialYear || fy;
|
||||||
const cnQuarter = (creditNote as any).quarter || q;
|
const cnQuarter = (creditNote as any).quarter || q;
|
||||||
const debitNum = formatForm16DebitNoteNumber(dealerCode || 'XX', cnFy, cnQuarter, version, creditNoteCertNumber);
|
const creditNoteNumber = (creditNote as any).creditNoteNumber || (creditNote as any).credit_note_number || '';
|
||||||
|
const debitNum = formatForm16DebitNoteNumber(creditNoteNumber);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const debit = await Form16DebitNote.create({
|
const debit = await Form16DebitNote.create({
|
||||||
creditNoteId: creditNote.id,
|
creditNoteId: creditNote.id,
|
||||||
@ -2036,8 +2066,8 @@ export async function process26asUploadAggregation(uploadLogId: number): Promise
|
|||||||
const finYearAndQuarter = fyCompact && cnQuarter ? `FY ${fyCompact}_${cnQuarter}` : '';
|
const finYearAndQuarter = fyCompact && cnQuarter ? `FY ${fyCompact}_${cnQuarter}` : '';
|
||||||
const csvRow: Record<string, string | number> = {
|
const csvRow: Record<string, string | number> = {
|
||||||
TRNS_UNIQ_NO: trnsUniqNo,
|
TRNS_UNIQ_NO: trnsUniqNo,
|
||||||
TDS_TRNS_ID: debitNum,
|
TDS_TRNS_ID: creditNoteNumber,
|
||||||
DEALER_CODE: dealerCode || 'XX',
|
DEALER_CODE: padDealerCode(dealerCode),
|
||||||
TDS_TRNS_DOC_TYP: 'ZTDS',
|
TDS_TRNS_DOC_TYP: 'ZTDS',
|
||||||
'Org.Document Number': debit.id,
|
'Org.Document Number': debit.id,
|
||||||
DLR_TAN_NO: tanNumber,
|
DLR_TAN_NO: tanNumber,
|
||||||
|
|||||||
@ -33,11 +33,6 @@ export class PWCIntegrationService {
|
|||||||
* Resolve GL Code based on Activity Type and Internal Order
|
* Resolve GL Code based on Activity Type and Internal Order
|
||||||
*/
|
*/
|
||||||
private async resolveGLCode(activityTypeId: string, ioNumber?: string): Promise<string> {
|
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
|
// Default Fallback or IO based logic if required
|
||||||
// Based on "IO GL will be changed" comment in user screenshot
|
// Based on "IO GL will be changed" comment in user screenshot
|
||||||
if (ioNumber) {
|
if (ioNumber) {
|
||||||
@ -148,9 +143,9 @@ export class PWCIntegrationService {
|
|||||||
const groupedExpenses: Record<string, any> = {};
|
const groupedExpenses: Record<string, any> = {};
|
||||||
|
|
||||||
expenses.forEach((expense: any) => {
|
expenses.forEach((expense: any) => {
|
||||||
const hsnCd = expense.hsnCode || activity.hsnCode || activity.sacCode || "998311";
|
const hsnCd = expense.hsnCode || "998311";
|
||||||
const gstRate = (expense.gstRate === undefined || expense.gstRate === null || Number(expense.gstRate) === 0)
|
const gstRate = (expense.gstRate === undefined || expense.gstRate === null || Number(expense.gstRate) === 0)
|
||||||
? Number(activity.gstRate || 18)
|
? 18
|
||||||
: Number(expense.gstRate);
|
: Number(expense.gstRate);
|
||||||
|
|
||||||
const groupKey = `${hsnCd}_${gstRate}`;
|
const groupKey = `${hsnCd}_${gstRate}`;
|
||||||
@ -288,7 +283,7 @@ export class PWCIntegrationService {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Fallback to single line item if no expenses found
|
// Fallback to single line item if no expenses found
|
||||||
const gstRate = isNonGSTActivity ? 0 : Number(activity.gstRate || 18);
|
const gstRate = isNonGSTActivity ? 0 : 18;
|
||||||
const assAmt = finalAmount;
|
const assAmt = finalAmount;
|
||||||
let igstAmt = 0, cgstAmt = 0, sgstAmt = 0;
|
let igstAmt = 0, cgstAmt = 0, sgstAmt = 0;
|
||||||
|
|
||||||
@ -309,7 +304,7 @@ export class PWCIntegrationService {
|
|||||||
|
|
||||||
const slNo = 1;
|
const slNo = 1;
|
||||||
|
|
||||||
const fallbackHsn = activity.hsnCode || activity.sacCode || "998311";
|
const fallbackHsn = "998311";
|
||||||
const fallbackIsService = this.isServiceHSN(fallbackHsn) === "Y";
|
const fallbackIsService = this.isServiceHSN(fallbackHsn) === "Y";
|
||||||
const transactionCode = `${customInvoiceNumber}-${String(slNo).padStart(2, '0')}`;
|
const transactionCode = `${customInvoiceNumber}-${String(slNo).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
|||||||
@ -158,7 +158,7 @@ export class WFMFileService {
|
|||||||
* Get credit note details from outgoing CSV
|
* Get credit note details from outgoing CSV
|
||||||
*/
|
*/
|
||||||
async getCreditNoteDetails(dealerCode: string, requestNumber: string, isNonGst: boolean = false): Promise<any[]> {
|
async getCreditNoteDetails(dealerCode: string, requestNumber: string, isNonGst: boolean = false): Promise<any[]> {
|
||||||
const fileName = `CN_${dealerCode}_${requestNumber}.csv`;
|
const fileName = `CN_${String(dealerCode).padStart(6, '0')}_${requestNumber}.csv`;
|
||||||
const filePath = this.getOutgoingPath(fileName, isNonGst);
|
const filePath = this.getOutgoingPath(fileName, isNonGst);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -106,3 +106,12 @@ export const generateChecksum = (data: string): string => {
|
|||||||
export const sleep = (ms: number): Promise<void> => {
|
export const sleep = (ms: number): Promise<void> => {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pad dealer code to 6 digits with leading zeros
|
||||||
|
* Example: '73' -> '000073'
|
||||||
|
*/
|
||||||
|
export const padDealerCode = (dealerCode: string | number): string => {
|
||||||
|
if (dealerCode === null || dealerCode === undefined) return '';
|
||||||
|
return String(dealerCode).padStart(6, '0');
|
||||||
|
};
|
||||||
|
|||||||
@ -58,6 +58,9 @@ export const createActivityTypeSchema = z.object({
|
|||||||
errorMap: () => ({ message: 'Taxation type must be GST or Non GST' }),
|
errorMap: () => ({ message: 'Taxation type must be GST or Non GST' }),
|
||||||
}),
|
}),
|
||||||
sapRefNo: z.string().min(1, 'SAP ref number (Claim Document Type) is required').max(50, 'SAP ref number too long'),
|
sapRefNo: z.string().min(1, 'SAP ref number (Claim Document Type) is required').max(50, 'SAP ref number too long'),
|
||||||
|
creditPostingOn: z.enum(['Spares', 'Vehicle', 'GMA', 'Apparel'], {
|
||||||
|
errorMap: () => ({ message: 'Credit posting on must be Spares, Vehicle, GMA or Apparel' }),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateActivityTypeSchema = createActivityTypeSchema.partial();
|
export const updateActivityTypeSchema = createActivityTypeSchema.partial();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user