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:
Sathira Sri Sathara
2026-09-03 14:58:26 +05:30
parent b6b345f245
commit fd4e324571
36 changed files with 406 additions and 6 deletions
+13
View File
@@ -0,0 +1,13 @@
module.exports = (sequelize, DataTypes) => {
const Address = sequelize.define("Address", {
id: { type: DataTypes.STRING, primaryKey: true },
user_id: { type: DataTypes.STRING, allowNull: true },
business_profile_id: { type: DataTypes.STRING, allowNull: true },
label: { type: DataTypes.STRING(50), allowNull: false }, recipient_name: { type: DataTypes.STRING(150), allowNull: false }, phone_number: { type: DataTypes.STRING(30), allowNull: false },
address_line_1: { type: DataTypes.STRING(255), allowNull: false }, address_line_2: DataTypes.STRING(255), city: { type: DataTypes.STRING(100), allowNull: false }, district: DataTypes.STRING(100), province: DataTypes.STRING(100), postal_code: DataTypes.STRING(20),
country_code: { type: DataTypes.STRING(2), allowNull: false }, address_type: { type: DataTypes.ENUM("CUSTOMER", "REGISTERED", "BILLING", "DELIVERY"), allowNull: false, defaultValue: "CUSTOMER" },
is_default_shipping: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, is_default_billing: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
}, { tableName: "addresses", timestamps: true });
Address.associate = (models) => { Address.belongsTo(models.User, { foreignKey: "user_id", as: "user" }); Address.belongsTo(models.BusinessCustomer, { foreignKey: "business_profile_id", as: "businessProfile" }); };
return Address;
};
@@ -0,0 +1,9 @@
module.exports = (sequelize, DataTypes) => {
const Model = sequelize.define("BusinessApplication", {
id: { type: DataTypes.STRING, primaryKey: true }, user_id: { type: DataTypes.STRING, allowNull: false },
business_name: { type: DataTypes.STRING(180), allowNull: false }, legal_name: { type: DataTypes.STRING(180), allowNull: false }, registration_number: { type: DataTypes.STRING(100), allowNull: false }, tax_number: DataTypes.STRING(100), business_type: { type: DataTypes.STRING(80), allowNull: false }, contact_email: { type: DataTypes.STRING, allowNull: false }, contact_phone: { type: DataTypes.STRING(30), allowNull: false }, website: DataTypes.STRING,
status: { type: DataTypes.ENUM("PENDING", "UNDER_REVIEW", "APPROVED", "REJECTED", "CANCELLED"), allowNull: false, defaultValue: "PENDING" }, reviewed_by: DataTypes.STRING, reviewed_at: DataTypes.DATE, rejection_reason: DataTypes.STRING(500),
}, { tableName: "business_applications", timestamps: true });
Model.associate = (models) => { Model.belongsTo(models.User, { foreignKey: "user_id", as: "applicant" }); Model.belongsTo(models.User, { foreignKey: "reviewed_by", as: "reviewer", constraints: false }); };
return Model;
};
+4
View File
@@ -0,0 +1,4 @@
module.exports = (sequelize, DataTypes) => {
const Model = sequelize.define("BusinessContact", { id: { type: DataTypes.STRING, primaryKey: true }, business_profile_id: { type: DataTypes.STRING, allowNull: false }, name: { type: DataTypes.STRING(150), allowNull: false }, title: DataTypes.STRING(100), email: DataTypes.STRING, phone: DataTypes.STRING(30), is_primary: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false } }, { tableName: "business_contacts", timestamps: true });
Model.associate = (models) => Model.belongsTo(models.BusinessCustomer, { foreignKey: "business_profile_id", as: "businessProfile" }); return Model;
};
@@ -0,0 +1,4 @@
module.exports = (sequelize, DataTypes) => {
const Model = sequelize.define("BusinessCreditAccount", { id: { type: DataTypes.STRING, primaryKey: true }, business_profile_id: { type: DataTypes.STRING, allowNull: false, unique: true }, currency: { type: DataTypes.STRING(3), allowNull: false, defaultValue: "LKR" }, credit_limit: { type: DataTypes.DECIMAL(15,2), allowNull: false, defaultValue: 0 }, status: { type: DataTypes.ENUM("DISABLED", "ACTIVE", "SUSPENDED"), allowNull: false, defaultValue: "DISABLED" } }, { tableName: "business_credit_accounts", timestamps: true });
Model.associate = (models) => Model.belongsTo(models.BusinessCustomer, { foreignKey: "business_profile_id", as: "businessProfile" }); return Model;
};
+13 -1
View File
@@ -21,7 +21,7 @@ module.exports = (sequelize, DataTypes) => {
},
businessRegistrationNumber: {
type: DataTypes.STRING,
allowNull: false,
allowNull: false, unique: true,
},
businessType: {
type: DataTypes.STRING,
@@ -47,6 +47,14 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.STRING,
allowNull: true,
},
legalName: DataTypes.STRING,
taxNumber: DataTypes.STRING,
website: DataTypes.STRING,
partnerId: { type: DataTypes.STRING, allowNull: false, unique: true },
status: { type: DataTypes.ENUM("ACTIVE", "SUSPENDED", "CLOSED"), allowNull: false, defaultValue: "ACTIVE" },
approvedAt: { type: DataTypes.DATE, allowNull: false },
approvedBy: { type: DataTypes.STRING, allowNull: false },
settlement_term_id: { type: DataTypes.STRING, allowNull: true },
},
{
tableName: "business_customers",
@@ -59,6 +67,10 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: "user_id",
as: "user",
});
BusinessCustomer.hasMany(db.BusinessContact, { foreignKey: "business_profile_id", as: "contacts" });
BusinessCustomer.hasMany(db.Address, { foreignKey: "business_profile_id", as: "addresses" });
BusinessCustomer.hasOne(db.BusinessCreditAccount, { foreignKey: "business_profile_id", as: "creditAccount" });
BusinessCustomer.belongsTo(db.SettlementTerm, { foreignKey: "settlement_term_id", as: "settlementTerm" });
};
return BusinessCustomer;
+5 -1
View File
@@ -47,7 +47,11 @@ module.exports = (sequelize, DataTypes) => {
phone_number:{
type: DataTypes.STRING,
allowNull: true,
}
},
preferred_language: { type: DataTypes.ENUM("en", "si", "ta"), allowNull: false, defaultValue: "en" },
marketing_email_enabled: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
marketing_push_enabled: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false },
in_app_notifications_enabled: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
},
{
tableName: "profiles",
+1
View File
@@ -0,0 +1 @@
module.exports = (sequelize, DataTypes) => sequelize.define("SettlementTerm", { id: { type: DataTypes.STRING, primaryKey: true }, code: { type: DataTypes.STRING(30), allowNull: false, unique: true }, name: { type: DataTypes.STRING(100), allowNull: false }, days: { type: DataTypes.INTEGER, allowNull: false }, is_active: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true } }, { tableName: "settlement_terms", timestamps: true });
+2
View File
@@ -88,6 +88,8 @@ module.exports = (sequelize, DataTypes) => {
User.hasMany(db.AuthSession, { foreignKey: "user_id", as: "authSessions" });
User.hasMany(db.UserIdentity, { foreignKey: "user_id", as: "identities" });
User.hasMany(db.UserRole, { foreignKey: "user_id", as: "userRoles" });
User.hasMany(db.Address, { foreignKey: "user_id", as: "addresses" });
User.hasMany(db.BusinessApplication, { foreignKey: "user_id", as: "businessApplications" });
};
return User;