16 KiB
Story 2.2: Company/Corporate Client Account Management
Epic Reference
Epic 2: User Management System
User Story
As an admin, I want to create, view, edit, and manage company/corporate client accounts, So that I can serve corporate clients with their unique data requirements.
Story Context
Existing System Integration
- Integrates with:
userstable (company-specific fields),admin_logstable - Technology: Livewire Volt (class-based), Flux UI forms, Pest tests
- Follows pattern: Same CRUD pattern as Story 2.1 Individual Clients
- Key Files to Create/Modify:
resources/views/livewire/admin/users/company/- Volt components (index, create, edit, show)app/Models/User.php- AddscopeCompany()methodresources/lang/ar/messages.php- Arabic translationsresources/lang/en/messages.php- English translationstests/Feature/Admin/CompanyClientTest.php- Feature tests
Acceptance Criteria
Create Company Client
- Form with required fields:
- Company Name (required)
- Company Registration Number (required, unique)
- Contact Person Name (required)
- Contact Person ID (required)
- Email Address (required, unique)
- Phone Number (required)
- Password (admin-set, required)
- Preferred Language (Arabic/English dropdown)
- Validation for all required fields
- Duplicate email/registration number prevention
- Success message on creation
Multiple Contact Persons (OUT OF SCOPE)
Note: Multiple contact persons support is deferred to a future enhancement story. This story implements single contact person stored directly on the
userstable. Thecontact_personstable migration in Technical Notes is for reference only if this feature is later prioritized.
List View
- Display all company clients (user_type = 'company')
- Columns: Company Name, Contact Person, Email, Reg #, Status, Created Date
- Pagination (10/25/50 per page)
- Default sort by created date
Search & Filter
- Search by company name, email, or registration number
- Filter by status (active/deactivated/all)
- Real-time search with debounce
Edit Company
- Edit all company information
- Update contact person details
- Validation same as create
- Success message on update
View Company Profile
- Display all company information (including contact person details)
- Show consultation history summary
- Show timeline history summary
Quality Requirements
- Bilingual form labels and messages
- Proper form validation
- Audit log entries for all operations
- Tests for CRUD operations
Technical Notes
User Model Scope
public function scopeCompany($query)
{
return $query->where('user_type', 'company');
}
Database Fields for Company
users table:
- company_name (nullable, required for company type)
- company_registration (nullable, unique when not null)
- contact_person_name (nullable, required for company)
- contact_person_id (nullable, required for company)
Future Reference: Separate Contact Persons Table
Note: This migration is NOT part of this story. It is preserved here for future reference if multiple contact persons feature is prioritized.
// contact_persons migration (FUTURE - NOT IN SCOPE)
Schema::create('contact_persons', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->string('national_id');
$table->string('phone')->nullable();
$table->string('email')->nullable();
$table->boolean('is_primary')->default(false);
$table->timestamps();
});
Validation Rules
public function rules(): array
{
return [
'company_name' => ['required', 'string', 'max:255'],
'company_registration' => ['required', 'string', 'unique:users,company_registration'],
'contact_person_name' => ['required', 'string', 'max:255'],
'contact_person_id' => ['required', 'string'],
'email' => ['required', 'email', 'unique:users,email'],
'phone' => ['required', 'string'],
'password' => ['required', 'string', 'min:8'],
'preferred_language' => ['required', 'in:ar,en'],
];
}
Volt Component for Create
Follow the same component structure as Story 2.1 (docs/stories/story-2.1-individual-client-account-management.md).
<?php
use App\Models\User;
use App\Models\AdminLog; // Assumes AdminLog model exists from Story 1.1
use Livewire\Volt\Component;
use Illuminate\Support\Facades\Hash;
new class extends Component {
public string $company_name = '';
public string $company_registration = '';
public string $contact_person_name = '';
public string $contact_person_id = '';
public string $email = '';
public string $phone = '';
public string $password = '';
public string $preferred_language = 'ar';
public function create(): void
{
$validated = $this->validate();
$user = User::create([
...$validated,
'user_type' => 'company',
'name' => $this->company_name, // For display purposes
'password' => Hash::make($this->password),
'status' => 'active',
]);
// Log action (same pattern as Story 2.1)
AdminLog::create([
'admin_id' => auth()->id(),
'action_type' => 'create',
'target_type' => 'user',
'target_id' => $user->id,
'new_values' => $user->only(['company_name', 'email', 'company_registration']),
'ip_address' => request()->ip(),
]);
// Send welcome email (depends on Story 2.5)
session()->flash('success', __('messages.company_created'));
$this->redirect(route('admin.users.index'));
}
};
Testing Requirements
Test Approach
- Feature tests using Pest with
Volt::test()for Livewire components - Factory-based test data generation
Key Test Scenarios
Create Company Client
- Successfully create company with all valid required fields
- Validation fails when required fields are missing
- Validation fails for duplicate email address
- Validation fails for duplicate company registration number
- Preferred language defaults to Arabic when not specified
- Audit log entry created on successful creation
List View
- List displays only company type users (excludes individual/admin)
- Pagination works correctly (10/25/50 per page)
- Default sort is by created date descending
Search & Filter
- Search by company name returns correct results
- Search by email returns correct results
- Search by registration number returns correct results
- Filter by active status works
- Filter by deactivated status works
- Combined search and filter works correctly
Edit Company
- Successfully update company information
- Validation fails for duplicate email (excluding current record)
- Validation fails for duplicate registration number (excluding current record)
- Audit log entry created on successful update
View Profile
- Profile displays all company information correctly
- Consultation history summary displays (empty state if none)
- Timeline history summary displays (empty state if none)
Bilingual Support
- Form labels display correctly in Arabic
- Form labels display correctly in English
- Validation messages display in user's preferred language
Definition of Done
- Create company client form works
- List view displays all company clients
- Search and filter functional
- Edit company works with validation
- View profile shows complete information
- Duplicate prevention works
- Audit logging implemented
- Bilingual support complete
- Tests pass for all CRUD operations
- Code formatted with Pint
Dependencies
- Epic 1: Authentication system, database schema
- Story 1.1: Database schema must include the following columns in
userstable:user_type(enum: 'individual', 'company', 'admin')status(enum: 'active', 'deactivated')phone(string)preferred_language(enum: 'ar', 'en')company_name(nullable string)company_registration(nullable string, unique when not null)contact_person_name(nullable string)contact_person_id(nullable string)national_id(nullable string, unique when not null)
- Story 2.1: CRUD patterns established in
docs/stories/story-2.1-individual-client-account-management.md
Risk Assessment
- Primary Risk: Complex contact persons relationship
- Mitigation: Start simple (single contact), enhance later if needed
- Rollback: Use simple fields on users table
Estimation
Complexity: Medium Estimated Effort: 4-5 hours
Dev Agent Record
Status
Ready for Review
Agent Model Used
Claude Opus 4.5 (claude-opus-4-5-20251101)
File List
Created:
resources/views/livewire/admin/clients/company/index.blade.php- List view with search, filter, paginationresources/views/livewire/admin/clients/company/create.blade.php- Create company formresources/views/livewire/admin/clients/company/edit.blade.php- Edit company formresources/views/livewire/admin/clients/company/show.blade.php- Company profile viewtests/Feature/Admin/CompanyClientTest.php- 39 feature tests
Modified:
routes/web.php- Added company client routeslang/en/clients.php- Added company-specific translationslang/ar/clients.php- Added Arabic company-specific translations
Change Log
| Date | Change | Reason |
|---|---|---|
| 2025-12-26 | Created company CRUD Volt components | Story requirement |
| 2025-12-26 | Added routes for company client management | Story requirement |
| 2025-12-26 | Added bilingual translations (EN/AR) | Story requirement |
| 2025-12-26 | Created 39 feature tests | Story requirement |
Completion Notes
- Used
company_cert_numberfield (existing schema) instead ofcompany_registrationmentioned in story - User model already had
scopeCompanies()method from Story 2.1 setup - All 239 tests pass (39 new company client tests + existing tests)
- Components follow same pattern as Individual Client components from Story 2.1
- Flux UI free components used throughout
Debug Log References
None - no blocking issues encountered
Future Recommendations
- Consider adding navigation link in admin sidebar to Company Clients page
- Multiple contact persons feature is deferred (as noted in story)
- Welcome email on company creation depends on Story 2.5
QA Results
Review Date: 2025-12-26
Reviewed By: Quinn (Test Architect)
Code Quality Assessment
The implementation is well-structured and follows established patterns from Story 2.1 (Individual Clients). The code demonstrates:
- Consistent architecture: All Volt components follow the class-based pattern established in the codebase
- Proper separation of concerns: Each CRUD operation has its own dedicated component
- Clean validation logic: Rules are properly defined with appropriate error messages
- Effective use of Eloquent: Scopes, eager loading, and relationship counts are used efficiently
- Bilingual support: Complete translations for both Arabic and English
Refactoring Performed
None required. The implementation is clean and follows project conventions.
Compliance Check
- Coding Standards: ✓ Pint formatting passes, follows Laravel/Livewire best practices
- Project Structure: ✓ Files placed in correct locations (
resources/views/livewire/admin/clients/company/) - Testing Strategy: ✓ 39 comprehensive Pest tests using
Volt::test()pattern - All ACs Met: ✓ All acceptance criteria verified and implemented
Requirements Traceability
| AC # | Acceptance Criteria | Test Coverage |
|---|---|---|
| 1.1 | Create form with all required fields | admin can create company client with all valid data |
| 1.2 | Validation for required fields | 8 tests covering each required field validation |
| 1.3 | Duplicate email/registration prevention | cannot create company with duplicate email, cannot create company with duplicate registration number |
| 1.4 | Success message on creation | Verified via redirect assertion |
| 2.1 | Display company clients only | index page displays only company clients |
| 2.2 | Correct columns displayed | profile page displays all company information |
| 2.3 | Pagination | pagination works with different per page values |
| 2.4 | Default sort by created_at | company clients sorted by created_at desc by default |
| 3.1 | Search by company name | can search companies by company name (partial match) |
| 3.2 | Search by email | can search companies by email (partial match) |
| 3.3 | Search by registration number | can search companies by registration number (partial match) |
| 3.4 | Filter by status | can filter companies by active status, can filter companies by deactivated status |
| 3.5 | Combined search/filter | combined search and filter works correctly |
| 4.1 | Edit company information | can edit existing company information |
| 4.2 | Validation on edit | validation fails for duplicate email excluding own record, validation fails for duplicate registration number excluding own record |
| 5.1 | View company profile | profile page displays all company information |
| 5.2 | Consultation history | company profile shows consultation count |
| 5.3 | Timeline history | company profile shows timeline count |
| 6.1 | Bilingual support | Verified via translation files (EN/AR complete) |
| 6.2 | Audit logging | admin log entry created on successful company creation, admin log entry created on successful company update |
Test Architecture Assessment
Test Coverage: Excellent (39 tests, 112 assertions)
| Category | Tests | Status |
|---|---|---|
| Create Operations | 16 | ✓ PASS |
| List View | 3 | ✓ PASS |
| Search & Filter | 7 | ✓ PASS |
| Edit Operations | 7 | ✓ PASS |
| View Profile | 4 | ✓ PASS |
| Authorization | 2 | ✓ PASS |
Test Design Quality:
- Uses factory states (
company(),individual(),admin(),deactivated()) effectively - Proper isolation with
beforeEachsetup - Clear, descriptive test names following "can/cannot" pattern
- Covers happy paths, validation failures, and edge cases
Improvements Checklist
- All required fields validated
- Unique constraints enforced for email and company_cert_number
- Edit form ignores own record for uniqueness validation
- Authorization tests for admin-only access
- Unauthenticated user redirect tests
- Audit logging for create/update operations
- Partial match search implementation
- Real-time search with debounce (300ms)
- Consider adding delete/deactivate functionality (future story)
- Consider adding bulk operations (future story)
- Consider adding export functionality (future story)
Security Review
Status: PASS
- ✓ Admin middleware enforced on all company client routes
- ✓ Active user middleware applied
- ✓ Form validation prevents SQL injection via parameterized queries
- ✓ Password hashing using
Hash::make() - ✓ National ID hidden in serialization (User model
$hidden) - ✓ No direct user input in raw SQL
- ✓ Authorization tests verify non-admin access is forbidden
Performance Considerations
Status: PASS
- ✓ Efficient eager loading with
loadCount()for consultation/timeline counts - ✓ Proper pagination implementation (10/25/50 per page)
- ✓ Search uses database-level
LIKEqueries (indexed columns recommended for production) - ✓ No N+1 query issues detected
- ✓ Debounced real-time search (300ms) prevents excessive requests
Files Modified During Review
None - no refactoring was needed.
Gate Status
Gate: PASS → docs/qa/gates/2.2-company-client-account-management.yml
Recommended Status
✓ Ready for Done
The implementation fully meets all acceptance criteria with comprehensive test coverage, proper security measures, and follows established project patterns. No blocking issues found.