feat: implement customer profile management and business application features
- Added customer profile controller with endpoints for retrieving, updating, and deactivating user profiles. - Introduced address model and routes for managing user addresses. - Created business application model and service for handling business applications, including approval and rejection processes. - Developed business contact and credit account models to support business customer functionalities. - Implemented settlement term model for managing payment terms. - Added email templates for business application notifications (approved, rejected, received, and status changes). - Enhanced validation schemas for customer and business inputs to ensure data integrity. - Created unit tests for business application service and validation schemas to ensure functionality and correctness. - Added migration scripts to set up new database tables and columns for customer and business features.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
module.exports={async up(q,S){await q.sequelize.transaction(async transaction=>{
|
||||
for(const [name,def] of [["preferred_language",{type:S.ENUM("en","si","ta"),allowNull:false,defaultValue:"en"}],["marketing_email_enabled",{type:S.BOOLEAN,allowNull:false,defaultValue:true}],["marketing_push_enabled",{type:S.BOOLEAN,allowNull:false,defaultValue:false}],["in_app_notifications_enabled",{type:S.BOOLEAN,allowNull:false,defaultValue:true}]])await q.addColumn("profiles",name,def,{transaction});
|
||||
await q.createTable("addresses",{id:{type:S.STRING,primaryKey:true},user_id:{type:S.STRING,allowNull:true,references:{model:"users",key:"id"},onDelete:"CASCADE"},business_profile_id:{type:S.STRING,allowNull:true},label:{type:S.STRING(50),allowNull:false},recipient_name:{type:S.STRING(150),allowNull:false},phone_number:{type:S.STRING(30),allowNull:false},address_line_1:{type:S.STRING(255),allowNull:false},address_line_2:S.STRING(255),city:{type:S.STRING(100),allowNull:false},district:S.STRING(100),province:S.STRING(100),postal_code:S.STRING(20),country_code:{type:S.STRING(2),allowNull:false},address_type:{type:S.ENUM("CUSTOMER","REGISTERED","BILLING","DELIVERY"),allowNull:false,defaultValue:"CUSTOMER"},is_default_shipping:{type:S.BOOLEAN,allowNull:false,defaultValue:false},is_default_billing:{type:S.BOOLEAN,allowNull:false,defaultValue:false},createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}},{transaction});await q.addIndex("addresses",["user_id"],{name:"addresses_user_idx",transaction});await q.addIndex("addresses",["business_profile_id"],{name:"addresses_business_idx",transaction});
|
||||
await q.createTable("business_applications",{id:{type:S.STRING,primaryKey:true},user_id:{type:S.STRING,allowNull:false,references:{model:"users",key:"id"}},business_name:{type:S.STRING(180),allowNull:false},legal_name:{type:S.STRING(180),allowNull:false},registration_number:{type:S.STRING(100),allowNull:false},tax_number:S.STRING(100),business_type:{type:S.STRING(80),allowNull:false},contact_email:{type:S.STRING,allowNull:false},contact_phone:{type:S.STRING(30),allowNull:false},website:S.STRING,status:{type:S.ENUM("PENDING","UNDER_REVIEW","APPROVED","REJECTED","CANCELLED"),allowNull:false,defaultValue:"PENDING"},reviewed_by:S.STRING,reviewed_at:S.DATE,rejection_reason:S.STRING(500),createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}},{transaction});await q.addIndex("business_applications",["user_id","status"],{name:"business_applicant_status_idx",transaction});await q.addIndex("business_applications",["status","createdAt"],{name:"business_application_review_idx",transaction});
|
||||
for(const [name,def] of [["legalName",{type:S.STRING}],["taxNumber",{type:S.STRING}],["website",{type:S.STRING}],["partnerId",{type:S.STRING,allowNull:true}],["status",{type:S.ENUM("ACTIVE","SUSPENDED","CLOSED"),allowNull:false,defaultValue:"ACTIVE"}],["approvedAt",{type:S.DATE,allowNull:true}],["approvedBy",{type:S.STRING,allowNull:true}],["settlement_term_id",{type:S.STRING,allowNull:true}]])await q.addColumn("business_customers",name,def,{transaction});await q.addIndex("business_customers",["partnerId"],{unique:true,name:"business_partner_unique",transaction});await q.addIndex("business_customers",["businessRegistrationNumber"],{unique:true,name:"business_registration_unique",transaction});await q.addIndex("business_customers",["status"],{name:"business_status_idx",transaction});
|
||||
await q.createTable("business_contacts",{id:{type:S.STRING,primaryKey:true},business_profile_id:{type:S.STRING,allowNull:false,references:{model:"business_customers",key:"business_customer_id"},onDelete:"CASCADE"},name:{type:S.STRING(150),allowNull:false},title:S.STRING(100),email:S.STRING,phone:S.STRING(30),is_primary:{type:S.BOOLEAN,allowNull:false,defaultValue:false},createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}},{transaction});await q.addIndex("business_contacts",["business_profile_id"],{name:"business_contacts_profile_idx",transaction});
|
||||
await q.createTable("business_credit_accounts",{id:{type:S.STRING,primaryKey:true},business_profile_id:{type:S.STRING,allowNull:false,references:{model:"business_customers",key:"business_customer_id"},onDelete:"CASCADE"},currency:{type:S.STRING(3),allowNull:false,defaultValue:"LKR"},credit_limit:{type:S.DECIMAL(15,2),allowNull:false,defaultValue:0},status:{type:S.ENUM("DISABLED","ACTIVE","SUSPENDED"),allowNull:false,defaultValue:"DISABLED"},createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}},{transaction});await q.addIndex("business_credit_accounts",["business_profile_id"],{unique:true,name:"business_credit_profile_unique",transaction});
|
||||
await q.createTable("settlement_terms",{id:{type:S.STRING,primaryKey:true},code:{type:S.STRING(30),allowNull:false,unique:true},name:{type:S.STRING(100),allowNull:false},days:{type:S.INTEGER,allowNull:false},is_active:{type:S.BOOLEAN,allowNull:false,defaultValue:true},createdAt:{type:S.DATE,allowNull:false},updatedAt:{type:S.DATE,allowNull:false}},{transaction});
|
||||
const now=new Date();await q.bulkInsert("settlement_terms",[{id:"term_prepaid",code:"PREPAID",name:"Prepaid",days:0,is_active:true,createdAt:now,updatedAt:now},{id:"term_net_7",code:"NET_7",name:"Net 7",days:7,is_active:true,createdAt:now,updatedAt:now},{id:"term_net_14",code:"NET_14",name:"Net 14",days:14,is_active:true,createdAt:now,updatedAt:now},{id:"term_net_30",code:"NET_30",name:"Net 30",days:30,is_active:true,createdAt:now,updatedAt:now}],{transaction});
|
||||
await q.addConstraint("addresses",{fields:["business_profile_id"],type:"foreign key",name:"addresses_business_fk",references:{table:"business_customers",field:"business_customer_id"},onDelete:"CASCADE",transaction});
|
||||
await q.sequelize.query("ALTER TABLE addresses ADD CONSTRAINT addresses_exactly_one_owner CHECK ((user_id IS NOT NULL) + (business_profile_id IS NOT NULL) = 1)",{transaction});
|
||||
await q.addConstraint("business_customers",{fields:["settlement_term_id"],type:"foreign key",name:"business_settlement_term_fk",references:{table:"settlement_terms",field:"id"},onDelete:"SET NULL",transaction});
|
||||
});},async down(){throw new Error("Phase 3 migration is forward-only; restore from backup for rollback");}};
|
||||
Reference in New Issue
Block a user