MukeshKapoor25 commited on
Commit
731c213
Β·
1 Parent(s): b83bbd7

feat(employees): implement automatic system user creation for employees

Browse files

- Add `is_system_user` boolean field to employee model, schemas, and database
- Implement `_create_employee_system_user` method to generate system users with temporary passwords
- Map employee designations to system user roles (ASM, SALES_EXECUTIVE, etc.)
- Generate system usernames from employee codes with automatic formatting
- Store employee metadata in system user records for relationship tracking
- Add comprehensive documentation for employee-system user integration workflow
- Include error handling to prevent system user creation failures from blocking employee creation
- Add test script to verify system user creation functionality
- System user creation is triggered when `is_system_user=True` during employee creation

EMPLOYEE_SYSTEM_USER_INTEGRATION.md ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Employee System User Integration
2
+
3
+ ## Overview
4
+
5
+ This document describes the implementation of automatic system user creation when an employee is created with the `is_system_user` flag set to `true`.
6
+
7
+ ## Implementation Details
8
+
9
+ ### 1. Schema Changes
10
+
11
+ #### Employee Create Schema (`EmployeeCreate`)
12
+ - Added `is_system_user: bool` field with default value `False`
13
+ - When set to `True`, triggers system user creation after employee creation
14
+
15
+ #### Employee Update Schema (`EmployeeUpdate`)
16
+ - Added `is_system_user: Optional[bool]` field for updates
17
+
18
+ #### Employee Response Schema (`EmployeeResponse`)
19
+ - Added `is_system_user: bool` field to include in responses
20
+
21
+ #### Employee Model (`EmployeeModel`)
22
+ - Added `is_system_user: bool` field for database storage
23
+
24
+ ### 2. Service Layer Changes
25
+
26
+ #### Employee Service (`EmployeeService`)
27
+
28
+ **Modified `create_employee` method:**
29
+ - After successful employee creation, checks if `is_system_user` is `True`
30
+ - If true, calls `_create_employee_system_user` method
31
+ - System user creation failure doesn't fail employee creation (logged as error)
32
+
33
+ **Added `_create_employee_system_user` method:**
34
+ - Creates a system user in the `SCM_SYSTEM_USERS` collection
35
+ - Generates username from employee code (lowercase, hyphens β†’ underscores)
36
+ - Creates temporary password with format `Temp@{random_token}`
37
+ - Maps employee designation to role_id
38
+ - Stores employee metadata in system user record
39
+
40
+ ### 3. System User Creation Logic
41
+
42
+ When `is_system_user=True`, the following system user is created:
43
+
44
+ ```python
45
+ {
46
+ "username": "emp_test_001", # from employee_code
47
+ "email": "employee@company.com", # same as employee
48
+ "password": "Temp@{random}", # temporary password
49
+ "full_name": "First Last", # from employee name
50
+ "role_id": "role_asm", # from designation
51
+ "merchant_id": "created_by_value", # from created_by
52
+ "metadata": {
53
+ "employee_user_id": "usr_xxx",
54
+ "employee_code": "EMP-TEST-001",
55
+ "designation": "ASM",
56
+ "created_from": "employee_creation"
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### 4. Role Mapping
62
+
63
+ Employee designations are mapped to system user roles:
64
+ - `ASM` β†’ `role_asm`
65
+ - `SALES_EXECUTIVE` β†’ `role_sales_executive`
66
+ - etc.
67
+
68
+ ### 5. Error Handling
69
+
70
+ - System user creation errors are logged but don't fail employee creation
71
+ - Employee record is always created successfully
72
+ - System user creation can be retried separately if needed
73
+
74
+ ## Usage Examples
75
+
76
+ ### Creating Employee with System User
77
+
78
+ ```python
79
+ employee_data = EmployeeCreate(
80
+ employee_code="EMP-MUM-001",
81
+ first_name="John",
82
+ last_name="Doe",
83
+ email="john.doe@company.com",
84
+ phone="+919876543210",
85
+ designation=Designation.ASM,
86
+ base_city="Mumbai",
87
+ base_state="Maharashtra",
88
+ doj=date.today(),
89
+ emergency_contact={
90
+ "name": "Jane Doe",
91
+ "relation": "Spouse",
92
+ "phone": "+919876543211"
93
+ },
94
+ is_system_user=True, # This triggers system user creation
95
+ created_by="admin_001"
96
+ )
97
+
98
+ employee = await EmployeeService.create_employee(employee_data)
99
+ ```
100
+
101
+ ### Creating Employee without System User
102
+
103
+ ```python
104
+ employee_data = EmployeeCreate(
105
+ # ... same fields ...
106
+ is_system_user=False, # No system user created
107
+ created_by="admin_001"
108
+ )
109
+
110
+ employee = await EmployeeService.create_employee(employee_data)
111
+ ```
112
+
113
+ ## API Response
114
+
115
+ The employee response now includes the `is_system_user` field:
116
+
117
+ ```json
118
+ {
119
+ "user_id": "usr_01HZQX5K3N2P8R6T4V9W",
120
+ "employee_code": "EMP-MUM-001",
121
+ "first_name": "John",
122
+ "last_name": "Doe",
123
+ "email": "john.doe@company.com",
124
+ "designation": "ASM",
125
+ "is_system_user": true,
126
+ "status": "onboarding",
127
+ "created_by": "admin_001",
128
+ "created_at": "2023-01-10T08:00:00Z"
129
+ }
130
+ ```
131
+
132
+ ## Testing
133
+
134
+ Use the provided test script to verify the functionality:
135
+
136
+ ```bash
137
+ cd cuatrolabs-scm-ms
138
+ python test_employee_system_user_creation.py
139
+ ```
140
+
141
+ ## Security Considerations
142
+
143
+ 1. **Temporary Passwords**: System users are created with temporary passwords that should be changed on first login
144
+ 2. **Role Mapping**: Ensure proper role mapping for security permissions
145
+ 3. **Metadata Tracking**: Employee-system user relationship is tracked via metadata
146
+ 4. **Error Isolation**: System user creation failures don't affect employee creation
147
+
148
+ ## Future Enhancements
149
+
150
+ 1. **Email Notifications**: Send temporary password via secure email
151
+ 2. **Role Customization**: Allow custom role assignment during employee creation
152
+ 3. **Bulk Operations**: Support bulk employee creation with system users
153
+ 4. **Audit Trail**: Enhanced logging for system user creation events
app/employees/models/model.py CHANGED
@@ -139,6 +139,12 @@ class EmployeeModel(BaseModel):
139
  description="Application access configuration"
140
  )
141
 
 
 
 
 
 
 
142
  # Location tracking
143
  location_settings: LocationSettingsModel = Field(
144
  default_factory=LocationSettingsModel,
 
139
  description="Application access configuration"
140
  )
141
 
142
+ # System user flag
143
+ is_system_user: bool = Field(
144
+ default=False,
145
+ description="Whether this employee has a corresponding system user"
146
+ )
147
+
148
  # Location tracking
149
  location_settings: LocationSettingsModel = Field(
150
  default_factory=LocationSettingsModel,
app/employees/schemas/schema.py CHANGED
@@ -261,6 +261,12 @@ class EmployeeCreate(BaseModel):
261
  description="Application access configuration"
262
  )
263
 
 
 
 
 
 
 
264
  # Location tracking
265
  location_settings: LocationSettingsSchema = Field(
266
  default_factory=LocationSettingsSchema,
@@ -433,6 +439,7 @@ class EmployeeCreate(BaseModel):
433
  "precision_level": "MEDIUM",
434
  "location_retention_days": 90
435
  },
 
436
  "created_by": "admin_001"
437
  }
438
  }
@@ -457,6 +464,7 @@ class EmployeeUpdate(BaseModel):
457
  status: Optional[EmployeeStatus] = None
458
  app_access: Optional[AppAccessSchema] = None
459
  location_settings: Optional[LocationSettingsSchema] = None
 
460
  metadata: Optional[Dict[str, Any]] = None
461
 
462
  @field_validator("phone")
@@ -529,6 +537,7 @@ class EmployeeResponse(BaseModel):
529
  status: EmployeeStatus
530
  app_access: Dict[str, Any]
531
  location_settings: Dict[str, Any]
 
532
  created_by: str
533
  created_at: str
534
  updated_at: Optional[str]
@@ -578,6 +587,7 @@ class EmployeeResponse(BaseModel):
578
  "precision_level": "MEDIUM",
579
  "location_retention_days": 90
580
  },
 
581
  "created_by": "admin_001",
582
  "created_at": "2023-01-10T08:00:00Z",
583
  "updated_at": "2024-11-24T10:00:00Z",
 
261
  description="Application access configuration"
262
  )
263
 
264
+ # System user flag
265
+ is_system_user: bool = Field(
266
+ default=False,
267
+ description="Whether to create a system user for this employee"
268
+ )
269
+
270
  # Location tracking
271
  location_settings: LocationSettingsSchema = Field(
272
  default_factory=LocationSettingsSchema,
 
439
  "precision_level": "MEDIUM",
440
  "location_retention_days": 90
441
  },
442
+ "is_system_user": False,
443
  "created_by": "admin_001"
444
  }
445
  }
 
464
  status: Optional[EmployeeStatus] = None
465
  app_access: Optional[AppAccessSchema] = None
466
  location_settings: Optional[LocationSettingsSchema] = None
467
+ is_system_user: Optional[bool] = None
468
  metadata: Optional[Dict[str, Any]] = None
469
 
470
  @field_validator("phone")
 
537
  status: EmployeeStatus
538
  app_access: Dict[str, Any]
539
  location_settings: Dict[str, Any]
540
+ is_system_user: bool
541
  created_by: str
542
  created_at: str
543
  updated_at: Optional[str]
 
587
  "precision_level": "MEDIUM",
588
  "location_retention_days": 90
589
  },
590
+ "is_system_user": False,
591
  "created_by": "admin_001",
592
  "created_at": "2023-01-10T08:00:00Z",
593
  "updated_at": "2024-11-24T10:00:00Z",
app/employees/services/service.py CHANGED
@@ -18,6 +18,8 @@ from app.constants.employee_types import (
18
  )
19
  from app.employees.models.model import EmployeeModel
20
  from app.employees.schemas.schema import EmployeeCreate, EmployeeUpdate, EmployeeResponse
 
 
21
 
22
  logger = get_logger(__name__)
23
 
@@ -291,6 +293,16 @@ class EmployeeService:
291
  }
292
  )
293
 
 
 
 
 
 
 
 
 
 
 
294
  # Return response
295
  return EmployeeResponse(**employee_dict)
296
 
@@ -679,3 +691,65 @@ class EmployeeService:
679
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
680
  detail="Error fetching widget data"
681
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  )
19
  from app.employees.models.model import EmployeeModel
20
  from app.employees.schemas.schema import EmployeeCreate, EmployeeUpdate, EmployeeResponse
21
+ from app.system_users.services.service import SystemUserService
22
+ from app.system_users.schemas.schema import CreateUserRequest
23
 
24
  logger = get_logger(__name__)
25
 
 
293
  }
294
  )
295
 
296
+ # Create system user if is_system_user flag is true
297
+ if payload.is_system_user:
298
+ try:
299
+ await EmployeeService._create_employee_system_user(user_id, payload)
300
+ logger.info(f"Created system user for employee {user_id}")
301
+ except Exception as e:
302
+ logger.error(f"Failed to create system user for employee {user_id}: {str(e)}")
303
+ # Note: We don't fail the employee creation if system user creation fails
304
+ # The employee record will still be created successfully
305
+
306
  # Return response
307
  return EmployeeResponse(**employee_dict)
308
 
 
691
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
692
  detail="Error fetching widget data"
693
  )
694
+
695
+ @staticmethod
696
+ async def _create_employee_system_user(employee_user_id: str, employee_payload: EmployeeCreate):
697
+ """
698
+ Create a system user for the newly created employee.
699
+
700
+ Args:
701
+ employee_user_id: The employee's user_id
702
+ employee_payload: The employee creation payload
703
+ """
704
+ try:
705
+ # Initialize system user service
706
+ system_user_service = SystemUserService(get_database())
707
+
708
+ # Generate username from employee code (lowercase, replace hyphens with underscores)
709
+ username = employee_payload.employee_code.lower().replace('-', '_')
710
+
711
+ # Generate a temporary password (employee should change this on first login)
712
+ temp_password = f"Temp@{secrets.token_urlsafe(8)}"
713
+
714
+ # Determine role_id based on designation (you may want to customize this mapping)
715
+ role_id = f"role_{employee_payload.designation.value.lower().replace(' ', '_')}"
716
+
717
+ # Create system user request
718
+ system_user_request = CreateUserRequest(
719
+ username=username,
720
+ email=employee_payload.email,
721
+ merchant_id=employee_payload.created_by, # Using created_by as merchant_id for now
722
+ password=temp_password,
723
+ full_name=f"{employee_payload.first_name} {employee_payload.last_name or ''}".strip(),
724
+ role_id=role_id,
725
+ is_active=True,
726
+ metadata={
727
+ "employee_user_id": employee_user_id,
728
+ "employee_code": employee_payload.employee_code,
729
+ "designation": employee_payload.designation.value,
730
+ "created_from": "employee_creation"
731
+ }
732
+ )
733
+
734
+ # Create the system user
735
+ system_user = await system_user_service.create_user(
736
+ system_user_request,
737
+ employee_payload.created_by
738
+ )
739
+
740
+ logger.info(
741
+ f"System user created for employee",
742
+ extra={
743
+ "employee_user_id": employee_user_id,
744
+ "system_user_id": system_user.user_id,
745
+ "username": username,
746
+ "temp_password": temp_password # In production, this should be sent via secure channel
747
+ }
748
+ )
749
+
750
+ except Exception as e:
751
+ logger.error(
752
+ f"Error creating system user for employee {employee_user_id}",
753
+ exc_info=e
754
+ )
755
+ raise
test_employee_system_user_creation.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to demonstrate employee creation with system user functionality.
4
+ """
5
+ import asyncio
6
+ import json
7
+ from datetime import date
8
+ from app.employees.services.service import EmployeeService
9
+ from app.employees.schemas.schema import EmployeeCreate
10
+ from app.constants.employee_types import Designation, EmployeeStatus
11
+ from app.nosql import get_database
12
+ from app.system_users.services.service import SystemUserService
13
+
14
+
15
+ async def test_employee_with_system_user():
16
+ """Test creating an employee with is_system_user=True"""
17
+
18
+ print("πŸ§ͺ Testing Employee Creation with System User")
19
+ print("=" * 50)
20
+
21
+ # Sample employee data with is_system_user=True
22
+ employee_data = EmployeeCreate(
23
+ employee_code="EMP-TEST-001",
24
+ first_name="John",
25
+ last_name="Doe",
26
+ email="john.doe@company.com",
27
+ phone="+919876543210",
28
+ designation=Designation.ASM,
29
+ base_city="Mumbai",
30
+ base_state="Maharashtra",
31
+ doj=date.today(),
32
+ emergency_contact={
33
+ "name": "Jane Doe",
34
+ "relation": "Spouse",
35
+ "phone": "+919876543211"
36
+ },
37
+ is_system_user=True, # This will trigger system user creation
38
+ created_by="admin_001"
39
+ )
40
+
41
+ try:
42
+ # Create employee (this should also create a system user)
43
+ print("πŸ“ Creating employee with system user flag...")
44
+ employee = await EmployeeService.create_employee(employee_data)
45
+
46
+ print(f"βœ… Employee created successfully!")
47
+ print(f" - User ID: {employee.user_id}")
48
+ print(f" - Employee Code: {employee.employee_code}")
49
+ print(f" - Name: {employee.first_name} {employee.last_name}")
50
+ print(f" - Is System User: {employee.is_system_user}")
51
+
52
+ # Check if system user was created
53
+ system_user_service = SystemUserService(get_database())
54
+
55
+ # Look for system user with matching metadata
56
+ db = get_database()
57
+ system_user_doc = await db["scm_system_users"].find_one({
58
+ "metadata.employee_user_id": employee.user_id
59
+ })
60
+
61
+ if system_user_doc:
62
+ print(f"βœ… System user created successfully!")
63
+ print(f" - System User ID: {system_user_doc['user_id']}")
64
+ print(f" - Username: {system_user_doc['username']}")
65
+ print(f" - Email: {system_user_doc['email']}")
66
+ print(f" - Role ID: {system_user_doc['role_id']}")
67
+ print(f" - Full Name: {system_user_doc['full_name']}")
68
+ else:
69
+ print("❌ System user was not created")
70
+
71
+ except Exception as e:
72
+ print(f"❌ Error: {str(e)}")
73
+
74
+
75
+ async def test_employee_without_system_user():
76
+ """Test creating an employee with is_system_user=False"""
77
+
78
+ print("\nπŸ§ͺ Testing Employee Creation without System User")
79
+ print("=" * 50)
80
+
81
+ # Sample employee data with is_system_user=False (default)
82
+ employee_data = EmployeeCreate(
83
+ employee_code="EMP-TEST-002",
84
+ first_name="Alice",
85
+ last_name="Smith",
86
+ email="alice.smith@company.com",
87
+ phone="+919876543212",
88
+ designation=Designation.SALES_EXECUTIVE,
89
+ base_city="Delhi",
90
+ base_state="Delhi",
91
+ doj=date.today(),
92
+ emergency_contact={
93
+ "name": "Bob Smith",
94
+ "relation": "Spouse",
95
+ "phone": "+919876543213"
96
+ },
97
+ is_system_user=False, # This will NOT create a system user
98
+ created_by="admin_001"
99
+ )
100
+
101
+ try:
102
+ # Create employee (this should NOT create a system user)
103
+ print("πŸ“ Creating employee without system user flag...")
104
+ employee = await EmployeeService.create_employee(employee_data)
105
+
106
+ print(f"βœ… Employee created successfully!")
107
+ print(f" - User ID: {employee.user_id}")
108
+ print(f" - Employee Code: {employee.employee_code}")
109
+ print(f" - Name: {employee.first_name} {employee.last_name}")
110
+ print(f" - Is System User: {employee.is_system_user}")
111
+
112
+ # Check if system user was created (should not exist)
113
+ db = get_database()
114
+ system_user_doc = await db["scm_system_users"].find_one({
115
+ "metadata.employee_user_id": employee.user_id
116
+ })
117
+
118
+ if system_user_doc:
119
+ print("❌ System user was created (unexpected)")
120
+ else:
121
+ print("βœ… System user was not created (as expected)")
122
+
123
+ except Exception as e:
124
+ print(f"❌ Error: {str(e)}")
125
+
126
+
127
+ async def main():
128
+ """Main test function"""
129
+ print("πŸš€ Employee System User Integration Test")
130
+ print("=" * 60)
131
+
132
+ await test_employee_with_system_user()
133
+ await test_employee_without_system_user()
134
+
135
+ print("\n" + "=" * 60)
136
+ print("✨ Test completed!")
137
+
138
+
139
+ if __name__ == "__main__":
140
+ asyncio.run(main())