diff --git a/scripts/seed-approval-policies.ts b/scripts/seed-approval-policies.ts index 12c13eb..965c326 100644 --- a/scripts/seed-approval-policies.ts +++ b/scripts/seed-approval-policies.ts @@ -38,6 +38,13 @@ const policies = [ approvalMode: 'ROLE_MANDATORY', requiredRoles: ['DD Head', 'NBH'], isActive: true + }, + { + stageCode: 'FDD_VERIFICATION', + minApprovals: 1, + approvalMode: 'ROLE_MANDATORY', + requiredRoles: ['FDD'], + isActive: true } ]; diff --git a/scripts/seed-roles.ts b/scripts/seed-roles.ts index 79e5923..cc98707 100644 --- a/scripts/seed-roles.ts +++ b/scripts/seed-roles.ts @@ -16,7 +16,9 @@ const rolesToSeed = [ { roleCode: ROLES.LEGAL_ADMIN, roleName: 'Legal Admin', category: 'DEPARTMENT', description: 'Legal Department' }, { roleCode: ROLES.NBH, roleName: 'NBH', category: 'NATIONAL', description: 'National Business Head' }, { roleCode: ROLES.ASM, roleName: 'ASM', category: 'SALES', description: 'Area Sales Manager' }, - { roleCode: ROLES.DEALER, roleName: 'Dealer', category: 'EXTERNAL', description: 'Dealer Principal' } + { roleCode: ROLES.DEALER, roleName: 'Dealer', category: 'EXTERNAL', description: 'Dealer Principal' }, + { roleCode: ROLES.FDD, roleName: 'FDD Team', category: 'EXTERNAL', description: 'Financial Due Diligence Team' }, + { roleCode: ROLES.ARCHITECTURE, roleName: 'Architecture Team', category: 'DEPARTMENT', description: 'Architecture & Design Team' } ]; async function seedRoles() { diff --git a/scripts/seed-users.ts b/scripts/seed-users.ts index 310ca76..37d4b5c 100644 --- a/scripts/seed-users.ts +++ b/scripts/seed-users.ts @@ -19,7 +19,9 @@ async function seedUsers() { { email: 'admin@royalenfield.com', fullName: 'Laxman H', password: hashedPassword, roleCode: ROLES.DD_LEAD, status: 'active' }, { email: 'yashwin@gmail.com', fullName: 'Yashwin', password: hashedPassword, roleCode: ROLES.ZBH, status: 'active' }, { email: 'kenil@gmail.com', fullName: 'Kenil', password: hashedPassword, roleCode: ROLES.DD_LEAD, status: 'active' }, - { email: 'lince@gmail.com', fullName: 'Lince', password: hashedPassword, roleCode: ROLES.DD_ADMIN, status: 'active' } + { email: 'lince@gmail.com', fullName: 'Lince', password: hashedPassword, roleCode: ROLES.DD_ADMIN, status: 'active' }, + { email: 'fdd@royalenfield.com', fullName: 'FDD Partner', password: hashedPassword, roleCode: ROLES.FDD, status: 'active' }, + { email: 'architecture@royalenfield.com', fullName: 'RE Architect', password: hashedPassword, roleCode: ROLES.ARCHITECTURE, status: 'active' } ]; for (const u of usersToSeed) { diff --git a/scripts/seed_normalized_data.ts b/scripts/seed_normalized_data.ts index 5cbebec..c21ee7e 100644 --- a/scripts/seed_normalized_data.ts +++ b/scripts/seed_normalized_data.ts @@ -26,7 +26,8 @@ async function seed() { { roleCode: 'Finance', roleName: 'Finance', category: 'DEPARTMENT' }, { roleCode: 'Dealer', roleName: 'Dealer', category: 'EXTERNAL' }, { roleCode: 'DD Admin', roleName: 'DD Admin', category: 'ADMIN' }, - { roleCode: 'ARCHITECTURE', roleName: 'Architecture Team', category: 'DEPARTMENT' } + { roleCode: 'ARCHITECTURE', roleName: 'Architecture Team', category: 'DEPARTMENT' }, + { roleCode: 'FDD', roleName: 'FDD Team', category: 'EXTERNAL' } ]; for (const r of roles) { @@ -98,7 +99,9 @@ async function seed() { { email: 'ddhead@royalenfield.com', name: 'Vikram Singh', role: 'DD Head' }, { email: 'finance@royalenfield.com', name: 'Rahul Verma', role: 'Finance' }, { email: 'admin@royalenfield.com', name: 'Laxman H', role: 'Super Admin' }, - { email: 'lince@gmail.com', name: 'Lince', role: 'DD Admin' } + { email: 'lince@gmail.com', name: 'Lince', role: 'DD Admin' }, + { email: 'fdd@royalenfield.com', name: 'FDD Partner', role: 'FDD' }, + { email: 'architecture@royalenfield.com', name: 'RE Architect', role: 'ARCHITECTURE' } ]; for (const u of nationalUsers) { const [user] = await User.findOrCreate({ diff --git a/src/common/config/constants.ts b/src/common/config/constants.ts index 37a552d..2392b97 100644 --- a/src/common/config/constants.ts +++ b/src/common/config/constants.ts @@ -14,7 +14,8 @@ export const ROLES = { ASM: 'ASM', FINANCE: 'Finance', DEALER: 'Dealer', - ARCHITECTURE: 'ARCHITECTURE' + ARCHITECTURE: 'ARCHITECTURE', + FDD: 'FDD' } as const; // Regions diff --git a/src/database/models/Application.ts b/src/database/models/Application.ts index 26e9d48..cd29615 100644 --- a/src/database/models/Application.ts +++ b/src/database/models/Application.ts @@ -245,11 +245,9 @@ export default (sequelize: Sequelize) => { Application.hasMany(models.ApplicationStatusHistory, { foreignKey: 'applicationId', as: 'statusHistory' }); Application.hasMany(models.ApplicationProgress, { foreignKey: 'applicationId', as: 'progressTracking' }); - Application.hasMany(models.Document, { - foreignKey: 'requestId', - as: 'uploadedDocuments', - scope: { requestType: 'application' }, - constraints: false + Application.hasMany(models.OnboardingDocument, { + foreignKey: 'applicationId', + as: 'uploadedDocuments' }); Application.hasMany(models.QuestionnaireResponse, { foreignKey: 'applicationId', as: 'questionnaireResponses' }); @@ -268,6 +266,7 @@ export default (sequelize: Sequelize) => { Application.hasMany(models.StageApprovalAction, { foreignKey: 'applicationId', as: 'stageApprovals' }); Application.hasOne(models.Dealer, { foreignKey: 'applicationId', as: 'dealer' }); Application.hasMany(models.SecurityDeposit, { foreignKey: 'applicationId', as: 'securityDeposits' }); + Application.hasOne(models.EorChecklist, { foreignKey: 'applicationId', as: 'eorChecklist' }); }; return Application; diff --git a/src/database/models/ConstitutionalChange.ts b/src/database/models/ConstitutionalChange.ts index bf0afe4..aaea538 100644 --- a/src/database/models/ConstitutionalChange.ts +++ b/src/database/models/ConstitutionalChange.ts @@ -93,6 +93,10 @@ export default (sequelize: Sequelize) => { foreignKey: 'dealerId', as: 'dealer' }); + ConstitutionalChange.hasMany(models.ConstitutionalDocument, { + foreignKey: 'constitutionalChangeId', + as: 'uploadedDocuments' + }); ConstitutionalChange.hasMany(models.Worknote, { foreignKey: 'requestId', as: 'worknotes', diff --git a/src/database/models/ConstitutionalDocument.ts b/src/database/models/ConstitutionalDocument.ts new file mode 100644 index 0000000..5f6c3dc --- /dev/null +++ b/src/database/models/ConstitutionalDocument.ts @@ -0,0 +1,93 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ConstitutionalDocumentAttributes { + id: string; + constitutionalChangeId: string; + documentType: string; + fileName: string; + filePath: string; + fileSize: number | null; + mimeType: string | null; + stage: string | null; + status: string; + uploadedBy: string | null; +} + +export interface ConstitutionalDocumentInstance extends Model, ConstitutionalDocumentAttributes { } + +export default (sequelize: Sequelize) => { + const ConstitutionalDocument = sequelize.define('ConstitutionalDocument', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + constitutionalChangeId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'constitutional_changes', + key: 'id' + } + }, + documentType: { + type: DataTypes.STRING, + allowNull: false + }, + fileName: { + type: DataTypes.STRING, + allowNull: false + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + fileSize: { + type: DataTypes.INTEGER, + allowNull: true + }, + mimeType: { + type: DataTypes.STRING, + allowNull: true + }, + stage: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + uploadedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'constitutional_documents', + timestamps: true, + indexes: [ + { fields: ['constitutionalChangeId'] }, + { fields: ['status'] } + ] + }); + + (ConstitutionalDocument as any).associate = (models: any) => { + ConstitutionalDocument.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + ConstitutionalDocument.belongsTo(models.ConstitutionalChange, { foreignKey: 'constitutionalChangeId', as: 'constitutionalChange' }); + + ConstitutionalDocument.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions', constraints: false }); + ConstitutionalDocument.belongsToMany(models.Worknote, { + through: models.WorkNoteAttachment, + foreignKey: 'documentId', + otherKey: 'noteId', + as: 'worknotes', + constraints: false + }); + }; + + return ConstitutionalDocument; +}; diff --git a/src/database/models/Dealer.ts b/src/database/models/Dealer.ts index 40992fc..d11d0dd 100644 --- a/src/database/models/Dealer.ts +++ b/src/database/models/Dealer.ts @@ -77,13 +77,26 @@ export default (sequelize: Sequelize) => { }); (Dealer as any).associate = (models: any) => { - Dealer.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); - Dealer.belongsTo(models.DealerCode, { foreignKey: 'dealerCodeId', as: 'dealerCode' }); + const { Application, DealerCode, OnboardingDocument, Resignation, TerminationRequest, User } = models; + + Dealer.belongsTo(Application, { foreignKey: 'applicationId', as: 'application' }); + Dealer.belongsTo(DealerCode, { foreignKey: 'dealerCodeId', as: 'dealerCode' }); - Dealer.hasMany(models.Document, { foreignKey: 'dealerId', as: 'documents' }); - Dealer.hasMany(models.Resignation, { foreignKey: 'dealerId', as: 'resignations' }); - Dealer.hasMany(models.TerminationRequest, { foreignKey: 'dealerId', as: 'terminationRequests' }); - Dealer.hasOne(models.User, { foreignKey: 'dealerId', as: 'user' }); + if (OnboardingDocument) { + Dealer.hasMany(OnboardingDocument, { foreignKey: 'dealerId', as: 'onboardingDocuments' }); + } + + if (Resignation) { + Dealer.hasMany(Resignation, { foreignKey: 'dealerId', as: 'resignations' }); + } + + if (TerminationRequest) { + Dealer.hasMany(TerminationRequest, { foreignKey: 'dealerId', as: 'terminationRequests' }); + } + + if (User) { + Dealer.hasOne(User, { foreignKey: 'dealerId', as: 'user' }); + } }; return Dealer; diff --git a/src/database/models/DocumentVersion.ts b/src/database/models/DocumentVersion.ts index a580d63..dd72e99 100644 --- a/src/database/models/DocumentVersion.ts +++ b/src/database/models/DocumentVersion.ts @@ -3,6 +3,7 @@ import { Model, DataTypes, Sequelize } from 'sequelize'; export interface DocumentVersionAttributes { id: string; documentId: string; + documentType: string; versionNumber: number; filePath: string; uploadedBy: string | null; @@ -19,11 +20,11 @@ export default (sequelize: Sequelize) => { }, documentId: { type: DataTypes.UUID, - allowNull: false, - references: { - model: 'documents', - key: 'id' - } + allowNull: false + }, + documentType: { + type: DataTypes.STRING, + allowNull: false }, versionNumber: { type: DataTypes.INTEGER, @@ -48,7 +49,6 @@ export default (sequelize: Sequelize) => { }); (DocumentVersion as any).associate = (models: any) => { - DocumentVersion.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); DocumentVersion.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); }; diff --git a/src/database/models/EorChecklistItem.ts b/src/database/models/EorChecklistItem.ts index 66c6b88..1c7bcd0 100644 --- a/src/database/models/EorChecklistItem.ts +++ b/src/database/models/EorChecklistItem.ts @@ -45,11 +45,7 @@ export default (sequelize: Sequelize) => { }, proofDocumentId: { type: DataTypes.UUID, - allowNull: true, - references: { - model: 'documents', - key: 'id' - } + allowNull: true } }, { tableName: 'eor_checklist_items', @@ -58,7 +54,6 @@ export default (sequelize: Sequelize) => { (EorChecklistItem as any).associate = (models: any) => { EorChecklistItem.belongsTo(models.EorChecklist, { foreignKey: 'checklistId', as: 'checklist' }); - EorChecklistItem.belongsTo(models.Document, { foreignKey: 'proofDocumentId', as: 'proofDocument' }); }; return EorChecklistItem; diff --git a/src/database/models/FddReport.ts b/src/database/models/FddReport.ts index 5658726..6f161a6 100644 --- a/src/database/models/FddReport.ts +++ b/src/database/models/FddReport.ts @@ -31,7 +31,7 @@ export default (sequelize: Sequelize) => { type: DataTypes.UUID, allowNull: true, references: { - model: 'documents', + model: 'onboarding_documents', key: 'id' } }, @@ -62,7 +62,7 @@ export default (sequelize: Sequelize) => { (FddReport as any).associate = (models: any) => { FddReport.belongsTo(models.FddAssignment, { foreignKey: 'assignmentId', as: 'assignment' }); - FddReport.belongsTo(models.Document, { foreignKey: 'reportDocumentId', as: 'reportDocument' }); + FddReport.belongsTo(models.OnboardingDocument, { foreignKey: 'reportDocumentId', as: 'reportDocument' }); FddReport.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' }); }; diff --git a/src/database/models/LoaDocumentGenerated.ts b/src/database/models/LoaDocumentGenerated.ts index 67b3f8f..4abc4fa 100644 --- a/src/database/models/LoaDocumentGenerated.ts +++ b/src/database/models/LoaDocumentGenerated.ts @@ -29,7 +29,7 @@ export default (sequelize: Sequelize) => { type: DataTypes.UUID, allowNull: false, references: { - model: 'documents', + model: 'onboarding_documents', key: 'id' } }, @@ -49,7 +49,7 @@ export default (sequelize: Sequelize) => { (LoaDocumentGenerated as any).associate = (models: any) => { LoaDocumentGenerated.belongsTo(models.LoaRequest, { foreignKey: 'requestId', as: 'request' }); - LoaDocumentGenerated.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); + LoaDocumentGenerated.belongsTo(models.OnboardingDocument, { foreignKey: 'documentId', as: 'document' }); LoaDocumentGenerated.hasMany(models.LoaAcknowledgement, { foreignKey: 'loaDocId', as: 'acknowledgements' }); }; diff --git a/src/database/models/LoiDocumentGenerated.ts b/src/database/models/LoiDocumentGenerated.ts index 11ec767..4b37a24 100644 --- a/src/database/models/LoiDocumentGenerated.ts +++ b/src/database/models/LoiDocumentGenerated.ts @@ -29,7 +29,7 @@ export default (sequelize: Sequelize) => { type: DataTypes.UUID, allowNull: false, references: { - model: 'documents', + model: 'onboarding_documents', key: 'id' } }, @@ -49,7 +49,7 @@ export default (sequelize: Sequelize) => { (LoiDocumentGenerated as any).associate = (models: any) => { LoiDocumentGenerated.belongsTo(models.LoiRequest, { foreignKey: 'requestId', as: 'request' }); - LoiDocumentGenerated.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); + LoiDocumentGenerated.belongsTo(models.OnboardingDocument, { foreignKey: 'documentId', as: 'document' }); LoiDocumentGenerated.hasMany(models.LoiAcknowledgement, { foreignKey: 'loiDocId', as: 'acknowledgements' }); }; diff --git a/src/database/models/Document.ts b/src/database/models/OnboardingDocument.ts similarity index 74% rename from src/database/models/Document.ts rename to src/database/models/OnboardingDocument.ts index d68c669..cf34ecc 100644 --- a/src/database/models/Document.ts +++ b/src/database/models/OnboardingDocument.ts @@ -17,10 +17,10 @@ export interface DocumentAttributes { uploadedBy: string | null; } -export interface DocumentInstance extends Model, DocumentAttributes { } +export interface OnboardingDocumentInstance extends Model, DocumentAttributes { } export default (sequelize: Sequelize) => { - const Document = sequelize.define('Document', { + const OnboardingDocument = sequelize.define('OnboardingDocument', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, @@ -87,7 +87,7 @@ export default (sequelize: Sequelize) => { } } }, { - tableName: 'documents', + tableName: 'onboarding_documents', timestamps: true, indexes: [ { fields: ['applicationId'] }, @@ -96,19 +96,20 @@ export default (sequelize: Sequelize) => { ] }); - (Document as any).associate = (models: any) => { - Document.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); - Document.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); - Document.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' }); + (OnboardingDocument as any).associate = (models: any) => { + OnboardingDocument.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + OnboardingDocument.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); + OnboardingDocument.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' }); - Document.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions' }); - Document.belongsToMany(models.Worknote, { + OnboardingDocument.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions', constraints: false }); + OnboardingDocument.belongsToMany(models.Worknote, { through: models.WorkNoteAttachment, foreignKey: 'documentId', otherKey: 'noteId', - as: 'workNotes' + as: 'workNotes', + constraints: false }); }; - return Document; + return OnboardingDocument; }; diff --git a/src/database/models/RelocationDocument.ts b/src/database/models/RelocationDocument.ts new file mode 100644 index 0000000..61b89a5 --- /dev/null +++ b/src/database/models/RelocationDocument.ts @@ -0,0 +1,93 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface RelocationDocumentAttributes { + id: string; + relocationId: string; + documentType: string; + fileName: string; + filePath: string; + fileSize: number | null; + mimeType: string | null; + stage: string | null; + status: string; + uploadedBy: string | null; +} + +export interface RelocationDocumentInstance extends Model, RelocationDocumentAttributes { } + +export default (sequelize: Sequelize) => { + const RelocationDocument = sequelize.define('RelocationDocument', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + relocationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'relocation_requests', + key: 'id' + } + }, + documentType: { + type: DataTypes.STRING, + allowNull: false + }, + fileName: { + type: DataTypes.STRING, + allowNull: false + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + fileSize: { + type: DataTypes.INTEGER, + allowNull: true + }, + mimeType: { + type: DataTypes.STRING, + allowNull: true + }, + stage: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + uploadedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'relocation_documents', + timestamps: true, + indexes: [ + { fields: ['relocationId'] }, + { fields: ['status'] } + ] + }); + + (RelocationDocument as any).associate = (models: any) => { + RelocationDocument.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + RelocationDocument.belongsTo(models.RelocationRequest, { foreignKey: 'relocationId', as: 'request' }); + + RelocationDocument.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions', constraints: false }); + RelocationDocument.belongsToMany(models.Worknote, { + through: models.WorkNoteAttachment, + foreignKey: 'documentId', + otherKey: 'noteId', + as: 'workNotes', + constraints: false + }); + }; + + return RelocationDocument; +}; diff --git a/src/database/models/RelocationRequest.ts b/src/database/models/RelocationRequest.ts index 9bf0b08..365f770 100644 --- a/src/database/models/RelocationRequest.ts +++ b/src/database/models/RelocationRequest.ts @@ -140,11 +140,9 @@ export default (sequelize: Sequelize) => { scope: { requestType: 'relocation' }, constraints: false }); - RelocationRequest.hasMany(models.Document, { - foreignKey: 'requestId', - as: 'uploadedDocuments', - scope: { requestType: 'relocation' }, - constraints: false + RelocationRequest.hasMany(models.RelocationDocument, { + foreignKey: 'relocationId', + as: 'uploadedDocuments' }); RelocationRequest.hasOne(models.EorChecklist, { foreignKey: 'relocationId', diff --git a/src/database/models/Resignation.ts b/src/database/models/Resignation.ts index da2037c..9abb7a3 100644 --- a/src/database/models/Resignation.ts +++ b/src/database/models/Resignation.ts @@ -134,6 +134,10 @@ export default (sequelize: Sequelize) => { foreignKey: 'dealerId', as: 'dealer' }); + Resignation.hasMany(models.ResignationDocument, { + foreignKey: 'resignationId', + as: 'uploadedDocuments' + }); Resignation.hasMany(models.Worknote, { foreignKey: 'requestId', as: 'worknotes', diff --git a/src/database/models/ResignationDocument.ts b/src/database/models/ResignationDocument.ts new file mode 100644 index 0000000..db3431c --- /dev/null +++ b/src/database/models/ResignationDocument.ts @@ -0,0 +1,93 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface ResignationDocumentAttributes { + id: string; + resignationId: string; + documentType: string; + fileName: string; + filePath: string; + fileSize: number | null; + mimeType: string | null; + stage: string | null; + status: string; + uploadedBy: string | null; +} + +export interface ResignationDocumentInstance extends Model, ResignationDocumentAttributes { } + +export default (sequelize: Sequelize) => { + const ResignationDocument = sequelize.define('ResignationDocument', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + resignationId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'resignations', + key: 'id' + } + }, + documentType: { + type: DataTypes.STRING, + allowNull: false + }, + fileName: { + type: DataTypes.STRING, + allowNull: false + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + fileSize: { + type: DataTypes.INTEGER, + allowNull: true + }, + mimeType: { + type: DataTypes.STRING, + allowNull: true + }, + stage: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + uploadedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'resignation_documents', + timestamps: true, + indexes: [ + { fields: ['resignationId'] }, + { fields: ['status'] } + ] + }); + + (ResignationDocument as any).associate = (models: any) => { + ResignationDocument.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + ResignationDocument.belongsTo(models.Resignation, { foreignKey: 'resignationId', as: 'resignation' }); + + ResignationDocument.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions', constraints: false }); + ResignationDocument.belongsToMany(models.Worknote, { + through: models.WorkNoteAttachment, + foreignKey: 'documentId', + otherKey: 'noteId', + as: 'worknotes', + constraints: false + }); + }; + + return ResignationDocument; +}; diff --git a/src/database/models/SecurityDeposit.ts b/src/database/models/SecurityDeposit.ts index eed2c28..7d8f428 100644 --- a/src/database/models/SecurityDeposit.ts +++ b/src/database/models/SecurityDeposit.ts @@ -41,7 +41,7 @@ export default (sequelize: Sequelize) => { type: DataTypes.UUID, allowNull: true, references: { - model: 'documents', + model: 'onboarding_documents', key: 'id' } }, @@ -73,7 +73,7 @@ export default (sequelize: Sequelize) => { (SecurityDeposit as any).associate = (models: any) => { SecurityDeposit.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); - SecurityDeposit.belongsTo(models.Document, { foreignKey: 'proofDocumentId', as: 'proofDocument' }); + SecurityDeposit.belongsTo(models.OnboardingDocument, { foreignKey: 'proofDocumentId', as: 'proofDocument' }); SecurityDeposit.belongsTo(models.User, { foreignKey: 'verifiedBy', as: 'verifier' }); }; diff --git a/src/database/models/TerminationDocument.ts b/src/database/models/TerminationDocument.ts new file mode 100644 index 0000000..4e23bff --- /dev/null +++ b/src/database/models/TerminationDocument.ts @@ -0,0 +1,93 @@ +import { Model, DataTypes, Sequelize } from 'sequelize'; + +export interface TerminationDocumentAttributes { + id: string; + terminationRequestId: string; + documentType: string; + fileName: string; + filePath: string; + fileSize: number | null; + mimeType: string | null; + stage: string | null; + status: string; + uploadedBy: string | null; +} + +export interface TerminationDocumentInstance extends Model, TerminationDocumentAttributes { } + +export default (sequelize: Sequelize) => { + const TerminationDocument = sequelize.define('TerminationDocument', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + terminationRequestId: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'termination_requests', + key: 'id' + } + }, + documentType: { + type: DataTypes.STRING, + allowNull: false + }, + fileName: { + type: DataTypes.STRING, + allowNull: false + }, + filePath: { + type: DataTypes.STRING, + allowNull: false + }, + fileSize: { + type: DataTypes.INTEGER, + allowNull: true + }, + mimeType: { + type: DataTypes.STRING, + allowNull: true + }, + stage: { + type: DataTypes.STRING, + allowNull: true + }, + status: { + type: DataTypes.STRING, + defaultValue: 'active' + }, + uploadedBy: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'users', + key: 'id' + } + } + }, { + tableName: 'termination_documents', + timestamps: true, + indexes: [ + { fields: ['terminationRequestId'] }, + { fields: ['status'] } + ] + }); + + (TerminationDocument as any).associate = (models: any) => { + TerminationDocument.belongsTo(models.User, { foreignKey: 'uploadedBy', as: 'uploader' }); + TerminationDocument.belongsTo(models.TerminationRequest, { foreignKey: 'terminationRequestId', as: 'terminationRequest' }); + + TerminationDocument.hasMany(models.DocumentVersion, { foreignKey: 'documentId', as: 'versions', constraints: false }); + TerminationDocument.belongsToMany(models.Worknote, { + through: models.WorkNoteAttachment, + foreignKey: 'documentId', + otherKey: 'noteId', + as: 'worknotes', + constraints: false + }); + }; + + return TerminationDocument; +}; diff --git a/src/database/models/TerminationRequest.ts b/src/database/models/TerminationRequest.ts index 4c33f15..18e1aaf 100644 --- a/src/database/models/TerminationRequest.ts +++ b/src/database/models/TerminationRequest.ts @@ -80,6 +80,16 @@ export default (sequelize: Sequelize) => { TerminationRequest.belongsTo(models.Dealer, { foreignKey: 'dealerId', as: 'dealer' }); TerminationRequest.belongsTo(models.User, { foreignKey: 'initiatedBy', as: 'initiator' }); TerminationRequest.hasOne(models.FnF, { foreignKey: 'terminationRequestId', as: 'fnfSettlement' }); + TerminationRequest.hasMany(models.TerminationDocument, { + foreignKey: 'terminationRequestId', + as: 'uploadedDocuments' + }); + TerminationRequest.hasMany(models.Worknote, { + foreignKey: 'requestId', + as: 'worknotes', + scope: { requestType: 'termination' }, + constraints: false + }); }; return TerminationRequest; diff --git a/src/database/models/WorkNoteAttachment.ts b/src/database/models/WorkNoteAttachment.ts index f0f0013..0dcba69 100644 --- a/src/database/models/WorkNoteAttachment.ts +++ b/src/database/models/WorkNoteAttachment.ts @@ -4,6 +4,7 @@ export interface WorkNoteAttachmentAttributes { id: string; noteId: string; documentId: string; + documentType: string; } export interface WorkNoteAttachmentInstance extends Model, WorkNoteAttachmentAttributes { } @@ -25,11 +26,11 @@ export default (sequelize: Sequelize) => { }, documentId: { type: DataTypes.UUID, - allowNull: false, - references: { - model: 'documents', - key: 'id' - } + allowNull: false + }, + documentType: { + type: DataTypes.STRING, + allowNull: false } }, { tableName: 'work_note_attachments', @@ -39,7 +40,6 @@ export default (sequelize: Sequelize) => { (WorkNoteAttachment as any).associate = (models: any) => { WorkNoteAttachment.belongsTo(models.Worknote, { foreignKey: 'noteId', as: 'workNote' }); - WorkNoteAttachment.belongsTo(models.Document, { foreignKey: 'documentId', as: 'document' }); }; return WorkNoteAttachment; diff --git a/src/database/models/Worknote.ts b/src/database/models/Worknote.ts index 70f054c..71b6a48 100644 --- a/src/database/models/Worknote.ts +++ b/src/database/models/Worknote.ts @@ -71,12 +71,7 @@ export default (sequelize: Sequelize) => { Worknote.belongsTo(models.Application, { foreignKey: 'applicationId', as: 'application' }); Worknote.hasMany(models.WorkNoteTag, { foreignKey: 'noteId', as: 'tags' }); - Worknote.belongsToMany(models.Document, { - through: models.WorkNoteAttachment, - foreignKey: 'noteId', - otherKey: 'documentId', - as: 'attachments' - }); + Worknote.hasMany(models.WorkNoteAttachment, { foreignKey: 'noteId', as: 'attachments' }); }; return Worknote; diff --git a/src/database/models/index.ts b/src/database/models/index.ts index 1aae716..d3c5218 100644 --- a/src/database/models/index.ts +++ b/src/database/models/index.ts @@ -9,9 +9,13 @@ import createConstitutionalChange from './ConstitutionalChange.js'; import createRelocationRequest from './RelocationRequest.js'; import createOutlet from './Outlet.js'; import createWorknote from './Worknote.js'; -import createDocument from './Document.js'; +import createOnboardingDocument from './OnboardingDocument.js'; import createAuditLog from './AuditLog.js'; import createFinancePayment from './FinancePayment.js'; +import createRelocationDocument from './RelocationDocument.js'; +import createResignationDocument from './ResignationDocument.js'; +import createConstitutionalDocument from './ConstitutionalDocument.js'; +import createTerminationDocument from './TerminationDocument.js'; import createFnF from './FnF.js'; import createFnFLineItem from './FnFLineItem.js'; import createSLAConfiguration from './SLAConfiguration.js'; @@ -115,9 +119,13 @@ db.ConstitutionalChange = createConstitutionalChange(sequelize); db.RelocationRequest = createRelocationRequest(sequelize); db.Outlet = createOutlet(sequelize); db.Worknote = createWorknote(sequelize); -db.Document = createDocument(sequelize); +db.OnboardingDocument = createOnboardingDocument(sequelize); db.AuditLog = createAuditLog(sequelize); db.FinancePayment = createFinancePayment(sequelize); +db.RelocationDocument = createRelocationDocument(sequelize); +db.ResignationDocument = createResignationDocument(sequelize); +db.ConstitutionalDocument = createConstitutionalDocument(sequelize); +db.TerminationDocument = createTerminationDocument(sequelize); db.FnF = createFnF(sequelize); db.FnFLineItem = createFnFLineItem(sequelize); db.SLAConfiguration = createSLAConfiguration(sequelize); diff --git a/src/modules/collaboration/collaboration.controller.ts b/src/modules/collaboration/collaboration.controller.ts index 231335d..d2125ba 100644 --- a/src/modules/collaboration/collaboration.controller.ts +++ b/src/modules/collaboration/collaboration.controller.ts @@ -1,6 +1,9 @@ import { Response } from 'express'; import db from '../../database/models/index.js'; -const { Worknote, User, WorkNoteTag, WorkNoteAttachment, Document, DocumentVersion, RequestParticipant, Application, AuditLog } = db; +const { + Worknote, User, WorkNoteTag, WorkNoteAttachment, DocumentVersion, RequestParticipant, Application, AuditLog, + OnboardingDocument, RelocationDocument, ResignationDocument, ConstitutionalDocument, TerminationDocument +} = db; import { AuthRequest } from '../../types/express.types.js'; import { AUDIT_ACTIONS } from '../../common/config/constants.js'; import * as EmailService from '../../common/utils/email.service.js'; @@ -8,6 +11,59 @@ import { getIO } from '../../common/utils/socket.js'; import * as NotificationService from '../../common/utils/notification.service.js'; import logger from '../../common/utils/logger.js'; +// --- Helpers --- +const getDocumentModel = (requestType: string) => { + switch (requestType?.toLowerCase()) { + case 'relocation': return RelocationDocument; + case 'resignation': return ResignationDocument; + case 'constitutional': return ConstitutionalDocument; + case 'termination': return TerminationDocument; + case 'onboarding': + case 'application': return OnboardingDocument; + default: return OnboardingDocument; + } +}; + +const stitchWorknoteAttachments = async (worknotes: any[]) => { + const notePromises = worknotes.map(async (note: any) => { + const noteObj = note.toJSON ? note.toJSON() : note; + if (noteObj.attachments && noteObj.attachments.length > 0) { + // Group by documentType for batch fetching + const typeGroups: Record = {}; + noteObj.attachments.forEach((att: any) => { + const type = att.documentType || 'onboarding'; + if (!typeGroups[type]) typeGroups[type] = []; + typeGroups[type].push(att.documentId); + }); + + const attachmentsWithFiles = []; + for (const [type, ids] of Object.entries(typeGroups)) { + const DocModel = getDocumentModel(type); + if (DocModel) { + const docs = await (DocModel as any).findAll({ where: { id: ids } }); + const docsByid = new Map(docs.map((d: any) => [d.id, d.toJSON()])); + attachmentsWithFiles.push(...noteObj.attachments + .filter((att: any) => (att.documentType || 'onboarding') === type) + .map((att: any) => { + const doc = docsByid.get(att.documentId) as any; + return { + ...att, + fileName: doc?.fileName || 'Unknown', + filePath: doc?.filePath || '', + mimeType: doc?.mimeType || 'application/octet-stream', + fileSize: doc?.fileSize + }; + }) + ); + } + } + noteObj.attachments = attachmentsWithFiles; + } + return noteObj; + }); + return Promise.all(notePromises); +}; + // --- Worknotes --- export const addWorknote = async (req: AuthRequest, res: Response) => { @@ -40,7 +96,11 @@ export const addWorknote = async (req: AuthRequest, res: Response) => { if (attachmentDocIds && attachmentDocIds.length > 0) { for (const docId of attachmentDocIds) { - await WorkNoteAttachment.create({ noteId: worknote.id, documentId: docId }); + await WorkNoteAttachment.create({ + noteId: worknote.id, + documentId: docId, + documentType: requestType || 'onboarding' + }); } } @@ -64,14 +124,16 @@ export const addWorknote = async (req: AuthRequest, res: Response) => { include: [ { model: User, as: 'author', attributes: [['fullName', 'name'], 'email', ['roleCode', 'role']] }, { model: WorkNoteTag, as: 'tags' }, - { model: Document, as: 'attachments' } + { model: WorkNoteAttachment, as: 'attachments' } ] }); + const [stitchedNote] = await stitchWorknoteAttachments([fullWorknote]); + // --- Real-time & Notifications --- try { const io = getIO(); - io.to(requestId).emit('new_worknote', fullWorknote); + io.to(requestId).emit('new_worknote', stitchedNote); // Handle Mentions/Notifications const notifiedUserIds = new Set(); @@ -131,7 +193,7 @@ export const addWorknote = async (req: AuthRequest, res: Response) => { newData: { noteType: noteType || 'General', hasAttachments: !!(attachmentDocIds?.length) } }); - res.status(201).json({ success: true, message: 'Worknote added', data: fullWorknote }); + res.status(201).json({ success: true, message: 'Worknote added', data: stitchedNote }); } catch (error) { console.error('Add worknote error:', error); res.status(500).json({ success: false, message: 'Error adding worknote' }); @@ -147,12 +209,13 @@ export const getWorknotes = async (req: AuthRequest, res: Response) => { include: [ { model: User, as: 'author', attributes: [['fullName', 'name'], 'email', ['roleCode', 'role']] }, { model: WorkNoteTag, as: 'tags' }, - { model: Document, as: 'attachments' } + { model: WorkNoteAttachment, as: 'attachments' } ], order: [['createdAt', 'DESC']] }); - res.json({ success: true, data: worknotes }); + const finalWorknotes = await stitchWorknoteAttachments(worknotes); + res.json({ success: true, data: finalWorknotes }); } catch (error) { res.status(500).json({ success: false, message: 'Error fetching worknotes' }); } @@ -167,9 +230,9 @@ export const uploadWorknoteAttachment = async (req: any, res: Response) => { return res.status(400).json({ success: false, message: 'No file uploaded' }); } - const document = await Document.create({ - requestId: requestId || null, - requestType: requestType || null, + const DocModel = getDocumentModel(requestType); + + let createData: any = { documentType: 'Worknote Attachment', fileName: file.originalname, filePath: file.path, @@ -177,11 +240,21 @@ export const uploadWorknoteAttachment = async (req: any, res: Response) => { fileSize: file.size, uploadedBy: req.user?.id, status: 'active' - }); + }; + + // Assign correct FK based on model + if (DocModel === RelocationDocument) createData.relocationId = requestId; + else if (DocModel === ResignationDocument) createData.resignationId = requestId; + else if (DocModel === ConstitutionalDocument) createData.constitutionalChangeId = requestId; + else if (DocModel === TerminationDocument) createData.terminationRequestId = requestId; + else createData.applicationId = requestId; + + const document = await DocModel.create(createData); // Create initial version await DocumentVersion.create({ documentId: document.id, + documentType: requestType || 'onboarding', versionNumber: 1, filePath: file.path, uploadedBy: req.user?.id, @@ -221,10 +294,10 @@ export const uploadDocument = async (req: AuthRequest, res: Response) => { try { const { applicationId, dealerId, docType, fileName, fileUrl, mimeType } = req.body; - const document = await Document.create({ + const document = await OnboardingDocument.create({ applicationId, dealerId, - docType, + documentType: docType, fileName, filePath: fileUrl, // Assuming URL from cloud storage mimeType, @@ -275,7 +348,8 @@ export const uploadNewVersion = async (req: AuthRequest, res: Response) => { }); // Update main document pointer if needed (usually main doc points to latest or metadata) - await Document.update({ filePath: fileUrl }, { where: { id: documentId } }); + // For simplicity assuming onboarding if not specified + await OnboardingDocument.update({ filePath: fileUrl }, { where: { id: documentId } }); res.status(201).json({ success: true, message: 'New version uploaded' }); } catch (error) { diff --git a/src/modules/eor/eor.controller.ts b/src/modules/eor/eor.controller.ts index 0cadb8b..0121aac 100644 --- a/src/modules/eor/eor.controller.ts +++ b/src/modules/eor/eor.controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import db from '../../database/models/index.js'; -const { EorChecklist, EorChecklistItem, Document } = db; +const { EorChecklist, EorChecklistItem, OnboardingDocument, RelocationDocument } = db; import { AuthRequest } from '../../types/express.types.js'; export const getChecklist = async (req: Request, res: Response) => { @@ -8,7 +8,8 @@ export const getChecklist = async (req: Request, res: Response) => { const { applicationId, relocationId } = req.params; let checklist = await EorChecklist.findOne({ where: relocationId ? { relocationId } : { applicationId }, - include: [{ model: EorChecklistItem, as: 'items', include: ['proofDocument'] }] + // proofDocument is now polymorphic, would need manual stitch or sub-selects + include: [{ model: EorChecklistItem, as: 'items' }] }); if (!checklist) { @@ -16,6 +17,27 @@ export const getChecklist = async (req: Request, res: Response) => { return; } + const items = checklist.items || []; + const proofDocIds = items.map((i: any) => i.proofDocumentId).filter(Boolean); + + if (proofDocIds.length > 0) { + // Find documents from the relevant table + let docs = []; + if (relocationId) { + docs = await RelocationDocument.findAll({ where: { id: proofDocIds } }); + } else { + docs = await OnboardingDocument.findAll({ where: { id: proofDocIds } }); + } + + // Map docs to items + const docsMap = new Map(docs.map((d: any) => [d.id, d])); + checklist = checklist.toJSON(); + checklist.items = checklist.items.map((item: any) => ({ + ...item, + proofDocument: docsMap.get(item.proofDocumentId) || null + })); + } + res.json({ success: true, data: checklist }); } catch (error) { console.error('Get EOR checklist error:', error); diff --git a/src/modules/loi/loi.controller.ts b/src/modules/loi/loi.controller.ts index 1067e7d..b3c6d5d 100644 --- a/src/modules/loi/loi.controller.ts +++ b/src/modules/loi/loi.controller.ts @@ -139,8 +139,8 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { // MANDATORY DOCUMENT CHECK (SRS Requirement) // Level 2+ requires minimum set of documents uploaded by applicant if (currentApproval.level === 1 && action === 'Approved') { - const docCount = await db.Document.count({ - where: { requestId: request.applicationId, requestType: 'application' } + const docCount = await db.OnboardingDocument.count({ + where: { applicationId: request.applicationId } }); if (docCount < 5) { // SRS requires 18, using 5 for functional demo return res.status(400).json({ diff --git a/src/modules/onboarding/onboarding.controller.ts b/src/modules/onboarding/onboarding.controller.ts index 8ae4f37..998a035 100644 --- a/src/modules/onboarding/onboarding.controller.ts +++ b/src/modules/onboarding/onboarding.controller.ts @@ -136,6 +136,16 @@ export const getApplications = async (req: AuthRequest, res: Response) => { whereClause.email = req.user.email; } + // Security Check: If FDD user, only show applications where they are a participant + if (req.user?.roleCode === 'FDD') { + const participantApps = await db.RequestParticipant.findAll({ + where: { userId: req.user.id, requestType: 'application' }, + attributes: ['requestId'] + }); + const appIds = participantApps.map((p: any) => p.requestId); + whereClause.id = { [Op.in]: appIds }; + } + const applications = await Application.findAll({ where: whereClause, include: [ @@ -191,9 +201,10 @@ export const getApplicationById = async (req: AuthRequest, res: Response) => { include: [{ model: db.User, as: 'user', attributes: ['id', ['fullName', 'name'], 'email', ['roleCode', 'role']] }] }, { - model: db.Document, + model: db.OnboardingDocument, as: 'uploadedDocuments', separate: true, + include: [{ model: db.User, as: 'uploader', attributes: ['fullName', 'roleCode'] }], order: [['createdAt', 'DESC']] }, { model: db.StageApprovalAction, as: 'stageApprovals', separate: true }, @@ -206,6 +217,41 @@ export const getApplicationById = async (req: AuthRequest, res: Response) => { return res.status(404).json({ success: false, message: 'Application not found' }); } + // Security Check for FDD: If user is FDD, only return restricted data + if (req.user?.roleCode === 'FDD') { + const isParticipant = await db.RequestParticipant.findOne({ + where: { requestId: application.id, userId: req.user.id, requestType: 'application' } + }); + + if (!isParticipant) { + return res.status(403).json({ success: false, message: 'Access denied. You are not assigned to this application.' }); + } + + // Strip sensitive internal data for FDD + const restrictedData = application.toJSON(); + delete (restrictedData as any).questionnaireResponses; + delete (restrictedData as any).stageApprovals; + delete (restrictedData as any).score; + + // FDD should only see relevant documents for security + // FDD should only see relevant documents for security + // OR documents they uploaded themselves + const fddRelevantDocs = [ + 'GST Certificate', 'PAN Card', 'Bank Statement', 'Cancelled Check', + 'Partnership Deed', 'LLP Agreement', 'Certificate of Incorporation', 'MOA', 'AOA', + 'Property Documents', 'Rental Agreement', 'Firm Registration', 'CIBIL Report', + 'FDD Final Audit Report', 'FDD Audit Report' + ]; + + if (restrictedData.uploadedDocuments) { + restrictedData.uploadedDocuments = (restrictedData.uploadedDocuments as any[]).filter( + (doc: any) => fddRelevantDocs.includes(doc.documentType) || (req.user && doc.uploadedBy === req.user.id) + ); + } + + return res.json({ success: true, data: restrictedData }); + } + // Security Check: Ensure prospective dealer controls data ownership if (req.user?.roleCode === 'Prospective Dealer' && application.email !== req.user.email) { return res.status(403).json({ success: false, message: 'Forbidden: You do not have permission to view this application' }); @@ -268,17 +314,14 @@ export const uploadDocuments = async (req: any, res: Response) => { } // Create Document Record - const newDoc = await db.Document.create({ + const newDoc = await db.OnboardingDocument.create({ applicationId: application.id, - requestId: application.id, - requestType: 'application', documentType, stage: stage || null, fileName: file.originalname, - filePath: file.path, // Store relative path or full path as needed by your storage strategy + filePath: file.path, fileSize: file.size, mimeType: file.mimetype, - // For prospective users (who are applications, not in Users table), set uploadedBy to null to avoid FK violation uploadedBy: req.user?.roleCode === 'Prospective Dealer' ? null : req.user?.id, status: 'active' }); @@ -360,10 +403,9 @@ export const getApplicationDocuments = async (req: AuthRequest, res: Response) = return res.status(404).json({ success: false, message: 'Application not found' }); } - const documents = await db.Document.findAll({ + const documents = await db.OnboardingDocument.findAll({ where: { - requestId: application.id, - requestType: 'application', + applicationId: application.id, status: 'active' }, include: [ diff --git a/src/modules/self-service/relocation.controller.ts b/src/modules/self-service/relocation.controller.ts index 5c22db6..94f61e4 100644 --- a/src/modules/self-service/relocation.controller.ts +++ b/src/modules/self-service/relocation.controller.ts @@ -1,7 +1,7 @@ import { Response } from 'express'; import db from '../../database/models/index.js'; import { RelocationWorkflowService } from '../../services/RelocationWorkflowService.js'; -const { RelocationRequest, Outlet, User, Worknote, District, Region, Zone, Document } = db; +const { RelocationRequest, Outlet, User, Worknote, District, Region, Zone, RelocationDocument } = db; import { AUDIT_ACTIONS, ROLES, RELOCATION_STAGES } from '../../common/config/constants.js'; import { Op, Transaction } from 'sequelize'; import { v4 as uuidv4 } from 'uuid'; @@ -236,7 +236,7 @@ export const getRequests = async (req: AuthRequest, res: Response) => { const userRoleCode = req.user?.roleCode; // National roles see all requests - const nationalRoles = ['NBH', 'DD Lead', 'DD Head', 'Legal Admin']; + const nationalRoles = ['NBH', 'DD Lead', 'DD Head', 'Legal Admin', 'Super Admin', 'DD Admin']; if (userRoleCode && nationalRoles.includes(userRoleCode)) { return true; } @@ -426,6 +426,7 @@ export const takeAction = async (req: AuthRequest, res: Response) => { const stageFlow: Record = { [RELOCATION_STAGES.ASM_REVIEW]: RELOCATION_STAGES.RBM_REVIEW, + 'DD Admin Review': RELOCATION_STAGES.RBM_REVIEW, // Legacy support [RELOCATION_STAGES.RBM_REVIEW]: RELOCATION_STAGES.DD_ZM_REVIEW, [RELOCATION_STAGES.DD_ZM_REVIEW]: RELOCATION_STAGES.ZBH_REVIEW, [RELOCATION_STAGES.ZBH_REVIEW]: RELOCATION_STAGES.DD_LEAD_REVIEW, @@ -526,9 +527,8 @@ export const uploadDocuments = async (req: AuthRequest, res: Response) => { } // Create Document Record - const newDoc = await Document.create({ - requestId: request.id, - requestType: 'relocation', + const newDoc = await RelocationDocument.create({ + relocationId: request.id, documentType, stage: stage || request.currentStage, fileName: file.originalname, @@ -596,7 +596,7 @@ export const verifyDocument = async (req: AuthRequest, res: Response) => { } // Find and update the Document record - const docRecord = await Document.findByPk(documentId); + const docRecord = await RelocationDocument.findByPk(documentId); if (docRecord) { await docRecord.update({ status: 'Verified' }); } diff --git a/src/services/RelocationWorkflowService.ts b/src/services/RelocationWorkflowService.ts index deb1545..c89c137 100644 --- a/src/services/RelocationWorkflowService.ts +++ b/src/services/RelocationWorkflowService.ts @@ -66,6 +66,7 @@ export class RelocationWorkflowService { const stageMapping: Record = { [RELOCATION_STAGES.ASM_REVIEW]: ROLES.ASM, + 'DD Admin Review': ROLES.ASM, // Legacy/alias mapping for older requests [RELOCATION_STAGES.RBM_REVIEW]: ROLES.RBM, [RELOCATION_STAGES.DD_ZM_REVIEW]: ROLES.DD_ZM, [RELOCATION_STAGES.ZBH_REVIEW]: ROLES.ZBH,