feat: L'Ami Fiduciaire V1.0.0 — full codebase with Story 0.1 complete
Initial commit of the L'Ami Fiduciaire SaaS platform built on Laravel 12, Vue 3, Inertia.js 2, and Tailwind CSS 4. Story 0.1 (rename folders to declarations in database) is implemented and code-reviewed: migration, rollback, and 6 Pest tests all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
37
database/factories/ClientContactFactory.php
Normal file
37
database/factories/ClientContactFactory.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Client;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ClientContact>
|
||||
*/
|
||||
class ClientContactFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => Client::factory(),
|
||||
'full_name' => fake()->name(),
|
||||
'job_title' => fake()->jobTitle(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'phone' => fake()->phoneNumber(),
|
||||
'is_principal' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the contact as principal.
|
||||
*/
|
||||
public function principal(): static
|
||||
{
|
||||
return $this->state(fn () => ['is_principal' => true]);
|
||||
}
|
||||
}
|
||||
63
database/factories/ClientFactory.php
Normal file
63
database/factories/ClientFactory.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\ClientStatus;
|
||||
use App\Enums\LegalForm;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Client>
|
||||
*/
|
||||
class ClientFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'workspace_id' => Workspace::factory(),
|
||||
'company_name' => fake()->company(),
|
||||
'legal_form' => fake()->randomElement(LegalForm::getValues()),
|
||||
'ice' => (string) fake()->numerify('##########'),
|
||||
'fiscal_id' => (string) fake()->numerify('##########'),
|
||||
'rc' => (string) fake()->numerify('######'),
|
||||
'cnss' => (string) fake()->numerify('##########'),
|
||||
'patente' => (string) fake()->numerify('########'),
|
||||
'contact_last_name' => fake()->lastName(),
|
||||
'contact_first_name' => fake()->firstName(),
|
||||
'contact_job_title' => fake()->jobTitle(),
|
||||
'contact_email' => fake()->unique()->safeEmail(),
|
||||
'contact_phone' => fake()->phoneNumber(),
|
||||
'internal_responsible_id' => null,
|
||||
'status' => ClientStatus::Active,
|
||||
'internal_notes' => fake()->optional(0.3)->sentence(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the model factory.
|
||||
*/
|
||||
public function configure(): static
|
||||
{
|
||||
return $this->afterCreating(function ($client) {
|
||||
ClientContact::factory()->principal()->create([
|
||||
'client_id' => $client->id,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the client with an internal responsible user.
|
||||
*/
|
||||
public function withInternalResponsible(User $user): static
|
||||
{
|
||||
return $this->state(fn () => ['internal_responsible_id' => $user->id]);
|
||||
}
|
||||
}
|
||||
78
database/factories/FolderFactory.php
Normal file
78
database/factories/FolderFactory.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\FolderPriority;
|
||||
use App\Enums\FolderStatus;
|
||||
use App\Enums\FolderType;
|
||||
use App\Models\Client;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Folder>
|
||||
*/
|
||||
class FolderFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$workspace = Workspace::factory()->create();
|
||||
$client = Client::factory()->create(['workspace_id' => $workspace->id]);
|
||||
|
||||
$year = fake()->numberBetween(2024, 2026);
|
||||
$excludeOldVat = array_filter(FolderType::getValues(), fn ($v) => $v !== 'vat');
|
||||
$type = fake()->randomElement(array_values($excludeOldVat));
|
||||
|
||||
$isVatMonthly = $type === 'vat_monthly';
|
||||
$isVatQuarterly = $type === 'vat_quarterly';
|
||||
|
||||
return [
|
||||
'workspace_id' => $workspace->id,
|
||||
'client_id' => $client->id,
|
||||
'created_by' => null,
|
||||
'title' => 'Déclaration '.$this->typeLabel($type).' - '.$year,
|
||||
'type' => $type,
|
||||
'period_year' => $year,
|
||||
'period_month' => $isVatMonthly ? fake()->numberBetween(1, 12) : null,
|
||||
'period_quarter' => $isVatQuarterly ? fake()->numberBetween(1, 4) : null,
|
||||
'due_date' => fake()->dateTimeBetween('now', '+3 months'),
|
||||
'status' => fake()->randomElement(FolderStatus::getValues()),
|
||||
'priority' => fake()->randomElement(FolderPriority::getValues()),
|
||||
'assigned_to' => null,
|
||||
'validated_at' => null,
|
||||
'closed_at' => null,
|
||||
'notes_internal' => fake()->optional(0.3)->sentence(),
|
||||
'notes_client' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the model factory to use existing workspace and client.
|
||||
*/
|
||||
public function forWorkspace(Workspace $workspace): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'workspace_id' => $workspace->id,
|
||||
'client_id' => Client::factory()->create(['workspace_id' => $workspace->id])->id,
|
||||
]);
|
||||
}
|
||||
|
||||
private function typeLabel(string $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
'vat' => 'TVA',
|
||||
'vat_monthly' => 'TVA mensuelle',
|
||||
'vat_quarterly' => 'TVA trimestrielle',
|
||||
'corporate_tax' => 'IS',
|
||||
'income_tax' => 'IR',
|
||||
'cnss' => 'CNSS',
|
||||
'annual_balance' => 'Bilan',
|
||||
default => 'Autre',
|
||||
};
|
||||
}
|
||||
}
|
||||
81
database/factories/UserFactory.php
Normal file
81
database/factories/UserFactory.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\UserGroup;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'group' => UserGroup::User,
|
||||
'remember_token' => Str::random(10),
|
||||
'two_factor_secret' => null,
|
||||
'two_factor_recovery_codes' => null,
|
||||
'two_factor_confirmed_at' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the user has superadmin privileges.
|
||||
*/
|
||||
public function superadmin(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'group' => UserGroup::Superadmin,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the user has admin privileges.
|
||||
*/
|
||||
public function admin(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'group' => UserGroup::Admin,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model has two-factor authentication configured.
|
||||
*/
|
||||
public function withTwoFactor(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'two_factor_secret' => encrypt('secret'),
|
||||
'two_factor_recovery_codes' => encrypt(json_encode(['recovery-code-1'])),
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
28
database/factories/WorkspaceFactory.php
Normal file
28
database/factories/WorkspaceFactory.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Workspace>
|
||||
*/
|
||||
class WorkspaceFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$name = fake()->company();
|
||||
$slug = Str::slug($name);
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'slug' => $slug,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user