feat: Complete Z.CRM system with all 6 modules
✨ Features: - Complete authentication system with JWT - Dashboard with all 6 modules visible - Contact Management module (Salesforce-style) - CRM & Sales Pipeline module (Pipedrive-style) - Inventory & Assets module (SAP-style) - Tasks & Projects module (Jira/Asana-style) - HR Management module (BambooHR-style) - Marketing Management module (HubSpot-style) - Admin Panel with user management and role matrix - World-class UI/UX with RTL Arabic support - Cairo font (headings) + Readex Pro font (body) - Sample data for all modules - Protected routes and authentication flow - Backend API with Prisma + PostgreSQL - Comprehensive documentation 🎨 Design: - Color-coded modules - Professional data tables - Stats cards with metrics - Progress bars and status badges - Search and filters - Responsive layout 📊 Tech Stack: - Frontend: Next.js 14, TypeScript, Tailwind CSS - Backend: Node.js, Express, Prisma - Database: PostgreSQL - Auth: JWT with bcrypt 🚀 Production-ready frontend with all features accessible
This commit is contained in:
1386
backend/prisma/migrations/20260106091550_init/migration.sql
Normal file
1386
backend/prisma/migrations/20260106091550_init/migration.sql
Normal file
File diff suppressed because it is too large
Load Diff
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
1341
backend/prisma/schema.prisma
Normal file
1341
backend/prisma/schema.prisma
Normal file
File diff suppressed because it is too large
Load Diff
349
backend/prisma/seed.ts
Normal file
349
backend/prisma/seed.ts
Normal file
@@ -0,0 +1,349 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Starting database seeding...');
|
||||
|
||||
// Create Departments
|
||||
const salesDept = await prisma.department.create({
|
||||
data: {
|
||||
name: 'Sales Department',
|
||||
nameAr: 'قسم المبيعات',
|
||||
code: 'SALES',
|
||||
description: 'Sales and Business Development',
|
||||
},
|
||||
});
|
||||
|
||||
const itDept = await prisma.department.create({
|
||||
data: {
|
||||
name: 'IT Department',
|
||||
nameAr: 'قسم تقنية المعلومات',
|
||||
code: 'IT',
|
||||
description: 'Information Technology',
|
||||
},
|
||||
});
|
||||
|
||||
const hrDept = await prisma.department.create({
|
||||
data: {
|
||||
name: 'HR Department',
|
||||
nameAr: 'قسم الموارد البشرية',
|
||||
code: 'HR',
|
||||
description: 'Human Resources',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created departments');
|
||||
|
||||
// Create Positions
|
||||
const gmPosition = await prisma.position.create({
|
||||
data: {
|
||||
title: 'General Manager',
|
||||
titleAr: 'المدير العام',
|
||||
code: 'GM',
|
||||
departmentId: salesDept.id,
|
||||
level: 1,
|
||||
description: 'Chief Executive - Full Access',
|
||||
},
|
||||
});
|
||||
|
||||
const salesManagerPosition = await prisma.position.create({
|
||||
data: {
|
||||
title: 'Sales Manager',
|
||||
titleAr: 'مدير المبيعات',
|
||||
code: 'SALES_MGR',
|
||||
departmentId: salesDept.id,
|
||||
level: 2,
|
||||
description: 'Sales Department Manager',
|
||||
},
|
||||
});
|
||||
|
||||
const salesRepPosition = await prisma.position.create({
|
||||
data: {
|
||||
title: 'Sales Representative',
|
||||
titleAr: 'مندوب مبيعات',
|
||||
code: 'SALES_REP',
|
||||
departmentId: salesDept.id,
|
||||
level: 3,
|
||||
description: 'Sales Representative',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created positions');
|
||||
|
||||
// Create Permissions for GM (Full Access)
|
||||
const modules = ['contacts', 'crm', 'inventory', 'projects', 'hr', 'marketing'];
|
||||
const resources = ['*'];
|
||||
const actions = ['*'];
|
||||
|
||||
for (const module of modules) {
|
||||
await prisma.positionPermission.create({
|
||||
data: {
|
||||
positionId: gmPosition.id,
|
||||
module,
|
||||
resource: resources[0],
|
||||
actions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Create Permissions for Sales Manager
|
||||
await prisma.positionPermission.createMany({
|
||||
data: [
|
||||
{
|
||||
positionId: salesManagerPosition.id,
|
||||
module: 'contacts',
|
||||
resource: 'contacts',
|
||||
actions: ['create', 'read', 'update', 'merge'],
|
||||
},
|
||||
{
|
||||
positionId: salesManagerPosition.id,
|
||||
module: 'crm',
|
||||
resource: 'deals',
|
||||
actions: ['create', 'read', 'update', 'approve'],
|
||||
},
|
||||
{
|
||||
positionId: salesManagerPosition.id,
|
||||
module: 'crm',
|
||||
resource: 'quotes',
|
||||
actions: ['create', 'read', 'update', 'approve'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Create Permissions for Sales Rep
|
||||
await prisma.positionPermission.createMany({
|
||||
data: [
|
||||
{
|
||||
positionId: salesRepPosition.id,
|
||||
module: 'contacts',
|
||||
resource: 'contacts',
|
||||
actions: ['create', 'read', 'update'],
|
||||
},
|
||||
{
|
||||
positionId: salesRepPosition.id,
|
||||
module: 'crm',
|
||||
resource: 'deals',
|
||||
actions: ['create', 'read', 'update'],
|
||||
},
|
||||
{
|
||||
positionId: salesRepPosition.id,
|
||||
module: 'crm',
|
||||
resource: 'quotes',
|
||||
actions: ['create', 'read'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log('✅ Created permissions');
|
||||
|
||||
// Create Employees
|
||||
const gmEmployee = await prisma.employee.create({
|
||||
data: {
|
||||
uniqueEmployeeId: 'EMP-2024-0001',
|
||||
firstName: 'Ahmed',
|
||||
lastName: 'Al-Mutairi',
|
||||
firstNameAr: 'أحمد',
|
||||
lastNameAr: 'المطيري',
|
||||
email: 'gm@atmata.com',
|
||||
mobile: '+966500000001',
|
||||
dateOfBirth: new Date('1980-01-01'),
|
||||
gender: 'MALE',
|
||||
nationality: 'Saudi',
|
||||
employmentType: 'Full-time',
|
||||
contractType: 'Unlimited',
|
||||
hireDate: new Date('2020-01-01'),
|
||||
departmentId: salesDept.id,
|
||||
positionId: gmPosition.id,
|
||||
basicSalary: 50000,
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
});
|
||||
|
||||
const salesManagerEmployee = await prisma.employee.create({
|
||||
data: {
|
||||
uniqueEmployeeId: 'EMP-2024-0002',
|
||||
firstName: 'Fatima',
|
||||
lastName: 'Al-Zahrani',
|
||||
firstNameAr: 'فاطمة',
|
||||
lastNameAr: 'الزهراني',
|
||||
email: 'sales.manager@atmata.com',
|
||||
mobile: '+966500000002',
|
||||
dateOfBirth: new Date('1985-05-15'),
|
||||
gender: 'FEMALE',
|
||||
nationality: 'Saudi',
|
||||
employmentType: 'Full-time',
|
||||
contractType: 'Unlimited',
|
||||
hireDate: new Date('2021-06-01'),
|
||||
departmentId: salesDept.id,
|
||||
positionId: salesManagerPosition.id,
|
||||
reportingToId: gmEmployee.id,
|
||||
basicSalary: 25000,
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
});
|
||||
|
||||
const salesRepEmployee = await prisma.employee.create({
|
||||
data: {
|
||||
uniqueEmployeeId: 'EMP-2024-0003',
|
||||
firstName: 'Mohammed',
|
||||
lastName: 'Al-Qahtani',
|
||||
firstNameAr: 'محمد',
|
||||
lastNameAr: 'القحطاني',
|
||||
email: 'sales.rep@atmata.com',
|
||||
mobile: '+966500000003',
|
||||
dateOfBirth: new Date('1992-08-20'),
|
||||
gender: 'MALE',
|
||||
nationality: 'Saudi',
|
||||
employmentType: 'Full-time',
|
||||
contractType: 'Fixed',
|
||||
hireDate: new Date('2023-01-15'),
|
||||
departmentId: salesDept.id,
|
||||
positionId: salesRepPosition.id,
|
||||
reportingToId: salesManagerEmployee.id,
|
||||
basicSalary: 12000,
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created employees');
|
||||
|
||||
// Create Users
|
||||
const hashedPassword = await bcrypt.hash('Admin@123', 10);
|
||||
|
||||
const gmUser = await prisma.user.create({
|
||||
data: {
|
||||
email: 'gm@atmata.com',
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
employeeId: gmEmployee.id,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
const salesManagerUser = await prisma.user.create({
|
||||
data: {
|
||||
email: 'sales.manager@atmata.com',
|
||||
username: 'salesmanager',
|
||||
password: hashedPassword,
|
||||
employeeId: salesManagerEmployee.id,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
const salesRepUser = await prisma.user.create({
|
||||
data: {
|
||||
email: 'sales.rep@atmata.com',
|
||||
username: 'salesrep',
|
||||
password: hashedPassword,
|
||||
employeeId: salesRepEmployee.id,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created users');
|
||||
|
||||
// Create Contact Categories
|
||||
await prisma.contactCategory.createMany({
|
||||
data: [
|
||||
{ name: 'Customer', nameAr: 'عميل', description: 'Paying customers' },
|
||||
{ name: 'Supplier', nameAr: 'مورد', description: 'Product/Service suppliers' },
|
||||
{ name: 'Partner', nameAr: 'شريك', description: 'Business partners' },
|
||||
{ name: 'Lead', nameAr: 'عميل محتمل', description: 'Potential customers' },
|
||||
],
|
||||
});
|
||||
|
||||
console.log('✅ Created contact categories');
|
||||
|
||||
// Create Product Categories
|
||||
await prisma.productCategory.createMany({
|
||||
data: [
|
||||
{ name: 'Electronics', nameAr: 'إلكترونيات', code: 'ELEC' },
|
||||
{ name: 'Software', nameAr: 'برمجيات', code: 'SOFT' },
|
||||
{ name: 'Services', nameAr: 'خدمات', code: 'SERV' },
|
||||
],
|
||||
});
|
||||
|
||||
console.log('✅ Created product categories');
|
||||
|
||||
// Create Pipelines
|
||||
await prisma.pipeline.create({
|
||||
data: {
|
||||
name: 'B2B Sales Pipeline',
|
||||
nameAr: 'مسار مبيعات الشركات',
|
||||
structure: 'B2B',
|
||||
stages: [
|
||||
{ name: 'OPEN', nameAr: 'مفتوحة', order: 1 },
|
||||
{ name: 'QUALIFIED', nameAr: 'مؤهلة', order: 2 },
|
||||
{ name: 'NEGOTIATION', nameAr: 'تفاوض', order: 3 },
|
||||
{ name: 'PROPOSAL', nameAr: 'عرض سعر', order: 4 },
|
||||
{ name: 'WON', nameAr: 'فازت', order: 5 },
|
||||
{ name: 'LOST', nameAr: 'خسرت', order: 6 },
|
||||
],
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.pipeline.create({
|
||||
data: {
|
||||
name: 'B2C Sales Pipeline',
|
||||
nameAr: 'مسار مبيعات الأفراد',
|
||||
structure: 'B2C',
|
||||
stages: [
|
||||
{ name: 'LEAD', nameAr: 'عميل محتمل', order: 1 },
|
||||
{ name: 'CONTACTED', nameAr: 'تم التواصل', order: 2 },
|
||||
{ name: 'QUALIFIED', nameAr: 'مؤهل', order: 3 },
|
||||
{ name: 'WON', nameAr: 'بيع', order: 4 },
|
||||
{ name: 'LOST', nameAr: 'خسارة', order: 5 },
|
||||
],
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created pipelines');
|
||||
|
||||
// Create sample warehouse
|
||||
await prisma.warehouse.create({
|
||||
data: {
|
||||
code: 'WH-MAIN',
|
||||
name: 'Main Warehouse',
|
||||
nameAr: 'المستودع الرئيسي',
|
||||
type: 'MAIN',
|
||||
city: 'Riyadh',
|
||||
country: 'Saudi Arabia',
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Created warehouse');
|
||||
|
||||
console.log('\n🎉 Database seeding completed successfully!\n');
|
||||
console.log('📋 Default Users Created:');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.log('1. General Manager');
|
||||
console.log(' Email: gm@atmata.com');
|
||||
console.log(' Password: Admin@123');
|
||||
console.log(' Access: Full System Access');
|
||||
console.log('');
|
||||
console.log('2. Sales Manager');
|
||||
console.log(' Email: sales.manager@atmata.com');
|
||||
console.log(' Password: Admin@123');
|
||||
console.log(' Access: Contacts, CRM with approvals');
|
||||
console.log('');
|
||||
console.log('3. Sales Representative');
|
||||
console.log(' Email: sales.rep@atmata.com');
|
||||
console.log(' Password: Admin@123');
|
||||
console.log(' Access: Basic Contacts and CRM');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('❌ Error seeding database:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user