diff --git a/app/Enums/Invoice/TaskType.php b/app/Enums/Invoice/TaskType.php new file mode 100644 index 0000000..d2e1b9f --- /dev/null +++ b/app/Enums/Invoice/TaskType.php @@ -0,0 +1,73 @@ + 1, + 'sio' => 2, + 'package' => 3, + 'arbitrary' => 4, + ]; + } + + /** + * Enum labels + * + * @return array + */ + protected static function labels(): array + { + return [ + 'web' => trans('Web'), + 'sio' => trans('Sio'), + 'package' => trans('Forfait'), + 'arbitrary' => trans('Libre'), + ]; + } + + /** + * Enum prices + * + * @return array + */ + protected static function prices(): array + { + return [ + 'web' => 460, + 'sio' => 690, + // * Linked to packages table + 'package' => 0, + // * Arbitrary number + 'arbitrary' => 0, + ]; + } + + /** + * @param string $attribute + * @return integer|string + */ + public function __get(string $attribute) + { + if ($attribute === 'price') { + return $this->prices()[$this->getDefinition()]; + } + return parent::__get($attribute); + } +} diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php new file mode 100644 index 0000000..28b6032 --- /dev/null +++ b/app/Http/Controllers/CompanyController.php @@ -0,0 +1,21 @@ + [ + 'label' => trans('Nom'), + 'type' => CrudType::string(), + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::update(), + CrudAction::create() + ] + ], + ]; + parent::__construct($attributes); + } +} diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php new file mode 100644 index 0000000..5ba7c7b --- /dev/null +++ b/app/Models/Invoice.php @@ -0,0 +1,314 @@ + 'array' + ]; + + /** + * Invoice + * + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + self::$editableProperties = [ + 'ref' => [ + 'label' => trans('Réference'), + 'type' => CrudType::string(), + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view() + ] + ], + 'firstname' => [ + 'label' => trans('Prénom'), + 'type' => CrudType::string(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'lastname' => [ + 'label' => trans('Nom'), + 'type' => CrudType::string(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'object' => [ + 'label' => trans('Object'), + 'type' => CrudType::string(), + 'nullable' => false, + 'actions' => [ + // * Truncated on view any + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'tasks' => [ + 'label' => trans('Tâches'), + 'type' => CrudType::json(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'total' => [ + 'label' => trans('Montant total'), + 'type' => CrudType::float(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'total_days' => [ + 'label' => trans('Montant total jours'), + 'type' => CrudType::float(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'comment' => [ + 'label' => trans('Commentaire'), + 'type' => CrudType::text(), + 'nullable' => true, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'show_days' => [ + 'label' => trans('Afficher les jours'), + 'type' => CrudType::boolean(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'signed_date' => [ + 'label' => trans('Date de signature'), + 'type' => CrudType::datetime(), + 'nullable' => true, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'sent_client_date' => [ + 'label' => trans('Date d\'envoi client'), + 'type' => CrudType::datetime(), + 'nullable' => true, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'sent_accounting_date' => [ + 'label' => trans('Date d\'envoi compta'), + 'type' => CrudType::datetime(), + 'nullable' => true, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'site_id' => [ + 'label' => trans('models.classes.sites'), + 'type' => CrudType::belongsTo(), + 'belongsTo' => Site::class, + 'belongsToLabel' => 'site', + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'company_id' => [ + 'label' => trans('models.classes.companies'), + 'type' => CrudType::belongsTo(), + 'belongsTo' => Company::class, + 'belongsToLabel' => 'companie', + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'invoiced_to_id' => [ + 'label' => trans('models.classes.companies'), + 'type' => CrudType::belongsTo(), + 'belongsTo' => Company::class, + 'belongsToLabel' => 'entité', + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + CrudAction::create(), + CrudAction::update() + ] + ], + 'created_at' => [ + 'label' => trans('Crée le'), + 'type' => CrudType::datetime(), + 'nullable' => false, + 'actions' => [ + CrudAction::view(), + ] + ], + 'updated_at' => [ + 'label' => trans('Mis à jour'), + 'type' => CrudType::datetime(), + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + ] + ], + ]; + parent::__construct($attributes); + } + + /** + * The attributes that are mass assignable. + * + * @return void + */ + protected static function booted() + { + static::creating(function (self $model) { + $model->ref = \sprintf( + '%s_%s_%s', + Carbon::now()->isoFormat('Y-m'), + Str::padLeft(Invoice::whereYear('created_at', Carbon::now()->isoFormat('Y')) + ->whereMonth('created_at', Carbon::now()->isoFormat('M')) + ->count() ?: 1, 4, '0'), + Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 2) + ); + }); + } + + // * METHODS + + /** + * Get the tasks line structure skel + * + * @return array + */ + public static function getTaskStructure(): array + { + return [ + // * string + 'desc' => '', + // * usigned float + 'days' => '', + // * Invoice\TaskType + 'type' => '', + // * usigned float + 'amount' => '', + ]; + } + + // * RELATIONSHIPS + + /** + * BelongsTo Site + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function site() + { + return $this->BelongsTo(Site::class); + } + + /** + * BelongsTo Company + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function company() + { + return $this->BelongsTo(Company::class); + } + /** + * BelongsTo Company + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function companyInvoiced() + { + return $this->BelongsTo(Company::class, 'invoiced_to_id'); + } +} diff --git a/app/Models/Package.php b/app/Models/Package.php index eb1e49a..d80ab35 100644 --- a/app/Models/Package.php +++ b/app/Models/Package.php @@ -7,13 +7,11 @@ use Illuminate\Database\Eloquent\Model; use Kwaadpepper\CrudPolicies\Enums\CrudAction; use Kwaadpepper\CrudPolicies\Enums\CrudType; use Kwaadpepper\CrudPolicies\Traits\IsCrudModel; -use Kwaadpepper\Enum\Traits\CastsEnums; class Package extends Model { use HasFactory; use IsCrudModel; - use CastsEnums; /** * Indicates if the model should be timestamped. @@ -46,7 +44,7 @@ class Package extends Model ]; /** - * File + * Package * * @param array $attributes */ @@ -65,7 +63,7 @@ class Package extends Model ] ], 'amount' => [ - 'label' => trans('Montant'), + 'label' => trans('Montant (par jour)'), 'type' => CrudType::float(), 'nullable' => false, 'actions' => [ diff --git a/app/Policies/CompanyPolicy.php b/app/Policies/CompanyPolicy.php new file mode 100644 index 0000000..8a26884 --- /dev/null +++ b/app/Policies/CompanyPolicy.php @@ -0,0 +1,38 @@ +policies = [ + 'company' => [ + 'viewAny' => URR::make('userIsAdmin', $user), + 'create' => URR::make('userIsAdmin', $user), + 'update' => URR::make('userIsAdmin', $user), + 'delete' => URR::make('userIsAdmin', $user) + ] + ]; + } +} diff --git a/app/Policies/InvoicePolicy.php b/app/Policies/InvoicePolicy.php new file mode 100644 index 0000000..70c06fb --- /dev/null +++ b/app/Policies/InvoicePolicy.php @@ -0,0 +1,38 @@ +policies = [ + 'invoice' => [ + 'viewAny' => URR::make('userIsAdmin', $user), + 'create' => URR::make('userIsAdmin', $user), + 'update' => URR::make('userIsAdmin', $user), + 'delete' => URR::make('userIsAdmin', $user) + ] + ]; + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index a341400..8c5438b 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -3,9 +3,11 @@ namespace App\Providers; use App\Models\Brand; +use App\Models\Company; use App\Models\Credit\Account; use App\Models\Credit\CreditLog; use App\Models\File; +use App\Models\Invoice; use App\Models\Label; use App\Models\Log; use App\Models\MacroSchedule\Marker; @@ -89,6 +91,8 @@ class RouteServiceProvider extends ServiceProvider Route::model('label', Label::class); Route::model('marker', Marker::class); Route::model('package', Package::class); + Route::model('company', Company::class); + Route::model('invoice', Invoice::class); Route::model('file', File::class); Route::model('log', Log::class); Route::model('user', User::class); diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php new file mode 100644 index 0000000..aefb175 --- /dev/null +++ b/database/factories/CompanyFactory.php @@ -0,0 +1,28 @@ + $this->faker->name, + ]; + } +} diff --git a/database/factories/InvoiceFactory.php b/database/factories/InvoiceFactory.php new file mode 100644 index 0000000..a551c1a --- /dev/null +++ b/database/factories/InvoiceFactory.php @@ -0,0 +1,80 @@ +faker->numberBetween(1, 4); + $type = \collect(TaskType::toArray())->random(); + $amount = 0; + switch ($type) { + case TaskType::web(): + $amount = $days * $type->price; + break; + case TaskType::sio(): + $amount = $days * $type->price; + break; + case TaskType::package(): + $amount = $days * (\optional(Package::query()->inRandomOrder()->first())->amount ?? + $this->faker->randomFloat(null, 150, 650)); + break; + case TaskType::package(): + $amount = $days * $this->faker->randomFloat(null, 150, 650); + break; + } + $totalDays += $days; + $total += $amount; + $tasks[] = \array_merge(Invoice::getTaskStructure(), [ + 'desc' => $this->faker->words(9, true), + 'days' => $days, + 'type' => \collect(TaskType::toArray())->random()->value, + 'amount' => $amount, + ]); + } //end for + + return [ + 'firstname' => $this->faker->firstName, + 'lastname' => $this->faker->lastName, + 'object' => $this->faker->words(3, true), + // * constructed object + 'tasks' => $tasks, + // * constructed object result + 'total' => $total, + // * constructed object result + 'total_days' => $totalDays, + 'comment' => $this->faker->text(\rand(20, 40)), + 'show_days' => $this->faker->boolean(75), + 'signed_date' => $this->faker->boolean(25) ? + $this->faker->dateTimeBetween('-1 year', '+1 year') : null, + 'sent_client_date' => $this->faker->boolean(25) ? + $this->faker->dateTimeBetween('-1 year', '+1 year') : null, + 'sent_accounting_date' => $this->faker->boolean(25) ? + $this->faker->dateTimeBetween('-1 year', '+1 year') : null, + ]; + } +} diff --git a/database/migrations/2022_08_22_154457_create_packages_table.php b/database/migrations/2022_08_22_154457_create_packages_table.php index c68332e..65d1bbb 100644 --- a/database/migrations/2022_08_22_154457_create_packages_table.php +++ b/database/migrations/2022_08_22_154457_create_packages_table.php @@ -17,7 +17,7 @@ class CreatePackagesTable extends Migration $table->id(); $table->string('name'); - $table->unsignedFloat('amount'); + $table->unsignedFloat('amount')->comment('The cost per day'); }); } diff --git a/database/migrations/2022_08_23_085321_create_companies_table.php b/database/migrations/2022_08_23_085321_create_companies_table.php new file mode 100644 index 0000000..1de59aa --- /dev/null +++ b/database/migrations/2022_08_23_085321_create_companies_table.php @@ -0,0 +1,34 @@ +id(); + + $table->string('name'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('companies'); + } +} diff --git a/database/migrations/2022_08_23_085833_create_invoices_table.php b/database/migrations/2022_08_23_085833_create_invoices_table.php new file mode 100644 index 0000000..0d62f83 --- /dev/null +++ b/database/migrations/2022_08_23_085833_create_invoices_table.php @@ -0,0 +1,73 @@ +id(); + $table->string('ref') + ->comment(sprintf( + '%s. %s. %s. %s.', + 'The invoice reference built as YYYY_MM_INCREMENT_USER', + 'INCREMENT is the N invoice of the month', + 'USER is the first two letters of the user who last edited the invoice', + 'This field has to generated only on create' + )); + + $table->string('firstname')->comment('The person to which the invoice is addressed.'); + $table->string('lastname')->comment('The person to which the invoice is addressed.'); + + $table->string('object')->comment('The invoice object.'); + + $table->text('tasks') + ->comment( + // phpcs:ignore Generic.Files.LineLength.TooLong + 'A JSON object, each line has to be contructed using these fields description, days, type Invoice\TaskType, amount' + ); + + $table->unsignedFloat('total')->comment('The total calculated using all tasks'); + $table->unsignedFloat('total_days')->comment('The total days calculated using all tasks'); + + $table->text('comment')->nullable() + ->comment('Any comment joined to the invoice'); + + $table->boolean('show_days')->comment('Show days on the printed invoice'); + + $table->timestamp('signed_date')->nullable() + ->comment('The date the invoice was signed'); + $table->timestamp('sent_client_date')->nullable() + ->comment('The date the invoice was send to the client'); + $table->timestamp('sent_accounting_date')->nullable() + ->comment('The date the invoice was send to the client'); + + $table->foreignIdFor(Site::class)->constrained(); + $table->foreignIdFor(Company::class)->constrained(); + $table->foreignId('invoiced_to_id') + ->constrained((new Company())->getTable()); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('invoices'); + } +} diff --git a/database/seeders/InvoicingSeeder.php b/database/seeders/InvoicingSeeder.php index d932124..9cd2f4c 100644 --- a/database/seeders/InvoicingSeeder.php +++ b/database/seeders/InvoicingSeeder.php @@ -2,9 +2,15 @@ namespace Database\Seeders; +use App\Models\Company; +use App\Models\Invoice; use App\Models\Package; +use App\Models\Site; +use App\Models\User; use Illuminate\Database\Seeder; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Str; class InvoicingSeeder extends Seeder { @@ -15,8 +21,34 @@ class InvoicingSeeder extends Seeder */ public function run() { + /** Reset all invoices */ + DB::table((new Invoice())->getTable())->truncate(); + /** Reset all packages (forfaits) */ - DB::table((new Package())->getTable())->truncate(); + Package::all()->map(function (Package $model) { + $model->deleteOrFail(); + }); Package::factory(6)->create(); + + /** Reset all companies */ + Company::all()->map(function (Company $model) { + $model->deleteOrFail(); + }); + Company::factory(6)->create(); + + Invoice::factory(45)->make()->each(function (Invoice $model) { + $model->ref = \sprintf( + '%s_%s_%s', + Carbon::now()->isoFormat('Y-m'), + Str::padLeft(Invoice::whereYear('created_at', Carbon::now()->isoFormat('Y')) + ->whereMonth('created_at', Carbon::now()->isoFormat('M')) + ->count() ?: 1, 4, '0'), + Str::substr(Str::upper(Str::slug(User::query()->inRandomOrder()->first()->name)), 0, 2) + ); + $model->site()->associate(Site::query()->inRandomOrder()->first()); + $model->company()->associate(Company::query()->inRandomOrder()->first()); + $model->companyInvoiced()->associate(Company::query()->inRandomOrder()->first()); + $model->saveQuietly(); + }); } } diff --git a/resources/lang/en/models.php b/resources/lang/en/models.php index b2d027d..e4446c6 100644 --- a/resources/lang/en/models.php +++ b/resources/lang/en/models.php @@ -14,5 +14,7 @@ return [ 'brands' => 'Brands', 'labels' => 'Labels', 'packages' => 'Packages', + 'invoices' => 'Invoices', + 'companies' => 'Companies', ] ]; diff --git a/resources/lang/fr/models.php b/resources/lang/fr/models.php index a83c74b..b85e130 100644 --- a/resources/lang/fr/models.php +++ b/resources/lang/fr/models.php @@ -14,5 +14,7 @@ return [ 'brands' => 'Marques', 'labels' => 'Labels', 'packages' => 'Forfaits', + 'invoices' => 'Factures', + 'companies' => 'Compagnies', ] ];