complete story 15.1
This commit is contained in:
parent
5803410584
commit
959cc0e717
|
|
@ -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'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
@ -246,15 +246,15 @@ return [
|
|||
|
||||
## Dev Checklist
|
||||
|
||||
- [ ] Create migration file with proper schema
|
||||
- [ ] Run migration successfully
|
||||
- [ ] Create `PotentialClientType` enum with `label()` method
|
||||
- [ ] Create `PotentialClient` model with fillable and casts
|
||||
- [ ] Create factory with all state methods
|
||||
- [ ] Create English translations
|
||||
- [ ] Create Arabic translations
|
||||
- [ ] Write model unit tests
|
||||
- [ ] Verify factory generates valid data
|
||||
- [x] Create migration file with proper schema
|
||||
- [x] Run migration successfully
|
||||
- [x] Create `PotentialClientType` enum with `label()` method
|
||||
- [x] Create `PotentialClient` model with fillable and casts
|
||||
- [x] Create factory with all state methods
|
||||
- [x] Create English translations
|
||||
- [x] Create Arabic translations
|
||||
- [x] Write model unit tests
|
||||
- [x] Verify factory generates valid data
|
||||
|
||||
## Estimation
|
||||
|
||||
|
|
@ -264,3 +264,47 @@ return [
|
|||
## Dependencies
|
||||
|
||||
- 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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'العملاء المحتملون',
|
||||
'singular' => 'عميل محتمل',
|
||||
'types' => [
|
||||
'individual' => 'فرد',
|
||||
'company' => 'شركة',
|
||||
'agency' => 'وكالة',
|
||||
],
|
||||
'fields' => [
|
||||
'type' => 'النوع',
|
||||
'name' => 'الاسم',
|
||||
'phone' => 'الهاتف',
|
||||
'email' => 'البريد الإلكتروني',
|
||||
'address' => 'العنوان',
|
||||
'social_media' => 'وسائل التواصل',
|
||||
'website' => 'الموقع الإلكتروني',
|
||||
'notes' => 'ملاحظات',
|
||||
],
|
||||
];
|
||||
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
|
|
@ -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');
|
||||
});
|
||||
|
|
@ -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('وكالة');
|
||||
});
|
||||
Loading…
Reference in New Issue