complete story 15.1

This commit is contained in:
Naser Mansour 2026-01-09 18:45:17 +02:00
parent 5803410584
commit 959cc0e717
9 changed files with 338 additions and 9 deletions

View File

@ -0,0 +1,19 @@
<?php
namespace App\Enums;
enum PotentialClientType: string
{
case Individual = 'individual';
case Company = 'company';
case Agency = 'agency';
public function label(): string
{
return match ($this) {
self::Individual => __('potential-clients.types.individual'),
self::Company => __('potential-clients.types.company'),
self::Agency => __('potential-clients.types.agency'),
};
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Models;
use App\Enums\PotentialClientType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PotentialClient extends Model
{
use HasFactory;
protected $fillable = [
'type',
'name',
'phone',
'email',
'address',
'social_media',
'website',
'notes',
];
protected function casts(): array
{
return [
'type' => PotentialClientType::class,
];
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Database\Factories;
use App\Enums\PotentialClientType;
use App\Models\PotentialClient;
use Illuminate\Database\Eloquent\Factories\Factory;
class PotentialClientFactory extends Factory
{
protected $model = PotentialClient::class;
public function definition(): array
{
return [
'type' => fake()->randomElement(PotentialClientType::cases()),
'name' => fake()->name(),
'phone' => fake()->phoneNumber(),
'email' => fake()->safeEmail(),
'address' => fake()->address(),
'social_media' => fake()->url(),
'website' => fake()->url(),
'notes' => fake()->optional()->sentence(),
];
}
public function individual(): static
{
return $this->state(['type' => PotentialClientType::Individual]);
}
public function company(): static
{
return $this->state(['type' => PotentialClientType::Company]);
}
public function agency(): static
{
return $this->state(['type' => PotentialClientType::Agency]);
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('potential_clients', function (Blueprint $table) {
$table->id();
$table->string('type');
$table->string('name')->nullable();
$table->string('phone', 50)->nullable();
$table->string('email')->nullable();
$table->text('address')->nullable();
$table->string('social_media')->nullable();
$table->string('website')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('potential_clients');
}
};

View File

@ -246,15 +246,15 @@ return [
## Dev Checklist ## Dev Checklist
- [ ] Create migration file with proper schema - [x] Create migration file with proper schema
- [ ] Run migration successfully - [x] Run migration successfully
- [ ] Create `PotentialClientType` enum with `label()` method - [x] Create `PotentialClientType` enum with `label()` method
- [ ] Create `PotentialClient` model with fillable and casts - [x] Create `PotentialClient` model with fillable and casts
- [ ] Create factory with all state methods - [x] Create factory with all state methods
- [ ] Create English translations - [x] Create English translations
- [ ] Create Arabic translations - [x] Create Arabic translations
- [ ] Write model unit tests - [x] Write model unit tests
- [ ] Verify factory generates valid data - [x] Verify factory generates valid data
## Estimation ## Estimation
@ -264,3 +264,47 @@ return [
## Dependencies ## Dependencies
- None (foundation for other stories in this epic) - None (foundation for other stories in this epic)
---
## Dev Agent Record
### Status
Ready for Review
### Agent Model Used
Claude Opus 4.5 (claude-opus-4-5-20251101)
### Completion Notes
All acceptance criteria met:
- AC1: Migration created with all specified fields and proper schema
- AC2: PotentialClientType enum with Individual, Company, Agency cases and label() method
- AC3: PotentialClient model with fillable array and type casting to enum
- AC4: Factory with definition() and state methods (individual(), company(), agency())
- AC5: English and Arabic translation files with all required keys
### File List
| File | Action |
|------|--------|
| `database/migrations/2026_01_09_163956_create_potential_clients_table.php` | Created |
| `app/Enums/PotentialClientType.php` | Created |
| `app/Models/PotentialClient.php` | Created |
| `database/factories/PotentialClientFactory.php` | Created |
| `lang/en/potential-clients.php` | Created |
| `lang/ar/potential-clients.php` | Created |
| `tests/Unit/Models/PotentialClientTest.php` | Created |
| `tests/Unit/Enums/PotentialClientTypeTest.php` | Created |
### Change Log
- 2026-01-09: Initial implementation of Story 15.1
- Created potential_clients table migration with all specified columns
- Created PotentialClientType enum with label() method for bilingual support
- Created PotentialClient model with proper fillable and casts
- Created factory with realistic data and state methods
- Added English and Arabic translations
- Added comprehensive unit tests for model and enum (13 tests, 32 assertions)

View File

@ -0,0 +1,21 @@
<?php
return [
'title' => 'العملاء المحتملون',
'singular' => 'عميل محتمل',
'types' => [
'individual' => 'فرد',
'company' => 'شركة',
'agency' => 'وكالة',
],
'fields' => [
'type' => 'النوع',
'name' => 'الاسم',
'phone' => 'الهاتف',
'email' => 'البريد الإلكتروني',
'address' => 'العنوان',
'social_media' => 'وسائل التواصل',
'website' => 'الموقع الإلكتروني',
'notes' => 'ملاحظات',
],
];

View File

@ -0,0 +1,21 @@
<?php
return [
'title' => 'Potential Clients',
'singular' => 'Potential Client',
'types' => [
'individual' => 'Individual',
'company' => 'Company',
'agency' => 'Agency',
],
'fields' => [
'type' => 'Type',
'name' => 'Name',
'phone' => 'Phone',
'email' => 'Email',
'address' => 'Address',
'social_media' => 'Social Media',
'website' => 'Website',
'notes' => 'Notes',
],
];

View File

@ -0,0 +1,19 @@
<?php
use App\Enums\PotentialClientType;
test('potential client type has correct cases', function () {
expect(PotentialClientType::cases())->toHaveCount(3);
});
test('potential client type individual has correct value', function () {
expect(PotentialClientType::Individual->value)->toBe('individual');
});
test('potential client type company has correct value', function () {
expect(PotentialClientType::Company->value)->toBe('company');
});
test('potential client type agency has correct value', function () {
expect(PotentialClientType::Agency->value)->toBe('agency');
});

View File

@ -0,0 +1,99 @@
<?php
use App\Enums\PotentialClientType;
use App\Models\PotentialClient;
test('type is cast to PotentialClientType enum', function () {
$client = PotentialClient::factory()->create([
'type' => PotentialClientType::Individual,
]);
expect($client->type)->toBeInstanceOf(PotentialClientType::class)
->and($client->type)->toBe(PotentialClientType::Individual);
});
test('all fields are fillable', function () {
$data = [
'type' => PotentialClientType::Company,
'name' => 'Test Company',
'phone' => '+1234567890',
'email' => 'test@example.com',
'address' => '123 Test Street',
'social_media' => 'https://twitter.com/test',
'website' => 'https://example.com',
'notes' => 'Some notes here',
];
$client = PotentialClient::create($data);
expect($client->type)->toBe(PotentialClientType::Company)
->and($client->name)->toBe('Test Company')
->and($client->phone)->toBe('+1234567890')
->and($client->email)->toBe('test@example.com')
->and($client->address)->toBe('123 Test Street')
->and($client->social_media)->toBe('https://twitter.com/test')
->and($client->website)->toBe('https://example.com')
->and($client->notes)->toBe('Some notes here');
});
test('nullable fields can be null', function () {
$client = PotentialClient::factory()->create([
'type' => PotentialClientType::Individual,
'name' => null,
'phone' => null,
'email' => null,
'address' => null,
'social_media' => null,
'website' => null,
'notes' => null,
]);
expect($client->name)->toBeNull()
->and($client->phone)->toBeNull()
->and($client->email)->toBeNull()
->and($client->address)->toBeNull()
->and($client->social_media)->toBeNull()
->and($client->website)->toBeNull()
->and($client->notes)->toBeNull();
});
test('factory individual state creates individual type', function () {
$client = PotentialClient::factory()->individual()->create();
expect($client->type)->toBe(PotentialClientType::Individual);
});
test('factory company state creates company type', function () {
$client = PotentialClient::factory()->company()->create();
expect($client->type)->toBe(PotentialClientType::Company);
});
test('factory agency state creates agency type', function () {
$client = PotentialClient::factory()->agency()->create();
expect($client->type)->toBe(PotentialClientType::Agency);
});
test('factory generates valid data', function () {
$client = PotentialClient::factory()->create();
expect($client->type)->toBeInstanceOf(PotentialClientType::class)
->and($client->id)->toBeInt();
});
test('type enum returns english labels', function () {
app()->setLocale('en');
expect(PotentialClientType::Individual->label())->toBe('Individual')
->and(PotentialClientType::Company->label())->toBe('Company')
->and(PotentialClientType::Agency->label())->toBe('Agency');
});
test('type enum returns arabic labels', function () {
app()->setLocale('ar');
expect(PotentialClientType::Individual->label())->toBe('فرد')
->and(PotentialClientType::Company->label())->toBe('شركة')
->and(PotentialClientType::Agency->label())->toBe('وكالة');
});