From 66c615b4f31da2d008f5b9706dffa83e1196beca Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 05:09:23 +0700 Subject: [PATCH 01/71] chore(db): init basic tables for `queue`, `notifications` and `sessions` Signed-off-by: Fery Wardiyanto --- ...022_05_10_000000_create_sessions_table.php | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 database/migrations/2022_05_10_000000_create_sessions_table.php diff --git a/database/migrations/2022_05_10_000000_create_sessions_table.php b/database/migrations/2022_05_10_000000_create_sessions_table.php new file mode 100644 index 0000000..d02620c --- /dev/null +++ b/database/migrations/2022_05_10_000000_create_sessions_table.php @@ -0,0 +1,87 @@ +string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + if (config('queue.default') !== 'database') { + Schema::create(config('queue.connections.database.table'), function (Blueprint $table) { + $table->id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->text('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + } + + Schema::create('notifications', function (Blueprint $table) { + $table->uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + + Schema::create(config('queue.failed.table'), function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists(config('queue.connections.database.table')); + Schema::dropIfExists('notifications'); + + if (config('queue.default') !== 'database') { + Schema::dropIfExists('job_batches'); + Schema::dropIfExists(config('queue.failed.table')); + } + + if (config('session.driver') === 'database') { + Schema::dropIfExists(config('session.table')); + } + } +}; From 3bcaaf80201f69189583ab5d502a75240e8b2d56 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 05:11:54 +0700 Subject: [PATCH 02/71] chore(resources): init basic resources needed on ever apps Signed-off-by: Fery Wardiyanto --- resources/assets/app-bg.jpeg | Bin 0 -> 406903 bytes resources/assets/apple-touch-icon.png | Bin 0 -> 2788 bytes resources/assets/favicon.svg | 4 ++ resources/assets/icon-192x192.png | Bin 0 -> 3007 bytes resources/assets/icon-512x512.png | Bin 0 -> 7328 bytes resources/assets/mstile-150x150.png | Bin 0 -> 3128 bytes resources/assets/safari-pinned-tab.svg | 62 +++++++++++++++++++++++++ resources/views/app.blade.php | 28 +++++++++++ 8 files changed, 94 insertions(+) create mode 100644 resources/assets/app-bg.jpeg create mode 100644 resources/assets/apple-touch-icon.png create mode 100644 resources/assets/favicon.svg create mode 100644 resources/assets/icon-192x192.png create mode 100644 resources/assets/icon-512x512.png create mode 100644 resources/assets/mstile-150x150.png create mode 100644 resources/assets/safari-pinned-tab.svg create mode 100644 resources/views/app.blade.php diff --git a/resources/assets/app-bg.jpeg b/resources/assets/app-bg.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e97f6b1d882bad817a407605292551d32fb3f909 GIT binary patch literal 406903 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 >r3G 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%0AS*cGIjZ2+#Uf)En=# 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 + + + diff --git a/resources/assets/icon-192x192.png b/resources/assets/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..19dec415e4b0639153b2a25b3797c2422acd362f GIT binary patch literal 3007 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 literal 0 HcmV?d00001 diff --git a/resources/assets/icon-512x512.png b/resources/assets/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..27d779d9df09fa7a78d55c652e89152e2c32c494 GIT binary patch literal 7328 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 literal 0 HcmV?d00001 diff --git a/resources/assets/safari-pinned-tab.svg b/resources/assets/safari-pinned-tab.svg new file mode 100644 index 0000000..b088740 --- /dev/null +++ b/resources/assets/safari-pinned-tab.svg @@ -0,0 +1,62 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/resources/views/app.blade.php b/resources/views/app.blade.php new file mode 100644 index 0000000..3978e6f --- /dev/null +++ b/resources/views/app.blade.php @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + @vite('resources/client/app.ts') + + @inertiaHead + + + + @inertia + +

+ + From d0987836ecbbb4ac37075faf110fe442a8c3b9e9 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 05:27:04 +0700 Subject: [PATCH 03/71] fix: fix service provider name in `composer.json` file Signed-off-by: Fery Wardiyanto --- composer.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index f035142..45a1c4b 100644 --- a/composer.json +++ b/composer.json @@ -53,11 +53,8 @@ "extra": { "laravel": { "providers": [ - "Creasi\\Laravel\\Accounts\\ServiceProvider" - ], - "aliases": { - "Accounts": "Creasi\\Laravel\\Accounts" - } + "Creasi\\Base\\ServiceProvider" + ] } }, "minimum-stability": "dev", From 5a238ccfc7364ff4174adafb21eb253c5755f90f Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 06:24:09 +0700 Subject: [PATCH 04/71] fix(db): fix nested relationship issue on postgre and rename `documents` to `files` Signed-off-by: Fery Wardiyanto --- .../2022_05_10_000001_create_profiles_table.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/database/migrations/2022_05_10_000001_create_profiles_table.php b/database/migrations/2022_05_10_000001_create_profiles_table.php index 41a50da..88467ee 100644 --- a/database/migrations/2022_05_10_000001_create_profiles_table.php +++ b/database/migrations/2022_05_10_000001_create_profiles_table.php @@ -51,9 +51,9 @@ public function up(): void $table->softDeletes(); }); - Schema::create('documents', function (Blueprint $table) { + Schema::create('files', function (Blueprint $table) { $table->uuid('id')->primary(); - $table->foreignUuid('revision_id')->nullable()->constrained('documents')->nullOnDelete(); + $table->uuid('revision_id')->nullable(); $table->string('title')->nullable(); $table->string('name'); @@ -65,8 +65,12 @@ public function up(): void $table->softDeletes(); }); - Schema::create('attachments', function (Blueprint $table) { - $table->foreignUuid('document_id')->constrained()->cascadeOnDelete(); + Schema::table('files', function (Blueprint $table) { + $table->foreign('revision_id')->references('id')->on('files')->nullOnDelete(); + }); + + Schema::create('file_attached', function (Blueprint $table) { + $table->foreignUuid('file_id')->constrained()->cascadeOnDelete(); $table->nullableMorphs('attached_to'); }); } @@ -76,8 +80,8 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('attachments'); - Schema::dropIfExists('documents'); + Schema::dropIfExists('file_attached'); + Schema::dropIfExists('files'); Schema::dropIfExists('addresses'); Schema::dropIfExists('profiles'); } From 9a608bc021bacbadc5f93f59ca42586bafb02b30 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 06:25:48 +0700 Subject: [PATCH 05/71] refactor(model)!: rename all `Document` related models to `File` regarding 5a238cc Signed-off-by: Fery Wardiyanto --- .../factories/{DocumentFactory.php => FileFactory.php} | 10 +++++----- src/Models/Concerns/HasDocuments.php | 8 ++++---- src/Models/{Document.php => File.php} | 6 +++--- tests/Models/CompanyTest.php | 10 +++++----- tests/Models/{DocumentTest.php => FileTest.php} | 8 ++++---- tests/Models/PersonnelTest.php | 8 ++++---- 6 files changed, 25 insertions(+), 25 deletions(-) rename database/factories/{DocumentFactory.php => FileFactory.php} (75%) rename src/Models/{Document.php => File.php} (91%) rename tests/Models/{DocumentTest.php => FileTest.php} (81%) diff --git a/database/factories/DocumentFactory.php b/database/factories/FileFactory.php similarity index 75% rename from database/factories/DocumentFactory.php rename to database/factories/FileFactory.php index fd1713b..4482cd6 100644 --- a/database/factories/DocumentFactory.php +++ b/database/factories/FileFactory.php @@ -2,16 +2,16 @@ namespace Database\Factories; -use Creasi\Base\Models\Document; +use Creasi\Base\Models\File; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; /** - * @extends Factory + * @extends Factory */ -class DocumentFactory extends Factory +class FileFactory extends Factory { - protected $model = Document::class; + protected $model = File::class; /** * @return array @@ -28,7 +28,7 @@ public function definition(): array ]; } - public function asRevisionOf(Document $document): static + public function asRevisionOf(File $document): static { return $this->state(fn () => [ 'revision_id' => $document->getKey(), diff --git a/src/Models/Concerns/HasDocuments.php b/src/Models/Concerns/HasDocuments.php index c885125..d26c1fa 100644 --- a/src/Models/Concerns/HasDocuments.php +++ b/src/Models/Concerns/HasDocuments.php @@ -2,10 +2,10 @@ namespace Creasi\Base\Models\Concerns; -use Creasi\Base\Models\Document; +use Creasi\Base\Models\File; /** - * @property-read \Illuminate\Database\Eloquent\Collection $documents + * @property-read \Illuminate\Database\Eloquent\Collection $files * * @mixin \Illuminate\Database\Eloquent\Model */ @@ -14,8 +14,8 @@ trait HasDocuments /** * @return \Illuminate\Database\Eloquent\Relations\MorphMany */ - public function documents() + public function files() { - return $this->morphToMany(Document::class, 'attached_to', 'attachments', null, 'document_id'); + return $this->morphToMany(File::class, 'attached_to', 'file_attached', null, 'file_id'); } } diff --git a/src/Models/Document.php b/src/Models/File.php similarity index 91% rename from src/Models/Document.php rename to src/Models/File.php index 29a3d86..7b495ba 100644 --- a/src/Models/Document.php +++ b/src/Models/File.php @@ -17,9 +17,9 @@ * @property-read \Illuminate\Database\Eloquent\Collection $ownedByCompanies * @property-read \Illuminate\Database\Eloquent\Collection $ownedByPersonnels * - * @method static \Database\Factories\DocumentFactory factory() + * @method static \Database\Factories\FileFactory factory() */ -class Document extends Model +class File extends Model { use HasUuids; use SoftDeletes; @@ -39,7 +39,7 @@ class Document extends Model protected function attachedTo(string $owner) { - return $this->morphedByMany($owner, 'attached_to', 'attachments', 'document_id') + return $this->morphedByMany($owner, 'attached_to', 'file_attached', 'file_id') ->as('attachments'); } diff --git a/tests/Models/CompanyTest.php b/tests/Models/CompanyTest.php index afe6ed2..e039768 100644 --- a/tests/Models/CompanyTest.php +++ b/tests/Models/CompanyTest.php @@ -4,7 +4,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Company; -use Creasi\Base\Models\Document; +use Creasi\Base\Models\File; use Creasi\Base\Models\Enums\CompanyRelativeType; use Creasi\Base\Models\Enums\EmploymentStatus; use Creasi\Base\Models\Enums\EmploymentType; @@ -37,12 +37,12 @@ public function should_have_addresses() public function should_have_documents() { $company = Company::factory()->createOne(); - $document = Document::factory()->createOne(); + $file = File::factory()->createOne(); - $company->documents()->save($document); + $company->files()->save($file); - $this->assertCount(1, $company->documents); - $this->assertCount(1, $document->ownedByCompanies); + $this->assertCount(1, $company->files); + $this->assertCount(1, $file->ownedByCompanies); } #[Test] diff --git a/tests/Models/DocumentTest.php b/tests/Models/FileTest.php similarity index 81% rename from tests/Models/DocumentTest.php rename to tests/Models/FileTest.php index 9c3a80c..dacf939 100644 --- a/tests/Models/DocumentTest.php +++ b/tests/Models/FileTest.php @@ -2,18 +2,18 @@ namespace Creasi\Tests\Models; -use Creasi\Base\Models\Document; +use Creasi\Base\Models\File; use Creasi\Tests\TestCase; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; #[Group('document')] -class DocumentTest extends TestCase +class FileTest extends TestCase { #[Test] public function should_be_exists() { - $model = Document::factory()->createOne(); + $model = File::factory()->createOne(); $this->assertModelExists($model); @@ -24,7 +24,7 @@ public function should_be_exists() public function should_have_revisions() { - $original = Document::factory()->createOne([ + $original = File::factory()->createOne([ 'path' => 'some/place/elsewhere.pdf', ]); diff --git a/tests/Models/PersonnelTest.php b/tests/Models/PersonnelTest.php index 861aa11..309fc30 100644 --- a/tests/Models/PersonnelTest.php +++ b/tests/Models/PersonnelTest.php @@ -3,7 +3,7 @@ namespace Creasi\Tests\Models; use Creasi\Base\Models\Address; -use Creasi\Base\Models\Document; +use Creasi\Base\Models\File; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; @@ -34,11 +34,11 @@ public function should_have_addresses() public function should_have_documents() { $company = Personnel::factory()->createOne(); - $document = Document::factory()->createOne(); + $document = File::factory()->createOne(); - $company->documents()->save($document); + $company->files()->save($document); - $this->assertCount(1, $company->documents); + $this->assertCount(1, $company->files); $this->assertCount(1, $document->ownedByPersonnels); } From f95513e0da21e31205115cbaedece96d15275d4e Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 06:30:53 +0700 Subject: [PATCH 06/71] fix(db): fix migration `down` issues due to table definitions miss-matched Signed-off-by: Fery Wardiyanto --- .../migrations/2022_05_10_000000_create_sessions_table.php | 4 ++-- .../migrations/2022_05_10_000001_create_profiles_table.php | 2 +- ...2022_05_10_000002_create_companies_and_employees_table.php | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/database/migrations/2022_05_10_000000_create_sessions_table.php b/database/migrations/2022_05_10_000000_create_sessions_table.php index d02620c..b21e45e 100644 --- a/database/migrations/2022_05_10_000000_create_sessions_table.php +++ b/database/migrations/2022_05_10_000000_create_sessions_table.php @@ -72,12 +72,12 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists(config('queue.connections.database.table')); + Schema::dropIfExists(config('queue.failed.table')); Schema::dropIfExists('notifications'); if (config('queue.default') !== 'database') { Schema::dropIfExists('job_batches'); - Schema::dropIfExists(config('queue.failed.table')); + Schema::dropIfExists(config('queue.connections.database.table')); } if (config('session.driver') === 'database') { diff --git a/database/migrations/2022_05_10_000001_create_profiles_table.php b/database/migrations/2022_05_10_000001_create_profiles_table.php index 88467ee..ee53275 100644 --- a/database/migrations/2022_05_10_000001_create_profiles_table.php +++ b/database/migrations/2022_05_10_000001_create_profiles_table.php @@ -83,6 +83,6 @@ public function down(): void Schema::dropIfExists('file_attached'); Schema::dropIfExists('files'); Schema::dropIfExists('addresses'); - Schema::dropIfExists('profiles'); + Schema::dropIfExists('identities'); } }; diff --git a/database/migrations/2022_05_10_000002_create_companies_and_employees_table.php b/database/migrations/2022_05_10_000002_create_companies_and_employees_table.php index ce6550d..58397e0 100644 --- a/database/migrations/2022_05_10_000002_create_companies_and_employees_table.php +++ b/database/migrations/2022_05_10_000002_create_companies_and_employees_table.php @@ -77,7 +77,8 @@ public function down(): void { Schema::dropIfExists('employments'); Schema::dropIfExists('personnel_relatives'); - Schema::dropIfExists('employees'); + Schema::dropIfExists('personnels'); + Schema::dropIfExists('company_relatives'); Schema::dropIfExists('companies'); } }; From e8c75a3ff5ed45b760aab7917c09a0c57d46e384 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 08:33:19 +0700 Subject: [PATCH 07/71] chore: update service provider configuration Signed-off-by: Fery Wardiyanto --- composer.json | 1 + composer.lock | 201 ++++++++++++++++++++++++++++++++++------ src/ServiceProvider.php | 47 +++++++++- 3 files changed, 216 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 45a1c4b..cec0716 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ }, "require-dev": { "composer-runtime-api": "*", + "laravel/dusk": "^7.0", "laravel/pint": "^1.1", "mockery/mockery": "^1.5", "orchestra/testbench": "^8.5", diff --git a/composer.lock b/composer.lock index eb60113..82f7f26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f864ab80f6a3ccc1b225068d3df29db3", + "content-hash": "70e015f6eddcfdac0812ea08a360de6e", "packages": [ { "name": "brick/math", @@ -720,16 +720,16 @@ }, { "name": "laravel/framework", - "version": "v10.14.1", + "version": "v10.15.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6f89a2b74b232d8bf2e1d9ed87e311841263dfcb" + "reference": "c7599dc92e04532824bafbd226c2936ce6a905b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6f89a2b74b232d8bf2e1d9ed87e311841263dfcb", - "reference": "6f89a2b74b232d8bf2e1d9ed87e311841263dfcb", + "url": "https://api.github.com/repos/laravel/framework/zipball/c7599dc92e04532824bafbd226c2936ce6a905b8", + "reference": "c7599dc92e04532824bafbd226c2936ce6a905b8", "shasum": "" }, "require": { @@ -916,7 +916,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-06-28T14:25:16+00:00" + "time": "2023-07-11T13:43:52+00:00" }, { "name": "laravel/serializable-closure", @@ -5017,18 +5017,93 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "laravel/dusk", + "version": "v7.8.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/dusk.git", + "reference": "82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/dusk/zipball/82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5", + "reference": "82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-zip": "*", + "illuminate/console": "^9.0|^10.0", + "illuminate/support": "^9.0|^10.0", + "nesbot/carbon": "^2.0", + "php": "^8.0", + "php-webdriver/webdriver": "^1.9.0", + "symfony/console": "^6.0", + "symfony/finder": "^6.0", + "symfony/process": "^6.0", + "vlucas/phpdotenv": "^5.2" + }, + "require-dev": { + "mockery/mockery": "^1.4.2", + "orchestra/testbench": "^7.0|^8.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.10|^10.0.1", + "psy/psysh": "^0.11.12" + }, + "suggest": { + "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Dusk\\DuskServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Dusk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", + "keywords": [ + "laravel", + "testing", + "webdriver" + ], + "support": { + "issues": "https://github.com/laravel/dusk/issues", + "source": "https://github.com/laravel/dusk/tree/v7.8.0" + }, + "time": "2023-07-08T21:23:29+00:00" + }, { "name": "laravel/pint", - "version": "v1.10.3", + "version": "v1.10.4", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "c472786bca01e4812a9bb7933b23edfc5b6877b7" + "reference": "f56798088068af8bd75a8f2c4ecae022990fdf75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/c472786bca01e4812a9bb7933b23edfc5b6877b7", - "reference": "c472786bca01e4812a9bb7933b23edfc5b6877b7", + "url": "https://api.github.com/repos/laravel/pint/zipball/f56798088068af8bd75a8f2c4ecae022990fdf75", + "reference": "f56798088068af8bd75a8f2c4ecae022990fdf75", "shasum": "" }, "require": { @@ -5039,7 +5114,7 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.18.0", + "friendsofphp/php-cs-fixer": "^3.21.1", "illuminate/view": "^10.5.1", "laravel-zero/framework": "^10.0.2", "mockery/mockery": "^1.5.1", @@ -5081,7 +5156,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-06-20T15:55:03+00:00" + "time": "2023-07-11T15:18:27+00:00" }, { "name": "mockery/mockery", @@ -5278,22 +5353,22 @@ }, { "name": "orchestra/testbench", - "version": "v8.5.9", + "version": "v8.5.10", "source": { "type": "git", "url": "https://github.com/orchestral/testbench.git", - "reference": "c669d2cb6dc869f98d299c5dbf600051ba6deb3e" + "reference": "6878b8119f74bfc77779554dc81f4d9ede3dbe49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/c669d2cb6dc869f98d299c5dbf600051ba6deb3e", - "reference": "c669d2cb6dc869f98d299c5dbf600051ba6deb3e", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/6878b8119f74bfc77779554dc81f4d9ede3dbe49", + "reference": "6878b8119f74bfc77779554dc81f4d9ede3dbe49", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "fakerphp/faker": "^1.21", - "laravel/framework": ">=10.14.0 <10.15.0", + "laravel/framework": ">=10.14.0 <10.16.0", "mockery/mockery": "^1.5.1", "orchestra/testbench-core": ">=8.5.7 <8.6.0", "php": "^8.1", @@ -5327,22 +5402,22 @@ ], "support": { "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench/tree/v8.5.9" + "source": "https://github.com/orchestral/testbench/tree/v8.5.10" }, - "time": "2023-06-27T13:42:07+00:00" + "time": "2023-07-11T13:52:30+00:00" }, { "name": "orchestra/testbench-core", - "version": "v8.5.8", + "version": "v8.5.9", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "c9c36e8ef9a8b91cbcc85f629be5ff3670133737" + "reference": "03049a27967ab80972a7c0c0a5be87e421b27ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/c9c36e8ef9a8b91cbcc85f629be5ff3670133737", - "reference": "c9c36e8ef9a8b91cbcc85f629be5ff3670133737", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/03049a27967ab80972a7c0c0a5be87e421b27ff4", + "reference": "03049a27967ab80972a7c0c0a5be87e421b27ff4", "shasum": "" }, "require": { @@ -5410,7 +5485,7 @@ "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "time": "2023-06-22T05:28:42+00:00" + "time": "2023-07-12T00:16:23+00:00" }, { "name": "phar-io/manifest", @@ -5523,6 +5598,72 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-webdriver/webdriver", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "3ea4f924afb43056bf9c630509e657d951608563" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3ea4f924afb43056bf9c630509e657d951608563", + "reference": "3ea4f924afb43056bf9c630509e657d951608563", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^7.3 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^5.0 || ^6.0" + }, + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.20.0", + "ondram/ci-detector": "^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^5.0 || ^6.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Exception/TimeoutException.php" + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", + "keywords": [ + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" + ], + "support": { + "issues": "https://github.com/php-webdriver/php-webdriver/issues", + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.14.0" + }, + "time": "2023-02-09T12:12:19+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "10.1.2", @@ -5845,16 +5986,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.2.3", + "version": "10.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e" + "reference": "68484779b5a2ed711fbdeba6ca01910d87acdff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35c8cac1734ede2ae354a6644f7088356ff5b08e", - "reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68484779b5a2ed711fbdeba6ca01910d87acdff2", + "reference": "68484779b5a2ed711fbdeba6ca01910d87acdff2", "shasum": "" }, "require": { @@ -5926,7 +6067,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.4" }, "funding": [ { @@ -5942,7 +6083,7 @@ "type": "tidelift" } ], - "time": "2023-06-30T06:17:38+00:00" + "time": "2023-07-10T04:06:08+00:00" }, { "name": "pimple/pimple", diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index dd3fd0c..65d3d25 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -4,7 +4,9 @@ use Creasi\Base\Models\Address; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; +use Laravel\Dusk\Browser; class ServiceProvider extends IlluminateServiceProvider { @@ -12,15 +14,26 @@ class ServiceProvider extends IlluminateServiceProvider public function boot() { + /** + * While not in production, send all email tho the following address instead. + * + * @see https://laravel.com/docs/9.x/mail#using-a-global-to-address + */ + if (! app()->environment('production') && $devMail = env('MAIL_DEVELOPMENT')) { + Mail::alwaysTo($devMail); + } + if (app()->runningInConsole()) { $this->registerPublishables(); $this->registerCommands(); } + $this->loadMigrationsFrom(self::LIB_PATH.'/database/migrations'); + $this->loadTranslationsFrom(self::LIB_PATH.'/resources/lang', 'creasico'); - $this->loadMigrationsFrom(self::LIB_PATH.'/database/migrations'); + $this->loadViewsFrom(self::LIB_PATH.'/resources/views', 'creasico'); } public function register() @@ -39,7 +52,12 @@ public function register() Factory::guessFactoryNamesUsing(function (string $modelName) { return Factory::$namespace.\class_basename($modelName).'Factory'; }); + + if (\class_exists(Browser::class)) { + $this->registerDuskMacroForInertia(); + } } + } protected function registerPublishables() @@ -49,8 +67,12 @@ protected function registerPublishables() ], ['creasi-config', 'creasi-base-config']); $this->publishes([ - self::LIB_PATH.'/resources/lang' => \resource_path('vendor/creasico'), - ], 'creasi-lang'); + self::LIB_PATH.'/resources/lang' => \resource_path('lang/vendor/creasico'), + ], ['creasi-lang']); + + $this->publishes([ + self::LIB_PATH.'/resources/views' => \resource_path('views/vendor/creasico'), + ], ['creasi-views']); } protected function registerCommands() @@ -59,4 +81,23 @@ protected function registerCommands() // . ]); } + + /** + * Register inertia.js helper for dusk testing + * + * @see https://github.com/protonemedia/inertiajs-events-laravel-dusk + */ + private function registerDuskMacroForInertia(): void + { + Browser::macro('waitForInertia', function (?int $seconds = null): Browser { + /** @var Browser $this */ + $driver = $this->driver; + + $currentCount = $driver->executeScript('return window.__inertiaNavigatedCount;'); + + return $this->waitUsing($seconds, 100, fn () => $driver->executeScript( + "return window.__inertiaNavigatedCount > {$currentCount};" + ), 'Waited %s seconds for Inertia.js to increase the navigate count.'); + }); + } } From 12f2be12f7df852fdfe91ca31f99c8b242384636 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 13 Jul 2023 11:43:18 +0700 Subject: [PATCH 08/71] chore: add public assets to publishes Signed-off-by: Fery Wardiyanto --- src/ServiceProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 65d3d25..1ba36a3 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -66,6 +66,10 @@ protected function registerPublishables() self::LIB_PATH.'/config/creasico.php' => \config_path('creasi/base.php'), ], ['creasi-config', 'creasi-base-config']); + $this->publishes([ + self::LIB_PATH.'/resources/assets' => \public_path('vendor/creasico'), + ], ['creasi-assets', 'creasi-base-assets', 'laravel-assets']); + $this->publishes([ self::LIB_PATH.'/resources/lang' => \resource_path('lang/vendor/creasico'), ], ['creasi-lang']); From 827324f47dc97372cd927f49fc3c1d868ff25c3c Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Fri, 14 Jul 2023 23:40:32 +0700 Subject: [PATCH 09/71] feat(locale): share locale translation to view Signed-off-by: Fery Wardiyanto --- src/ServiceProvider.php | 13 +++++- src/View/.gitignore | 0 src/View/Composers/TranslationsComposer.php | 52 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/View/.gitignore create mode 100644 src/View/Composers/TranslationsComposer.php diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 1ba36a3..7cd29cd 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,8 +3,10 @@ namespace Creasi\Base; use Creasi\Base\Models\Address; +use Creasi\Base\View\Composers\TranslationsComposer; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; use Laravel\Dusk\Browser; @@ -27,13 +29,15 @@ public function boot() $this->registerPublishables(); $this->registerCommands(); - } - $this->loadMigrationsFrom(self::LIB_PATH.'/database/migrations'); + $this->loadMigrationsFrom(self::LIB_PATH.'/database/migrations'); + } $this->loadTranslationsFrom(self::LIB_PATH.'/resources/lang', 'creasico'); $this->loadViewsFrom(self::LIB_PATH.'/resources/views', 'creasico'); + + $this->bootViewComposers(); } public function register() @@ -104,4 +108,9 @@ private function registerDuskMacroForInertia(): void ), 'Waited %s seconds for Inertia.js to increase the navigate count.'); }); } + + private function bootViewComposers(): void + { + View::composer('*', TranslationsComposer::class); + } } diff --git a/src/View/.gitignore b/src/View/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/View/Composers/TranslationsComposer.php b/src/View/Composers/TranslationsComposer.php new file mode 100644 index 0000000..0e5dfbb --- /dev/null +++ b/src/View/Composers/TranslationsComposer.php @@ -0,0 +1,52 @@ +loadMessages($loader, \resource_path('lang')); + + foreach ($loader->namespaces() as $namespace => $path) { + foreach ($this->loadMessages($loader, $path, $namespace) as $locale => $vendorMessages) { + $messages[$locale] = array_merge($messages[$locale], $vendorMessages); + } + } + + $view->with('translations', $messages); + } + + /** + * @param Loader $loader + * @param string $path + * @param null|string $namespace + * @return array> + */ + private function loadMessages(Loader $loader, string $path, ?string $namespace = null) + { + $messages = []; + + foreach (File::directories($path) as $dir) { + $locale = \basename($dir); + $trans = collect([]); + + foreach (File::files($dir) as $file) { + $group = $file->getBasename('.php'); + $key = $namespace ? $namespace.'::'.$group : $group; + + $trans[$key] = $loader->load($locale, $group, $namespace); + } + + $messages[$locale] = $trans->dot()->filter(fn ($i) => !empty($i))->toArray(); + } + + return $messages; + } +} From 1bf4c4849dc0fa1beb78044d68282ab4c6edc249 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 15 Jul 2023 02:48:59 +0700 Subject: [PATCH 10/71] chore: get rid of base views Signed-off-by: Fery Wardiyanto --- resources/views/app.blade.php | 28 ---------------------------- src/ServiceProvider.php | 6 ------ 2 files changed, 34 deletions(-) delete mode 100644 resources/views/app.blade.php diff --git a/resources/views/app.blade.php b/resources/views/app.blade.php deleted file mode 100644 index 3978e6f..0000000 --- a/resources/views/app.blade.php +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - @vite('resources/client/app.ts') - - @inertiaHead - - - - @inertia - -
- - diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 7cd29cd..23e132e 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -35,8 +35,6 @@ public function boot() $this->loadTranslationsFrom(self::LIB_PATH.'/resources/lang', 'creasico'); - $this->loadViewsFrom(self::LIB_PATH.'/resources/views', 'creasico'); - $this->bootViewComposers(); } @@ -77,10 +75,6 @@ protected function registerPublishables() $this->publishes([ self::LIB_PATH.'/resources/lang' => \resource_path('lang/vendor/creasico'), ], ['creasi-lang']); - - $this->publishes([ - self::LIB_PATH.'/resources/views' => \resource_path('views/vendor/creasico'), - ], ['creasi-views']); } protected function registerCommands() From 51ab4bc60513196de4b13c81faac8d584296eb3d Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 15 Jul 2023 06:03:25 +0700 Subject: [PATCH 11/71] refactor!: rename migrations file name Simply to make theme more descriptive Signed-off-by: Fery Wardiyanto --- ...5_10_000000_create_sessions_queue_and_notifications_table.php} | 0 ...22_05_10_000001_create_identities_address_and_files_table.php} | 0 ...> 2022_05_10_000002_create_companies_and_personnels_table.php} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename database/migrations/{2022_05_10_000000_create_sessions_table.php => 2022_05_10_000000_create_sessions_queue_and_notifications_table.php} (100%) rename database/migrations/{2022_05_10_000001_create_profiles_table.php => 2022_05_10_000001_create_identities_address_and_files_table.php} (100%) rename database/migrations/{2022_05_10_000002_create_companies_and_employees_table.php => 2022_05_10_000002_create_companies_and_personnels_table.php} (100%) diff --git a/database/migrations/2022_05_10_000000_create_sessions_table.php b/database/migrations/2022_05_10_000000_create_sessions_queue_and_notifications_table.php similarity index 100% rename from database/migrations/2022_05_10_000000_create_sessions_table.php rename to database/migrations/2022_05_10_000000_create_sessions_queue_and_notifications_table.php diff --git a/database/migrations/2022_05_10_000001_create_profiles_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php similarity index 100% rename from database/migrations/2022_05_10_000001_create_profiles_table.php rename to database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php diff --git a/database/migrations/2022_05_10_000002_create_companies_and_employees_table.php b/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php similarity index 100% rename from database/migrations/2022_05_10_000002_create_companies_and_employees_table.php rename to database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php From 2a633038a36528c1d33a018913189adada722f87 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 15 Jul 2023 19:55:17 +0700 Subject: [PATCH 12/71] chore(docs): add database structure documentation Signed-off-by: Fery Wardiyanto --- database/README.md | 366 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 database/README.md diff --git a/database/README.md b/database/README.md new file mode 100644 index 0000000..9e8bd96 --- /dev/null +++ b/database/README.md @@ -0,0 +1,366 @@ +# Database Structure + +```mermaid +erDiagram + companies ||--o{ company_relatives : hasMany + companies { + unsignedBigInt id PK + string code + string name + string email + string phone_number + string logo_path + string summary + } + + company_relatives ||--|| companies : stakeholder + company_relatives ||--|| personnels : stakeholder + company_relatives { + unsignedBigInt id PK + unsignedBigInt company_id FK + morph stakeholder + int type + boolean is_internal + string remark + } + + companies ||--o{ employments : employees + personnels ||--o{ employments : employer + employments { + unsignedBigInt company_id FK + unsignedBigInt employee_id FK + boolean is_primary + int type + int status + date start_date + date finish_date + string remark + } + + personnels { + unsignedBigInt id PK + string code + string name + string email + string phone_number + string logo_path + string summary + } + + personnels ||--o{ personnel_relatives : hasMany + personnel_relatives ||--|| personnels : family + personnel_relatives { + unsignedBigInt personnel_id FK + unsignedBigInt relative_id FK + string status + string remark + } + + files ||--|| file_attached : hasMany + files ||--o{ files : revisions + files { + unsignedBigInt id PK + unsignedBigInt revision_id FK + string title + string name + string path + string drive + string summary + } + file_attached }o..|| companies : uploadedFiles + file_attached }o..|| personnels : uploadedFiles + file_attached { + unsignedBigInt file_id FK + morph attached_to + } + + addresses }o..|| companies : own + addresses }o..|| personnels : own + addresses { + unsignedBigInt id PK + morph owner + boolean is_resident + string line + string rt + string rw + int village_code + int district_code + int regency_code + int province_code + int postal_code + string summary + } + + personnels ||..|| identities : morphOne + identities { + unsignedBigInt id PK + morphs identity + string nik + string prefix + string fullname + string suffix + string gender + string birth_date + string birth_place_code + string education + string religion + string phone_number + string photo_path + string summary + } +``` +--- +## Companies + +```mermaid +classDiagram + Company "1" <|--|> "*" Company : CompanyRelative + Company "1" <|--|> "*" Personnel : CompanyRelative + + class Company { + unsignedBigInt id + timestamps() static + softDeletes() static + } + class Personnel { + unsignedBigInt id + timestamps() static + softDeletes() static + } +``` + +### `companies` + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `id` | `unsignedBigInt` | `primary` | - | +| `code` | `string`, `nullable` | `unique` | - | +| `name` | `string` | | - | +| `email` | `string`, `nullable` | `unique` | - | +| `phone_number` | `varchar(20)`, `nullable` | | - | +| `logo_path` | `string`, `nullable` | | - | +| `summary` | `text`, `nullable` | | - | + +**Model Attributes** +- `timestamps` +- `softDeletes` + +### `company_relatives` (morphPivot) + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `id` | `unsignedBigInt` | `primary` | - | +| `company_id` | `unsignedBigInt` | `foreign` | - | +| `stakeholder` | `morphs`, `nullable` | | - | +| `type` | `unsignedSmallInt`, `nullable` | | - | +| `is_internal` | `boolean`, `default: false` | | - | +| `remark` | `text`, `nullable` | | - | + +**Relation Properties** +- `company_id` : reference `companies` + +## Employment + +```mermaid +classDiagram + Company "1" <|--|> "*" Personnel : Employment + class Company { + unsignedBigInt id + timestamps() static + softDeletes() static + } + class Personnel { + unsignedBigInt id + timestamps() static + softDeletes() static + } +``` + +### `employments` (morphPivot) + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `company_id` | `unsignedBigInt` | `foreign` | - | +| `employee_id` | `unsignedBigInt` | `foreign` | - | +| `is_primary` | `boolean`, `default: false` | | - | +| `type` | `unsignedSmallInt`, `nullable` | | - | +| `status` | `unsignedSmallInt`, `nullable` | | - | +| `start_date` | `date`, `nullable` | | - | +| `finish_date` | `date`, `nullable` | | - | +| `remark` | `text`, `nullable` | | - | + +**Relation Properties** +- `company_id` : reference `companies` +- `employee_id` : reference `personnels` + +## Personnel and Identities + +```mermaid +classDiagram + Personnel "1" <|--|> "1" Identity : Profile + Personnel "1" <|--|> "1" Personnel : PersonnelRelative + class Personnel { + unsignedBigInt id + timestamps() static + softDeletes() static + } + class Identity { + unsignedBigInt id + timestamps() static + softDeletes() static + } +``` + +### `personnels` + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `id` | `unsignedBigInt` | `primary` | - | +| `code` | `string`, `nullable` | `unique` | - | +| `name` | `string` | | - | +| `email` | `string`, `nullable` | `unique` | - | +| `phone_number` | `varchar(20)`, `nullable` | | - | +| `photo_path` | `string`, `nullable` | | - | +| `summary` | `text`, `nullable` | | - | + +**Model Attributes** +- `timestamps` +- `softDeletes` + +### `personnel_relatives` (morphPivot) + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `personnel_id` | `unsignedBigInt` | `foreign` | - | +| `relative_id` | `unsignedBigInt` | `foreign` | - | +| `status` | `unsignedSmallInt`, `nullable` | | - | +| `remark` | `text`, `nullable` | | - | + +**Relation Properties** +- `personnel_id` : reference `personnels` +- `relative_id` : reference `personnels` + +### `identities` + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `id` | `unsignedBigInt` | `primary` | - | +| `identity` | `morphs`, `nullable` | | - | +| `nik` | `char(16)`, `nullable` | | - | +| `prefix` | `varchar(10)`, `nullable` | | - | +| `fullname` | `string` | | - | +| `suffix` | `varchar(10)`, `nullable` | | - | +| `gender` | `char(1)` | | - | +| `birth_date` | `date`, `nullable` | | - | +| `birth_place_code` | `char(4)`, `nullable` | | - | +| `education` | `varchar(3)`, `nullable` | | - | +| `religion` | `unsignedTinyInt`, `nullable` | | - | +| `phone_number` | `varchar(20)`, `nullable` | | - | +| `photo_path` | `string`, `nullable` | | - | +| `summary` | `text`, `nullable` | | - | + +**Model Attributes** +- `timestamps` +- `softDeletes` + +## Addresses + +```mermaid +classDiagram + Company "*" <|--|> "1" Address : Own + Personnel "*" <|--|> "1" Address : Own + class Address { + unsignedBigInt id + morph owner + timestamps() static + softDeletes() static + } + class Company { + unsignedBigInt id + timestamps() static + softDeletes() static + } + class Personnel { + unsignedBigInt id + timestamps() static + softDeletes() static + } +``` + +### `addresses` + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `id` | `unsignedBigInt` | `primary` | - | +| `owner` | `morphs`, `nullable` | | - | +| `is_resident` | `boolean` | | - | +| `line` | `string` | | - | +| `rt` | `char(3)`, `nullable` | | - | +| `rw` | `char(3)`, `nullable` | | - | +| `village_code` | `char(10)`, `nullable` | | - | +| `district_code` | `char(6)`, `nullable` | | - | +| `regency_code` | `char(4)`, `nullable` | | - | +| `province_code` | `char(2)`, `nullable` | | - | +| `postal_code` | `char(5)`, `nullable` | | - | +| `summary` | `text`, `nullable` | | - | + +**Model Attributes** +- `timestamps` +- `softDeletes` + +## Uploaded Files + +```mermaid +classDiagram + Company "*" <--> "1" FileAttached : AttachedTo + Personnel "*" <--> "1" FileAttached : AttachedTo + class File { + unsignedBigInt id + timestamps() static + softDeletes() static + } + FileAttached "1" --> "1" File : Attachment + class FileAttached { + unsignedBigInt id + morph attached_to + } + class Company { + unsignedBigInt id + timestamps() static + softDeletes() static + } + class Personnel { + unsignedBigInt id + timestamps() static + softDeletes() static + } +``` + +### `files` + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `id` | `uuid` | `primary` | - | +| `revision_id` | `uuid`, `nullable` | `foreign` | Indicates that this row is actually a revision of parent `id` | +| `title` | `string`, `nullable` | | - | +| `name` | `string` | | - | +| `path` | `string`, `nullable` | | - | +| `drive` | `string`, `nullable` | | - | +| `summary` | `string`, `nullable` | | - | + +**Model Attributes** +- `timestamps` +- `softDeletes` + +**Relation Properties** +- `revision_id` : reference `files` + +### `file_attached` (pivot) + +| Field | Attribute | Key | Description | +| --- | --- | :---: | --- | +| `file_id` | `uuid` | `foreign` | - | +| `attached_to` | `morphs`, `nullable` | | - | + +**Relation Properties** +- `file_id` : reference `files` From bdb15efcdb3c9441a379e0bb20d4c5ecc5640e45 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 16 Jul 2023 02:03:49 +0700 Subject: [PATCH 13/71] chore(docs): update database documentation Signed-off-by: Fery Wardiyanto --- database/README.md | 131 ++++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/database/README.md b/database/README.md index 9e8bd96..74f369d 100644 --- a/database/README.md +++ b/database/README.md @@ -8,9 +8,9 @@ erDiagram string code string name string email - string phone_number + varchar(20) phone_number string logo_path - string summary + text summary } company_relatives ||--|| companies : stakeholder @@ -19,22 +19,22 @@ erDiagram unsignedBigInt id PK unsignedBigInt company_id FK morph stakeholder - int type + unsignedSmallInt type boolean is_internal - string remark + text remark } companies ||--o{ employments : employees - personnels ||--o{ employments : employer + employments }o--|| personnels : employees employments { unsignedBigInt company_id FK unsignedBigInt employee_id FK boolean is_primary - int type - int status + unsignedSmallInt type + unsignedSmallInt status date start_date date finish_date - string remark + text remark } personnels { @@ -42,9 +42,9 @@ erDiagram string code string name string email - string phone_number + varchar(20) phone_number string logo_path - string summary + text summary } personnels ||--o{ personnel_relatives : hasMany @@ -52,11 +52,11 @@ erDiagram personnel_relatives { unsignedBigInt personnel_id FK unsignedBigInt relative_id FK - string status - string remark + unsignedSmallInt status + text remark } - files ||--|| file_attached : hasMany + files ||--|| file_attached : attachments files ||--o{ files : revisions files { unsignedBigInt id PK @@ -65,57 +65,82 @@ erDiagram string name string path string drive - string summary + text summary } - file_attached }o..|| companies : uploadedFiles - file_attached }o..|| personnels : uploadedFiles + companies }o..|| file_attached : uploadedFiles + personnels }o..|| file_attached : uploadedFiles file_attached { unsignedBigInt file_id FK morph attached_to } - addresses }o..|| companies : own - addresses }o..|| personnels : own + addresses }o..|| companies : addresses + addresses }o..|| personnels : addresses addresses { unsignedBigInt id PK morph owner boolean is_resident string line - string rt - string rw - int village_code - int district_code - int regency_code - int province_code - int postal_code - string summary + char(3) rt + char(3) rw + char(10) village_code + char(6) district_code + char(4) regency_code + char(2) province_code + char(5) postal_code + text summary } - personnels ||..|| identities : morphOne + personnels ||..|| identities : profile identities { unsignedBigInt id PK morphs identity - string nik + char(16) nik string prefix string fullname string suffix - string gender - string birth_date - string birth_place_code - string education - string religion - string phone_number + char(1) gender + date birth_date + char(4) birth_place_code + unsignedSmallInt education + unsignedSmallInt religion + varchar(20) phone_number string photo_path - string summary + text summary } ``` --- ## Companies +A Company tent to have some short of business relationship either to another companies or individuals regardless of its size, we call it `company_relatives` or the most common term is `stakeholder`. In this first implementation we try to cover 5 most basic business relationship, which are : + +- **Owner** + + Cover business relation between company to individuals who own the company. + +- **Subsidiary** + + Cover business relation between company to other companies that act as a child company. The parent company can be called parent or holding company, and the child company can be called child company or operating company. + +- **Customer** + + Cover business relation between company to either individuals or other companies where the revenues are generated from. + +- **Supplier** + + Cover business relation between company to either individuals or other companies where the production raw materials are came from. + +- **Vendor** + + Cover business relation between company to either individuals or other companies where the tangible assets are provided from. + + +The term `stakeholder` itself is actually covers a lot more than that, it can be `investor`, `founder`, and even `employee`. But at this stage we can't afford to comply those types of stakeholding simply because that's beyond our cababilities to handle them. + ```mermaid classDiagram - Company "1" <|--|> "*" Company : CompanyRelative - Company "1" <|--|> "*" Personnel : CompanyRelative + Company "1" --> "*" Company : CompanyRelative + Company "1" --> "*" Personnel : CompanyRelative class Company { unsignedBigInt id @@ -133,7 +158,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt` | `primary` | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `code` | `string`, `nullable` | `unique` | - | | `name` | `string` | | - | | `email` | `string`, `nullable` | `unique` | - | @@ -149,7 +174,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt` | `primary` | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `company_id` | `unsignedBigInt` | `foreign` | - | | `stakeholder` | `morphs`, `nullable` | | - | | `type` | `unsignedSmallInt`, `nullable` | | - | @@ -161,9 +186,11 @@ classDiagram ## Employment +Essentially the `employments` workflow's can be done using `company_relatives`, but since it has certain entities that differs compared to the other stakeholders we should pivot it into different table. Another reason is it could be easier to manage the spesific relation using dedicated table. + ```mermaid classDiagram - Company "1" <|--|> "*" Personnel : Employment + Company "1" <--> "*" Personnel : Employment class Company { unsignedBigInt id timestamps() static @@ -195,10 +222,14 @@ classDiagram ## Personnel and Identities +Every individuals should have its own Identity, but there's some circumstance that we don't really need that kind of details for every individuals in our business. + +In some instance of business it might be required to have some short of personnel relatiove defined and managed by the company. That way the company can have contact of its employees' relative so they can be contacted in case of the unexpected happens with the specific employee. + ```mermaid classDiagram - Personnel "1" <|--|> "1" Identity : Profile - Personnel "1" <|--|> "1" Personnel : PersonnelRelative + Personnel "1" <..> "1" Identity : Profile + Personnel "1" ..> "*" Personnel : PersonnelRelative class Personnel { unsignedBigInt id timestamps() static @@ -215,7 +246,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt` | `primary` | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `code` | `string`, `nullable` | `unique` | - | | `name` | `string` | | - | | `email` | `string`, `nullable` | `unique` | - | @@ -244,7 +275,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt` | `primary` | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `identity` | `morphs`, `nullable` | | - | | `nik` | `char(16)`, `nullable` | | - | | `prefix` | `varchar(10)`, `nullable` | | - | @@ -267,8 +298,8 @@ classDiagram ```mermaid classDiagram - Company "*" <|--|> "1" Address : Own - Personnel "*" <|--|> "1" Address : Own + Company "1" ..> "*" Address : addresses + Personnel "1" ..> "*" Address : addresses class Address { unsignedBigInt id morph owner @@ -291,7 +322,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt` | `primary` | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `owner` | `morphs`, `nullable` | | - | | `is_resident` | `boolean` | | - | | `line` | `string` | | - | @@ -312,14 +343,14 @@ classDiagram ```mermaid classDiagram - Company "*" <--> "1" FileAttached : AttachedTo - Personnel "*" <--> "1" FileAttached : AttachedTo + Company "1" ..> "*" FileAttached : uploadedFiles + Personnel "1" ..> "*" FileAttached : uploadedFiles class File { unsignedBigInt id timestamps() static softDeletes() static } - FileAttached "1" --> "1" File : Attachment + FileAttached "1" <--> "1" File : attachments class FileAttached { unsignedBigInt id morph attached_to From 5ac02e1646cdf05a7e588c24abce24ef5add288e Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 16 Jul 2023 03:37:46 +0700 Subject: [PATCH 14/71] refactor!: workaround with `files`-related models - rename base table for file uploads - ensure instance of `HasFileUploads` always have latest revision if available Signed-off-by: Fery Wardiyanto --- database/README.md | 16 +-- ...{FileFactory.php => FileUploadFactory.php} | 17 +-- ...ate_identities_address_and_files_table.php | 14 +-- src/Contracts/HasFileUploads.php | 16 +++ src/Models/Company.php | 7 +- src/Models/Concerns/HasDocuments.php | 21 ---- src/Models/Concerns/WithFileUploads.php | 21 ++++ src/Models/FileAttached.php | 34 +++++ src/Models/{File.php => FileUpload.php} | 26 +++- src/Models/Personnel.php | 7 +- tests/Models/CompanyTest.php | 13 -- tests/Models/FileTest.php | 38 ------ tests/Models/FileUploadTest.php | 117 ++++++++++++++++++ tests/Models/PersonnelTest.php | 13 -- 14 files changed, 242 insertions(+), 118 deletions(-) rename database/factories/{FileFactory.php => FileUploadFactory.php} (55%) create mode 100644 src/Contracts/HasFileUploads.php delete mode 100644 src/Models/Concerns/HasDocuments.php create mode 100644 src/Models/Concerns/WithFileUploads.php create mode 100644 src/Models/FileAttached.php rename src/Models/{File.php => FileUpload.php} (74%) delete mode 100644 tests/Models/FileTest.php create mode 100644 tests/Models/FileUploadTest.php diff --git a/database/README.md b/database/README.md index 74f369d..1a00ee4 100644 --- a/database/README.md +++ b/database/README.md @@ -56,9 +56,9 @@ erDiagram text remark } - files ||--|| file_attached : attachments - files ||--o{ files : revisions - files { + file_uploads ||--|| file_attached : attachments + file_uploads ||--o{ file_uploads : revisions + file_uploads { unsignedBigInt id PK unsignedBigInt revision_id FK string title @@ -70,7 +70,7 @@ erDiagram companies }o..|| file_attached : uploadedFiles personnels }o..|| file_attached : uploadedFiles file_attached { - unsignedBigInt file_id FK + unsignedBigInt file_upload_id FK morph attached_to } @@ -367,7 +367,7 @@ classDiagram } ``` -### `files` +### `file_uploads` | Field | Attribute | Key | Description | | --- | --- | :---: | --- | @@ -384,14 +384,14 @@ classDiagram - `softDeletes` **Relation Properties** -- `revision_id` : reference `files` +- `revision_id` : reference `file_uploads` ### `file_attached` (pivot) | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `file_id` | `uuid` | `foreign` | - | +| `file_upload_id` | `uuid` | `foreign` | - | | `attached_to` | `morphs`, `nullable` | | - | **Relation Properties** -- `file_id` : reference `files` +- `file_upload_id` : reference `file_uploads` diff --git a/database/factories/FileFactory.php b/database/factories/FileUploadFactory.php similarity index 55% rename from database/factories/FileFactory.php rename to database/factories/FileUploadFactory.php index 4482cd6..d9b8471 100644 --- a/database/factories/FileFactory.php +++ b/database/factories/FileUploadFactory.php @@ -2,16 +2,17 @@ namespace Database\Factories; -use Creasi\Base\Models\File; +use Creasi\Base\Models\FileUpload; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Http\Testing\File; use Illuminate\Support\Str; /** - * @extends Factory + * @extends Factory */ -class FileFactory extends Factory +class FileUploadFactory extends Factory { - protected $model = File::class; + protected $model = FileUpload::class; /** * @return array @@ -21,17 +22,17 @@ public function definition(): array return [ 'revision_id' => null, 'title' => $title = $this->faker->word(), - 'name' => Str::slug($title), - 'path' => null, + 'name' => $name = Str::slug($title), + 'path' => File::fake()->create($name)->path(), 'drive' => null, 'summary' => $this->faker->sentence(4), ]; } - public function asRevisionOf(File $document): static + public function asRevisionOf(FileUpload $file): static { return $this->state(fn () => [ - 'revision_id' => $document->getKey(), + 'revision_id' => $file->getKey(), ]); } } diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index ee53275..3991a93 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -51,13 +51,13 @@ public function up(): void $table->softDeletes(); }); - Schema::create('files', function (Blueprint $table) { + Schema::create('file_uploads', function (Blueprint $table) { $table->uuid('id')->primary(); $table->uuid('revision_id')->nullable(); $table->string('title')->nullable(); - $table->string('name'); - $table->string('path')->nullable(); + $table->string('name')->nullable(); + $table->string('path'); $table->string('drive')->nullable(); $table->string('summary')->nullable(); @@ -65,12 +65,12 @@ public function up(): void $table->softDeletes(); }); - Schema::table('files', function (Blueprint $table) { - $table->foreign('revision_id')->references('id')->on('files')->nullOnDelete(); + Schema::table('file_uploads', function (Blueprint $table) { + $table->foreign('revision_id')->references('id')->on('file_uploads')->nullOnDelete(); }); Schema::create('file_attached', function (Blueprint $table) { - $table->foreignUuid('file_id')->constrained()->cascadeOnDelete(); + $table->foreignUuid('file_upload_id')->constrained('file_uploads')->cascadeOnDelete(); $table->nullableMorphs('attached_to'); }); } @@ -81,7 +81,7 @@ public function up(): void public function down(): void { Schema::dropIfExists('file_attached'); - Schema::dropIfExists('files'); + Schema::dropIfExists('file_uploads'); Schema::dropIfExists('addresses'); Schema::dropIfExists('identities'); } diff --git a/src/Contracts/HasFileUploads.php b/src/Contracts/HasFileUploads.php new file mode 100644 index 0000000..582c2a6 --- /dev/null +++ b/src/Contracts/HasFileUploads.php @@ -0,0 +1,16 @@ + $files + * + * @mixin \Illuminate\Database\Eloquent\Model + */ +interface HasFileUploads +{ + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphMany|\Creasi\Base\Models\FileUpload + */ + public function files(); +} diff --git a/src/Models/Company.php b/src/Models/Company.php index b40ffba..8790c80 100644 --- a/src/Models/Company.php +++ b/src/Models/Company.php @@ -2,8 +2,9 @@ namespace Creasi\Base\Models; +use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Contracts\Contactable; -use Creasi\Base\Models\Concerns\HasDocuments; +use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Enums\CompanyRelativeType; use Creasi\Nusa\Contracts\Addressable; use Creasi\Nusa\Support\HasAddresses; @@ -20,10 +21,10 @@ * * @method static \Database\Factories\CompanyFactory factory() */ -class Company extends Model implements Addressable, Contactable +class Company extends Model implements Addressable, Contactable, HasFileUploads { use HasAddresses; - use HasDocuments; + use WithFileUploads; protected $fillable = [ 'code', diff --git a/src/Models/Concerns/HasDocuments.php b/src/Models/Concerns/HasDocuments.php deleted file mode 100644 index d26c1fa..0000000 --- a/src/Models/Concerns/HasDocuments.php +++ /dev/null @@ -1,21 +0,0 @@ - $files - * - * @mixin \Illuminate\Database\Eloquent\Model - */ -trait HasDocuments -{ - /** - * @return \Illuminate\Database\Eloquent\Relations\MorphMany - */ - public function files() - { - return $this->morphToMany(File::class, 'attached_to', 'file_attached', null, 'file_id'); - } -} diff --git a/src/Models/Concerns/WithFileUploads.php b/src/Models/Concerns/WithFileUploads.php new file mode 100644 index 0000000..223d55f --- /dev/null +++ b/src/Models/Concerns/WithFileUploads.php @@ -0,0 +1,21 @@ +morphToMany(FileUpload::class, 'attached_to', 'file_attached', null, 'file_upload_id') + ->using(FileAttached::class); + } +} diff --git a/src/Models/FileAttached.php b/src/Models/FileAttached.php new file mode 100644 index 0000000..cb0f7d2 --- /dev/null +++ b/src/Models/FileAttached.php @@ -0,0 +1,34 @@ + 'int', + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphTo + */ + public function attachedTo() + { + return $this->morphTo('attached_to'); + } +} diff --git a/src/Models/File.php b/src/Models/FileUpload.php similarity index 74% rename from src/Models/File.php rename to src/Models/FileUpload.php index 7b495ba..083a84e 100644 --- a/src/Models/File.php +++ b/src/Models/FileUpload.php @@ -13,13 +13,14 @@ * @property null|string $drive * @property null|string $summary * @property-read \Illuminate\Database\Eloquent\Collection $revisions + * @property-read \Illuminate\Database\Eloquent\Collection $attaches * @property-read null|static $revisionOf * @property-read \Illuminate\Database\Eloquent\Collection $ownedByCompanies * @property-read \Illuminate\Database\Eloquent\Collection $ownedByPersonnels * - * @method static \Database\Factories\FileFactory factory() + * @method static \Database\Factories\FileUploadFactory factory() */ -class File extends Model +class FileUpload extends Model { use HasUuids; use SoftDeletes; @@ -39,7 +40,7 @@ class File extends Model protected function attachedTo(string $owner) { - return $this->morphedByMany($owner, 'attached_to', 'file_attached', 'file_id') + return $this->morphedByMany($owner, 'attached_to', 'file_attached', 'file_upload_id') ->as('attachments'); } @@ -61,6 +62,14 @@ public function revisions() return $this->hasMany(static::class, 'revision_id'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany|FileAttached + */ + public function attaches() + { + return $this->hasMany(FileAttached::class); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|static */ @@ -71,12 +80,21 @@ public function revisionOf() public function addRevision(string $filePath, ?string $name = null, ?string $summary = null): static { - return $this->revisions()->create([ + /** @var static */ + $revision = $this->revisions()->create([ 'title' => $this->title, 'drive' => $this->drive, 'name' => $name ?: $this->name, 'path' => $filePath, 'summary' => $summary, ]); + + foreach ($this->attaches as $model) { + $model->attachedTo->files()->sync($revision); + } + + $this->refresh(); + + return $revision; } } diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index e01dac5..c841672 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -2,8 +2,9 @@ namespace Creasi\Base\Models; +use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Contracts\Contactable; -use Creasi\Base\Models\Concerns\HasDocuments; +use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Concerns\HasIdentity; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; use Creasi\Nusa\Contracts\Addressable; @@ -17,11 +18,11 @@ * * @method static \Database\Factories\PersonnelFactory factory() */ -class Personnel extends Model implements Addressable, Contactable +class Personnel extends Model implements Addressable, Contactable, HasFileUploads { use HasAddresses; - use HasDocuments; use HasIdentity; + use WithFileUploads; protected $fillable = [ 'code', diff --git a/tests/Models/CompanyTest.php b/tests/Models/CompanyTest.php index e039768..2ded159 100644 --- a/tests/Models/CompanyTest.php +++ b/tests/Models/CompanyTest.php @@ -4,7 +4,6 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Company; -use Creasi\Base\Models\File; use Creasi\Base\Models\Enums\CompanyRelativeType; use Creasi\Base\Models\Enums\EmploymentStatus; use Creasi\Base\Models\Enums\EmploymentType; @@ -33,18 +32,6 @@ public function should_have_addresses() $this->assertInstanceOf(Address::class, $company->addresses->first()); } - #[Test] - public function should_have_documents() - { - $company = Company::factory()->createOne(); - $file = File::factory()->createOne(); - - $company->files()->save($file); - - $this->assertCount(1, $company->files); - $this->assertCount(1, $file->ownedByCompanies); - } - #[Test] public function should_have_employees() { diff --git a/tests/Models/FileTest.php b/tests/Models/FileTest.php deleted file mode 100644 index dacf939..0000000 --- a/tests/Models/FileTest.php +++ /dev/null @@ -1,38 +0,0 @@ -createOne(); - - $this->assertModelExists($model); - - return $model; - } - - #[Test] - public function should_have_revisions() - { - - $original = File::factory()->createOne([ - 'path' => 'some/place/elsewhere.pdf', - ]); - - $revision = $original->addRevision('other/place/elsewhere.pdf'); - - $this->assertTrue($original->is($revision->revisionOf)); - $this->assertCount(1, $original->revisions); - $this->assertNotSame($original, $revision); - $this->assertSame($original->name, $revision->name); - } -} diff --git a/tests/Models/FileUploadTest.php b/tests/Models/FileUploadTest.php new file mode 100644 index 0000000..c9390d5 --- /dev/null +++ b/tests/Models/FileUploadTest.php @@ -0,0 +1,117 @@ +createOne(); + + $this->assertModelExists($model); + + return $model; + } + + #[Test] + public function should_have_revisions() + { + $original = FileUpload::factory()->createOne([ + 'path' => 'path/to/file.pdf', + ]); + + $revision = $original->addRevision('path/to/revision.pdf'); + + $this->assertTrue($original->is($revision->revisionOf)); + $this->assertCount(1, $original->revisions); + $this->assertNotSame($original, $revision); + $this->assertSame($original->name, $revision->name); + } + + #[Test] + public function could_attached_to_personnel() + { + $person = Personnel::factory()->createOne(); + $files = FileUpload::factory(2)->sequence(fn (Sequence $seq) => [ + 'path' => "file-{$seq->index}.pdf" + ])->create(); + + $person->files()->saveMany($files); + + $this->assertCount(2, $person->files); + + foreach ($person->files as $i => $file) { + $this->assertTrue($file->is($files[$i])); + + $this->assertCount($file->attaches()->count(), $file->ownedByPersonnels); + } + } + + #[Test] + public function could_attached_to_many_personnels() + { + $people = Personnel::factory(2)->create(); + $file = FileUpload::factory()->createOne([ + 'path' => 'path/to/file.pdf', + ]); + + foreach ($people as $person) { + $person->files()->sync($file); + + $this->assertCount(1, $person->files); + } + + $this->assertCount($file->attaches()->count(), $file->ownedByPersonnels); + + $revision = $file->addRevision('path/to/revision.pdf'); + + $this->assertCount($revision->attaches()->count(), $revision->ownedByPersonnels); + } + + #[Test] + public function could_attached_to_company() + { + $company = Company::factory()->createOne(); + $files = FileUpload::factory(2)->sequence(fn (Sequence $seq) => [ + 'path' => "file-{$seq->index}.pdf" + ])->create(); + + $company->files()->saveMany($files); + + $this->assertCount(2, $company->files); + + foreach ($company->files as $i => $file) { + $this->assertTrue($file->is($files[$i])); + + $this->assertCount($file->attaches()->count(), $file->ownedByCompanies); + } + } + + #[Test] + public function could_attached_to_many_companies() + { + $companies = Company::factory(2)->create(); + $file = FileUpload::factory()->createOne([ + 'path' => 'path/to/file.pdf', + ]); + + foreach ($companies as $company) { + $company->files()->sync($file); + + $this->assertCount(1, $company->files); + } + + $this->assertCount($file->attaches()->count(), $file->ownedByCompanies); + } +} diff --git a/tests/Models/PersonnelTest.php b/tests/Models/PersonnelTest.php index 309fc30..3d7c5cc 100644 --- a/tests/Models/PersonnelTest.php +++ b/tests/Models/PersonnelTest.php @@ -3,7 +3,6 @@ namespace Creasi\Tests\Models; use Creasi\Base\Models\Address; -use Creasi\Base\Models\File; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; @@ -30,18 +29,6 @@ public function should_have_addresses() $this->assertInstanceOf(Address::class, $person->addresses->first()); } - #[Test] - public function should_have_documents() - { - $company = Personnel::factory()->createOne(); - $document = File::factory()->createOne(); - - $company->files()->save($document); - - $this->assertCount(1, $company->files); - $this->assertCount(1, $document->ownedByPersonnels); - } - #[Test] public function should_have_relatives() { From c74f69e491b04518a4c4589194daffcb7c6259f1 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 16 Jul 2023 15:40:12 +0700 Subject: [PATCH 15/71] feat: add ability to store `personnel` and `company` avatars in `file_uploads` table Signed-off-by: Fery Wardiyanto --- database/factories/CompanyFactory.php | 1 - database/factories/FileUploadFactory.php | 4 +- database/factories/IdentityFactory.php | 1 - database/factories/PersonnelFactory.php | 1 - ...ate_identities_address_and_files_table.php | 4 +- ..._create_companies_and_personnels_table.php | 2 - src/Contracts/HasFileUploads.php | 14 +++- src/Models/Company.php | 3 +- src/Models/Concerns/HasAvatar.php | 34 +++++++++ src/Models/Concerns/WithFileUploads.php | 19 ++++- src/Models/Enums/FileUploadType.php | 16 +++++ src/Models/FileUpload.php | 70 ++++++++++++++++--- src/Models/Personnel.php | 3 +- tests/Models/CompanyTest.php | 21 ++++++ tests/Models/FileUploadTest.php | 54 +++++++------- tests/Models/PersonnelTest.php | 17 +++++ 16 files changed, 215 insertions(+), 49 deletions(-) create mode 100644 src/Models/Concerns/HasAvatar.php create mode 100644 src/Models/Enums/FileUploadType.php diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 7806233..31dfb3b 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -23,7 +23,6 @@ public function definition(): array 'name' => $this->faker->company(), 'email' => $this->faker->safeEmail(), 'phone_number' => '08'.$this->faker->numerify('##########'), - 'logo_path' => null, 'summary' => $this->faker->sentence(4), ]; } diff --git a/database/factories/FileUploadFactory.php b/database/factories/FileUploadFactory.php index d9b8471..3cab160 100644 --- a/database/factories/FileUploadFactory.php +++ b/database/factories/FileUploadFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Http\Testing\File; @@ -24,7 +25,8 @@ public function definition(): array 'title' => $title = $this->faker->word(), 'name' => $name = Str::slug($title), 'path' => File::fake()->create($name)->path(), - 'drive' => null, + 'type' => $this->faker->randomElement(FileUploadType::cases()), + 'disk' => null, 'summary' => $this->faker->sentence(4), ]; } diff --git a/database/factories/IdentityFactory.php b/database/factories/IdentityFactory.php index e6861c4..e650d65 100644 --- a/database/factories/IdentityFactory.php +++ b/database/factories/IdentityFactory.php @@ -39,7 +39,6 @@ public function definition(): array 'education' => $this->faker->randomElement(Education::cases()), 'gender' => $gender, 'religion' => $this->faker->randomElement(Religion::cases()), - 'photo_path' => null, 'summary' => $this->faker->sentence(4), ]; } diff --git a/database/factories/PersonnelFactory.php b/database/factories/PersonnelFactory.php index 6132a68..c17f191 100644 --- a/database/factories/PersonnelFactory.php +++ b/database/factories/PersonnelFactory.php @@ -25,7 +25,6 @@ public function definition(): array 'name' => $this->faker->firstName(), 'email' => $this->faker->safeEmail(), 'phone_number' => '08'.$this->faker->numerify('##########'), - 'photo_path' => null, 'summary' => $this->faker->sentence(4), ]; } diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index 3991a93..2e0f871 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -25,7 +25,6 @@ public function up(): void $table->string('education', 3)->nullable(); $table->unsignedTinyInteger('religion')->nullable(); $table->string('phone_number', 20)->nullable(); - $table->string('photo_path')->nullable(); $table->text('summary')->nullable(); $table->timestamps(); @@ -58,7 +57,8 @@ public function up(): void $table->string('title')->nullable(); $table->string('name')->nullable(); $table->string('path'); - $table->string('drive')->nullable(); + $table->unsignedSmallInteger('type')->nullable(); + $table->string('disk')->nullable(); $table->string('summary')->nullable(); $table->timestamps(); diff --git a/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php b/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php index 58397e0..c6a21fd 100644 --- a/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php +++ b/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php @@ -18,7 +18,6 @@ public function up(): void $table->string('name'); $table->string('email')->unique()->nullable(); $table->string('phone_number', 20)->nullable(); - $table->string('logo_path')->nullable(); $table->text('summary')->nullable(); $table->timestamps(); @@ -42,7 +41,6 @@ public function up(): void $table->string('name'); $table->string('email')->unique()->nullable(); $table->string('phone_number', 20)->nullable(); - $table->string('photo_path')->nullable(); $table->text('summary')->nullable(); $table->timestamps(); diff --git a/src/Contracts/HasFileUploads.php b/src/Contracts/HasFileUploads.php index 582c2a6..dfa4c39 100644 --- a/src/Contracts/HasFileUploads.php +++ b/src/Contracts/HasFileUploads.php @@ -2,6 +2,9 @@ namespace Creasi\Base\Contracts; +use Creasi\Base\Models\Enums\FileUploadType; +use Illuminate\Http\UploadedFile; + /** * @property-read \Illuminate\Database\Eloquent\Collection $files * @@ -10,7 +13,16 @@ interface HasFileUploads { /** - * @return \Illuminate\Database\Eloquent\Relations\MorphMany|\Creasi\Base\Models\FileUpload + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function files(); + + public function storeFile( + FileUploadType $type, + string|UploadedFile $path, + string $name, + ?string $title = null, + ?string $summary = null, + ?string $disk = null, + ); } diff --git a/src/Models/Company.php b/src/Models/Company.php index 8790c80..cc3672a 100644 --- a/src/Models/Company.php +++ b/src/Models/Company.php @@ -4,6 +4,7 @@ use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Contracts\Contactable; +use Creasi\Base\Models\Concerns\HasAvatar; use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Enums\CompanyRelativeType; use Creasi\Nusa\Contracts\Addressable; @@ -24,6 +25,7 @@ class Company extends Model implements Addressable, Contactable, HasFileUploads { use HasAddresses; + use HasAvatar; use WithFileUploads; protected $fillable = [ @@ -31,7 +33,6 @@ class Company extends Model implements Addressable, Contactable, HasFileUploads 'name', 'email', 'phone_number', - 'logo_path', 'summary', ]; diff --git a/src/Models/Concerns/HasAvatar.php b/src/Models/Concerns/HasAvatar.php new file mode 100644 index 0000000..f5a546d --- /dev/null +++ b/src/Models/Concerns/HasAvatar.php @@ -0,0 +1,34 @@ + $this->getAvatarFile?->first()); + } + + public function setAvatar(string|UploadedFile $image) + { + return $this->storeFile(FileUploadType::Avatar, $image, $this->getRouteKey(), 'Avatar Image'); + } + + protected function getAvatarFile() + { + return $this->files()->where('type', FileUploadType::Avatar); + } +} diff --git a/src/Models/Concerns/WithFileUploads.php b/src/Models/Concerns/WithFileUploads.php index 223d55f..fcbf40e 100644 --- a/src/Models/Concerns/WithFileUploads.php +++ b/src/Models/Concerns/WithFileUploads.php @@ -2,8 +2,10 @@ namespace Creasi\Base\Models\Concerns; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\FileAttached; +use Illuminate\Http\UploadedFile; /** * @mixin \Creasi\Base\Contracts\HasFileUploads @@ -11,11 +13,26 @@ trait WithFileUploads { /** - * @return \Illuminate\Database\Eloquent\Relations\MorphMany + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function files() { return $this->morphToMany(FileUpload::class, 'attached_to', 'file_attached', null, 'file_upload_id') ->using(FileAttached::class); } + + public function storeFile( + FileUploadType $type, + string|UploadedFile $path, + string $name, + ?string $title = null, + ?string $summary = null, + ?string $disk = null, + ): FileUpload { + $file = FileUpload::store($type, $path, $name, $title, $summary, $disk); + + $this->files()->attach($file); + + return $file; + } } diff --git a/src/Models/Enums/FileUploadType.php b/src/Models/Enums/FileUploadType.php new file mode 100644 index 0000000..f9cee89 --- /dev/null +++ b/src/Models/Enums/FileUploadType.php @@ -0,0 +1,16 @@ + $revisions * @property-read \Illuminate\Database\Eloquent\Collection $attaches @@ -18,6 +27,7 @@ * @property-read \Illuminate\Database\Eloquent\Collection $ownedByCompanies * @property-read \Illuminate\Database\Eloquent\Collection $ownedByPersonnels * + * @method static static store(FileUploadType $type, string|UploadedFile $path, string $name, ?string $title = null, ?string $summary = null, ?string $disk = null) * @method static \Database\Factories\FileUploadFactory factory() */ class FileUpload extends Model @@ -30,14 +40,27 @@ class FileUpload extends Model 'title', 'name', 'path', - 'drive', + 'type', + 'disk', 'summary', ]; protected $casts = [ - // . + 'type' => FileUploadType::class ]; + public function url(): Attribute + { + return Attribute::get(fn ($_, array $attrs) => $this->is_internal + ? Storage::url($attrs['path']) + : $attrs['path']); + } + + public function isInternal(): Attribute + { + return Attribute::get(fn ($_, array $attrs) => !\str_contains($attrs['path'], '://')); + } + protected function attachedTo(string $owner) { return $this->morphedByMany($owner, 'attached_to', 'file_attached', 'file_upload_id') @@ -78,17 +101,42 @@ public function revisionOf() return $this->belongsTo(static::class, 'revision_id'); } - public function addRevision(string $filePath, ?string $name = null, ?string $summary = null): static - { - /** @var static */ - $revision = $this->revisions()->create([ - 'title' => $this->title, - 'drive' => $this->drive, - 'name' => $name ?: $this->name, - 'path' => $filePath, + public function scopeStore( + Builder $query, + FileUploadType $type, + string|UploadedFile $path, + string $name, + ?string $title = null, + ?string $summary = null, + ?string $disk = null + ): static { + $name = Str::slug($name); + + if ($path instanceof UploadedFile) { + $path = $path->store($type->key().'/'.$name, $disk ?? []); + } + + $instance = $query->newModelInstance([ + 'title' => $title, + 'disk' => $disk, + 'type' => $type, + 'name' => $name, + 'path' => $path, 'summary' => $summary, ]); + $instance->save(); + + return $instance; + } + + public function createRevision(string|UploadedFile $path, ?string $summary = null): static + { + $revision = static::store($this->type, $path, $this->name, $this->title, $summary, $this->disk); + + /** @var static */ + $this->revisions()->save($revision); + foreach ($this->attaches as $model) { $model->attachedTo->files()->sync($revision); } diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index c841672..0dd3432 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -4,6 +4,7 @@ use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Contracts\Contactable; +use Creasi\Base\Models\Concerns\HasAvatar; use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Concerns\HasIdentity; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; @@ -21,6 +22,7 @@ class Personnel extends Model implements Addressable, Contactable, HasFileUploads { use HasAddresses; + use HasAvatar; use HasIdentity; use WithFileUploads; @@ -29,7 +31,6 @@ class Personnel extends Model implements Addressable, Contactable, HasFileUpload 'name', 'email', 'phone_number', - 'photo_path', 'summary', ]; diff --git a/tests/Models/CompanyTest.php b/tests/Models/CompanyTest.php index 2ded159..09231ef 100644 --- a/tests/Models/CompanyTest.php +++ b/tests/Models/CompanyTest.php @@ -7,8 +7,11 @@ use Creasi\Base\Models\Enums\CompanyRelativeType; use Creasi\Base\Models\Enums\EmploymentStatus; use Creasi\Base\Models\Enums\EmploymentType; +use Creasi\Base\Models\Enums\FileUploadType; +use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; +use Illuminate\Http\UploadedFile; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; @@ -32,6 +35,24 @@ public function should_have_addresses() $this->assertInstanceOf(Address::class, $company->addresses->first()); } + #[Test] + public function should_have_avatar_image() + { + $company = Company::factory()->createOne(); + + // $this->assertNull($company->avatar); + + // dump($company->toArray()); + + $avatar = $company->setAvatar( + UploadedFile::fake()->image('logo.png') + ); + + $this->assertInstanceOf(FileUpload::class, $company->avatar); + $this->assertEquals(FileUploadType::Avatar, $company->avatar->type); + $this->assertTrue($company->avatar->is_internal); + } + #[Test] public function should_have_employees() { diff --git a/tests/Models/FileUploadTest.php b/tests/Models/FileUploadTest.php index c9390d5..7e42eee 100644 --- a/tests/Models/FileUploadTest.php +++ b/tests/Models/FileUploadTest.php @@ -3,11 +3,13 @@ namespace Creasi\Tests\Models; use Creasi\Base\Models\Company; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileAttached; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; use Illuminate\Database\Eloquent\Factories\Sequence; +use Illuminate\Http\UploadedFile; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; @@ -27,11 +29,13 @@ public function should_be_exists() #[Test] public function should_have_revisions() { - $original = FileUpload::factory()->createOne([ - 'path' => 'path/to/file.pdf', - ]); + $file = UploadedFile::fake()->create('original.pdf'); + + $original = FileUpload::store(FileUploadType::Document, $file, 'document'); - $revision = $original->addRevision('path/to/revision.pdf'); + $revision = $original->createRevision( + UploadedFile::fake()->create('revision.pdf') + ); $this->assertTrue($original->is($revision->revisionOf)); $this->assertCount(1, $original->revisions); @@ -43,17 +47,18 @@ public function should_have_revisions() public function could_attached_to_personnel() { $person = Personnel::factory()->createOne(); - $files = FileUpload::factory(2)->sequence(fn (Sequence $seq) => [ - 'path' => "file-{$seq->index}.pdf" - ])->create(); + $files = [ + 'first' => UploadedFile::fake()->create('first.pdf'), + 'second' => UploadedFile::fake()->create('second.pdf'), + ]; - $person->files()->saveMany($files); + foreach ($files as $name => $file) { + $person->storeFile(FileUploadType::Document, $file, $name); + } $this->assertCount(2, $person->files); - foreach ($person->files as $i => $file) { - $this->assertTrue($file->is($files[$i])); - + foreach ($person->files as $file) { $this->assertCount($file->attaches()->count(), $file->ownedByPersonnels); } } @@ -67,14 +72,14 @@ public function could_attached_to_many_personnels() ]); foreach ($people as $person) { - $person->files()->sync($file); + $person->files()->attach($file); $this->assertCount(1, $person->files); } $this->assertCount($file->attaches()->count(), $file->ownedByPersonnels); - $revision = $file->addRevision('path/to/revision.pdf'); + $revision = $file->createRevision('path/to/revision.pdf'); $this->assertCount($revision->attaches()->count(), $revision->ownedByPersonnels); } @@ -83,17 +88,18 @@ public function could_attached_to_many_personnels() public function could_attached_to_company() { $company = Company::factory()->createOne(); - $files = FileUpload::factory(2)->sequence(fn (Sequence $seq) => [ - 'path' => "file-{$seq->index}.pdf" - ])->create(); + $files = [ + 'first' => UploadedFile::fake()->create('first.pdf'), + 'second' => UploadedFile::fake()->create('second.pdf'), + ]; - $company->files()->saveMany($files); + foreach ($files as $name => $file) { + $company->storeFile(FileUploadType::Document, $file, $name); + } $this->assertCount(2, $company->files); - foreach ($company->files as $i => $file) { - $this->assertTrue($file->is($files[$i])); - + foreach ($company->files as $file) { $this->assertCount($file->attaches()->count(), $file->ownedByCompanies); } } @@ -102,16 +108,12 @@ public function could_attached_to_company() public function could_attached_to_many_companies() { $companies = Company::factory(2)->create(); - $file = FileUpload::factory()->createOne([ - 'path' => 'path/to/file.pdf', - ]); + $file = UploadedFile::fake()->create('document.pdf'); foreach ($companies as $company) { - $company->files()->sync($file); + $company->storeFile(FileUploadType::Document, $file, 'document'); $this->assertCount(1, $company->files); } - - $this->assertCount($file->attaches()->count(), $file->ownedByCompanies); } } diff --git a/tests/Models/PersonnelTest.php b/tests/Models/PersonnelTest.php index 3d7c5cc..6552cb6 100644 --- a/tests/Models/PersonnelTest.php +++ b/tests/Models/PersonnelTest.php @@ -3,9 +3,12 @@ namespace Creasi\Tests\Models; use Creasi\Base\Models\Address; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; +use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; +use Illuminate\Http\UploadedFile; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; @@ -29,6 +32,20 @@ public function should_have_addresses() $this->assertInstanceOf(Address::class, $person->addresses->first()); } + #[Test] + public function should_have_avatar_image() + { + $person = Personnel::factory()->createOne(); + + $avatar = $person->setAvatar( + UploadedFile::fake()->image('avatar.png') + ); + + $this->assertInstanceOf(FileUpload::class, $person->avatar); + $this->assertEquals(FileUploadType::Avatar, $person->avatar->type); + $this->assertTrue($person->avatar->is_internal); + } + #[Test] public function should_have_relatives() { From c683ba0c9331c751259382f752fb0707fd01a9cf Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 16 Jul 2023 23:32:38 +0700 Subject: [PATCH 16/71] fix(tests): fix creasico/laravel-package#47 and see creasico/laravel-package#48 for the guide Signed-off-by: Fery Wardiyanto --- .github/workflows/test.yml | 7 ++----- phpunit.xml | 4 ++-- tests/TestCase.php | 22 ++++++++++++++++------ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 605c656..f8e2165 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,9 +102,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: test-reports - path: | - tests/coverage - tests/reports + path: tests/reports reports: name: Report Test Coverages @@ -116,10 +114,9 @@ jobs: uses: actions/download-artifact@v3 with: name: test-reports - path: tests - name: Report to CodeClimate run: | curl -LSs $CC_TEST_REPORTER_URL > ./cc-test-reporter && chmod +x ./cc-test-reporter - ./cc-test-reporter sum-coverage -o - tests/reports/codeclimate.*.json | ./cc-test-reporter upload-coverage --input - + ./cc-test-reporter sum-coverage -o - codeclimate.*.json | ./cc-test-reporter upload-coverage --input - diff --git a/phpunit.xml b/phpunit.xml index a227f01..8a5827c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,5 @@ - + tests @@ -8,7 +8,7 @@ - + diff --git a/tests/TestCase.php b/tests/TestCase.php index 14f8a02..e71907c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -36,21 +36,31 @@ protected function getEnvironmentSetUp($app): void $config->set('app.locale', 'id'); $config->set('app.faker_locale', 'id_ID'); - if (env('DB_CONNECTION', 'sqlite')) { - $config->set('database.default', 'sqlite'); + $conn = env('DB_CONNECTION', 'sqlite'); - $database = __DIR__.'/test.sqlite'; + $config->set('database.default', $conn); - if (! file_exists($database)) { + if ($conn === 'sqlite') { + if (! file_exists($database = __DIR__.'/test.sqlite')) { touch($database); } - $config->set('database.connections.sqlite', [ - 'driver' => 'sqlite', + $this->mergeConfig($config, 'database.connections.sqlite', [ 'database' => $database, 'foreign_key_constraints' => true, ]); + } else { + $this->mergeConfig($config, 'database.connections.'.$conn, [ + 'database' => env('DB_DATABASE', 'creasi_test'), + 'username' => env('DB_USERNAME', 'creasico'), + 'password' => env('DB_PASSWORD', 'secret'), + ]); } }); } + + private function mergeConfig(Repository $config, string $key, array $value) + { + $config->set($key, array_merge($config->get($key, []), $value)); + } } From b965cb172ee0189c83810250eb3147d01494fd57 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 03:36:10 +0700 Subject: [PATCH 17/71] chore(deps): bump `creasi/laravel-nusa` v0.1.0 see https://github.com/creasico/laravel-nusa/releases/tag/v0.1.0 Signed-off-by: Fery Wardiyanto --- composer.json | 2 +- composer.lock | 12 ++++++------ src/Models/Company.php | 8 ++++---- src/Models/Personnel.php | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index cec0716..f56b00c 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ }, "require": { "php": "^8.1", - "creasi/laravel-nusa": "^0.0.5", + "creasi/laravel-nusa": "^0.1.0", "illuminate/contracts": "^10.0", "illuminate/database": "^10.0", "illuminate/support": "^10.0" diff --git a/composer.lock b/composer.lock index 82f7f26..4dd804e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "70e015f6eddcfdac0812ea08a360de6e", + "content-hash": "205b32c12784e1825b1adf6ac161cfd9", "packages": [ { "name": "brick/math", @@ -63,16 +63,16 @@ }, { "name": "creasi/laravel-nusa", - "version": "v0.0.5", + "version": "v0.1.0", "source": { "type": "git", "url": "https://github.com/creasico/laravel-nusa.git", - "reference": "dc0c560441c4bd0e5e4d904472d320fb81c0a4b9" + "reference": "7004d32213f885ba17fe981ea1de4e7fdea99cf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/creasico/laravel-nusa/zipball/dc0c560441c4bd0e5e4d904472d320fb81c0a4b9", - "reference": "dc0c560441c4bd0e5e4d904472d320fb81c0a4b9", + "url": "https://api.github.com/repos/creasico/laravel-nusa/zipball/7004d32213f885ba17fe981ea1de4e7fdea99cf4", + "reference": "7004d32213f885ba17fe981ea1de4e7fdea99cf4", "shasum": "" }, "require": { @@ -128,7 +128,7 @@ "issues": "https://github.com/creasico/laravel-nusa/issues", "source": "https://github.com/creasico/laravel-nusa" }, - "time": "2023-07-07T06:00:45+00:00" + "time": "2023-07-16T20:14:52+00:00" }, { "name": "dflydev/dot-access-data", diff --git a/src/Models/Company.php b/src/Models/Company.php index cc3672a..315fa01 100644 --- a/src/Models/Company.php +++ b/src/Models/Company.php @@ -7,8 +7,8 @@ use Creasi\Base\Models\Concerns\HasAvatar; use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Enums\CompanyRelativeType; -use Creasi\Nusa\Contracts\Addressable; -use Creasi\Nusa\Support\HasAddresses; +use Creasi\Nusa\Contracts\HasAddresses; +use Creasi\Nusa\Models\Concerns\WithAddresses; /** * @property null|bool $is_internal @@ -22,10 +22,10 @@ * * @method static \Database\Factories\CompanyFactory factory() */ -class Company extends Model implements Addressable, Contactable, HasFileUploads +class Company extends Model implements HasAddresses, Contactable, HasFileUploads { - use HasAddresses; use HasAvatar; + use WithAddresses; use WithFileUploads; protected $fillable = [ diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index 0dd3432..1091d99 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -8,8 +8,8 @@ use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Concerns\HasIdentity; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; -use Creasi\Nusa\Contracts\Addressable; -use Creasi\Nusa\Support\HasAddresses; +use Creasi\Nusa\Contracts\HasAddresses; +use Creasi\Nusa\Models\Concerns\WithAddresses; /** * @property ?string $photo_path @@ -19,11 +19,11 @@ * * @method static \Database\Factories\PersonnelFactory factory() */ -class Personnel extends Model implements Addressable, Contactable, HasFileUploads +class Personnel extends Model implements HasAddresses, Contactable, HasFileUploads { - use HasAddresses; use HasAvatar; use HasIdentity; + use WithAddresses; use WithFileUploads; protected $fillable = [ From 4d0548e400a8729869d36e199e7fc004029285d4 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 06:30:37 +0700 Subject: [PATCH 18/71] feat(deps): utilize `package:test` command from `orchestra/testbench` to run the tests Signed-off-by: Fery Wardiyanto --- composer.json | 14 ++-- composer.lock | 193 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 187 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index f56b00c..ce1a74f 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,10 @@ "pint --preset laravel" ], "test": [ - "vendor/bin/phpunit --colors=always --coverage-text" + "testbench package:test --coverage" + ], + "testbench": [ + "testbench" ] }, "autoload": { @@ -35,17 +38,14 @@ "require": { "php": "^8.1", "creasi/laravel-nusa": "^0.1.0", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/support": "^10.0" + "laravel/framework": "^9.0|^10.0" }, "require-dev": { "composer-runtime-api": "*", "laravel/dusk": "^7.0", "laravel/pint": "^1.1", - "mockery/mockery": "^1.5", - "orchestra/testbench": "^8.5", - "phpunit/phpunit": "^10.1" + "nunomaduro/collision": "^7.4", + "orchestra/testbench": "^8.5" }, "config": { "preferred-install": "dist", diff --git a/composer.lock b/composer.lock index 4dd804e..e8be75a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "205b32c12784e1825b1adf6ac161cfd9", + "content-hash": "4f56c80aaa36ab831d83194c166242dc", "packages": [ { "name": "brick/math", @@ -4850,6 +4850,77 @@ }, "time": "2023-06-12T08:44:38+00:00" }, + { + "name": "filp/whoops", + "version": "2.15.3", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "c83e88a30524f9360b11f585f71e6b17313b7187" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187", + "reference": "c83e88a30524f9360b11f585f71e6b17313b7187", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.15.3" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2023-07-13T12:00:00+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.5.0", @@ -5094,16 +5165,16 @@ }, { "name": "laravel/pint", - "version": "v1.10.4", + "version": "v1.10.5", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "f56798088068af8bd75a8f2c4ecae022990fdf75" + "reference": "a458fb057bfa2f5a09888a8aa349610be807b0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/f56798088068af8bd75a8f2c4ecae022990fdf75", - "reference": "f56798088068af8bd75a8f2c4ecae022990fdf75", + "url": "https://api.github.com/repos/laravel/pint/zipball/a458fb057bfa2f5a09888a8aa349610be807b0c3", + "reference": "a458fb057bfa2f5a09888a8aa349610be807b0c3", "shasum": "" }, "require": { @@ -5116,7 +5187,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.21.1", "illuminate/view": "^10.5.1", - "laravel-zero/framework": "^10.0.2", + "laravel-zero/framework": "^10.1.1", "mockery/mockery": "^1.5.1", "nunomaduro/larastan": "^2.5.1", "nunomaduro/termwind": "^1.15.1", @@ -5156,7 +5227,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-07-11T15:18:27+00:00" + "time": "2023-07-14T10:26:01+00:00" }, { "name": "mockery/mockery", @@ -5351,6 +5422,102 @@ }, "time": "2023-06-25T14:52:30+00:00" }, + { + "name": "nunomaduro/collision", + "version": "v7.7.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "69a07197d055456d29911116fca3bc2c985f524b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/69a07197d055456d29911116fca3bc2c985f524b", + "reference": "69a07197d055456d29911116fca3bc2c985f524b", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.15.2", + "nunomaduro/termwind": "^1.15.1", + "php": "^8.1.0", + "symfony/console": "^6.3.0" + }, + "conflict": { + "phpunit/phpunit": "<10.1.2" + }, + "require-dev": { + "brianium/paratest": "^7.2.2", + "laravel/framework": "^10.14.1", + "laravel/pint": "^1.10.3", + "laravel/sail": "^1.23.0", + "laravel/sanctum": "^3.2.5", + "laravel/tinker": "^2.8.1", + "nunomaduro/larastan": "^2.6.3", + "orchestra/testbench-core": "^8.5.8", + "pestphp/pest": "^2.8.1", + "phpunit/phpunit": "^10.2.2", + "sebastian/environment": "^6.0.1", + "spatie/laravel-ignition": "^2.2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2023-06-29T09:10:16+00:00" + }, { "name": "orchestra/testbench", "version": "v8.5.10", @@ -5986,16 +6153,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.2.4", + "version": "10.2.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "68484779b5a2ed711fbdeba6ca01910d87acdff2" + "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68484779b5a2ed711fbdeba6ca01910d87acdff2", - "reference": "68484779b5a2ed711fbdeba6ca01910d87acdff2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd", + "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd", "shasum": "" }, "require": { @@ -6067,7 +6234,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.5" }, "funding": [ { @@ -6083,7 +6250,7 @@ "type": "tidelift" } ], - "time": "2023-07-10T04:06:08+00:00" + "time": "2023-07-14T04:18:47+00:00" }, { "name": "pimple/pimple", From 6beef70844bf060aee39883b143af46b42c389cb Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 15:24:49 +0700 Subject: [PATCH 19/71] chore: add test for laravel-dusk macro Signed-off-by: Fery Wardiyanto --- tests/BaseTest.php | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/BaseTest.php diff --git a/tests/BaseTest.php b/tests/BaseTest.php new file mode 100644 index 0000000..b4307ac --- /dev/null +++ b/tests/BaseTest.php @@ -0,0 +1,68 @@ +assertTrue(Browser::hasMacro('waitForInertia')); + } + + #[Test] + public function it_throws_an_exception_if_the_count_doesnt_increase() + { + $driver = $this->mock(Driver::class); + + $driver->shouldReceive('executeScript') + ->with('return window.__inertiaNavigatedCount;') + ->once() + ->andReturn(0); + + $driver->shouldReceive('executeScript') + ->with('return window.__inertiaNavigatedCount > 0;') + ->andReturn(0); + + $browser = new Browser($driver); + + try { + $browser->waitForInertia(0.1); // wait for 0.1 seconds + } catch (TimeoutException $e) { + return $this->assertTrue(true); + } + + $this->fail('Should have thrown TimeoutException'); + } + + #[Test] + public function it_passes_when_the_count_increases() + { + $driver = $this->mock(Driver::class); + + $driver->shouldReceive('executeScript') + ->with('return window.__inertiaNavigatedCount;') + ->once() + ->andReturn(0); + + $driver->shouldReceive('executeScript') + ->with('return window.__inertiaNavigatedCount > 0;') + ->once() + ->andReturn(1); + + $browser = new Browser($driver); + $browser->waitForInertia(1); + + $this->assertTrue(true); + } +} From e356b76d7a1e43abf89e46b463ee1a2ecdae91de Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 15:25:38 +0700 Subject: [PATCH 20/71] chore: define routes Signed-off-by: Fery Wardiyanto --- config/creasico.php | 2 ++ routes/base.php | 17 +++++++++++++++++ src/ServiceProvider.php | 21 +++++++++++++++++---- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 routes/base.php diff --git a/config/creasico.php b/config/creasico.php index ca5d8ed..6c9c6d9 100644 --- a/config/creasico.php +++ b/config/creasico.php @@ -1,5 +1,7 @@ true, + 'routes_prefix' => 'base', ]; diff --git a/routes/base.php b/routes/base.php new file mode 100644 index 0000000..16c036e --- /dev/null +++ b/routes/base.php @@ -0,0 +1,17 @@ +loadTranslationsFrom(self::LIB_PATH.'/resources/lang', 'creasico'); $this->bootViewComposers(); + + $this->defineRoutes(); } - public function register() + public function register(): void { config([ 'creasi.nusa' => array_merge([ @@ -62,7 +65,7 @@ public function register() } - protected function registerPublishables() + protected function registerPublishables(): void { $this->publishes([ self::LIB_PATH.'/config/creasico.php' => \config_path('creasi/base.php'), @@ -77,13 +80,23 @@ protected function registerPublishables() ], ['creasi-lang']); } - protected function registerCommands() + protected function registerCommands(): void { $this->commands([ // . ]); } + protected function defineRoutes(): void + { + if (app()->routesAreCached() && config('creasi.base.routes_enable') === false) { + return; + } + + Route::prefix(config('creasi.base.routes_prefix', 'base')) + ->group(self::LIB_PATH.'/routes/base.php'); + } + /** * Register inertia.js helper for dusk testing * From 3652ceb0132cf91d3a65a8ba10e81a252e8d18ef Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 16:05:59 +0700 Subject: [PATCH 21/71] chore: add user model, migration and factory but exclude the migration and factory from distribution Signed-off-by: Fery Wardiyanto --- database/.gitattributes | 3 ++ database/factories/UserFactory.php | 43 +++++++++++++++++++ .../2014_10_12_000000_create_users_table.php | 32 ++++++++++++++ src/Models/User.php | 39 +++++++++++++++++ tests/Models/UserTest.php | 20 +++++++++ 5 files changed, 137 insertions(+) create mode 100644 database/.gitattributes create mode 100644 database/factories/UserFactory.php create mode 100644 database/migrations/2014_10_12_000000_create_users_table.php create mode 100644 src/Models/User.php create mode 100644 tests/Models/UserTest.php diff --git a/database/.gitattributes b/database/.gitattributes new file mode 100644 index 0000000..d6c566f --- /dev/null +++ b/database/.gitattributes @@ -0,0 +1,3 @@ +# Exclude unused files +2014_10_12_000000_create_users_table.php export-ignore +UserFactory.php export-ignore diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..c41ae4d --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,43 @@ + + */ +class UserFactory extends Factory +{ + protected $model = User::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => $name = Str::slug($this->faker->name()), + 'email' => str($name)->append('@example.com'), + 'email_verified_at' => now(), + 'password' => 'secret', + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + * + * @return static + */ + public function unverified(): static + { + return $this->state([ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php new file mode 100644 index 0000000..1f97419 --- /dev/null +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + } +}; diff --git a/src/Models/User.php b/src/Models/User.php new file mode 100644 index 0000000..69c45d7 --- /dev/null +++ b/src/Models/User.php @@ -0,0 +1,39 @@ + factory() + */ +class User extends Authenticatable +{ + use HasFactory; + use Notifiable; + + protected $fillable = ['name', 'email', 'password']; + + protected $hidden = ['password', 'remember_token']; + + protected $casts = [ + 'email_verified_at' => 'immutable_datetime', + ]; + + protected $appends = []; + + public function password(): Attribute + { + return Attribute::set(fn (string $value) => \bcrypt($value)); + } +} diff --git a/tests/Models/UserTest.php b/tests/Models/UserTest.php new file mode 100644 index 0000000..5a0df65 --- /dev/null +++ b/tests/Models/UserTest.php @@ -0,0 +1,20 @@ +createOne(); + + $this->assertModelExists($model); + } +} From 60813441d9d1a3927797ff95e13653065c0c84ae Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 17:19:44 +0700 Subject: [PATCH 22/71] feat: require `laravel/sanctum` as dev dependency and use it as default auth guard Signed-off-by: Fery Wardiyanto --- composer.json | 1 + composer.lock | 68 ++++++++++++++++++++++++++++++++++++++++++++- src/Models/User.php | 2 ++ tests/TestCase.php | 7 +++++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ce1a74f..0a8e9f1 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "composer-runtime-api": "*", "laravel/dusk": "^7.0", "laravel/pint": "^1.1", + "laravel/sanctum": "^3.2", "nunomaduro/collision": "^7.4", "orchestra/testbench": "^8.5" }, diff --git a/composer.lock b/composer.lock index e8be75a..dbde564 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4f56c80aaa36ab831d83194c166242dc", + "content-hash": "dffa6c26afc73744e753475e067d69ec", "packages": [ { "name": "brick/math", @@ -5229,6 +5229,72 @@ }, "time": "2023-07-14T10:26:01+00:00" }, + { + "name": "laravel/sanctum", + "version": "v3.2.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "8ebda85d59d3c414863a7f4d816ef8302faad876" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/8ebda85d59d3c414863a7f4d816ef8302faad876", + "reference": "8ebda85d59d3c414863a7f4d816ef8302faad876", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^9.21|^10.0", + "illuminate/contracts": "^9.21|^10.0", + "illuminate/database": "^9.21|^10.0", + "illuminate/support": "^9.21|^10.0", + "php": "^8.0.2" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^7.0|^8.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2023-05-01T19:39:51+00:00" + }, { "name": "mockery/mockery", "version": "1.6.2", diff --git a/src/Models/User.php b/src/Models/User.php index 69c45d7..ffdee8f 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; /** * @property-read int $id @@ -21,6 +22,7 @@ class User extends Authenticatable { use HasFactory; use Notifiable; + use HasApiTokens; protected $fillable = ['name', 'email', 'password']; diff --git a/tests/TestCase.php b/tests/TestCase.php index e71907c..2a41953 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,11 +2,13 @@ namespace Creasi\Tests; +use Creasi\Base\Models\User; use Creasi\Base\ServiceProvider; use Creasi\Nusa\ServiceProvider as NusaServiceProvider; use Illuminate\Contracts\Config\Repository; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\RefreshDatabase; +use Laravel\Sanctum\SanctumServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; class TestCase extends Orchestra @@ -22,6 +24,7 @@ protected function getPackageProviders($app): array return [ ServiceProvider::class, NusaServiceProvider::class, + SanctumServiceProvider::class, ]; } @@ -36,6 +39,10 @@ protected function getEnvironmentSetUp($app): void $config->set('app.locale', 'id'); $config->set('app.faker_locale', 'id_ID'); + $this->mergeConfig($config, 'auth.providers.users', [ + 'model' => User::class, + ]); + $conn = env('DB_CONNECTION', 'sqlite'); $config->set('database.default', $conn); From 468ab7db7fe4337436f3b1e6efd9ff78efd7d5b5 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 17:20:30 +0700 Subject: [PATCH 23/71] chore: initialize basic routing and test them Signed-off-by: Fery Wardiyanto --- routes/base.php | 18 ++++- .../Controllers/Account/HomeController.php | 25 +++++++ .../Controllers/Account/SettingController.php | 25 +++++++ src/Http/Controllers/CompanyController.php | 68 +++++++++++++++++++ src/Http/Controllers/Controller.php | 11 +++ src/Http/Controllers/SupportController.php | 16 +++++ src/Http/Requests/Company/StoreRequest.php | 21 ++++++ src/Http/Requests/Company/UpdateRequest.php | 25 +++++++ src/Http/Requests/FormRequest.php | 13 ++++ src/Http/Resources/CompanyResource.php | 19 ++++++ tests/Http/Account/HomeTest.php | 25 +++++++ tests/Http/Account/SettingTest.php | 25 +++++++ tests/Http/CompanyTest.php | 24 +++++++ tests/Http/SupportTest.php | 24 +++++++ 14 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 src/Http/Controllers/Account/HomeController.php create mode 100644 src/Http/Controllers/Account/SettingController.php create mode 100644 src/Http/Controllers/CompanyController.php create mode 100644 src/Http/Controllers/Controller.php create mode 100644 src/Http/Controllers/SupportController.php create mode 100644 src/Http/Requests/Company/StoreRequest.php create mode 100644 src/Http/Requests/Company/UpdateRequest.php create mode 100644 src/Http/Requests/FormRequest.php create mode 100644 src/Http/Resources/CompanyResource.php create mode 100644 tests/Http/Account/HomeTest.php create mode 100644 tests/Http/Account/SettingTest.php create mode 100644 tests/Http/CompanyTest.php create mode 100644 tests/Http/SupportTest.php diff --git a/routes/base.php b/routes/base.php index 16c036e..afde0c4 100644 --- a/routes/base.php +++ b/routes/base.php @@ -1,6 +1,6 @@ group(function () { + Route::apiResource('companies', Controllers\CompanyController::class); + + Route::controller(Controllers\Account\HomeController::class)->group(function () { + Route::get('account', 'show')->name('account.home'); + Route::put('account', 'update'); + }); + + Route::controller(Controllers\Account\SettingController::class)->group(function () { + Route::get('account/settings', 'show')->name('account.settings'); + Route::put('account/settings', 'update'); + }); + + Route::get('supports', Controllers\SupportController::class)->name('supports.home'); +}); diff --git a/src/Http/Controllers/Account/HomeController.php b/src/Http/Controllers/Account/HomeController.php new file mode 100644 index 0000000..14b27d5 --- /dev/null +++ b/src/Http/Controllers/Account/HomeController.php @@ -0,0 +1,25 @@ +authorizeResource(Company::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Collection + */ + public function index(Request $request) + { + $users = Company::query() + ->where('id', '<>', $request->user()->id) + ->latest(); + + return CompanyResource::collection($users->paginate()); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function store(StoreRequest $request) + { + /** @var Company $item */ + $item = Company::create($request->validated()); + + return $this->show($item, $request)->setStatusCode(201); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function show(Company $company, Request $request) + { + return CompanyResource::make($company)->toResponse($request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function update(UpdateRequest $request, Company $company) + { + $company->update($request->validated()); + + return $this->show($company, $request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(Company $company) + { + $company->delete(); + + return response()->noContent(); + } +} diff --git a/src/Http/Controllers/Controller.php b/src/Http/Controllers/Controller.php new file mode 100644 index 0000000..5b76fbb --- /dev/null +++ b/src/Http/Controllers/Controller.php @@ -0,0 +1,11 @@ + + */ + public function rules(): array + { + return [ + 'name' => ['required', 'string', 'unique:users,name'], + 'email' => ['required', 'email', 'unique:users,email'], + 'password' => ['required', 'confirmed', Password::defaults()], + ]; + } +} diff --git a/src/Http/Requests/Company/UpdateRequest.php b/src/Http/Requests/Company/UpdateRequest.php new file mode 100644 index 0000000..1a5d654 --- /dev/null +++ b/src/Http/Requests/Company/UpdateRequest.php @@ -0,0 +1,25 @@ + + */ + public function rules(): array + { + $user = $this->user(); + + return [ + 'name' => ['required', 'string', Rule::unique('users', 'name')->ignore($user->id)], + 'email' => ['required', 'email', Rule::unique('users', 'email')->ignore($user->id)], + 'password' => ['nullable', 'confirmed', Password::defaults()], + 'old-password' => ['required_unless:password,null'], + ]; + } +} diff --git a/src/Http/Requests/FormRequest.php b/src/Http/Requests/FormRequest.php new file mode 100644 index 0000000..97248b9 --- /dev/null +++ b/src/Http/Requests/FormRequest.php @@ -0,0 +1,13 @@ +createOne()); + + $response = $this->getJson('base/account'); + + $response->assertOk(); + } +} diff --git a/tests/Http/Account/SettingTest.php b/tests/Http/Account/SettingTest.php new file mode 100644 index 0000000..86f4222 --- /dev/null +++ b/tests/Http/Account/SettingTest.php @@ -0,0 +1,25 @@ +createOne()); + + $response = $this->getJson('base/account/settings'); + + $response->assertOk(); + } +} diff --git a/tests/Http/CompanyTest.php b/tests/Http/CompanyTest.php new file mode 100644 index 0000000..c9d6925 --- /dev/null +++ b/tests/Http/CompanyTest.php @@ -0,0 +1,24 @@ +createOne()); + + $response = $this->getJson('base/companies'); + + $response->assertOk(); + } +} diff --git a/tests/Http/SupportTest.php b/tests/Http/SupportTest.php new file mode 100644 index 0000000..57f98fb --- /dev/null +++ b/tests/Http/SupportTest.php @@ -0,0 +1,24 @@ +createOne()); + + $response = $this->getJson('base/supports'); + + $response->assertOk(); + } +} From 9c59c1d7b3361cf830d3eb8e95130f6690f2a85c Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 18:52:14 +0700 Subject: [PATCH 24/71] chore: rename trait `HasIdentity` to `WithIdentity` and add more interfaces Signed-off-by: Fery Wardiyanto --- src/Contracts/HasIdentity.php | 14 ++++++++++++++ src/Contracts/Identity.php | 16 ++++++++++++++++ .../{HasIdentity.php => WithIdentity.php} | 4 ++-- src/Models/Identity.php | 4 ++-- src/Models/Personnel.php | 4 ++-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 src/Contracts/HasIdentity.php create mode 100644 src/Contracts/Identity.php rename src/Models/Concerns/{HasIdentity.php => WithIdentity.php} (83%) diff --git a/src/Contracts/HasIdentity.php b/src/Contracts/HasIdentity.php new file mode 100644 index 0000000..79a9b30 --- /dev/null +++ b/src/Contracts/HasIdentity.php @@ -0,0 +1,14 @@ + factory() */ -class Identity extends Model +class Identity extends Model implements IdentityContract { protected $fillable = [ 'nik', diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index 1091d99..c5f72fb 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -6,7 +6,7 @@ use Creasi\Base\Contracts\Contactable; use Creasi\Base\Models\Concerns\HasAvatar; use Creasi\Base\Models\Concerns\WithFileUploads; -use Creasi\Base\Models\Concerns\HasIdentity; +use Creasi\Base\Models\Concerns\WithIdentity; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; use Creasi\Nusa\Contracts\HasAddresses; use Creasi\Nusa\Models\Concerns\WithAddresses; @@ -22,9 +22,9 @@ class Personnel extends Model implements HasAddresses, Contactable, HasFileUploads { use HasAvatar; - use HasIdentity; use WithAddresses; use WithFileUploads; + use WithIdentity; protected $fillable = [ 'code', From 1cc685eee5d4a7b2840568993b8f5ee4efd41a55 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 17 Jul 2023 19:50:55 +0700 Subject: [PATCH 25/71] chore: bring back relation between `users` and `identities` Signed-off-by: Fery Wardiyanto --- database/README.md | 5 +++++ database/factories/UserFactory.php | 2 +- ...001_create_identities_address_and_files_table.php | 1 + src/Models/Identity.php | 11 +++++++++++ src/Models/User.php | 5 +++++ tests/Models/UserTest.php | 12 ++++++++++++ 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/database/README.md b/database/README.md index 1a00ee4..b491e9e 100644 --- a/database/README.md +++ b/database/README.md @@ -94,6 +94,7 @@ erDiagram personnels ||..|| identities : profile identities { unsignedBigInt id PK + unsignedBigInt user_id FK morphs identity char(16) nik string prefix @@ -276,6 +277,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | +| `user_id` | `unsignedBigInt`, `nullable` | `foreign` | - | | `identity` | `morphs`, `nullable` | | - | | `nik` | `char(16)`, `nullable` | | - | | `prefix` | `varchar(10)`, `nullable` | | - | @@ -294,6 +296,9 @@ classDiagram - `timestamps` - `softDeletes` +**Relation Properties** +- `user_id` : reference `users` + ## Addresses ```mermaid diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index c41ae4d..0422d7d 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -7,7 +7,7 @@ use Illuminate\Support\Str; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> + * @extends Factory */ class UserFactory extends Factory { diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index 2e0f871..750b131 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -13,6 +13,7 @@ public function up(): void { Schema::create('identities', function (Blueprint $table) { $table->id(); + $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); $table->nullableMorphs('identity'); $table->char('nik', 16)->unique()->nullable(); diff --git a/src/Models/Identity.php b/src/Models/Identity.php index 79b7680..bf963b7 100644 --- a/src/Models/Identity.php +++ b/src/Models/Identity.php @@ -9,6 +9,7 @@ use Creasi\Nusa\Models\Regency; /** + * @property null|int $user_id * @property null|string $nik * @property null|string $prefix * @property string $fullname @@ -28,6 +29,7 @@ class Identity extends Model implements IdentityContract { protected $fillable = [ + 'user_id', 'nik', 'prefix', 'fullname', @@ -42,6 +44,7 @@ class Identity extends Model implements IdentityContract ]; protected $casts = [ + 'user_id' => 'int', 'birth_date' => 'immutable_date', 'birth_place_code' => 'int', 'education' => Education::class, @@ -49,6 +52,14 @@ class Identity extends Model implements IdentityContract 'religion' => Religion::class, ]; + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|User + */ + public function user() + { + return $this->belongsTo(User::class); + } + /** * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ diff --git a/src/Models/User.php b/src/Models/User.php index ffdee8f..aa88523 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -38,4 +38,9 @@ public function password(): Attribute { return Attribute::set(fn (string $value) => \bcrypt($value)); } + + public function profile() + { + return $this->hasOne(Identity::class); + } } diff --git a/tests/Models/UserTest.php b/tests/Models/UserTest.php index 5a0df65..a7aed75 100644 --- a/tests/Models/UserTest.php +++ b/tests/Models/UserTest.php @@ -2,6 +2,7 @@ namespace Creasi\Tests\Models; +use Creasi\Base\Models\Identity; use Creasi\Base\Models\User; use Creasi\Tests\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -17,4 +18,15 @@ public function should_be_exists(): void $this->assertModelExists($model); } + + #[Test] + public function it_could_have_profile() + { + $user = User::factory()->createOne(); + $identity = Identity::factory()->createOne(); + + $user->profile()->save($identity); + + $this->assertTrue($identity->user->is($user)); + } } From b729a4e24e6f11b85ce5d06a11019337f888cda2 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 18 Jul 2023 04:54:40 +0700 Subject: [PATCH 26/71] feat: initialize every resources possible Signed-off-by: Fery Wardiyanto --- routes/base.php | 37 +++++++--- .../Controllers/Account/HomeController.php | 25 ------- .../Controllers/Account/SettingController.php | 25 ------- src/Http/Controllers/AddressController.php | 66 ++++++++++++++++++ src/Http/Controllers/CompanyController.php | 5 +- src/Http/Controllers/Controller.php | 2 +- src/Http/Controllers/EmployeeController.php | 69 +++++++++++++++++++ src/Http/Controllers/FileUploadController.php | 65 +++++++++++++++++ src/Http/Controllers/ProfileController.php | 31 +++++++++ src/Http/Controllers/SettingController.php | 31 +++++++++ .../Controllers/StakeholderController.php | 69 +++++++++++++++++++ src/Http/Requests/Address/StoreRequest.php | 27 ++++++++ src/Http/Requests/Address/UpdateRequest.php | 20 ++++++ src/Http/Requests/Company/StoreRequest.php | 5 +- src/Http/Requests/Company/UpdateRequest.php | 7 +- src/Http/Requests/Employee/StoreRequest.php | 18 +++++ src/Http/Requests/Employee/UpdateRequest.php | 20 ++++++ src/Http/Requests/FileUpload/StoreRequest.php | 31 +++++++++ .../Requests/FileUpload/UpdateRequest.php | 20 ++++++ src/Http/Requests/FormRequest.php | 2 +- src/Http/Requests/ProfileRequest.php | 16 +++++ src/Http/Requests/SettingRequest.php | 18 +++++ .../Requests/Stakeholder/StoreRequest.php | 18 +++++ .../Requests/Stakeholder/UpdateRequest.php | 20 ++++++ .../Resources/Address/AddressCollection.php | 19 +++++ .../Resources/Address/AddressResource.php | 19 +++++ .../Resources/Company/CompanyCollection.php | 19 +++++ .../{ => Company}/CompanyResource.php | 8 +-- .../Resources/Employee/EmployeeCollection.php | 19 +++++ .../Resources/Employee/EmployeeResource.php | 19 +++++ .../FileUpload/FileUploadCollection.php | 19 +++++ .../FileUpload/FileUploadResource.php | 19 +++++ src/Http/Resources/ProfileResource.php | 19 +++++ src/Http/Resources/SettingResource.php | 19 +++++ .../Stakeholder/StakeholderCollection.php | 19 +++++ .../Stakeholder/StakeholderResource.php | 19 +++++ 36 files changed, 786 insertions(+), 78 deletions(-) delete mode 100644 src/Http/Controllers/Account/HomeController.php delete mode 100644 src/Http/Controllers/Account/SettingController.php create mode 100644 src/Http/Controllers/AddressController.php create mode 100644 src/Http/Controllers/EmployeeController.php create mode 100644 src/Http/Controllers/FileUploadController.php create mode 100644 src/Http/Controllers/ProfileController.php create mode 100644 src/Http/Controllers/SettingController.php create mode 100644 src/Http/Controllers/StakeholderController.php create mode 100644 src/Http/Requests/Address/StoreRequest.php create mode 100644 src/Http/Requests/Address/UpdateRequest.php create mode 100644 src/Http/Requests/Employee/StoreRequest.php create mode 100644 src/Http/Requests/Employee/UpdateRequest.php create mode 100644 src/Http/Requests/FileUpload/StoreRequest.php create mode 100644 src/Http/Requests/FileUpload/UpdateRequest.php create mode 100644 src/Http/Requests/ProfileRequest.php create mode 100644 src/Http/Requests/SettingRequest.php create mode 100644 src/Http/Requests/Stakeholder/StoreRequest.php create mode 100644 src/Http/Requests/Stakeholder/UpdateRequest.php create mode 100644 src/Http/Resources/Address/AddressCollection.php create mode 100644 src/Http/Resources/Address/AddressResource.php create mode 100644 src/Http/Resources/Company/CompanyCollection.php rename src/Http/Resources/{ => Company}/CompanyResource.php (52%) create mode 100644 src/Http/Resources/Employee/EmployeeCollection.php create mode 100644 src/Http/Resources/Employee/EmployeeResource.php create mode 100644 src/Http/Resources/FileUpload/FileUploadCollection.php create mode 100644 src/Http/Resources/FileUpload/FileUploadResource.php create mode 100644 src/Http/Resources/ProfileResource.php create mode 100644 src/Http/Resources/SettingResource.php create mode 100644 src/Http/Resources/Stakeholder/StakeholderCollection.php create mode 100644 src/Http/Resources/Stakeholder/StakeholderResource.php diff --git a/routes/base.php b/routes/base.php index afde0c4..a877160 100644 --- a/routes/base.php +++ b/routes/base.php @@ -1,6 +1,7 @@ group(function () { - Route::apiResource('companies', Controllers\CompanyController::class); + Route::apiResource('companies', Controllers\CompanyController::class)->withTrashed(); + Route::apiResource('employees', Controllers\EmployeeController::class); - Route::controller(Controllers\Account\HomeController::class)->group(function () { - Route::get('account', 'show')->name('account.home'); - Route::put('account', 'update'); - }); - - Route::controller(Controllers\Account\SettingController::class)->group(function () { - Route::get('account/settings', 'show')->name('account.settings'); - Route::put('account/settings', 'update'); - }); + Route::apiSingleton('profile', Controllers\ProfileController::class); + Route::apiSingleton('setting', Controllers\SettingController::class); Route::get('supports', Controllers\SupportController::class)->name('supports.home'); + + foreach (['companies', 'employees'] as $entity) { + Route::apiResources([ + "{$entity}.addresses" => Controllers\AddressController::class, + "{$entity}.files" => Controllers\FileUploadController::class, + ], [ + 'parameters' => [$entity => 'entity'], + 'shallow' => true, + ]); + } + + foreach (CompanyRelativeType::cases() as $stakeholder) { + if ($stakeholder->isInternal()) { + continue; + } + + $route = $stakeholder->key()->plural(); + + Route::apiResource($route, Controllers\StakeholderController::class)->parameters([ + (string) $route => 'stakeholder' + ]); + } }); diff --git a/src/Http/Controllers/Account/HomeController.php b/src/Http/Controllers/Account/HomeController.php deleted file mode 100644 index 14b27d5..0000000 --- a/src/Http/Controllers/Account/HomeController.php +++ /dev/null @@ -1,25 +0,0 @@ -authorizeResource(Address::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Collection + */ + public function index(HasAddresses $entity) + { + return new AddressCollection($entity->addresses()->paginate()); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function store(StoreRequest $request, HasAddresses $entity) + { + $item = $request->storeFor($entity); + + return $this->show($item, $request)->setStatusCode(201); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function show(Address $address, Request $request) + { + return AddressResource::make($address)->toResponse($request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function update(UpdateRequest $request, Address $address) + { + $address->update($request->validated()); + + return $this->show($address, $request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(Address $address) + { + $address->delete(); + + return response()->noContent(); + } +} diff --git a/src/Http/Controllers/CompanyController.php b/src/Http/Controllers/CompanyController.php index 7d2ffdc..2c0e4bc 100644 --- a/src/Http/Controllers/CompanyController.php +++ b/src/Http/Controllers/CompanyController.php @@ -4,7 +4,8 @@ use Creasi\Base\Http\Requests\Company\StoreRequest; use Creasi\Base\Http\Requests\Company\UpdateRequest; -use Creasi\Base\Http\Resources\CompanyResource; +use Creasi\Base\Http\Resources\Company\CompanyCollection; +use Creasi\Base\Http\Resources\Company\CompanyResource; use Creasi\Base\Models\Company; use Illuminate\Http\Request; @@ -24,7 +25,7 @@ public function index(Request $request) ->where('id', '<>', $request->user()->id) ->latest(); - return CompanyResource::collection($users->paginate()); + return new CompanyCollection($users->paginate()); } /** diff --git a/src/Http/Controllers/Controller.php b/src/Http/Controllers/Controller.php index 5b76fbb..a50c2a8 100644 --- a/src/Http/Controllers/Controller.php +++ b/src/Http/Controllers/Controller.php @@ -5,7 +5,7 @@ use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Routing\Controller as BaseController; -class Controller extends BaseController +abstract class Controller extends BaseController { use AuthorizesRequests; } diff --git a/src/Http/Controllers/EmployeeController.php b/src/Http/Controllers/EmployeeController.php new file mode 100644 index 0000000..aed5f87 --- /dev/null +++ b/src/Http/Controllers/EmployeeController.php @@ -0,0 +1,69 @@ +authorizeResource(Employee::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Collection + */ + public function index(Request $request, Employee $employee) + { + $users = $employee->newInstance() + ->where('id', '<>', $request->user()->id) + ->latest(); + + return new EmployeeCollection($users->paginate()); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function store(StoreRequest $request, Employee $employee) + { + /** @var Employee */ + $item = $employee->create($request->validated()); + + return $this->show($item, $request)->setStatusCode(201); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function show(Employee $employee, Request $request) + { + return EmployeeResource::make($employee)->toResponse($request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function update(UpdateRequest $request, Employee $employee) + { + $employee->update($request->validated()); + + return $this->show($employee, $request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(Employee $employee) + { + $employee->delete(); + + return response()->noContent(); + } +} diff --git a/src/Http/Controllers/FileUploadController.php b/src/Http/Controllers/FileUploadController.php new file mode 100644 index 0000000..63653ba --- /dev/null +++ b/src/Http/Controllers/FileUploadController.php @@ -0,0 +1,65 @@ +authorizeResource(FileUpload::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Collection + */ + public function index(HasFileUploads $entity) + { + return new FileUploadCollection($entity->files()->paginate()); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function store(StoreRequest $request, HasFileUploads $entity) + { + $item = $request->storeFor($entity); + + return $this->show($item, $request)->setStatusCode(201); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function show(FileUpload $file, Request $request) + { + return FileUploadResource::make($file)->toResponse($request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function update(UpdateRequest $request, FileUpload $file) + { + $file->update($request->validated()); + + return $this->show($file, $request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(FileUpload $file) + { + $file->delete(); + + return response()->noContent(); + } +} diff --git a/src/Http/Controllers/ProfileController.php b/src/Http/Controllers/ProfileController.php new file mode 100644 index 0000000..1c59589 --- /dev/null +++ b/src/Http/Controllers/ProfileController.php @@ -0,0 +1,31 @@ +user(); + + return ProfileResource::make($user); + } + + /** + * @return \Inertia\Response + */ + public function update(ProfileRequest $request) + { + $user = $request->user(); + + return ProfileResource::make($user); + } +} diff --git a/src/Http/Controllers/SettingController.php b/src/Http/Controllers/SettingController.php new file mode 100644 index 0000000..306aeb3 --- /dev/null +++ b/src/Http/Controllers/SettingController.php @@ -0,0 +1,31 @@ +user(); + + return SettingResource::make($user); + } + + /** + * @return \Inertia\Response + */ + public function update(SettingRequest $request) + { + $user = $request->user(); + + return SettingResource::make($user); + } +} diff --git a/src/Http/Controllers/StakeholderController.php b/src/Http/Controllers/StakeholderController.php new file mode 100644 index 0000000..d2f83c7 --- /dev/null +++ b/src/Http/Controllers/StakeholderController.php @@ -0,0 +1,69 @@ +authorizeResource(Stakeholder::class); + } + + /** + * @return \Illuminate\Database\Eloquent\Collection + */ + public function index(Request $request, Stakeholder $stakeholder) + { + $model = $stakeholder->newQuery() + ->where('id', '<>', $request->user()->id) + ->latest(); + + return new StakeholderCollection($model->paginate()); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function store(StoreRequest $request, Stakeholder $stakeholder) + { + /** @var Stakeholder */ + $model = $stakeholder->create($request->validated()); + + return $this->show($model, $request)->setStatusCode(201); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function show(Stakeholder $stakeholder, Request $request) + { + return StakeholderResource::make($stakeholder)->toResponse($request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function update(UpdateRequest $request, Stakeholder $stakeholder) + { + $stakeholder->update($request->validated()); + + return $this->show($stakeholder, $request); + } + + /** + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(Stakeholder $stakeholder) + { + $stakeholder->delete(); + + return response()->noContent(); + } +} diff --git a/src/Http/Requests/Address/StoreRequest.php b/src/Http/Requests/Address/StoreRequest.php new file mode 100644 index 0000000..510934b --- /dev/null +++ b/src/Http/Requests/Address/StoreRequest.php @@ -0,0 +1,27 @@ + + */ + public function rules(): array + { + return [ + // . + ]; + } + + public function storeFor(HasAddresses $entity) + { + $address = Address::query()->make($this->validated()); + + return $entity->addresses()->save($address); + } +} diff --git a/src/Http/Requests/Address/UpdateRequest.php b/src/Http/Requests/Address/UpdateRequest.php new file mode 100644 index 0000000..6a95ff0 --- /dev/null +++ b/src/Http/Requests/Address/UpdateRequest.php @@ -0,0 +1,20 @@ + + */ + public function rules(): array + { + $user = $this->user(); + + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/Company/StoreRequest.php b/src/Http/Requests/Company/StoreRequest.php index 05af9d3..137e663 100644 --- a/src/Http/Requests/Company/StoreRequest.php +++ b/src/Http/Requests/Company/StoreRequest.php @@ -3,7 +3,6 @@ namespace Creasi\Base\Http\Requests\Company; use Creasi\Base\Http\Requests\FormRequest; -use Illuminate\Validation\Rules\Password; class StoreRequest extends FormRequest { @@ -13,9 +12,7 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'name' => ['required', 'string', 'unique:users,name'], - 'email' => ['required', 'email', 'unique:users,email'], - 'password' => ['required', 'confirmed', Password::defaults()], + // . ]; } } diff --git a/src/Http/Requests/Company/UpdateRequest.php b/src/Http/Requests/Company/UpdateRequest.php index 1a5d654..beeb179 100644 --- a/src/Http/Requests/Company/UpdateRequest.php +++ b/src/Http/Requests/Company/UpdateRequest.php @@ -3,8 +3,6 @@ namespace Creasi\Base\Http\Requests\Company; use Creasi\Base\Http\Requests\FormRequest; -use Illuminate\Validation\Rule; -use Illuminate\Validation\Rules\Password; class UpdateRequest extends FormRequest { @@ -16,10 +14,7 @@ public function rules(): array $user = $this->user(); return [ - 'name' => ['required', 'string', Rule::unique('users', 'name')->ignore($user->id)], - 'email' => ['required', 'email', Rule::unique('users', 'email')->ignore($user->id)], - 'password' => ['nullable', 'confirmed', Password::defaults()], - 'old-password' => ['required_unless:password,null'], + // . ]; } } diff --git a/src/Http/Requests/Employee/StoreRequest.php b/src/Http/Requests/Employee/StoreRequest.php new file mode 100644 index 0000000..b545fb4 --- /dev/null +++ b/src/Http/Requests/Employee/StoreRequest.php @@ -0,0 +1,18 @@ + + */ + public function rules(): array + { + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/Employee/UpdateRequest.php b/src/Http/Requests/Employee/UpdateRequest.php new file mode 100644 index 0000000..aa05306 --- /dev/null +++ b/src/Http/Requests/Employee/UpdateRequest.php @@ -0,0 +1,20 @@ + + */ + public function rules(): array + { + $user = $this->user(); + + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/FileUpload/StoreRequest.php b/src/Http/Requests/FileUpload/StoreRequest.php new file mode 100644 index 0000000..7082738 --- /dev/null +++ b/src/Http/Requests/FileUpload/StoreRequest.php @@ -0,0 +1,31 @@ + + */ + public function rules(): array + { + return [ + // . + ]; + } + + public function storeFor(HasFileUploads $entity) + { + return $entity->storeFile( + FileUploadType::Document, + $this->file('upload'), + $this->input('name'), + $this->input('title'), + $this->input('summary'), + ); + } +} diff --git a/src/Http/Requests/FileUpload/UpdateRequest.php b/src/Http/Requests/FileUpload/UpdateRequest.php new file mode 100644 index 0000000..29faa39 --- /dev/null +++ b/src/Http/Requests/FileUpload/UpdateRequest.php @@ -0,0 +1,20 @@ + + */ + public function rules(): array + { + $user = $this->user(); + + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/FormRequest.php b/src/Http/Requests/FormRequest.php index 97248b9..308a7fe 100644 --- a/src/Http/Requests/FormRequest.php +++ b/src/Http/Requests/FormRequest.php @@ -7,7 +7,7 @@ /** * @method \Creasi\Base\Models\User|null user() */ -class FormRequest extends IlluminateFormRequest +abstract class FormRequest extends IlluminateFormRequest { // . } diff --git a/src/Http/Requests/ProfileRequest.php b/src/Http/Requests/ProfileRequest.php new file mode 100644 index 0000000..d4a07e8 --- /dev/null +++ b/src/Http/Requests/ProfileRequest.php @@ -0,0 +1,16 @@ + + */ + public function rules(): array + { + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/SettingRequest.php b/src/Http/Requests/SettingRequest.php new file mode 100644 index 0000000..164dd74 --- /dev/null +++ b/src/Http/Requests/SettingRequest.php @@ -0,0 +1,18 @@ + + */ + public function rules(): array + { + $user = $this->user(); + + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/Stakeholder/StoreRequest.php b/src/Http/Requests/Stakeholder/StoreRequest.php new file mode 100644 index 0000000..56dabbc --- /dev/null +++ b/src/Http/Requests/Stakeholder/StoreRequest.php @@ -0,0 +1,18 @@ + + */ + public function rules(): array + { + return [ + // . + ]; + } +} diff --git a/src/Http/Requests/Stakeholder/UpdateRequest.php b/src/Http/Requests/Stakeholder/UpdateRequest.php new file mode 100644 index 0000000..6ec7012 --- /dev/null +++ b/src/Http/Requests/Stakeholder/UpdateRequest.php @@ -0,0 +1,20 @@ + + */ + public function rules(): array + { + $user = $this->user(); + + return [ + // . + ]; + } +} diff --git a/src/Http/Resources/Address/AddressCollection.php b/src/Http/Resources/Address/AddressCollection.php new file mode 100644 index 0000000..71367d4 --- /dev/null +++ b/src/Http/Resources/Address/AddressCollection.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/Address/AddressResource.php b/src/Http/Resources/Address/AddressResource.php new file mode 100644 index 0000000..66b5207 --- /dev/null +++ b/src/Http/Resources/Address/AddressResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/Company/CompanyCollection.php b/src/Http/Resources/Company/CompanyCollection.php new file mode 100644 index 0000000..d935ae4 --- /dev/null +++ b/src/Http/Resources/Company/CompanyCollection.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/CompanyResource.php b/src/Http/Resources/Company/CompanyResource.php similarity index 52% rename from src/Http/Resources/CompanyResource.php rename to src/Http/Resources/Company/CompanyResource.php index fe65f5f..a404438 100644 --- a/src/Http/Resources/CompanyResource.php +++ b/src/Http/Resources/Company/CompanyResource.php @@ -1,7 +1,8 @@ */ - public function toArray($request) + public function toArray(Request $request): array { return parent::toArray($request); } diff --git a/src/Http/Resources/Employee/EmployeeCollection.php b/src/Http/Resources/Employee/EmployeeCollection.php new file mode 100644 index 0000000..60e98b6 --- /dev/null +++ b/src/Http/Resources/Employee/EmployeeCollection.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/Employee/EmployeeResource.php b/src/Http/Resources/Employee/EmployeeResource.php new file mode 100644 index 0000000..5877464 --- /dev/null +++ b/src/Http/Resources/Employee/EmployeeResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/FileUpload/FileUploadCollection.php b/src/Http/Resources/FileUpload/FileUploadCollection.php new file mode 100644 index 0000000..6754943 --- /dev/null +++ b/src/Http/Resources/FileUpload/FileUploadCollection.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/FileUpload/FileUploadResource.php b/src/Http/Resources/FileUpload/FileUploadResource.php new file mode 100644 index 0000000..2d48112 --- /dev/null +++ b/src/Http/Resources/FileUpload/FileUploadResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/ProfileResource.php b/src/Http/Resources/ProfileResource.php new file mode 100644 index 0000000..4ca3a28 --- /dev/null +++ b/src/Http/Resources/ProfileResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/SettingResource.php b/src/Http/Resources/SettingResource.php new file mode 100644 index 0000000..71b44a0 --- /dev/null +++ b/src/Http/Resources/SettingResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/Stakeholder/StakeholderCollection.php b/src/Http/Resources/Stakeholder/StakeholderCollection.php new file mode 100644 index 0000000..8ca43bd --- /dev/null +++ b/src/Http/Resources/Stakeholder/StakeholderCollection.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/src/Http/Resources/Stakeholder/StakeholderResource.php b/src/Http/Resources/Stakeholder/StakeholderResource.php new file mode 100644 index 0000000..7d4d22c --- /dev/null +++ b/src/Http/Resources/Stakeholder/StakeholderResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} From 5c3926b1d46573e23d90a6b4099c35eb973017ea Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 18 Jul 2023 04:56:58 +0700 Subject: [PATCH 27/71] chore: rearrange interfaces and add dummy bindings Signed-off-by: Fery Wardiyanto --- src/Contracts/Employee.php | 11 +++++++++ src/Contracts/{Contactable.php => Entity.php} | 2 +- src/Contracts/HasFileUploads.php | 3 ++- src/Contracts/Identity.php | 2 +- src/Contracts/Stakeholder.php | 11 +++++++++ src/Models/Company.php | 7 +++--- src/Models/Personnel.php | 6 +++-- src/ServiceProvider.php | 24 +++++++++++++++++++ 8 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 src/Contracts/Employee.php rename src/Contracts/{Contactable.php => Entity.php} (90%) create mode 100644 src/Contracts/Stakeholder.php diff --git a/src/Contracts/Employee.php b/src/Contracts/Employee.php new file mode 100644 index 0000000..a00d90d --- /dev/null +++ b/src/Contracts/Employee.php @@ -0,0 +1,11 @@ + factory() */ -class Company extends Model implements HasAddresses, Contactable, HasFileUploads +class Company extends Model implements Entity, HasAddresses, HasFileUploads, Stakeholder { use HasAvatar; use WithAddresses; @@ -80,7 +81,7 @@ public function stakeholders() public function addStakeholder( CompanyRelativeType $type, - Contactable $stakeholder, + Entity $stakeholder, ?bool $internal = null, ?string $remark = null, ): static { diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index c5f72fb..18cfd0b 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -3,7 +3,9 @@ namespace Creasi\Base\Models; use Creasi\Base\Contracts\HasFileUploads; -use Creasi\Base\Contracts\Contactable; +use Creasi\Base\Contracts\Entity; +use Creasi\Base\Contracts\Employee; +use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Concerns\HasAvatar; use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Concerns\WithIdentity; @@ -19,7 +21,7 @@ * * @method static \Database\Factories\PersonnelFactory factory() */ -class Personnel extends Model implements HasAddresses, Contactable, HasFileUploads +class Personnel extends Model implements Employee, Entity, HasAddresses, HasFileUploads, Stakeholder { use HasAvatar; use WithAddresses; diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 8b1ca3f..6a9e6e0 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,8 +2,13 @@ namespace Creasi\Base; +use Creasi\Base\Contracts\Employee; +use Creasi\Base\Contracts\HasFileUploads; +use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Address; +use Creasi\Base\Models\Personnel; use Creasi\Base\View\Composers\TranslationsComposer; +use Creasi\Nusa\Contracts\HasAddresses; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Mail; @@ -63,6 +68,25 @@ public function register(): void } } + $this->app->bind(Employee::class, function ($app) { + return new Personnel(); + }); + + $this->app->bind(Stakeholder::class, function ($app) { + return new Personnel(); + }); + + $this->app->bind(HasAddresses::class, function ($app) { + /** @var \Illuminate\Routing\Router */ + $router = $app->make('router'); + + dd($router->is('companies.addresses.*')); + return $app->make(Employee::class); + }); + + $this->app->bind(HasFileUploads::class, function ($app) { + return $app->make(Employee::class); + }); } protected function registerPublishables(): void From 8b5a1d58d5ac97b7faa6d5a2c9dc8b35fb137365 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 18 Jul 2023 04:58:30 +0700 Subject: [PATCH 28/71] chore(tests): add false positive tests regarding recent changes Signed-off-by: Fery Wardiyanto --- database/factories/CompanyFactory.php | 6 ++ database/factories/FileUploadFactory.php | 7 ++ database/factories/PersonnelFactory.php | 6 ++ src/Http/Requests/Address/StoreRequest.php | 11 ++- src/Http/Requests/Address/UpdateRequest.php | 13 ++- src/Http/Requests/Company/StoreRequest.php | 6 +- src/Http/Requests/Company/UpdateRequest.php | 8 +- src/Http/Requests/Employee/StoreRequest.php | 6 +- src/Http/Requests/Employee/UpdateRequest.php | 8 +- src/Http/Requests/FileUpload/StoreRequest.php | 18 +++- .../Requests/FileUpload/UpdateRequest.php | 8 +- .../Requests/Stakeholder/StoreRequest.php | 6 +- .../Requests/Stakeholder/UpdateRequest.php | 8 +- src/ServiceProvider.php | 1 - tests/Http/Account/HomeTest.php | 25 ----- tests/Http/Account/SettingTest.php | 25 ----- tests/Http/AddressTest.php | 73 ++++++++++++++ tests/Http/CompanyTest.php | 52 +++++++++- tests/Http/EmployeeTest.php | 68 +++++++++++++ tests/Http/FileUploadTest.php | 78 +++++++++++++++ tests/Http/ProfileTest.php | 34 +++++++ tests/Http/SettingTest.php | 34 +++++++ tests/Http/StakeholderTest.php | 95 +++++++++++++++++++ tests/Http/SupportTest.php | 5 +- tests/Models/FileUploadTest.php | 2 - tests/TestCase.php | 22 +++++ 26 files changed, 541 insertions(+), 84 deletions(-) delete mode 100644 tests/Http/Account/HomeTest.php delete mode 100644 tests/Http/Account/SettingTest.php create mode 100644 tests/Http/AddressTest.php create mode 100644 tests/Http/EmployeeTest.php create mode 100644 tests/Http/FileUploadTest.php create mode 100644 tests/Http/ProfileTest.php create mode 100644 tests/Http/SettingTest.php create mode 100644 tests/Http/StakeholderTest.php diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 31dfb3b..80d6fd4 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Company; +use Creasi\Base\Models\FileUpload; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -31,4 +32,9 @@ public function withAddress(): static { return $this->has(Address::factory()); } + + public function withFileUpload(): static + { + return $this->has(FileUpload::factory(), 'files'); + } } diff --git a/database/factories/FileUploadFactory.php b/database/factories/FileUploadFactory.php index 3cab160..492043c 100644 --- a/database/factories/FileUploadFactory.php +++ b/database/factories/FileUploadFactory.php @@ -37,4 +37,11 @@ public function asRevisionOf(FileUpload $file): static 'revision_id' => $file->getKey(), ]); } + + public function withoutFile(): static + { + return $this->state(fn () => [ + 'path' => null, + ]); + } } diff --git a/database/factories/PersonnelFactory.php b/database/factories/PersonnelFactory.php index c17f191..d993084 100644 --- a/database/factories/PersonnelFactory.php +++ b/database/factories/PersonnelFactory.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Enums\Gender; +use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Identity; use Creasi\Base\Models\Personnel; use Illuminate\Database\Eloquent\Factories\Factory; @@ -40,4 +41,9 @@ public function withAddress(): static { return $this->has(Address::factory()); } + + public function withFileUpload(): static + { + return $this->has(FileUpload::factory(), 'files'); + } } diff --git a/src/Http/Requests/Address/StoreRequest.php b/src/Http/Requests/Address/StoreRequest.php index 510934b..6af07eb 100644 --- a/src/Http/Requests/Address/StoreRequest.php +++ b/src/Http/Requests/Address/StoreRequest.php @@ -14,7 +14,16 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - // . + 'is_resident' => ['required', 'boolean'], + 'line' => ['required', 'string'], + 'rt' => ['required', 'numeric'], + 'rw' => ['required', 'numeric'], + 'village_code' => ['required', 'numeric'], + 'district_code' => ['required', 'numeric'], + 'regency_code' => ['required', 'numeric'], + 'province_code' => ['required', 'numeric'], + 'postal_code' => ['required', 'numeric'], + 'summary' => ['string', 'nullable'], ]; } diff --git a/src/Http/Requests/Address/UpdateRequest.php b/src/Http/Requests/Address/UpdateRequest.php index 6a95ff0..7b527f3 100644 --- a/src/Http/Requests/Address/UpdateRequest.php +++ b/src/Http/Requests/Address/UpdateRequest.php @@ -11,10 +11,17 @@ class UpdateRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); - return [ - // . + 'is_resident' => ['required', 'boolean'], + 'line' => ['required', 'string'], + 'rt' => ['required', 'numeric'], + 'rw' => ['required', 'numeric'], + 'village_code' => ['required', 'numeric'], + 'district_code' => ['required', 'numeric'], + 'regency_code' => ['required', 'numeric'], + 'province_code' => ['required', 'numeric'], + 'postal_code' => ['required', 'numeric'], + 'summary' => ['string', 'nullable'], ]; } } diff --git a/src/Http/Requests/Company/StoreRequest.php b/src/Http/Requests/Company/StoreRequest.php index 137e663..24d4534 100644 --- a/src/Http/Requests/Company/StoreRequest.php +++ b/src/Http/Requests/Company/StoreRequest.php @@ -12,7 +12,11 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - // . + 'code' => ['required', 'string'], + 'name' => ['required', 'string'], + 'email' => ['required', 'email'], + 'phone_number' => ['nullable', 'numeric'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/Company/UpdateRequest.php b/src/Http/Requests/Company/UpdateRequest.php index beeb179..b8b1371 100644 --- a/src/Http/Requests/Company/UpdateRequest.php +++ b/src/Http/Requests/Company/UpdateRequest.php @@ -11,10 +11,12 @@ class UpdateRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); - return [ - // . + 'code' => ['required', 'string'], + 'name' => ['required', 'string'], + 'email' => ['required', 'email'], + 'phone_number' => ['nullable', 'numeric'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/Employee/StoreRequest.php b/src/Http/Requests/Employee/StoreRequest.php index b545fb4..595d141 100644 --- a/src/Http/Requests/Employee/StoreRequest.php +++ b/src/Http/Requests/Employee/StoreRequest.php @@ -12,7 +12,11 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - // . + 'code' => ['required', 'string'], + 'name' => ['required', 'string'], + 'email' => ['required', 'email'], + 'phone_number' => ['nullable', 'numeric'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/Employee/UpdateRequest.php b/src/Http/Requests/Employee/UpdateRequest.php index aa05306..ca6396a 100644 --- a/src/Http/Requests/Employee/UpdateRequest.php +++ b/src/Http/Requests/Employee/UpdateRequest.php @@ -11,10 +11,12 @@ class UpdateRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); - return [ - // . + 'code' => ['required', 'string'], + 'name' => ['required', 'string'], + 'email' => ['required', 'email'], + 'phone_number' => ['nullable', 'numeric'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/FileUpload/StoreRequest.php b/src/Http/Requests/FileUpload/StoreRequest.php index 7082738..ac2cb86 100644 --- a/src/Http/Requests/FileUpload/StoreRequest.php +++ b/src/Http/Requests/FileUpload/StoreRequest.php @@ -5,6 +5,7 @@ use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Http\Requests\FormRequest; use Creasi\Base\Models\Enums\FileUploadType; +use Illuminate\Validation\Rule; class StoreRequest extends FormRequest { @@ -14,18 +15,25 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - // . + 'title' => ['required', 'string'], + 'name' => ['required', 'string'], + 'type' => ['required', Rule::enum(FileUploadType::class)], + 'upload' => ['nullable', 'file'], + 'summary' => ['nullable', 'string'], ]; } public function storeFor(HasFileUploads $entity) { + /** @var FileUploadType */ + $type = $this->enum('type', FileUploadType::class); + return $entity->storeFile( - FileUploadType::Document, + $type, $this->file('upload'), - $this->input('name'), - $this->input('title'), - $this->input('summary'), + $this->name, + $this->title, + $this->summary, ); } } diff --git a/src/Http/Requests/FileUpload/UpdateRequest.php b/src/Http/Requests/FileUpload/UpdateRequest.php index 29faa39..e01888a 100644 --- a/src/Http/Requests/FileUpload/UpdateRequest.php +++ b/src/Http/Requests/FileUpload/UpdateRequest.php @@ -11,10 +11,12 @@ class UpdateRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); - return [ - // . + 'title' => ['required', 'string'], + 'name' => ['required', 'string'], + 'path' => ['required', 'string'], + 'upload' => ['nullable', 'file'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/Stakeholder/StoreRequest.php b/src/Http/Requests/Stakeholder/StoreRequest.php index 56dabbc..5947494 100644 --- a/src/Http/Requests/Stakeholder/StoreRequest.php +++ b/src/Http/Requests/Stakeholder/StoreRequest.php @@ -12,7 +12,11 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - // . + 'code' => ['required', 'string'], + 'name' => ['required', 'string'], + 'email' => ['required', 'email'], + 'phone_number' => ['nullable', 'numeric'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/Stakeholder/UpdateRequest.php b/src/Http/Requests/Stakeholder/UpdateRequest.php index 6ec7012..6611434 100644 --- a/src/Http/Requests/Stakeholder/UpdateRequest.php +++ b/src/Http/Requests/Stakeholder/UpdateRequest.php @@ -11,10 +11,12 @@ class UpdateRequest extends FormRequest */ public function rules(): array { - $user = $this->user(); - return [ - // . + 'code' => ['required', 'string'], + 'name' => ['required', 'string'], + 'email' => ['required', 'email'], + 'phone_number' => ['nullable', 'numeric'], + 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 6a9e6e0..6f0047d 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -80,7 +80,6 @@ public function register(): void /** @var \Illuminate\Routing\Router */ $router = $app->make('router'); - dd($router->is('companies.addresses.*')); return $app->make(Employee::class); }); diff --git a/tests/Http/Account/HomeTest.php b/tests/Http/Account/HomeTest.php deleted file mode 100644 index 28dfe73..0000000 --- a/tests/Http/Account/HomeTest.php +++ /dev/null @@ -1,25 +0,0 @@ -createOne()); - - $response = $this->getJson('base/account'); - - $response->assertOk(); - } -} diff --git a/tests/Http/Account/SettingTest.php b/tests/Http/Account/SettingTest.php deleted file mode 100644 index 86f4222..0000000 --- a/tests/Http/Account/SettingTest.php +++ /dev/null @@ -1,25 +0,0 @@ -createOne()); - - $response = $this->getJson('base/account/settings'); - - $response->assertOk(); - } -} diff --git a/tests/Http/AddressTest.php b/tests/Http/AddressTest.php new file mode 100644 index 0000000..8f9e703 --- /dev/null +++ b/tests/Http/AddressTest.php @@ -0,0 +1,73 @@ +user()); + $model = $modelClass::factory()->withAddress()->create(); + + $response = $this->getJson("base/{$entity}/{$model->getKey()}/addresses"); + + $response->assertOk(); + } + + #[Test] + #[DataProviderExternal(TestCase::class, 'entities')] + public function should_able_to_store_new_data(string $entity, string $modelClass): void + { + Sanctum::actingAs($this->user()); + $model = $modelClass::factory()->withAddress()->create(); + $data = Address::factory()->raw(); + + $response = $this->postJson("base/{$entity}/{$model->getKey()}/addresses", $data); + + $response->assertCreated(); + } + + #[Test] + public function should_able_to_show_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Address::factory()->createOne(); + + $response = $this->getJson("base/addresses/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_update_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Address::factory()->createOne(); + + $response = $this->putJson("base/addresses/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_delete_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Address::factory()->createOne(); + + $response = $this->deleteJson("base/addresses/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } +} diff --git a/tests/Http/CompanyTest.php b/tests/Http/CompanyTest.php index c9d6925..c9cf5d3 100644 --- a/tests/Http/CompanyTest.php +++ b/tests/Http/CompanyTest.php @@ -2,23 +2,67 @@ namespace Creasi\Tests\Http; -use Creasi\Base\Models\User; +use Creasi\Base\Models\Company; use Creasi\Tests\TestCase; use Laravel\Sanctum\Sanctum; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; #[Group('api')] -#[Group('companies')] +#[Group('company')] class CompanyTest extends TestCase { #[Test] - public function should_be_exists(): void + public function should_able_to_retrieve_all_data(): void { - Sanctum::actingAs(User::factory()->createOne()); + Sanctum::actingAs($this->user()); $response = $this->getJson('base/companies'); $response->assertOk(); } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + $data = Company::factory()->raw(); + + $response = $this->postJson('base/companies', $data); + + $response->assertCreated(); + } + + #[Test] + public function should_able_to_show_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + + $response = $this->getJson("base/companies/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_update_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + + $response = $this->putJson("base/companies/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_delete_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + + $response = $this->deleteJson("base/companies/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } } diff --git a/tests/Http/EmployeeTest.php b/tests/Http/EmployeeTest.php new file mode 100644 index 0000000..6b615da --- /dev/null +++ b/tests/Http/EmployeeTest.php @@ -0,0 +1,68 @@ +user()); + + $response = $this->getJson('base/employees'); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + $data = Personnel::factory()->raw(); + + $response = $this->postJson('base/employees', $data); + + $response->assertCreated(); + } + + #[Test] + public function should_able_to_show_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + + $response = $this->getJson("base/employees/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_update_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + + $response = $this->putJson("base/employees/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_delete_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + + $response = $this->deleteJson("base/employees/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } +} diff --git a/tests/Http/FileUploadTest.php b/tests/Http/FileUploadTest.php new file mode 100644 index 0000000..84beac9 --- /dev/null +++ b/tests/Http/FileUploadTest.php @@ -0,0 +1,78 @@ +user()); + $model = $modelClass::factory()->withFileUpload()->create(); + + $response = $this->getJson("base/{$entity}/{$model->getKey()}/files"); + + $response->assertOk(); + } + + #[Test] + #[DataProviderExternal(TestCase::class, 'entities')] + public function should_able_to_store_new_data(string $entity, string $modelClass): void + { + Storage::fake(); + Sanctum::actingAs($this->user()); + + $model = $modelClass::factory()->withFileUpload()->create(); + $data = FileUpload::factory()->withoutFile()->raw(); + $data['upload'] = UploadedFile::fake()->create('file.pdf'); + + $response = $this->postJson("base/{$entity}/{$model->getKey()}/files", $data); + + $response->assertCreated(); + } + + #[Test] + public function should_able_to_show_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = FileUpload::factory()->createOne(); + + $response = $this->getJson("base/files/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_update_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = FileUpload::factory()->createOne(); + + $response = $this->putJson("base/files/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_delete_existing_data(): void + { + Sanctum::actingAs($this->user()); + $model = FileUpload::factory()->createOne(); + + $response = $this->deleteJson("base/files/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } +} diff --git a/tests/Http/ProfileTest.php b/tests/Http/ProfileTest.php new file mode 100644 index 0000000..9fa0420 --- /dev/null +++ b/tests/Http/ProfileTest.php @@ -0,0 +1,34 @@ +user()); + + $response = $this->getJson('base/profile'); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_update_profile_data(): void + { + Sanctum::actingAs($this->user()); + + $response = $this->putJson('base/profile'); + + $response->assertOk(); + } +} diff --git a/tests/Http/SettingTest.php b/tests/Http/SettingTest.php new file mode 100644 index 0000000..f0cdc86 --- /dev/null +++ b/tests/Http/SettingTest.php @@ -0,0 +1,34 @@ +user()); + + $response = $this->getJson('base/setting'); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_update_setting_data(): void + { + Sanctum::actingAs($this->user()); + + $response = $this->putJson('base/setting'); + + $response->assertOk(); + } +} diff --git a/tests/Http/StakeholderTest.php b/tests/Http/StakeholderTest.php new file mode 100644 index 0000000..486d431 --- /dev/null +++ b/tests/Http/StakeholderTest.php @@ -0,0 +1,95 @@ +isInternal()) { + continue; + } + + $stakeholders[(string) $stakeholder->key()] = [$stakeholder]; + } + + return $stakeholders; + } + + #[Test] + #[DataProvider('stakeholders')] + public function should_able_to_retrieve_all_data(CompanyRelativeType $stakeholder): void + { + Sanctum::actingAs($this->user()); + + $route = $stakeholder->key()->plural(); + $response = $this->getJson("base/{$route}"); + + $response->assertOk(); + } + + #[Test] + #[DataProvider('stakeholders')] + public function should_able_to_store_new_data(CompanyRelativeType $stakeholder): void + { + Sanctum::actingAs($this->user()); + $data = Personnel::factory()->raw(); + $route = $stakeholder->key()->plural(); + + $response = $this->postJson("base/{$route}", $data); + + $response->assertCreated(); + } + + #[Test] + #[DataProvider('stakeholders')] + public function should_able_to_show_existing_data(CompanyRelativeType $stakeholder): void + { + Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $route = $stakeholder->key()->plural(); + + $response = $this->getJson("base/{$route}/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + #[DataProvider('stakeholders')] + public function should_able_to_update_existing_data(CompanyRelativeType $stakeholder): void + { + Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $route = $stakeholder->key()->plural(); + + $response = $this->putJson("base/{$route}/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + #[DataProvider('stakeholders')] + public function should_able_to_delete_existing_data(CompanyRelativeType $stakeholder): void + { + Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $route = $stakeholder->key()->plural(); + + $response = $this->deleteJson("base/{$route}/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } +} diff --git a/tests/Http/SupportTest.php b/tests/Http/SupportTest.php index 57f98fb..976a455 100644 --- a/tests/Http/SupportTest.php +++ b/tests/Http/SupportTest.php @@ -2,7 +2,6 @@ namespace Creasi\Tests\Http; -use Creasi\Base\Models\User; use Creasi\Tests\TestCase; use Laravel\Sanctum\Sanctum; use PHPUnit\Framework\Attributes\Group; @@ -13,9 +12,9 @@ class SupportTest extends TestCase { #[Test] - public function should_be_exists(): void + public function should_able_to_retrieve_supports_data(): void { - Sanctum::actingAs(User::factory()->createOne()); + Sanctum::actingAs($this->user()); $response = $this->getJson('base/supports'); diff --git a/tests/Models/FileUploadTest.php b/tests/Models/FileUploadTest.php index 7e42eee..6af866c 100644 --- a/tests/Models/FileUploadTest.php +++ b/tests/Models/FileUploadTest.php @@ -4,11 +4,9 @@ use Creasi\Base\Models\Company; use Creasi\Base\Models\Enums\FileUploadType; -use Creasi\Base\Models\FileAttached; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; -use Illuminate\Database\Eloquent\Factories\Sequence; use Illuminate\Http\UploadedFile; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; diff --git a/tests/TestCase.php b/tests/TestCase.php index 2a41953..f974f78 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,9 @@ namespace Creasi\Tests; +use Closure; +use Creasi\Base\Models\Company; +use Creasi\Base\Models\Personnel; use Creasi\Base\Models\User; use Creasi\Base\ServiceProvider; use Creasi\Nusa\ServiceProvider as NusaServiceProvider; @@ -16,6 +19,16 @@ class TestCase extends Orchestra use RefreshDatabase; use DatabaseMigrations; + private ?User $currentUser = null; + + final public static function entities() + { + return [ + 'company' => ['companies', Company::class], + 'employee' => ['employees', Personnel::class], + ]; + } + /** * @param \Illuminate\Foundation\Application $app */ @@ -28,6 +41,15 @@ protected function getPackageProviders($app): array ]; } + final protected function user(array|Closure $attrs = []): User + { + if (! $this->currentUser?->exists) { + $this->currentUser = User::factory()->createOne($attrs); + } + + return $this->currentUser; + } + /** * @param \Illuminate\Foundation\Application $app */ From 191cb91acca2372ae8511f820ac73e355599d49b Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 18 Jul 2023 07:17:58 +0700 Subject: [PATCH 29/71] chore: force test script ansi Signed-off-by: Fery Wardiyanto --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0a8e9f1..3f99f44 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "pint --preset laravel" ], "test": [ - "testbench package:test --coverage" + "testbench package:test --coverage --ansi" ], "testbench": [ "testbench" From f4546f305f7cd63a0b12b16a68ac1a3ecedc080a Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 18 Jul 2023 17:34:06 +0700 Subject: [PATCH 30/71] chore: clean up the unnecessaries Signed-off-by: Fery Wardiyanto --- database/factories/IdentityFactory.php | 2 +- database/factories/PersonnelFactory.php | 2 +- database/factories/UserFactory.php | 2 -- src/Contracts/Entity.php | 15 -------- src/Contracts/HasFileUploads.php | 6 ++-- src/Http/Controllers/AddressController.php | 1 - src/Http/Controllers/ProfileController.php | 1 - src/Http/Controllers/SettingController.php | 1 - src/Http/Controllers/SupportController.php | 2 -- src/Models/Company.php | 25 ++----------- .../{HasAvatar.php => WithAvatar.php} | 2 +- src/Models/Concerns/WithFileUploads.php | 8 ++--- src/Models/Entity.php | 35 +++++++++++++++++++ src/Models/FileUpload.php | 13 ++++--- src/Models/Personnel.php | 22 ++---------- src/ServiceProvider.php | 4 +-- src/View/Composers/TranslationsComposer.php | 7 ++-- tests/Models/AddressTest.php | 9 +---- tests/Models/CompanyTest.php | 9 +---- tests/Models/FileUploadTest.php | 13 ++----- tests/Models/IdentityTest.php | 3 +- tests/Models/PersonnelTest.php | 9 +---- tests/Models/UserTest.php | 9 +---- 23 files changed, 68 insertions(+), 132 deletions(-) delete mode 100644 src/Contracts/Entity.php rename src/Models/Concerns/{HasAvatar.php => WithAvatar.php} (98%) create mode 100644 src/Models/Entity.php diff --git a/database/factories/IdentityFactory.php b/database/factories/IdentityFactory.php index e650d65..6378c91 100644 --- a/database/factories/IdentityFactory.php +++ b/database/factories/IdentityFactory.php @@ -50,7 +50,7 @@ public function withoutUser(): static ]); } - public function withGender(?Gender $gender = null, mixed $start = '-30 years', mixed $until = 'now'): static + public function withGender(Gender $gender = null, mixed $start = '-30 years', mixed $until = 'now'): static { $gender = $gender ?: $this->faker->randomElement(Gender::cases()); diff --git a/database/factories/PersonnelFactory.php b/database/factories/PersonnelFactory.php index d993084..82d00f2 100644 --- a/database/factories/PersonnelFactory.php +++ b/database/factories/PersonnelFactory.php @@ -30,7 +30,7 @@ public function definition(): array ]; } - public function withIdentity(?Gender $gender = null): static + public function withIdentity(Gender $gender = null): static { return $this->has(Identity::factory()->withGender($gender), 'identity')->state(fn () => [ 'name' => $this->faker->firstName($gender?->toFaker()), diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 0422d7d..c03ac6c 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -31,8 +31,6 @@ public function definition(): array /** * Indicate that the model's email address should be unverified. - * - * @return static */ public function unverified(): static { diff --git a/src/Contracts/Entity.php b/src/Contracts/Entity.php deleted file mode 100644 index 9dfcee0..0000000 --- a/src/Contracts/Entity.php +++ /dev/null @@ -1,15 +0,0 @@ - factory() */ -class Company extends Model implements Entity, HasAddresses, HasFileUploads, Stakeholder +class Company extends Entity { - use HasAvatar; - use WithAddresses; - use WithFileUploads; - - protected $fillable = [ - 'code', - 'name', - 'email', - 'phone_number', - 'summary', - ]; - protected $casts = [ // . ]; @@ -82,8 +63,8 @@ public function stakeholders() public function addStakeholder( CompanyRelativeType $type, Entity $stakeholder, - ?bool $internal = null, - ?string $remark = null, + bool $internal = null, + string $remark = null, ): static { $this->businessRelative(\get_class($stakeholder))->attach($stakeholder, [ 'type' => $type, diff --git a/src/Models/Concerns/HasAvatar.php b/src/Models/Concerns/WithAvatar.php similarity index 98% rename from src/Models/Concerns/HasAvatar.php rename to src/Models/Concerns/WithAvatar.php index f5a546d..290310a 100644 --- a/src/Models/Concerns/HasAvatar.php +++ b/src/Models/Concerns/WithAvatar.php @@ -12,7 +12,7 @@ * * @mixin \Creasi\Base\Contracts\HasFileUploads */ -trait HasAvatar +trait WithAvatar { /** * @return \Illuminate\Database\Eloquent\Relations\MorphOne|Identity diff --git a/src/Models/Concerns/WithFileUploads.php b/src/Models/Concerns/WithFileUploads.php index fcbf40e..ebd2332 100644 --- a/src/Models/Concerns/WithFileUploads.php +++ b/src/Models/Concerns/WithFileUploads.php @@ -3,8 +3,8 @@ namespace Creasi\Base\Models\Concerns; use Creasi\Base\Models\Enums\FileUploadType; -use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\FileAttached; +use Creasi\Base\Models\FileUpload; use Illuminate\Http\UploadedFile; /** @@ -25,9 +25,9 @@ public function storeFile( FileUploadType $type, string|UploadedFile $path, string $name, - ?string $title = null, - ?string $summary = null, - ?string $disk = null, + string $title = null, + string $summary = null, + string $disk = null, ): FileUpload { $file = FileUpload::store($type, $path, $name, $title, $summary, $disk); diff --git a/src/Models/Entity.php b/src/Models/Entity.php new file mode 100644 index 0000000..a4f1f4a --- /dev/null +++ b/src/Models/Entity.php @@ -0,0 +1,35 @@ +fillable, [ + 'code', + 'name', + 'email', + 'phone_number', + 'summary', + ]); + } +} diff --git a/src/Models/FileUpload.php b/src/Models/FileUpload.php index 5b86dff..76e5734 100644 --- a/src/Models/FileUpload.php +++ b/src/Models/FileUpload.php @@ -33,7 +33,6 @@ class FileUpload extends Model { use HasUuids; - use SoftDeletes; protected $fillable = [ 'revision_id', @@ -46,7 +45,7 @@ class FileUpload extends Model ]; protected $casts = [ - 'type' => FileUploadType::class + 'type' => FileUploadType::class, ]; public function url(): Attribute @@ -58,7 +57,7 @@ public function url(): Attribute public function isInternal(): Attribute { - return Attribute::get(fn ($_, array $attrs) => !\str_contains($attrs['path'], '://')); + return Attribute::get(fn ($_, array $attrs) => ! \str_contains($attrs['path'], '://')); } protected function attachedTo(string $owner) @@ -106,9 +105,9 @@ public function scopeStore( FileUploadType $type, string|UploadedFile $path, string $name, - ?string $title = null, - ?string $summary = null, - ?string $disk = null + string $title = null, + string $summary = null, + string $disk = null ): static { $name = Str::slug($name); @@ -130,7 +129,7 @@ public function scopeStore( return $instance; } - public function createRevision(string|UploadedFile $path, ?string $summary = null): static + public function createRevision(string|UploadedFile $path, string $summary = null): static { $revision = static::store($this->type, $path, $this->name, $this->title, $summary, $this->disk); diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index 18cfd0b..8d31144 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -2,16 +2,9 @@ namespace Creasi\Base\Models; -use Creasi\Base\Contracts\HasFileUploads; -use Creasi\Base\Contracts\Entity; use Creasi\Base\Contracts\Employee; -use Creasi\Base\Contracts\Stakeholder; -use Creasi\Base\Models\Concerns\HasAvatar; -use Creasi\Base\Models\Concerns\WithFileUploads; use Creasi\Base\Models\Concerns\WithIdentity; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; -use Creasi\Nusa\Contracts\HasAddresses; -use Creasi\Nusa\Models\Concerns\WithAddresses; /** * @property ?string $photo_path @@ -21,21 +14,10 @@ * * @method static \Database\Factories\PersonnelFactory factory() */ -class Personnel extends Model implements Employee, Entity, HasAddresses, HasFileUploads, Stakeholder +class Personnel extends Entity implements Employee { - use HasAvatar; - use WithAddresses; - use WithFileUploads; use WithIdentity; - protected $fillable = [ - 'code', - 'name', - 'email', - 'phone_number', - 'summary', - ]; - protected $casts = [ // . ]; @@ -51,7 +33,7 @@ public function relatives() ->as('relative'); } - public function addRelative(Personnel $relative, PersonnelRelativeStatus $status, ?string $remark = null) + public function addRelative(Personnel $relative, PersonnelRelativeStatus $status, string $remark = null) { $this->relatives()->attach($relative, [ 'status' => $status, diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 6f0047d..1b91866 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -10,8 +10,8 @@ use Creasi\Base\View\Composers\TranslationsComposer; use Creasi\Nusa\Contracts\HasAddresses; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; use Laravel\Dusk\Browser; @@ -127,7 +127,7 @@ protected function defineRoutes(): void */ private function registerDuskMacroForInertia(): void { - Browser::macro('waitForInertia', function (?int $seconds = null): Browser { + Browser::macro('waitForInertia', function (int $seconds = null): Browser { /** @var Browser $this */ $driver = $this->driver; diff --git a/src/View/Composers/TranslationsComposer.php b/src/View/Composers/TranslationsComposer.php index 0e5dfbb..4c82962 100644 --- a/src/View/Composers/TranslationsComposer.php +++ b/src/View/Composers/TranslationsComposer.php @@ -24,12 +24,9 @@ public function compose(View $view) } /** - * @param Loader $loader - * @param string $path - * @param null|string $namespace * @return array> */ - private function loadMessages(Loader $loader, string $path, ?string $namespace = null) + private function loadMessages(Loader $loader, string $path, string $namespace = null) { $messages = []; @@ -44,7 +41,7 @@ private function loadMessages(Loader $loader, string $path, ?string $namespace = $trans[$key] = $loader->load($locale, $group, $namespace); } - $messages[$locale] = $trans->dot()->filter(fn ($i) => !empty($i))->toArray(); + $messages[$locale] = $trans->dot()->filter(fn ($i) => ! empty($i))->toArray(); } return $messages; diff --git a/tests/Models/AddressTest.php b/tests/Models/AddressTest.php index 9bc78c0..da7955b 100644 --- a/tests/Models/AddressTest.php +++ b/tests/Models/AddressTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +#[Group('models')] #[Group('address')] class AddressTest extends TestCase { @@ -18,14 +19,6 @@ public static function sampleData(): array ]; } - #[Test] - public function should_be_exists(): void - { - $model = Address::factory()->createOne(); - - $this->assertModelExists($model); - } - #[Test] #[DataProvider('sampleData')] public function should_normalize_rt_rw_value(int $rt, int $rw, array $expected): void diff --git a/tests/Models/CompanyTest.php b/tests/Models/CompanyTest.php index 09231ef..378b90d 100644 --- a/tests/Models/CompanyTest.php +++ b/tests/Models/CompanyTest.php @@ -15,17 +15,10 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +#[Group('models')] #[Group('company')] class CompanyTest extends TestCase { - #[Test] - public function should_be_exists() - { - $model = Company::factory()->createOne(); - - $this->assertModelExists($model); - } - #[Test] public function should_have_addresses() { diff --git a/tests/Models/FileUploadTest.php b/tests/Models/FileUploadTest.php index 6af866c..11bb471 100644 --- a/tests/Models/FileUploadTest.php +++ b/tests/Models/FileUploadTest.php @@ -11,19 +11,10 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; -#[Group('files')] +#[Group('models')] +#[Group('fileUpload')] class FileUploadTest extends TestCase { - #[Test] - public function should_be_exists() - { - $model = FileUpload::factory()->createOne(); - - $this->assertModelExists($model); - - return $model; - } - #[Test] public function should_have_revisions() { diff --git a/tests/Models/IdentityTest.php b/tests/Models/IdentityTest.php index 0ddcdef..f628e9c 100644 --- a/tests/Models/IdentityTest.php +++ b/tests/Models/IdentityTest.php @@ -13,11 +13,12 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +#[Group('models')] #[Group('identity')] class IdentityTest extends TestCase { #[Test] - public function should_be_exists() + public function should_have_correct_attributes_cast() { $model = Identity::factory()->createOne(); diff --git a/tests/Models/PersonnelTest.php b/tests/Models/PersonnelTest.php index 6552cb6..fa2317d 100644 --- a/tests/Models/PersonnelTest.php +++ b/tests/Models/PersonnelTest.php @@ -12,17 +12,10 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +#[Group('models')] #[Group('personnel')] class PersonnelTest extends TestCase { - #[Test] - public function should_be_exists() - { - $model = Personnel::factory()->createOne(); - - $this->assertModelExists($model); - } - #[Test] public function should_have_addresses() { diff --git a/tests/Models/UserTest.php b/tests/Models/UserTest.php index a7aed75..e6b4196 100644 --- a/tests/Models/UserTest.php +++ b/tests/Models/UserTest.php @@ -8,17 +8,10 @@ use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; +#[Group('models')] #[Group('user')] class UserTest extends TestCase { - #[Test] - public function should_be_exists(): void - { - $model = User::factory()->createOne(); - - $this->assertModelExists($model); - } - #[Test] public function it_could_have_profile() { From 9ad9e50db9fcc54371684ee7b8f47fac1624bc4b Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 18 Jul 2023 21:58:35 +0700 Subject: [PATCH 31/71] feat(api): add default resource collection class To ensure it respond an appropriate status code when no data available Signed-off-by: Fery Wardiyanto --- .../Resources/Address/AddressCollection.php | 4 ++-- src/Http/Resources/Collection.php | 18 ++++++++++++++++++ .../Resources/Company/CompanyCollection.php | 4 ++-- .../Resources/Employee/EmployeeCollection.php | 4 ++-- .../FileUpload/FileUploadCollection.php | 4 ++-- .../Stakeholder/StakeholderCollection.php | 4 ++-- 6 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/Http/Resources/Collection.php diff --git a/src/Http/Resources/Address/AddressCollection.php b/src/Http/Resources/Address/AddressCollection.php index 71367d4..0f27b56 100644 --- a/src/Http/Resources/Address/AddressCollection.php +++ b/src/Http/Resources/Address/AddressCollection.php @@ -2,10 +2,10 @@ namespace Creasi\Base\Http\Resources\Address; +use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\ResourceCollection; -class AddressCollection extends ResourceCollection +class AddressCollection extends Collection { /** * Transform the resource collection into an array. diff --git a/src/Http/Resources/Collection.php b/src/Http/Resources/Collection.php new file mode 100644 index 0000000..29e6de5 --- /dev/null +++ b/src/Http/Resources/Collection.php @@ -0,0 +1,18 @@ +collection->isEmpty()) { + $response->setStatusCode(404); + return; + } + } +} diff --git a/src/Http/Resources/Company/CompanyCollection.php b/src/Http/Resources/Company/CompanyCollection.php index d935ae4..33a0f1c 100644 --- a/src/Http/Resources/Company/CompanyCollection.php +++ b/src/Http/Resources/Company/CompanyCollection.php @@ -2,10 +2,10 @@ namespace Creasi\Base\Http\Resources\Company; +use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\ResourceCollection; -class CompanyCollection extends ResourceCollection +class CompanyCollection extends Collection { /** * Transform the resource collection into an array. diff --git a/src/Http/Resources/Employee/EmployeeCollection.php b/src/Http/Resources/Employee/EmployeeCollection.php index 60e98b6..9ed37fc 100644 --- a/src/Http/Resources/Employee/EmployeeCollection.php +++ b/src/Http/Resources/Employee/EmployeeCollection.php @@ -2,10 +2,10 @@ namespace Creasi\Base\Http\Resources\Employee; +use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\ResourceCollection; -class EmployeeCollection extends ResourceCollection +class EmployeeCollection extends Collection { /** * Transform the resource collection into an array. diff --git a/src/Http/Resources/FileUpload/FileUploadCollection.php b/src/Http/Resources/FileUpload/FileUploadCollection.php index 6754943..8c658a5 100644 --- a/src/Http/Resources/FileUpload/FileUploadCollection.php +++ b/src/Http/Resources/FileUpload/FileUploadCollection.php @@ -2,10 +2,10 @@ namespace Creasi\Base\Http\Resources\FileUpload; +use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\ResourceCollection; -class FileUploadCollection extends ResourceCollection +class FileUploadCollection extends Collection { /** * Transform the resource collection into an array. diff --git a/src/Http/Resources/Stakeholder/StakeholderCollection.php b/src/Http/Resources/Stakeholder/StakeholderCollection.php index 8ca43bd..a89a8b2 100644 --- a/src/Http/Resources/Stakeholder/StakeholderCollection.php +++ b/src/Http/Resources/Stakeholder/StakeholderCollection.php @@ -2,10 +2,10 @@ namespace Creasi\Base\Http\Resources\Stakeholder; +use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\ResourceCollection; -class StakeholderCollection extends ResourceCollection +class StakeholderCollection extends Collection { /** * Transform the resource collection into an array. From 73ee460768d02abff10fa454907e2ccdd0785f20 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 03:17:34 +0700 Subject: [PATCH 32/71] chore(api): clean up controllers and resources Signed-off-by: Fery Wardiyanto --- routes/base.php | 2 +- src/Http/Controllers/AddressController.php | 20 +++++++++-------- src/Http/Controllers/CompanyController.php | 16 ++++++-------- src/Http/Controllers/EmployeeController.php | 18 +++++++-------- src/Http/Controllers/FileUploadController.php | 20 +++++++++-------- src/Http/Controllers/ProfileController.php | 4 ++-- src/Http/Controllers/SettingController.php | 4 ++-- .../Controllers/StakeholderController.php | 22 +++++++++---------- src/Http/Controllers/SupportController.php | 2 +- src/Http/Resources/Collection.php | 1 + 10 files changed, 54 insertions(+), 55 deletions(-) diff --git a/routes/base.php b/routes/base.php index a877160..2c2e071 100644 --- a/routes/base.php +++ b/routes/base.php @@ -16,7 +16,7 @@ */ Route::middleware('auth:sanctum')->group(function () { - Route::apiResource('companies', Controllers\CompanyController::class)->withTrashed(); + Route::apiResource('companies', Controllers\CompanyController::class); Route::apiResource('employees', Controllers\EmployeeController::class); Route::apiSingleton('profile', Controllers\ProfileController::class); diff --git a/src/Http/Controllers/AddressController.php b/src/Http/Controllers/AddressController.php index 4764b26..2de0494 100644 --- a/src/Http/Controllers/AddressController.php +++ b/src/Http/Controllers/AddressController.php @@ -7,7 +7,7 @@ use Creasi\Base\Http\Resources\Address\AddressCollection; use Creasi\Base\Http\Resources\Address\AddressResource; use Creasi\Base\Models\Address; -use Creasi\Nusa\Contracts\HasAddresses; +use Creasi\Base\Models\Entity; use Illuminate\Http\Request; class AddressController extends Controller @@ -18,17 +18,19 @@ public function __construct() } /** - * @return \Illuminate\Database\Eloquent\Collection + * @return AddressCollection */ - public function index(HasAddresses $entity) + public function index(Entity $entity) { - return new AddressCollection($entity->addresses()->paginate()); + $items = $entity->addresses()->latest(); + + return new AddressCollection($items->paginate()); } /** - * @return \Illuminate\Http\JsonResponse + * @return AddressResource */ - public function store(StoreRequest $request, HasAddresses $entity) + public function store(StoreRequest $request, Entity $entity) { $item = $request->storeFor($entity); @@ -36,7 +38,7 @@ public function store(StoreRequest $request, HasAddresses $entity) } /** - * @return \Illuminate\Http\JsonResponse + * @return AddressResource */ public function show(Address $address, Request $request) { @@ -44,7 +46,7 @@ public function show(Address $address, Request $request) } /** - * @return \Illuminate\Http\JsonResponse + * @return AddressResource */ public function update(UpdateRequest $request, Address $address) { @@ -54,7 +56,7 @@ public function update(UpdateRequest $request, Address $address) } /** - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\Response */ public function destroy(Address $address) { diff --git a/src/Http/Controllers/CompanyController.php b/src/Http/Controllers/CompanyController.php index 2c0e4bc..14a5eaf 100644 --- a/src/Http/Controllers/CompanyController.php +++ b/src/Http/Controllers/CompanyController.php @@ -17,19 +17,17 @@ public function __construct() } /** - * @return \Illuminate\Database\Eloquent\Collection + * @return CompanyCollection */ public function index(Request $request) { - $users = Company::query() - ->where('id', '<>', $request->user()->id) - ->latest(); + $items = Company::query()->latest(); - return new CompanyCollection($users->paginate()); + return new CompanyCollection($items->paginate()); } /** - * @return \Illuminate\Http\JsonResponse + * @return CompanyResource */ public function store(StoreRequest $request) { @@ -40,7 +38,7 @@ public function store(StoreRequest $request) } /** - * @return \Illuminate\Http\JsonResponse + * @return CompanyResource */ public function show(Company $company, Request $request) { @@ -48,7 +46,7 @@ public function show(Company $company, Request $request) } /** - * @return \Illuminate\Http\JsonResponse + * @return CompanyResource */ public function update(UpdateRequest $request, Company $company) { @@ -58,7 +56,7 @@ public function update(UpdateRequest $request, Company $company) } /** - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\Response */ public function destroy(Company $company) { diff --git a/src/Http/Controllers/EmployeeController.php b/src/Http/Controllers/EmployeeController.php index aed5f87..ac3061c 100644 --- a/src/Http/Controllers/EmployeeController.php +++ b/src/Http/Controllers/EmployeeController.php @@ -17,19 +17,17 @@ public function __construct() } /** - * @return \Illuminate\Database\Eloquent\Collection + * @return EmployeeCollection */ - public function index(Request $request, Employee $employee) + public function index(Employee $employee) { - $users = $employee->newInstance() - ->where('id', '<>', $request->user()->id) - ->latest(); + $items = $employee->newInstance()->latest(); - return new EmployeeCollection($users->paginate()); + return new EmployeeCollection($items->paginate()); } /** - * @return \Illuminate\Http\JsonResponse + * @return EmployeeResource */ public function store(StoreRequest $request, Employee $employee) { @@ -40,7 +38,7 @@ public function store(StoreRequest $request, Employee $employee) } /** - * @return \Illuminate\Http\JsonResponse + * @return EmployeeResource */ public function show(Employee $employee, Request $request) { @@ -48,7 +46,7 @@ public function show(Employee $employee, Request $request) } /** - * @return \Illuminate\Http\JsonResponse + * @return EmployeeResource */ public function update(UpdateRequest $request, Employee $employee) { @@ -58,7 +56,7 @@ public function update(UpdateRequest $request, Employee $employee) } /** - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\Response */ public function destroy(Employee $employee) { diff --git a/src/Http/Controllers/FileUploadController.php b/src/Http/Controllers/FileUploadController.php index 63653ba..df1aabb 100644 --- a/src/Http/Controllers/FileUploadController.php +++ b/src/Http/Controllers/FileUploadController.php @@ -2,11 +2,11 @@ namespace Creasi\Base\Http\Controllers; -use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Http\Requests\FileUpload\StoreRequest; use Creasi\Base\Http\Requests\FileUpload\UpdateRequest; use Creasi\Base\Http\Resources\FileUpload\FileUploadCollection; use Creasi\Base\Http\Resources\FileUpload\FileUploadResource; +use Creasi\Base\Models\Entity; use Creasi\Base\Models\FileUpload; use Illuminate\Http\Request; @@ -18,17 +18,19 @@ public function __construct() } /** - * @return \Illuminate\Database\Eloquent\Collection + * @return FileUploadCollection */ - public function index(HasFileUploads $entity) + public function index(Entity $entity) { - return new FileUploadCollection($entity->files()->paginate()); + $items = $entity->files()->latest(); + + return new FileUploadCollection($items->paginate()); } /** - * @return \Illuminate\Http\JsonResponse + * @return FileUploadResource */ - public function store(StoreRequest $request, HasFileUploads $entity) + public function store(StoreRequest $request, Entity $entity) { $item = $request->storeFor($entity); @@ -36,7 +38,7 @@ public function store(StoreRequest $request, HasFileUploads $entity) } /** - * @return \Illuminate\Http\JsonResponse + * @return FileUploadResource */ public function show(FileUpload $file, Request $request) { @@ -44,7 +46,7 @@ public function show(FileUpload $file, Request $request) } /** - * @return \Illuminate\Http\JsonResponse + * @return FileUploadResource */ public function update(UpdateRequest $request, FileUpload $file) { @@ -54,7 +56,7 @@ public function update(UpdateRequest $request, FileUpload $file) } /** - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\Response */ public function destroy(FileUpload $file) { diff --git a/src/Http/Controllers/ProfileController.php b/src/Http/Controllers/ProfileController.php index d6ec46e..05407f3 100644 --- a/src/Http/Controllers/ProfileController.php +++ b/src/Http/Controllers/ProfileController.php @@ -9,7 +9,7 @@ class ProfileController extends Controller { /** - * @return \Inertia\Response + * @return ProfileResource */ public function show(Request $request) { @@ -19,7 +19,7 @@ public function show(Request $request) } /** - * @return \Inertia\Response + * @return ProfileResource */ public function update(ProfileRequest $request) { diff --git a/src/Http/Controllers/SettingController.php b/src/Http/Controllers/SettingController.php index 97a0461..2aa98c4 100644 --- a/src/Http/Controllers/SettingController.php +++ b/src/Http/Controllers/SettingController.php @@ -9,7 +9,7 @@ class SettingController extends Controller { /** - * @return \Inertia\Response + * @return SettingResource */ public function show(Request $request) { @@ -19,7 +19,7 @@ public function show(Request $request) } /** - * @return \Inertia\Response + * @return SettingResource */ public function update(SettingRequest $request) { diff --git a/src/Http/Controllers/StakeholderController.php b/src/Http/Controllers/StakeholderController.php index d2f83c7..c6e2c69 100644 --- a/src/Http/Controllers/StakeholderController.php +++ b/src/Http/Controllers/StakeholderController.php @@ -17,30 +17,28 @@ public function __construct() } /** - * @return \Illuminate\Database\Eloquent\Collection + * @return StakeholderCollection */ - public function index(Request $request, Stakeholder $stakeholder) + public function index(Stakeholder $stakeholder) { - $model = $stakeholder->newQuery() - ->where('id', '<>', $request->user()->id) - ->latest(); + $items = $stakeholder->newQuery()->latest(); - return new StakeholderCollection($model->paginate()); + return new StakeholderCollection($items->paginate()); } /** - * @return \Illuminate\Http\JsonResponse + * @return StakeholderResource */ public function store(StoreRequest $request, Stakeholder $stakeholder) { /** @var Stakeholder */ - $model = $stakeholder->create($request->validated()); + $item = $stakeholder->create($request->validated()); - return $this->show($model, $request)->setStatusCode(201); + return $this->show($item, $request)->setStatusCode(201); } /** - * @return \Illuminate\Http\JsonResponse + * @return StakeholderResource */ public function show(Stakeholder $stakeholder, Request $request) { @@ -48,7 +46,7 @@ public function show(Stakeholder $stakeholder, Request $request) } /** - * @return \Illuminate\Http\JsonResponse + * @return StakeholderResource */ public function update(UpdateRequest $request, Stakeholder $stakeholder) { @@ -58,7 +56,7 @@ public function update(UpdateRequest $request, Stakeholder $stakeholder) } /** - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\Response */ public function destroy(Stakeholder $stakeholder) { diff --git a/src/Http/Controllers/SupportController.php b/src/Http/Controllers/SupportController.php index 57eb83a..b056a67 100644 --- a/src/Http/Controllers/SupportController.php +++ b/src/Http/Controllers/SupportController.php @@ -5,7 +5,7 @@ class SupportController extends Controller { /** - * @return \Inertia\Response + * @return array */ public function __invoke() { diff --git a/src/Http/Resources/Collection.php b/src/Http/Resources/Collection.php index 29e6de5..f86a7cc 100644 --- a/src/Http/Resources/Collection.php +++ b/src/Http/Resources/Collection.php @@ -12,6 +12,7 @@ public function withResponse(Request $request, JsonResponse $response) { if ($this->collection->isEmpty()) { $response->setStatusCode(404); + return; } } From d6d1e0f0d95364a21669e046ac0a909db56c2f70 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 03:25:17 +0700 Subject: [PATCH 33/71] chore: add repository class to help resolves route model bindings Signed-off-by: Fery Wardiyanto --- src/Models/FileUpload.php | 1 - src/Repository.php | 34 ++++++++++++++++++++++++++++++++++ src/ServiceProvider.php | 38 +++++++++++++++++--------------------- 3 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 src/Repository.php diff --git a/src/Models/FileUpload.php b/src/Models/FileUpload.php index 76e5734..02b8f28 100644 --- a/src/Models/FileUpload.php +++ b/src/Models/FileUpload.php @@ -6,7 +6,6 @@ use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Concerns\HasUuids; -use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; diff --git a/src/Repository.php b/src/Repository.php new file mode 100644 index 0000000..bb9ea08 --- /dev/null +++ b/src/Repository.php @@ -0,0 +1,34 @@ +router->is('companies.*') ? Company::class : Personnel::class); + } + + public function resolveEmployee(): Employee + { + return app(Personnel::class); + } + + public function resolveStakeholder(): Stakeholder + { + return app(Company::class); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 1b91866..091b874 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,12 +3,10 @@ namespace Creasi\Base; use Creasi\Base\Contracts\Employee; -use Creasi\Base\Contracts\HasFileUploads; use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Address; -use Creasi\Base\Models\Personnel; +use Creasi\Base\Models\Entity; use Creasi\Base\View\Composers\TranslationsComposer; -use Creasi\Nusa\Contracts\HasAddresses; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Route; @@ -68,24 +66,7 @@ public function register(): void } } - $this->app->bind(Employee::class, function ($app) { - return new Personnel(); - }); - - $this->app->bind(Stakeholder::class, function ($app) { - return new Personnel(); - }); - - $this->app->bind(HasAddresses::class, function ($app) { - /** @var \Illuminate\Routing\Router */ - $router = $app->make('router'); - - return $app->make(Employee::class); - }); - - $this->app->bind(HasFileUploads::class, function ($app) { - return $app->make(Employee::class); - }); + $this->registerBindings(); } protected function registerPublishables(): void @@ -120,6 +101,21 @@ protected function defineRoutes(): void ->group(self::LIB_PATH.'/routes/base.php'); } + protected function registerBindings() + { + $this->app->bind(Entity::class, function ($app) { + return $app->make(Repository::class)->resolveEntity(); + }); + + $this->app->bind(Employee::class, function ($app) { + return $app->make(Repository::class)->resolveEmployee(); + }); + + $this->app->bind(Stakeholder::class, function ($app) { + return $app->make(Repository::class)->resolveStakeholder(); + }); + } + /** * Register inertia.js helper for dusk testing * From 12367ead242d315df61fab9d9397d7aa4f32cf17 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 03:25:58 +0700 Subject: [PATCH 34/71] chore(tests): rearrange test files and directories structure Signed-off-by: Fery Wardiyanto --- tests/Http/AddressTest.php | 25 --- tests/Http/Business/CompanyTest.php | 159 +++++++++++++++++ tests/Http/Business/CustomerTest.php | 17 ++ tests/Http/Business/EmployeeTest.php | 162 ++++++++++++++++++ tests/Http/Business/SupplierTest.php | 17 ++ tests/Http/Business/VendorTest.php | 17 ++ tests/Http/CompanyTest.php | 68 -------- tests/Http/EmployeeTest.php | 68 -------- tests/Http/FileUploadTest.php | 28 --- tests/Http/StakeholderTest.php | 95 ---------- tests/Http/StakeholderTestCase.php | 99 +++++++++++ .../{BaseTest.php => ServiceProviderTest.php} | 2 +- tests/TestCase.php | 14 +- 13 files changed, 473 insertions(+), 298 deletions(-) create mode 100644 tests/Http/Business/CompanyTest.php create mode 100644 tests/Http/Business/CustomerTest.php create mode 100644 tests/Http/Business/EmployeeTest.php create mode 100644 tests/Http/Business/SupplierTest.php create mode 100644 tests/Http/Business/VendorTest.php delete mode 100644 tests/Http/CompanyTest.php delete mode 100644 tests/Http/EmployeeTest.php delete mode 100644 tests/Http/StakeholderTest.php create mode 100644 tests/Http/StakeholderTestCase.php rename tests/{BaseTest.php => ServiceProviderTest.php} (97%) diff --git a/tests/Http/AddressTest.php b/tests/Http/AddressTest.php index 8f9e703..2295a49 100644 --- a/tests/Http/AddressTest.php +++ b/tests/Http/AddressTest.php @@ -13,31 +13,6 @@ #[Group('address')] class AddressTest extends TestCase { - #[Test] - #[DataProviderExternal(TestCase::class, 'entities')] - public function should_able_to_retrieve_all_data(string $entity, string $modelClass): void - { - Sanctum::actingAs($this->user()); - $model = $modelClass::factory()->withAddress()->create(); - - $response = $this->getJson("base/{$entity}/{$model->getKey()}/addresses"); - - $response->assertOk(); - } - - #[Test] - #[DataProviderExternal(TestCase::class, 'entities')] - public function should_able_to_store_new_data(string $entity, string $modelClass): void - { - Sanctum::actingAs($this->user()); - $model = $modelClass::factory()->withAddress()->create(); - $data = Address::factory()->raw(); - - $response = $this->postJson("base/{$entity}/{$model->getKey()}/addresses", $data); - - $response->assertCreated(); - } - #[Test] public function should_able_to_show_existing_data(): void { diff --git a/tests/Http/Business/CompanyTest.php b/tests/Http/Business/CompanyTest.php new file mode 100644 index 0000000..189395b --- /dev/null +++ b/tests/Http/Business/CompanyTest.php @@ -0,0 +1,159 @@ +user()); + + $response = $this->getJson('base/companies'); + + $response->assertNotFound(); + } + + #[Test] + public function should_able_to_retrieve_all_data(): Company + { + Sanctum::actingAs($this->user()); + + $models = Company::factory(2)->create(); + $response = $this->getJson('base/companies'); + + $response->assertOk(); + + return $models->first(); + } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + + $data = Company::factory()->raw(); + + $response = $this->postJson('base/companies', $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_show_existing_data(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/companies/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_receive_404_when_no_addresses_available(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/companies/{$model->getRouteKey()}/addresses"); + + $response->assertNotFound(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_create_new_address(Company $model): void + { + Sanctum::actingAs($this->user()); + + $data = Address::factory()->raw(); + + $response = $this->postJson("base/companies/{$model->getRouteKey()}/addresses", $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_retrieve_all_addresses(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/companies/{$model->getRouteKey()}/addresses"); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_receive_404_when_no_files_available(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); + + $response->assertNotFound(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_upload_and_store_new_file(Company $model): void + { + Storage::fake(); + Sanctum::actingAs($this->user()); + + $data = FileUpload::factory()->withoutFile()->raw(); + $data['upload'] = UploadedFile::fake()->create('file.pdf'); + + $response = $this->postJson("base/companies/{$model->getRouteKey()}/files", $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_retrieve_all_uploaded_files(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_update_existing_data(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->putJson("base/companies/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_delete_existing_data(Company $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->deleteJson("base/companies/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } +} diff --git a/tests/Http/Business/CustomerTest.php b/tests/Http/Business/CustomerTest.php new file mode 100644 index 0000000..51d79d7 --- /dev/null +++ b/tests/Http/Business/CustomerTest.php @@ -0,0 +1,17 @@ +user()); + + $response = $this->getJson('base/employees'); + + $response->assertNotFound(); + } + + #[Test] + public function should_able_to_retrieve_all_data(): Personnel + { + Sanctum::actingAs($this->user()); + $models = Personnel::factory(2)->create(); + + $response = $this->getJson('base/employees'); + + $response->assertOk(); + + return $models->first(); + } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + $data = Personnel::factory()->raw(); + + $response = $this->postJson('base/employees', $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_show_existing_data(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/employees/{$model->getRouteKey()}"); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_receive_404_when_no_addresses_available(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/employees/{$model->getRouteKey()}/addresses"); + + $response->assertNotFound(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_create_new_address(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $data = Address::factory()->raw(); + + $response = $this->postJson("base/employees/{$model->getRouteKey()}/addresses", $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_retrieve_all_addresses(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $model->addresses()->saveMany(Address::factory(2)->create()); + + $response = $this->getJson("base/employees/{$model->getRouteKey()}/addresses"); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_receive_404_when_no_files_available(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson("base/employees/{$model->getRouteKey()}/files"); + + $response->assertNotFound(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_upload_and_store_new_file(Personnel $model): void + { + Storage::fake(); + Sanctum::actingAs($this->user()); + + $data = FileUpload::factory()->withoutFile()->raw(); + $data['upload'] = UploadedFile::fake()->create('file.pdf'); + + $response = $this->postJson("base/employees/{$model->getRouteKey()}/files", $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_retrieve_all_uploaded_files(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $model->files()->saveMany(FileUpload::factory(2)->create()); + + $response = $this->getJson("base/employees/{$model->getRouteKey()}/files"); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_update_existing_data(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->putJson("base/employees/{$model->getRouteKey()}", $model->toArray()); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_delete_existing_data(Personnel $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->deleteJson("base/employees/{$model->getRouteKey()}"); + + $response->assertNoContent(); + } +} diff --git a/tests/Http/Business/SupplierTest.php b/tests/Http/Business/SupplierTest.php new file mode 100644 index 0000000..3cbeda9 --- /dev/null +++ b/tests/Http/Business/SupplierTest.php @@ -0,0 +1,17 @@ +user()); - - $response = $this->getJson('base/companies'); - - $response->assertOk(); - } - - #[Test] - public function should_able_to_store_new_data(): void - { - Sanctum::actingAs($this->user()); - $data = Company::factory()->raw(); - - $response = $this->postJson('base/companies', $data); - - $response->assertCreated(); - } - - #[Test] - public function should_able_to_show_existing_data(): void - { - Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); - - $response = $this->getJson("base/companies/{$model->getRouteKey()}"); - - $response->assertOk(); - } - - #[Test] - public function should_able_to_update_existing_data(): void - { - Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); - - $response = $this->putJson("base/companies/{$model->getRouteKey()}", $model->toArray()); - - $response->assertOk(); - } - - #[Test] - public function should_able_to_delete_existing_data(): void - { - Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); - - $response = $this->deleteJson("base/companies/{$model->getRouteKey()}"); - - $response->assertNoContent(); - } -} diff --git a/tests/Http/EmployeeTest.php b/tests/Http/EmployeeTest.php deleted file mode 100644 index 6b615da..0000000 --- a/tests/Http/EmployeeTest.php +++ /dev/null @@ -1,68 +0,0 @@ -user()); - - $response = $this->getJson('base/employees'); - - $response->assertOk(); - } - - #[Test] - public function should_able_to_store_new_data(): void - { - Sanctum::actingAs($this->user()); - $data = Personnel::factory()->raw(); - - $response = $this->postJson('base/employees', $data); - - $response->assertCreated(); - } - - #[Test] - public function should_able_to_show_existing_data(): void - { - Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - - $response = $this->getJson("base/employees/{$model->getRouteKey()}"); - - $response->assertOk(); - } - - #[Test] - public function should_able_to_update_existing_data(): void - { - Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - - $response = $this->putJson("base/employees/{$model->getRouteKey()}", $model->toArray()); - - $response->assertOk(); - } - - #[Test] - public function should_able_to_delete_existing_data(): void - { - Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - - $response = $this->deleteJson("base/employees/{$model->getRouteKey()}"); - - $response->assertNoContent(); - } -} diff --git a/tests/Http/FileUploadTest.php b/tests/Http/FileUploadTest.php index 84beac9..7727a61 100644 --- a/tests/Http/FileUploadTest.php +++ b/tests/Http/FileUploadTest.php @@ -15,34 +15,6 @@ #[Group('fileUpload')] class FileUploadTest extends TestCase { - #[Test] - #[DataProviderExternal(TestCase::class, 'entities')] - public function should_able_to_retrieve_all_data(string $entity, string $modelClass): void - { - Sanctum::actingAs($this->user()); - $model = $modelClass::factory()->withFileUpload()->create(); - - $response = $this->getJson("base/{$entity}/{$model->getKey()}/files"); - - $response->assertOk(); - } - - #[Test] - #[DataProviderExternal(TestCase::class, 'entities')] - public function should_able_to_store_new_data(string $entity, string $modelClass): void - { - Storage::fake(); - Sanctum::actingAs($this->user()); - - $model = $modelClass::factory()->withFileUpload()->create(); - $data = FileUpload::factory()->withoutFile()->raw(); - $data['upload'] = UploadedFile::fake()->create('file.pdf'); - - $response = $this->postJson("base/{$entity}/{$model->getKey()}/files", $data); - - $response->assertCreated(); - } - #[Test] public function should_able_to_show_existing_data(): void { diff --git a/tests/Http/StakeholderTest.php b/tests/Http/StakeholderTest.php deleted file mode 100644 index 486d431..0000000 --- a/tests/Http/StakeholderTest.php +++ /dev/null @@ -1,95 +0,0 @@ -isInternal()) { - continue; - } - - $stakeholders[(string) $stakeholder->key()] = [$stakeholder]; - } - - return $stakeholders; - } - - #[Test] - #[DataProvider('stakeholders')] - public function should_able_to_retrieve_all_data(CompanyRelativeType $stakeholder): void - { - Sanctum::actingAs($this->user()); - - $route = $stakeholder->key()->plural(); - $response = $this->getJson("base/{$route}"); - - $response->assertOk(); - } - - #[Test] - #[DataProvider('stakeholders')] - public function should_able_to_store_new_data(CompanyRelativeType $stakeholder): void - { - Sanctum::actingAs($this->user()); - $data = Personnel::factory()->raw(); - $route = $stakeholder->key()->plural(); - - $response = $this->postJson("base/{$route}", $data); - - $response->assertCreated(); - } - - #[Test] - #[DataProvider('stakeholders')] - public function should_able_to_show_existing_data(CompanyRelativeType $stakeholder): void - { - Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - $route = $stakeholder->key()->plural(); - - $response = $this->getJson("base/{$route}/{$model->getRouteKey()}"); - - $response->assertOk(); - } - - #[Test] - #[DataProvider('stakeholders')] - public function should_able_to_update_existing_data(CompanyRelativeType $stakeholder): void - { - Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - $route = $stakeholder->key()->plural(); - - $response = $this->putJson("base/{$route}/{$model->getRouteKey()}", $model->toArray()); - - $response->assertOk(); - } - - #[Test] - #[DataProvider('stakeholders')] - public function should_able_to_delete_existing_data(CompanyRelativeType $stakeholder): void - { - Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - $route = $stakeholder->key()->plural(); - - $response = $this->deleteJson("base/{$route}/{$model->getRouteKey()}"); - - $response->assertNoContent(); - } -} diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php new file mode 100644 index 0000000..e68299b --- /dev/null +++ b/tests/Http/StakeholderTestCase.php @@ -0,0 +1,99 @@ +getRelativeType()->key()->plural(); + + $suffixs = \array_map( + fn ($suffix) => $suffix instanceof UrlRoutable ? $suffix->getRouteKey() : $suffix, + $suffixs + ); + + return \implode('/', \array_filter(['base', (string) $route, ...$suffixs])); + } + + #[Test] + public function should_receive_404_when_no_data_available(): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson($this->getRoutePath()); + + $response->assertNotFound(); + } + + #[Test] + public function should_able_to_retrieve_all_data(): Entity + { + Sanctum::actingAs($this->user()); + $models = Company::factory(2)->create(); + + $response = $this->getJson($this->getRoutePath()); + + $response->assertOk(); + + return $models->first(); + } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + $data = Company::factory()->raw(); + + $response = $this->postJson($this->getRoutePath(), $data); + + $response->assertCreated(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_show_existing_data(Entity $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->getJson($this->getRoutePath($model)); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_update_existing_data(Entity $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->putJson($this->getRoutePath($model), $model->toArray()); + + $response->assertOk(); + } + + #[Test] + #[Depends('should_able_to_retrieve_all_data')] + public function should_able_to_delete_existing_data(Entity $model): void + { + Sanctum::actingAs($this->user()); + + $response = $this->deleteJson($this->getRoutePath($model)); + + $response->assertNoContent(); + } +} diff --git a/tests/BaseTest.php b/tests/ServiceProviderTest.php similarity index 97% rename from tests/BaseTest.php rename to tests/ServiceProviderTest.php index b4307ac..69ce41a 100644 --- a/tests/BaseTest.php +++ b/tests/ServiceProviderTest.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\Attributes\Test; use SebastianBergmann\CodeCoverage\Driver\Driver; -class BaseTest extends TestCase +class ServiceProviderTest extends TestCase { use WithFaker; diff --git a/tests/TestCase.php b/tests/TestCase.php index f974f78..f8758f7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,32 +3,20 @@ namespace Creasi\Tests; use Closure; -use Creasi\Base\Models\Company; -use Creasi\Base\Models\Personnel; use Creasi\Base\Models\User; use Creasi\Base\ServiceProvider; use Creasi\Nusa\ServiceProvider as NusaServiceProvider; use Illuminate\Contracts\Config\Repository; -use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\RefreshDatabase; use Laravel\Sanctum\SanctumServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; -class TestCase extends Orchestra +abstract class TestCase extends Orchestra { use RefreshDatabase; - use DatabaseMigrations; private ?User $currentUser = null; - final public static function entities() - { - return [ - 'company' => ['companies', Company::class], - 'employee' => ['employees', Personnel::class], - ]; - } - /** * @param \Illuminate\Foundation\Application $app */ From 4da9a071b5ce4e70783b9e4188ff21dbe84fe97a Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 04:45:10 +0700 Subject: [PATCH 35/71] chore(tests): ensure we're able to disable routes if needed Signed-off-by: Fery Wardiyanto --- src/ServiceProvider.php | 4 ++-- tests/ServiceProviderTest.php | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 091b874..2a6ff0d 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -26,7 +26,7 @@ public function boot(): void * @see https://laravel.com/docs/9.x/mail#using-a-global-to-address */ if (! app()->environment('production') && $devMail = env('MAIL_DEVELOPMENT')) { - Mail::alwaysTo($devMail); + Mail::alwaysTo($devMail); // @codeCoverageIgnore } if (app()->runningInConsole()) { @@ -93,7 +93,7 @@ protected function registerCommands(): void protected function defineRoutes(): void { - if (app()->routesAreCached() && config('creasi.base.routes_enable') === false) { + if (app()->routesAreCached() || config('creasi.base.routes_enable') === false) { return; } diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index 69ce41a..c029bec 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -6,22 +6,48 @@ use Facebook\WebDriver\Exception\TimeoutException; use Illuminate\Foundation\Testing\WithFaker; +use Illuminate\Support\Facades\Route; use Laravel\Dusk\Browser; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; use SebastianBergmann\CodeCoverage\Driver\Driver; +#[Group('serviceProvider')] class ServiceProviderTest extends TestCase { use WithFaker; + /** + * @param \Illuminate\Foundation\Application $app + */ + protected function disableRoute($app) + { + $app->config->set('creasi.base.routes_enable', false); + } + + /** + * @define-env disableRoute + */ + #[Test] + #[Group('routes')] + public function should_able_to_disable_routes() + { + /** @var \Countable */ + $routes = Route::getRoutes(); + + $this->assertCount(4, $routes); + } + #[Test] - public function it_adds_dusk_macros() + #[Group('dusk')] + public function should_adds_dusk_macros() { $this->assertTrue(Browser::hasMacro('waitForInertia')); } #[Test] - public function it_throws_an_exception_if_the_count_doesnt_increase() + #[Group('dusk')] + public function should_throws_an_exception_if_the_count_doesnt_increase() { $driver = $this->mock(Driver::class); @@ -46,7 +72,8 @@ public function it_throws_an_exception_if_the_count_doesnt_increase() } #[Test] - public function it_passes_when_the_count_increases() + #[Group('dusk')] + public function should_passes_when_the_count_increases() { $driver = $this->mock(Driver::class); From 6614c55b53b3f98f8c11d55fe2147040479cd4a8 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 07:59:29 +0700 Subject: [PATCH 36/71] chore(tests): fix previous api testing issues see https://stackoverflow.com/a/76717314/881743 Signed-off-by: Fery Wardiyanto --- src/Repository.php | 7 +++- tests/Http/Business/CompanyTest.php | 49 +++++++++++++++------------- tests/Http/Business/EmployeeTest.php | 49 +++++++++++++++------------- tests/Http/StakeholderTestCase.php | 22 +++++++------ 4 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/Repository.php b/src/Repository.php index bb9ea08..d1a5d17 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -19,7 +19,12 @@ public function __construct( public function resolveEntity(): Entity { - return app($this->router->is('companies.*') ? Company::class : Personnel::class); + /** @var Entity */ + $entity = app($this->router->is('companies.*') ? Company::class : Personnel::class); + + // For some reason we do need to resolve the binding ourselves + // see: https://stackoverflow.com/a/76717314/881743 + return $entity->resolveRouteBinding($this->router->input('entity')) ?: $entity; } public function resolveEmployee(): Employee diff --git a/tests/Http/Business/CompanyTest.php b/tests/Http/Business/CompanyTest.php index 189395b..2d4a58b 100644 --- a/tests/Http/Business/CompanyTest.php +++ b/tests/Http/Business/CompanyTest.php @@ -28,16 +28,14 @@ public function should_receive_404_when_no_data_available(): void } #[Test] - public function should_able_to_retrieve_all_data(): Company + public function should_able_to_retrieve_all_data(): void { Sanctum::actingAs($this->user()); + Company::factory(2)->create(); - $models = Company::factory(2)->create(); $response = $this->getJson('base/companies'); $response->assertOk(); - - return $models->first(); } #[Test] @@ -53,34 +51,36 @@ public function should_able_to_store_new_data(): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_show_existing_data(Company $model): void + public function should_able_to_show_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->getJson("base/companies/{$model->getRouteKey()}"); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_receive_404_when_no_addresses_available(Company $model): void + public function should_receive_404_when_no_addresses_available(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->getJson("base/companies/{$model->getRouteKey()}/addresses"); $response->assertNotFound(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_create_new_address(Company $model): void + public function should_able_to_create_new_address(): void { Sanctum::actingAs($this->user()); $data = Address::factory()->raw(); + $model = Company::factory()->createOne(); $response = $this->postJson("base/companies/{$model->getRouteKey()}/addresses", $data); @@ -88,34 +88,36 @@ public function should_able_to_create_new_address(Company $model): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_retrieve_all_addresses(Company $model): void + public function should_able_to_retrieve_all_addresses(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->withAddress()->createOne(); + $response = $this->getJson("base/companies/{$model->getRouteKey()}/addresses"); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_receive_404_when_no_files_available(Company $model): void + public function should_receive_404_when_no_files_available(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); $response->assertNotFound(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_upload_and_store_new_file(Company $model): void + public function should_able_to_upload_and_store_new_file(): void { Storage::fake(); Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); $data = FileUpload::factory()->withoutFile()->raw(); $data['upload'] = UploadedFile::fake()->create('file.pdf'); @@ -125,33 +127,36 @@ public function should_able_to_upload_and_store_new_file(Company $model): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_retrieve_all_uploaded_files(Company $model): void + public function should_able_to_retrieve_all_uploaded_files(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->withFileUpload()->createOne(); + $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_update_existing_data(Company $model): void + public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->putJson("base/companies/{$model->getRouteKey()}", $model->toArray()); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_delete_existing_data(Company $model): void + public function should_able_to_delete_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->deleteJson("base/companies/{$model->getRouteKey()}"); $response->assertNoContent(); diff --git a/tests/Http/Business/EmployeeTest.php b/tests/Http/Business/EmployeeTest.php index 6249286..a062500 100644 --- a/tests/Http/Business/EmployeeTest.php +++ b/tests/Http/Business/EmployeeTest.php @@ -28,16 +28,14 @@ public function should_receive_404_when_no_data_available(): void } #[Test] - public function should_able_to_retrieve_all_data(): Personnel + public function should_able_to_retrieve_all_data(): void { Sanctum::actingAs($this->user()); - $models = Personnel::factory(2)->create(); + Personnel::factory(2)->create(); $response = $this->getJson('base/employees'); $response->assertOk(); - - return $models->first(); } #[Test] @@ -52,33 +50,35 @@ public function should_able_to_store_new_data(): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_show_existing_data(Personnel $model): void + public function should_able_to_show_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $response = $this->getJson("base/employees/{$model->getRouteKey()}"); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_receive_404_when_no_addresses_available(Personnel $model): void + public function should_receive_404_when_no_addresses_available(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $response = $this->getJson("base/employees/{$model->getRouteKey()}/addresses"); $response->assertNotFound(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_create_new_address(Personnel $model): void + public function should_able_to_create_new_address(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); $data = Address::factory()->raw(); $response = $this->postJson("base/employees/{$model->getRouteKey()}/addresses", $data); @@ -87,11 +87,12 @@ public function should_able_to_create_new_address(Personnel $model): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_retrieve_all_addresses(Personnel $model): void + public function should_able_to_retrieve_all_addresses(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $model->addresses()->saveMany(Address::factory(2)->create()); $response = $this->getJson("base/employees/{$model->getRouteKey()}/addresses"); @@ -100,23 +101,24 @@ public function should_able_to_retrieve_all_addresses(Personnel $model): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_receive_404_when_no_files_available(Personnel $model): void + public function should_receive_404_when_no_files_available(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $response = $this->getJson("base/employees/{$model->getRouteKey()}/files"); $response->assertNotFound(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_upload_and_store_new_file(Personnel $model): void + public function should_able_to_upload_and_store_new_file(): void { Storage::fake(); Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); $data = FileUpload::factory()->withoutFile()->raw(); $data['upload'] = UploadedFile::fake()->create('file.pdf'); @@ -126,11 +128,12 @@ public function should_able_to_upload_and_store_new_file(Personnel $model): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_retrieve_all_uploaded_files(Personnel $model): void + public function should_able_to_retrieve_all_uploaded_files(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $model->files()->saveMany(FileUpload::factory(2)->create()); $response = $this->getJson("base/employees/{$model->getRouteKey()}/files"); @@ -139,22 +142,24 @@ public function should_able_to_retrieve_all_uploaded_files(Personnel $model): vo } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_update_existing_data(Personnel $model): void + public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $response = $this->putJson("base/employees/{$model->getRouteKey()}", $model->toArray()); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_delete_existing_data(Personnel $model): void + public function should_able_to_delete_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Personnel::factory()->createOne(); + $response = $this->deleteJson("base/employees/{$model->getRouteKey()}"); $response->assertNoContent(); diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php index e68299b..b7be9e6 100644 --- a/tests/Http/StakeholderTestCase.php +++ b/tests/Http/StakeholderTestCase.php @@ -41,22 +41,21 @@ public function should_receive_404_when_no_data_available(): void } #[Test] - public function should_able_to_retrieve_all_data(): Entity + public function should_able_to_retrieve_all_data(): void { Sanctum::actingAs($this->user()); - $models = Company::factory(2)->create(); + Company::factory(2)->create(); $response = $this->getJson($this->getRoutePath()); $response->assertOk(); - - return $models->first(); } #[Test] public function should_able_to_store_new_data(): void { Sanctum::actingAs($this->user()); + $data = Company::factory()->raw(); $response = $this->postJson($this->getRoutePath(), $data); @@ -65,33 +64,36 @@ public function should_able_to_store_new_data(): void } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_show_existing_data(Entity $model): void + public function should_able_to_show_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->getJson($this->getRoutePath($model)); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_update_existing_data(Entity $model): void + public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->putJson($this->getRoutePath($model), $model->toArray()); $response->assertOk(); } #[Test] - #[Depends('should_able_to_retrieve_all_data')] - public function should_able_to_delete_existing_data(Entity $model): void + public function should_able_to_delete_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Company::factory()->createOne(); + $response = $this->deleteJson($this->getRoutePath($model)); $response->assertNoContent(); From 78aaff3c05d33d5e0cf7ec237da2209e898de315 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 08:35:26 +0700 Subject: [PATCH 37/71] feat(dev): utilize `lint-staged` to help execute `pint` before commit Signed-off-by: Fery Wardiyanto --- package.json | 6 ++++++ scripts/husky/pre-commit | 5 +++++ 2 files changed, 11 insertions(+) create mode 100755 scripts/husky/pre-commit diff --git a/package.json b/package.json index d5e6e5a..c711613 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", "husky": "^8.0.3", + "lint-staged": "^13.2.3", "standard-version": "^9.5.0" }, "commitlint": { @@ -19,5 +20,10 @@ "@commitlint/config-conventional" ] }, + "lint-staged": { + "{config,database,src,scripts,tests}/**/*.php": [ + "php vendor/bin/pint --preset laravel" + ] + }, "standard-version": {} } diff --git a/scripts/husky/pre-commit b/scripts/husky/pre-commit new file mode 100755 index 0000000..e98987c --- /dev/null +++ b/scripts/husky/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh + +. "$(dirname "$0")/_/husky.sh" + +./node_modules/.bin/lint-staged --allow-empty From 34fadec7b0a1886a6803bc8747d9601e1aa840dd Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 19 Jul 2023 08:35:42 +0700 Subject: [PATCH 38/71] chore: clean up Signed-off-by: Fery Wardiyanto --- routes/base.php | 5 ++--- tests/Http/AddressTest.php | 1 - tests/Http/Business/CompanyTest.php | 1 - tests/Http/Business/EmployeeTest.php | 1 - tests/Http/FileUploadTest.php | 3 --- tests/Http/StakeholderTestCase.php | 2 -- 6 files changed, 2 insertions(+), 11 deletions(-) diff --git a/routes/base.php b/routes/base.php index 2c2e071..e5f467c 100644 --- a/routes/base.php +++ b/routes/base.php @@ -41,8 +41,7 @@ $route = $stakeholder->key()->plural(); - Route::apiResource($route, Controllers\StakeholderController::class)->parameters([ - (string) $route => 'stakeholder' - ]); + Route::apiResource($route, Controllers\StakeholderController::class) + ->parameter((string) $route, 'stakeholder'); } }); diff --git a/tests/Http/AddressTest.php b/tests/Http/AddressTest.php index 2295a49..1a0edb0 100644 --- a/tests/Http/AddressTest.php +++ b/tests/Http/AddressTest.php @@ -5,7 +5,6 @@ use Creasi\Base\Models\Address; use Creasi\Tests\TestCase; use Laravel\Sanctum\Sanctum; -use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; diff --git a/tests/Http/Business/CompanyTest.php b/tests/Http/Business/CompanyTest.php index 2d4a58b..d8209b4 100644 --- a/tests/Http/Business/CompanyTest.php +++ b/tests/Http/Business/CompanyTest.php @@ -9,7 +9,6 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Laravel\Sanctum\Sanctum; -use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; diff --git a/tests/Http/Business/EmployeeTest.php b/tests/Http/Business/EmployeeTest.php index a062500..a9ce567 100644 --- a/tests/Http/Business/EmployeeTest.php +++ b/tests/Http/Business/EmployeeTest.php @@ -9,7 +9,6 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Laravel\Sanctum\Sanctum; -use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; diff --git a/tests/Http/FileUploadTest.php b/tests/Http/FileUploadTest.php index 7727a61..0399b39 100644 --- a/tests/Http/FileUploadTest.php +++ b/tests/Http/FileUploadTest.php @@ -4,10 +4,7 @@ use Creasi\Base\Models\FileUpload; use Creasi\Tests\TestCase; -use Illuminate\Http\UploadedFile; -use Illuminate\Support\Facades\Storage; use Laravel\Sanctum\Sanctum; -use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php index b7be9e6..6a2e32d 100644 --- a/tests/Http/StakeholderTestCase.php +++ b/tests/Http/StakeholderTestCase.php @@ -3,12 +3,10 @@ namespace Creasi\Tests\Http; use Creasi\Base\Models\Company; -use Creasi\Base\Models\Entity; use Creasi\Base\Models\Enums\CompanyRelativeType; use Creasi\Tests\TestCase; use Illuminate\Contracts\Routing\UrlRoutable; use Laravel\Sanctum\Sanctum; -use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; From cba214329de79b55b68ac7d8b2cf75fb606a464b Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 20 Jul 2023 03:12:51 +0700 Subject: [PATCH 39/71] chore(docs): update database structure documentation Signed-off-by: Fery Wardiyanto --- database/README.md | 384 ++++++++++++++++++++++------------ src/Models/Company.php | 2 - src/Models/Enums/Religion.php | 1 + 3 files changed, 248 insertions(+), 139 deletions(-) diff --git a/database/README.md b/database/README.md index b491e9e..e19d3ad 100644 --- a/database/README.md +++ b/database/README.md @@ -2,34 +2,38 @@ ```mermaid erDiagram - companies ||--o{ company_relatives : hasMany - companies { + businesses ||..o{ business_relatives : stakeholders + businesses { unsignedBigInt id PK - string code - string name - string email + varchar name + varchar alias + varchar email UK varchar(20) phone_number - string logo_path + unsignedSmallInt tax_status + varchar(16) tax_id text summary } - company_relatives ||--|| companies : stakeholder - company_relatives ||--|| personnels : stakeholder - company_relatives { + business_relatives ||--|| businesses : stakeholder + business_relatives ||--|| personnels : stakeholder + business_relatives { unsignedBigInt id PK - unsignedBigInt company_id FK + unsignedBigInt business_id FK morph stakeholder + varchar code UK unsignedSmallInt type boolean is_internal text remark } - companies ||--o{ employments : employees - employments }o--|| personnels : employees + businesses ||--o{ employments : employees + employments }o..|| personnels : employees employments { - unsignedBigInt company_id FK + unsignedBigInt id PK + unsignedBigInt business_id FK unsignedBigInt employee_id FK boolean is_primary + varchar code UK unsignedSmallInt type unsignedSmallInt status date start_date @@ -39,16 +43,32 @@ erDiagram personnels { unsignedBigInt id PK - string code - string name - string email + unsignedBigInt user_id FK + varchar name + varchar alias + varchar email UK varchar(20) phone_number - string logo_path + char(1) gender text summary } - personnels ||--o{ personnel_relatives : hasMany - personnel_relatives ||--|| personnels : family + personnels ||..|| profiles : profiles + profiles { + unsignedBigInt id PK + morphs identity + char(16) nik UK + varchar prefix + varchar suffix + date birth_date + char(4) birth_place_code + unsignedSmallInt education + unsignedSmallInt religion + unsignedSmallInt tax_status + varchar(16) tax_id + } + + personnels ||..o{ personnel_relatives : relatives + personnel_relatives ||--|| personnels : relative personnel_relatives { unsignedBigInt personnel_id FK unsignedBigInt relative_id FK @@ -56,31 +76,13 @@ erDiagram text remark } - file_uploads ||--|| file_attached : attachments - file_uploads ||--o{ file_uploads : revisions - file_uploads { - unsignedBigInt id PK - unsignedBigInt revision_id FK - string title - string name - string path - string drive - text summary - } - companies }o..|| file_attached : uploadedFiles - personnels }o..|| file_attached : uploadedFiles - file_attached { - unsignedBigInt file_upload_id FK - morph attached_to - } - - addresses }o..|| companies : addresses + addresses }o..|| businesses : addresses addresses }o..|| personnels : addresses addresses { unsignedBigInt id PK morph owner boolean is_resident - string line + varchar line char(3) rt char(3) rw char(10) village_code @@ -91,116 +93,176 @@ erDiagram text summary } - personnels ||..|| identities : profile - identities { + users ||..|| personnels : credential + users { unsignedBigInt id PK - unsignedBigInt user_id FK - morphs identity - char(16) nik - string prefix - string fullname - string suffix - char(1) gender - date birth_date - char(4) birth_place_code - unsignedSmallInt education - unsignedSmallInt religion - varchar(20) phone_number - string photo_path + varchar name + varchar email + varchar password + } + + file_uploads ||--o{ file_uploads : revisions + file_uploads { + unsignedBigInt id PK + unsignedBigInt revision_id FK + varchar title + varchar name + varchar path + varchar drive text summary } + businesses }o..|| file_attached : files + personnels }o..|| file_attached : files + file_attached ||--|| file_uploads : attachments + file_attached { + unsignedBigInt file_upload_id FK + morph attached_to + } + ``` --- -## Companies +## Entities + +Either a companies or an individuals are tent to have share some similarities, which is they must have a way for externals to communicate with them. The most common ways are by `email` or `phone`. In that regard, another similarity is they must have a name, but there's case that differenciate how we describe the way we call them. A businesses are commonly use term `legal_name` and `alias_name`, while an individuals are commonly using `full_name` and `nick_name`. Either of them serve the same purposes. -A Company tent to have some short of business relationship either to another companies or individuals regardless of its size, we call it `company_relatives` or the most common term is `stakeholder`. In this first implementation we try to cover 5 most basic business relationship, which are : +Despite those similarities they must have some differences, including : +- An individual does have gender, while a company doesn't +- An individual might have a credential so they can logging in to the system, while a company shouldn't + +```mermaid +classDiagram + Entity <|-- Business + Entity <|-- Personnel + Personnel "1" ..> "1" User + + class Entity { + varchar email + varchar phone_number + varchar name + varchar alias + unsignedSmallInt tax_status + } + class Business { + unsignedBigInt id + unsignedSmallInt tax_status + varchar~20~ tax_id + } + class Personnel { + unsignedBigInt id + unsignedBigInt user_id + char~1~ gender + } + class User { + unsignedBigInt id + varchar name + varchar password + } +``` + +## Businesses + +A business must have some short of [relationship](https://www.investopedia.com/terms/b/business-relations.asp) to the externals, either to another business or individuals regardless of its size. The most common term to describe it is [stakeholder](https://www.investopedia.com/terms/s/stakeholder.asp). + +> A stakeholder is a person or group with an interest in an enterprise - [What Are Stakeholders: Definition, Types, and Examples](https://www.investopedia.com/terms/s/stakeholder.asp) + +In this implementation we try to cover 5 most basic and common business relationship, which are : - **Owner** - Cover business relation between company to individuals who own the company. + Cover business relation between the company to a individual(s) who own the business. - **Subsidiary** - Cover business relation between company to other companies that act as a child company. The parent company can be called parent or holding company, and the child company can be called child company or operating company. + Cover business relation between the company to other businesses that act as a child company. The parent company can be called parent or holding company, and the child company can be called child company or operating company. - **Customer** - Cover business relation between company to either individuals or other companies where the revenues are generated from. + Cover business relation between the company either to individual or business who makes a business its money. - **Supplier** - Cover business relation between company to either individuals or other companies where the production raw materials are came from. + Cover business relation between the company either to individual or business who provide the raw materials to the business so the can produce their goods. - **Vendor** - Cover business relation between company to either individuals or other companies where the tangible assets are provided from. - + Cover business relation between the company either to individual or business who provide the tangible assets to the business so they can proceed the raw materials into goods. -The term `stakeholder` itself is actually covers a lot more than that, it can be `investor`, `founder`, and even `employee`. But at this stage we can't afford to comply those types of stakeholding simply because that's beyond our cababilities to handle them. +The term "stakeholders" itself is actually covers a lot more than that, even employees are counted as stakeholders. But at this stage we can't afford to comply those types of relationship, simply because that's beyond of our cababilities to handle them at the moment. ```mermaid classDiagram - Company "1" --> "*" Company : CompanyRelative - Company "1" --> "*" Personnel : CompanyRelative + BusinessRelative --> Business : stakeholders + BusinessRelative --> Personnel : stakeholders + Business --> BusinessRelative : businessRelatives - class Company { + class Business { unsignedBigInt id - timestamps() static - softDeletes() static } class Personnel { unsignedBigInt id - timestamps() static - softDeletes() static + } + class BusinessRelative { + unsignedBigInt id + stakeholders() Entity[] } ``` -### `companies` +### `businesses` | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `code` | `string`, `nullable` | `unique` | - | -| `name` | `string` | | - | -| `email` | `string`, `nullable` | `unique` | - | +| `name` | `varchar` | | - | +| `alias` | `varchar`, `nullable` | | - | +| `email` | `varchar`, `nullable` | `unique` | - | | `phone_number` | `varchar(20)`, `nullable` | | - | -| `logo_path` | `string`, `nullable` | | - | +| `tax_status` | `unsignedSmallInt`, `nullable` | | - | +| `tax_id` | `varchar(16)`, `nullable` | | - | | `summary` | `text`, `nullable` | | - | **Model Attributes** - `timestamps` - `softDeletes` -### `company_relatives` (morphPivot) +### `business_relatives` (morphPivot) | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `company_id` | `unsignedBigInt` | `foreign` | - | +| `business_id` | `unsignedBigInt` | `foreign` | - | | `stakeholder` | `morphs`, `nullable` | | - | +| `code` | `varchar`, `nullable` | `unique` | - | | `type` | `unsignedSmallInt`, `nullable` | | - | | `is_internal` | `boolean`, `default: false` | | - | | `remark` | `text`, `nullable` | | - | **Relation Properties** -- `company_id` : reference `companies` +- `business_id` : reference `businesses` ## Employment -Essentially the `employments` workflow's can be done using `company_relatives`, but since it has certain entities that differs compared to the other stakeholders we should pivot it into different table. Another reason is it could be easier to manage the spesific relation using dedicated table. +Essentially the `employments` mechanism can be done using `business_relatives`, but since it has certain entities that differs compared to the other stakeholders we should pivot it into different table. Another reason is it could be easier to manage the spesific relation when using dedicated table. ```mermaid classDiagram - Company "1" <--> "*" Personnel : Employment - class Company { + Employments --> Personnel : employments + Business --> Employments : employments + + class Business { unsignedBigInt id - timestamps() static - softDeletes() static + employees(): Personnel[] } class Personnel { unsignedBigInt id - timestamps() static - softDeletes() static + employer(): Business + } + class Employments { + unsignedBigInt id + unsignedBigInt business_id + unsignedBigInt employee_id + varchar code + boolean is_primary + stakeholders() Entity[] } ``` @@ -208,9 +270,11 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `company_id` | `unsignedBigInt` | `foreign` | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | +| `business_id` | `unsignedBigInt` | `foreign` | - | | `employee_id` | `unsignedBigInt` | `foreign` | - | | `is_primary` | `boolean`, `default: false` | | - | +| `code` | `varchar`, `nullable` | `unique` | - | | `type` | `unsignedSmallInt`, `nullable` | | - | | `status` | `unsignedSmallInt`, `nullable` | | - | | `start_date` | `date`, `nullable` | | - | @@ -218,28 +282,45 @@ classDiagram | `remark` | `text`, `nullable` | | - | **Relation Properties** -- `company_id` : reference `companies` +- `business_id` : reference `businesses` - `employee_id` : reference `personnels` -## Personnel and Identities +**Employment Types** +- Unemployeed +- Fulltime +- Parttime +- Internship +- Freelance + +**Employment Statuses** +- Permanent +- Contract +- Probation -Every individuals should have its own Identity, but there's some circumstance that we don't really need that kind of details for every individuals in our business. +## Personnel and its Profile -In some instance of business it might be required to have some short of personnel relatiove defined and managed by the company. That way the company can have contact of its employees' relative so they can be contacted in case of the unexpected happens with the specific employee. +Every individuals should have their own identity, it also can helps a business to identify better of their individuals. But there's a circumstance that a business doesn't really care about that, all they need is just a way to communicate with the individuals, and that's it. + +Meanwhile, a business might want to be able to also communicate with their personnel's relatieves. That case mostly used by a company to it employees in regards when there's an unexpected happens and the company decided to communicate it to their employee's relatives ```mermaid classDiagram - Personnel "1" <..> "1" Identity : Profile - Personnel "1" ..> "*" Personnel : PersonnelRelative + Personnel .. Profile : profile + Personnel ..> PersonnelRelative : relative + PersonnelRelative --> Personnel : personnel + class Personnel { unsignedBigInt id - timestamps() static - softDeletes() static + profile(): Profile } - class Identity { + class Profile { unsignedBigInt id - timestamps() static - softDeletes() static + identity(): Personnel + } + class PersonnelRelative { + unsignedBigInt personnel_id + unsignedBigInt relative_id + unsignedSmallInt status } ``` @@ -248,17 +329,22 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `code` | `string`, `nullable` | `unique` | - | -| `name` | `string` | | - | -| `email` | `string`, `nullable` | `unique` | - | +| `user_id` | `unsignedBigInt`, `nullable` | `foreign` | - | +| `code` | `varchar`, `nullable` | `unique` | - | +| `name` | `varchar` | | - | +| `alias` | `varchar`, `nullable` | | - | +| `email` | `varchar`, `nullable` | `unique` | - | | `phone_number` | `varchar(20)`, `nullable` | | - | -| `photo_path` | `string`, `nullable` | | - | +| `gender` | `char(1)` | | - | | `summary` | `text`, `nullable` | | - | **Model Attributes** - `timestamps` - `softDeletes` +**Relation Properties** +- `user_id` : reference `users` + ### `personnel_relatives` (morphPivot) | Field | Attribute | Key | Description | @@ -272,54 +358,80 @@ classDiagram - `personnel_id` : reference `personnels` - `relative_id` : reference `personnels` -### `identities` +**Personnel Relative Statuses** +- Child +- Spouse +- Sibling +- SiblingsChild +- Parent +- ParentsSibling +- Grandparent +- Grandchild +- Cousin + +### `profiles` | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `user_id` | `unsignedBigInt`, `nullable` | `foreign` | - | | `identity` | `morphs`, `nullable` | | - | | `nik` | `char(16)`, `nullable` | | - | | `prefix` | `varchar(10)`, `nullable` | | - | -| `fullname` | `string` | | - | | `suffix` | `varchar(10)`, `nullable` | | - | -| `gender` | `char(1)` | | - | | `birth_date` | `date`, `nullable` | | - | | `birth_place_code` | `char(4)`, `nullable` | | - | | `education` | `varchar(3)`, `nullable` | | - | | `religion` | `unsignedTinyInt`, `nullable` | | - | -| `phone_number` | `varchar(20)`, `nullable` | | - | -| `photo_path` | `string`, `nullable` | | - | -| `summary` | `text`, `nullable` | | - | +| `tax_status` | `unsignedSmallInt`, `nullable` | | - | +| `tax_id` | `varchar(16)`, `nullable` | | - | **Model Attributes** - `timestamps` - `softDeletes` - -**Relation Properties** -- `user_id` : reference `users` +- +**Profile Educations** +- Uneducated +- SD +- SMP +- SMA +- D1 +- D2 +- D3 +- S1 +- S2 +- S3 + +**Profile Religions** +- Other +- Islam +- Christian +- Catholic +- Hinduism +- Buddhism +- Confucianism ## Addresses +Another similarity either individual and business is they have address, even in some cases they might has multiple addresses. For instance that an individual might have multiple addresses when they're live in different location than their identity (NIK). In that case a business might want to have that information where its live also the location from its identity (NIK). + +It also possible that either business or individual put their address information for different purposes, e.g shipment address and billing address. Your mileage might vary. + ```mermaid classDiagram - Company "1" ..> "*" Address : addresses + Business "1" ..> "*" Address : addresses Personnel "1" ..> "*" Address : addresses + class Address { unsignedBigInt id morph owner - timestamps() static - softDeletes() static } - class Company { + class Business { unsignedBigInt id - timestamps() static - softDeletes() static + addresses(): Address[] } class Personnel { unsignedBigInt id - timestamps() static - softDeletes() static + addresses(): Address[] } ``` @@ -330,7 +442,7 @@ classDiagram | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `owner` | `morphs`, `nullable` | | - | | `is_resident` | `boolean` | | - | -| `line` | `string` | | - | +| `line` | `varchar` | | - | | `rt` | `char(3)`, `nullable` | | - | | `rw` | `char(3)`, `nullable` | | - | | `village_code` | `char(10)`, `nullable` | | - | @@ -346,29 +458,27 @@ classDiagram ## Uploaded Files +Last but not least, either company or individual might also be able to have certain files or documents to runs the business. This functionality can serve any kind of purposes and its might be vary depending of the business. For instance, as simple as both might want to upload their photo e.g an profile avatar or company logo. Meanwhile there's business that want to serve their invoice, quotation or any other document. + +It also possible to store an `.xlsx` or `.csv` file that would be used for data imports, so the import process can be done in the background and once the process is finished, the system can remove the stored files and keep it neat and clean as the business goes. + ```mermaid classDiagram - Company "1" ..> "*" FileAttached : uploadedFiles - Personnel "1" ..> "*" FileAttached : uploadedFiles - class File { + Business "1" ..> "*" FileAttached : files + Personnel "1" ..> "*" FileAttached : files + class FileUpload { unsignedBigInt id - timestamps() static - softDeletes() static } - FileAttached "1" <--> "1" File : attachments + FileAttached "1" <--> "1" FileUpload : attachments class FileAttached { unsignedBigInt id morph attached_to } - class Company { + class Business { unsignedBigInt id - timestamps() static - softDeletes() static } class Personnel { unsignedBigInt id - timestamps() static - softDeletes() static } ``` @@ -378,11 +488,11 @@ classDiagram | --- | --- | :---: | --- | | `id` | `uuid` | `primary` | - | | `revision_id` | `uuid`, `nullable` | `foreign` | Indicates that this row is actually a revision of parent `id` | -| `title` | `string`, `nullable` | | - | -| `name` | `string` | | - | -| `path` | `string`, `nullable` | | - | -| `drive` | `string`, `nullable` | | - | -| `summary` | `string`, `nullable` | | - | +| `title` | `varchar`, `nullable` | | - | +| `name` | `varchar` | | - | +| `path` | `varchar`, `nullable` | | - | +| `drive` | `varchar`, `nullable` | | - | +| `summary` | `varchar`, `nullable` | | - | **Model Attributes** - `timestamps` @@ -391,7 +501,7 @@ classDiagram **Relation Properties** - `revision_id` : reference `file_uploads` -### `file_attached` (pivot) +### `file_attached` (morphPivot) | Field | Attribute | Key | Description | | --- | --- | :---: | --- | diff --git a/src/Models/Company.php b/src/Models/Company.php index b1614ff..0e85fd7 100644 --- a/src/Models/Company.php +++ b/src/Models/Company.php @@ -5,8 +5,6 @@ use Creasi\Base\Models\Enums\CompanyRelativeType; /** - * @property null|bool $is_internal - * @property-read null|Personnel $owner * @property-read \Illuminate\Database\Eloquent\Collection $employees * @property-read \Illuminate\Database\Eloquent\Collection $individualRelatives * @property-read \Illuminate\Database\Eloquent\Collection $companyRelatives diff --git a/src/Models/Enums/Religion.php b/src/Models/Enums/Religion.php index 130c7f4..32b6dd3 100644 --- a/src/Models/Enums/Religion.php +++ b/src/Models/Enums/Religion.php @@ -6,6 +6,7 @@ enum Religion: int { use KeyableEnum; + case Other = 0; case Islam = 1; case Christian = 2; case Catholic = 3; From 3a17383b54b5d4263810a9db1fb5816884950e4f Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 20 Jul 2023 06:10:51 +0700 Subject: [PATCH 40/71] feat: add interface and trait in regards of taxes and credential info Signed-off-by: Fery Wardiyanto --- resources/lang/en/base.php | 15 ++++++++++++ resources/lang/id/base.php | 25 +++++++++++++++---- src/Contracts/HasCredential.php | 17 +++++++++++++ src/Contracts/HasTaxInfo.php | 14 +++++++++++ src/Models/Concerns/WithCredential.php | 33 ++++++++++++++++++++++++++ src/Models/Concerns/WithTaxInfo.php | 24 +++++++++++++++++++ src/Models/Enums/TaxStatus.php | 21 ++++++++++++++++ 7 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 src/Contracts/HasCredential.php create mode 100644 src/Contracts/HasTaxInfo.php create mode 100644 src/Models/Concerns/WithCredential.php create mode 100644 src/Models/Concerns/WithTaxInfo.php create mode 100644 src/Models/Enums/TaxStatus.php diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 61aef89..212f057 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -29,6 +29,21 @@ 'probation' => 'Probation', ], + 'tax-status' => [ + 'tk0' => 'TK/0', + 'tk1' => 'TK/1', + 'tk2' => 'TK/2', + 'tk3' => 'TK/3', + 'k0' => 'K/0', + 'k1' => 'K/1', + 'k2' => 'K/2', + 'k3' => 'K/3', + 'ki0' => 'K/I/0', + 'ki1' => 'K/I/1', + 'ki2' => 'K/I/2', + 'ki3' => 'K/I/3', + ], + 'personnel-relative-status' => [ 'child' => 'Child', 'spouse' => 'Spouse', diff --git a/resources/lang/id/base.php b/resources/lang/id/base.php index 7ee05f1..8e05813 100644 --- a/resources/lang/id/base.php +++ b/resources/lang/id/base.php @@ -15,7 +15,7 @@ 'confucianism' => 'Konghuchu', ], - 'employment_type' => [ + 'employment-type' => [ 'unemployeed' => 'Tidak Dipekerjakan', 'fulltime' => 'Full-time', 'parttime' => 'Part-time', @@ -23,19 +23,34 @@ 'freelance' => 'Freelance', ], - 'employment_status' => [ + 'employment-status' => [ 'permanent' => 'Permanent', 'contract' => 'Contract', 'probation' => 'Probation', ], - 'personnel_relative' => [ + 'tax-status' => [ + 'tk0' => 'TK/0', + 'tk1' => 'TK/1', + 'tk2' => 'TK/2', + 'tk3' => 'TK/3', + 'k0' => 'K/0', + 'k1' => 'K/1', + 'k2' => 'K/2', + 'k3' => 'K/3', + 'ki0' => 'K/I/0', + 'ki1' => 'K/I/1', + 'ki2' => 'K/I/2', + 'ki3' => 'K/I/3', + ], + + 'personnel-relative' => [ 'child' => 'Anak', 'spouse' => 'Pasangan', 'sibling' => 'Saudara Kandung', - 'siblings_child' => 'Keponakan', + 'siblings-child' => 'Keponakan', 'parent' => 'Orang Tua', - 'parents_sibling' => 'Saudara Kandung dari Orang Tua', + 'parents-sibling' => 'Saudara Kandung dari Orang Tua', 'grandparent' => 'Kakek / Nenek', 'grandchild' => 'Cucu', 'cousin' => 'Sepupu', diff --git a/src/Contracts/HasCredential.php b/src/Contracts/HasCredential.php new file mode 100644 index 0000000..c8c2d0f --- /dev/null +++ b/src/Contracts/HasCredential.php @@ -0,0 +1,17 @@ +mergeCasts([ + 'user_id' => 'int', + ]); + + $this->mergeFillable([ + 'user_id', + ]); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|User + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/src/Models/Concerns/WithTaxInfo.php b/src/Models/Concerns/WithTaxInfo.php new file mode 100644 index 0000000..b5a9e3f --- /dev/null +++ b/src/Models/Concerns/WithTaxInfo.php @@ -0,0 +1,24 @@ +mergeCasts([ + 'tax_status' => 'int', + ]); + + $this->mergeFillable([ + 'tax_status', + 'tax_id', + ]); + } +} diff --git a/src/Models/Enums/TaxStatus.php b/src/Models/Enums/TaxStatus.php new file mode 100644 index 0000000..6372de6 --- /dev/null +++ b/src/Models/Enums/TaxStatus.php @@ -0,0 +1,21 @@ + Date: Thu, 20 Jul 2023 11:13:27 +0700 Subject: [PATCH 41/71] refactor!: full rewrite, that's it Signed-off-by: Fery Wardiyanto --- composer.lock | 19 ++- database/README.md | 107 ++++++++-------- ...CompanyFactory.php => BusinessFactory.php} | 11 +- database/factories/IdentityFactory.php | 64 ---------- database/factories/PersonnelFactory.php | 22 +++- database/factories/ProfileFactory.php | 36 ++++++ .../2014_10_12_000000_create_users_table.php | 2 +- ...ate_identities_address_and_files_table.php | 17 ++- ..._create_companies_and_personnels_table.php | 40 +++--- routes/base.php | 4 +- src/Contracts/Employee.php | 8 +- src/Contracts/Employer.php | 17 +++ src/Contracts/HasIdentity.php | 5 +- src/Contracts/HasProfile.php | 14 +++ src/Contracts/Identity.php | 16 --- src/Http/Controllers/CompanyController.php | 16 +-- src/Http/Requests/Company/StoreRequest.php | 1 - src/Http/Requests/Company/UpdateRequest.php | 1 - src/Http/Requests/Employee/StoreRequest.php | 6 +- src/Http/Requests/Employee/UpdateRequest.php | 3 +- src/Http/Requests/FileUpload/StoreRequest.php | 2 - .../Requests/FileUpload/UpdateRequest.php | 1 - .../Requests/Stakeholder/StoreRequest.php | 1 - .../Requests/Stakeholder/UpdateRequest.php | 1 - src/Models/Business.php | 71 +++++++++++ ...mpanyRelative.php => BusinessRelative.php} | 15 +-- src/Models/Company.php | 75 ------------ src/Models/Concerns/AsEmployee.php | 23 ++++ src/Models/Concerns/AsEmployer.php | 23 ++++ src/Models/Concerns/WithAvatar.php | 3 +- src/Models/Concerns/WithIdentity.php | 21 ---- src/Models/Concerns/WithProfile.php | 21 ++++ src/Models/Employment.php | 5 +- src/Models/Entity.php | 8 +- ...ativeType.php => BusinessRelativeType.php} | 2 +- src/Models/FileUpload.php | 4 +- src/Models/Personnel.php | 34 ++++-- src/Models/PersonnelRelative.php | 1 - src/Models/{Identity.php => Profile.php} | 44 +++---- src/Models/User.php | 10 +- src/Repository.php | 12 +- src/ServiceProvider.php | 5 + .../{CompanyTest.php => BusinessTest.php} | 26 ++-- tests/Http/Business/CustomerTest.php | 6 +- tests/Http/Business/SupplierTest.php | 6 +- tests/Http/Business/VendorTest.php | 6 +- tests/Http/StakeholderTestCase.php | 16 +-- tests/Models/BusinessTest.php | 114 ++++++++++++++++++ tests/Models/CompanyTest.php | 114 ------------------ tests/Models/FileUploadTest.php | 6 +- tests/Models/PersonnelTest.php | 2 + .../{IdentityTest.php => ProfileTest.php} | 18 ++- tests/Models/UserTest.php | 6 +- 53 files changed, 580 insertions(+), 531 deletions(-) rename database/factories/{CompanyFactory.php => BusinessFactory.php} (71%) delete mode 100644 database/factories/IdentityFactory.php create mode 100644 database/factories/ProfileFactory.php create mode 100644 src/Contracts/Employer.php create mode 100644 src/Contracts/HasProfile.php delete mode 100644 src/Contracts/Identity.php create mode 100644 src/Models/Business.php rename src/Models/{CompanyRelative.php => BusinessRelative.php} (55%) delete mode 100644 src/Models/Company.php create mode 100644 src/Models/Concerns/AsEmployee.php create mode 100644 src/Models/Concerns/AsEmployer.php delete mode 100644 src/Models/Concerns/WithIdentity.php create mode 100644 src/Models/Concerns/WithProfile.php rename src/Models/Enums/{CompanyRelativeType.php => BusinessRelativeType.php} (96%) rename src/Models/{Identity.php => Profile.php} (52%) rename tests/Http/Business/{CompanyTest.php => BusinessTest.php} (84%) create mode 100644 tests/Models/BusinessTest.php delete mode 100644 tests/Models/CompanyTest.php rename tests/Models/{IdentityTest.php => ProfileTest.php} (62%) diff --git a/composer.lock b/composer.lock index dbde564..da9fcf4 100644 --- a/composer.lock +++ b/composer.lock @@ -63,31 +63,28 @@ }, { "name": "creasi/laravel-nusa", - "version": "v0.1.0", + "version": "v0.1.1", "source": { "type": "git", "url": "https://github.com/creasico/laravel-nusa.git", - "reference": "7004d32213f885ba17fe981ea1de4e7fdea99cf4" + "reference": "c83be1333e57fdfa0a12e79f7f1f6568c7edb01b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/creasico/laravel-nusa/zipball/7004d32213f885ba17fe981ea1de4e7fdea99cf4", - "reference": "7004d32213f885ba17fe981ea1de4e7fdea99cf4", + "url": "https://api.github.com/repos/creasico/laravel-nusa/zipball/c83be1333e57fdfa0a12e79f7f1f6568c7edb01b", + "reference": "c83be1333e57fdfa0a12e79f7f1f6568c7edb01b", "shasum": "" }, "require": { "ext-sqlite3": "*", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/support": "^10.0", + "laravel/framework": "^9.0|^10.0", "php": "^8.1" }, "require-dev": { "composer-runtime-api": "*", "laravel/pint": "^1.1", - "mockery/mockery": "^1.5", - "orchestra/testbench": "^8.5", - "phpunit/phpunit": "^10.1" + "nunomaduro/collision": "^7.4", + "orchestra/testbench": "^8.5" }, "type": "library", "extra": { @@ -128,7 +125,7 @@ "issues": "https://github.com/creasico/laravel-nusa/issues", "source": "https://github.com/creasico/laravel-nusa" }, - "time": "2023-07-16T20:14:52+00:00" + "time": "2023-07-20T02:04:36+00:00" }, { "name": "dflydev/dot-access-data", diff --git a/database/README.md b/database/README.md index e19d3ad..7272efb 100644 --- a/database/README.md +++ b/database/README.md @@ -5,13 +5,13 @@ erDiagram businesses ||..o{ business_relatives : stakeholders businesses { unsignedBigInt id PK - varchar name - varchar alias + varchar(150) name + varchar(50) alias varchar email UK - varchar(20) phone_number + varchar(20) phone unsignedSmallInt tax_status varchar(16) tax_id - text summary + varchar(200) summary } business_relatives ||--|| businesses : stakeholder @@ -20,39 +20,37 @@ erDiagram unsignedBigInt id PK unsignedBigInt business_id FK morph stakeholder - varchar code UK - unsignedSmallInt type boolean is_internal - text remark + varchar(100) code UK + unsignedSmallInt type } businesses ||--o{ employments : employees employments }o..|| personnels : employees employments { unsignedBigInt id PK - unsignedBigInt business_id FK + unsignedBigInt employer_id FK unsignedBigInt employee_id FK boolean is_primary - varchar code UK + varchar(100) code UK unsignedSmallInt type unsignedSmallInt status date start_date date finish_date - text remark } personnels { unsignedBigInt id PK unsignedBigInt user_id FK - varchar name - varchar alias + varchar(150) name + varchar(50) alias varchar email UK - varchar(20) phone_number + varchar(20) phone char(1) gender - text summary + varchar(200) summary } - personnels ||..|| profiles : profiles + personnels ||..|| profiles : identity profiles { unsignedBigInt id PK morphs identity @@ -73,14 +71,13 @@ erDiagram unsignedBigInt personnel_id FK unsignedBigInt relative_id FK unsignedSmallInt status - text remark } addresses }o..|| businesses : addresses addresses }o..|| personnels : addresses addresses { unsignedBigInt id PK - morph owner + morph addressable boolean is_resident varchar line char(3) rt @@ -90,13 +87,13 @@ erDiagram char(4) regency_code char(2) province_code char(5) postal_code - text summary + varchar summary } users ||..|| personnels : credential users { unsignedBigInt id PK - varchar name + varchar(150) name varchar email varchar password } @@ -109,7 +106,7 @@ erDiagram varchar name varchar path varchar drive - text summary + varchar summary } businesses }o..|| file_attached : files personnels }o..|| file_attached : files @@ -133,29 +130,32 @@ Despite those similarities they must have some differences, including : classDiagram Entity <|-- Business Entity <|-- Personnel - Personnel "1" ..> "1" User + Personnel "1" ..> "1" User : credential class Entity { + varchar~150~ name + varchar~50~ alias varchar email - varchar phone_number - varchar name - varchar alias - unsignedSmallInt tax_status + varchar~20~ phone + varchar~200~ summary } class Business { unsignedBigInt id unsignedSmallInt tax_status - varchar~20~ tax_id + varchar~16~ tax_id } class Personnel { unsignedBigInt id unsignedBigInt user_id char~1~ gender + credential() User } class User { unsignedBigInt id - varchar name + varchar~150~ name + varchar email varchar password + identity(): Personnel } ``` @@ -203,6 +203,8 @@ classDiagram } class BusinessRelative { unsignedBigInt id + boolean is_internal + varchar~100~ code stakeholders() Entity[] } ``` @@ -212,10 +214,10 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `name` | `varchar` | | - | -| `alias` | `varchar`, `nullable` | | - | +| `name` | `varchar(150)` | | - | +| `alias` | `varchar(50)`, `nullable` | | - | | `email` | `varchar`, `nullable` | `unique` | - | -| `phone_number` | `varchar(20)`, `nullable` | | - | +| `phone` | `varchar(20)`, `nullable` | | - | | `tax_status` | `unsignedSmallInt`, `nullable` | | - | | `tax_id` | `varchar(16)`, `nullable` | | - | | `summary` | `text`, `nullable` | | - | @@ -231,10 +233,9 @@ classDiagram | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `business_id` | `unsignedBigInt` | `foreign` | - | | `stakeholder` | `morphs`, `nullable` | | - | -| `code` | `varchar`, `nullable` | `unique` | - | -| `type` | `unsignedSmallInt`, `nullable` | | - | | `is_internal` | `boolean`, `default: false` | | - | -| `remark` | `text`, `nullable` | | - | +| `code` | `varchar(100)`, `nullable` | `unique` | - | +| `type` | `unsignedSmallInt`, `nullable` | | - | **Relation Properties** - `business_id` : reference `businesses` @@ -250,18 +251,22 @@ classDiagram class Business { unsignedBigInt id - employees(): Personnel[] + employees() Personnel[] } class Personnel { unsignedBigInt id - employer(): Business + employer() Business } class Employments { unsignedBigInt id unsignedBigInt business_id unsignedBigInt employee_id - varchar code boolean is_primary + varchar~100~ code + unsignedSmallInt type + unsignedSmallInt status + date start_date + date finish_date stakeholders() Entity[] } ``` @@ -271,18 +276,17 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `business_id` | `unsignedBigInt` | `foreign` | - | +| `employer_id` | `unsignedBigInt` | `foreign` | - | | `employee_id` | `unsignedBigInt` | `foreign` | - | | `is_primary` | `boolean`, `default: false` | | - | -| `code` | `varchar`, `nullable` | `unique` | - | +| `code` | `varchar(100)`, `nullable` | `unique` | - | | `type` | `unsignedSmallInt`, `nullable` | | - | | `status` | `unsignedSmallInt`, `nullable` | | - | | `start_date` | `date`, `nullable` | | - | | `finish_date` | `date`, `nullable` | | - | -| `remark` | `text`, `nullable` | | - | **Relation Properties** -- `business_id` : reference `businesses` +- `employer_id` : reference `businesses` - `employee_id` : reference `personnels` **Employment Types** @@ -305,17 +309,17 @@ Meanwhile, a business might want to be able to also communicate with their perso ```mermaid classDiagram - Personnel .. Profile : profile + Personnel .. Profile : identity Personnel ..> PersonnelRelative : relative PersonnelRelative --> Personnel : personnel class Personnel { unsignedBigInt id - profile(): Profile + profile() Profile } class Profile { unsignedBigInt id - identity(): Personnel + identity() Personnel } class PersonnelRelative { unsignedBigInt personnel_id @@ -330,11 +334,10 @@ classDiagram | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | | `user_id` | `unsignedBigInt`, `nullable` | `foreign` | - | -| `code` | `varchar`, `nullable` | `unique` | - | -| `name` | `varchar` | | - | -| `alias` | `varchar`, `nullable` | | - | +| `name` | `varchar(150)` | | - | +| `alias` | `varchar(50)`, `nullable` | | - | | `email` | `varchar`, `nullable` | `unique` | - | -| `phone_number` | `varchar(20)`, `nullable` | | - | +| `phone` | `varchar(20)`, `nullable` | | - | | `gender` | `char(1)` | | - | | `summary` | `text`, `nullable` | | - | @@ -423,15 +426,15 @@ classDiagram class Address { unsignedBigInt id - morph owner + addressable() Entity } class Business { unsignedBigInt id - addresses(): Address[] + addresses() Address[] } class Personnel { unsignedBigInt id - addresses(): Address[] + addresses() Address[] } ``` @@ -440,7 +443,7 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `owner` | `morphs`, `nullable` | | - | +| `addressable` | `morphs`, `nullable` | | - | | `is_resident` | `boolean` | | - | | `line` | `varchar` | | - | | `rt` | `char(3)`, `nullable` | | - | @@ -450,7 +453,7 @@ classDiagram | `regency_code` | `char(4)`, `nullable` | | - | | `province_code` | `char(2)`, `nullable` | | - | | `postal_code` | `char(5)`, `nullable` | | - | -| `summary` | `text`, `nullable` | | - | +| `summary` | `varchar`, `nullable` | | - | **Model Attributes** - `timestamps` diff --git a/database/factories/CompanyFactory.php b/database/factories/BusinessFactory.php similarity index 71% rename from database/factories/CompanyFactory.php rename to database/factories/BusinessFactory.php index 80d6fd4..9bd83a5 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/BusinessFactory.php @@ -3,16 +3,16 @@ namespace Database\Factories; use Creasi\Base\Models\Address; -use Creasi\Base\Models\Company; +use Creasi\Base\Models\Business; use Creasi\Base\Models\FileUpload; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory + * @extends Factory */ -class CompanyFactory extends Factory +class BusinessFactory extends Factory { - protected $model = Company::class; + protected $model = Business::class; /** * @return array @@ -20,10 +20,9 @@ class CompanyFactory extends Factory public function definition(): array { return [ - 'code' => $this->faker->numerify('######'), 'name' => $this->faker->company(), 'email' => $this->faker->safeEmail(), - 'phone_number' => '08'.$this->faker->numerify('##########'), + 'phone' => '08'.$this->faker->numerify('##########'), 'summary' => $this->faker->sentence(4), ]; } diff --git a/database/factories/IdentityFactory.php b/database/factories/IdentityFactory.php deleted file mode 100644 index 6378c91..0000000 --- a/database/factories/IdentityFactory.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -class IdentityFactory extends Factory -{ - protected $model = Identity::class; - - /** - * @return array - */ - public function definition(): array - { - /** @var Gender */ - $gender = $this->faker->randomElement(Gender::cases()); - - $birthPlace = Regency::query()->whereHas('province', function (Builder $query) { - $query->where('code', 33); - })->inRandomOrder()->first(); - - return [ - 'nik' => $this->faker->nik($gender->toFaker(), $birthDate = $this->faker->dateTime()), - 'prefix' => null, - 'fullname' => $this->faker->name($gender->toFaker()), - 'suffix' => null, - 'birth_date' => $birthDate->format('Y-m-d'), - 'birth_place_code' => $birthPlace->code, - 'education' => $this->faker->randomElement(Education::cases()), - 'gender' => $gender, - 'religion' => $this->faker->randomElement(Religion::cases()), - 'summary' => $this->faker->sentence(4), - ]; - } - - public function withoutUser(): static - { - return $this->state([ - 'user_id' => null, - ]); - } - - public function withGender(Gender $gender = null, mixed $start = '-30 years', mixed $until = 'now'): static - { - $gender = $gender ?: $this->faker->randomElement(Gender::cases()); - - return $this->state(fn () => [ - 'nik' => $this->faker->nik($gender->toFaker(), $birthDate = $this->faker->dateTimeBetween($start, $until)), - 'fullname' => $this->faker->name($gender->toFaker()), - 'gender' => $gender, - 'birth_date' => $birthDate->format('Y-m-d'), - ]); - } -} diff --git a/database/factories/PersonnelFactory.php b/database/factories/PersonnelFactory.php index 82d00f2..c9ea210 100644 --- a/database/factories/PersonnelFactory.php +++ b/database/factories/PersonnelFactory.php @@ -5,8 +5,8 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\FileUpload; -use Creasi\Base\Models\Identity; use Creasi\Base\Models\Personnel; +use Creasi\Base\Models\Profile; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -21,18 +21,28 @@ class PersonnelFactory extends Factory */ public function definition(): array { + /** @var Gender */ + $gender = $this->faker->randomElement(Gender::cases()); + return [ - 'code' => $this->faker->numerify('################'), - 'name' => $this->faker->firstName(), + 'name' => $this->faker->firstName($gender->toFaker()), 'email' => $this->faker->safeEmail(), - 'phone_number' => '08'.$this->faker->numerify('##########'), + 'phone' => '08'.$this->faker->numerify('##########'), + 'gender' => $gender, 'summary' => $this->faker->sentence(4), ]; } - public function withIdentity(Gender $gender = null): static + public function withoutUser(): static + { + return $this->state([ + 'user_id' => null, + ]); + } + + public function withProfile(Gender $gender = null): static { - return $this->has(Identity::factory()->withGender($gender), 'identity')->state(fn () => [ + return $this->has(Profile::factory(), 'profile')->state(fn () => [ 'name' => $this->faker->firstName($gender?->toFaker()), ]); } diff --git a/database/factories/ProfileFactory.php b/database/factories/ProfileFactory.php new file mode 100644 index 0000000..8980a03 --- /dev/null +++ b/database/factories/ProfileFactory.php @@ -0,0 +1,36 @@ + + */ +class ProfileFactory extends Factory +{ + protected $model = Profile::class; + + /** + * @return array + */ + public function definition(): array + { + $birthPlace = Regency::query()->whereHas('province', function (Builder $query) { + $query->where('code', 33); + })->inRandomOrder()->first(); + + return [ + 'nik' => $this->faker->nik(null, $birthDate = $this->faker->dateTime()), + 'birth_date' => $birthDate->format('Y-m-d'), + 'birth_place_code' => $birthPlace->code, + 'education' => $this->faker->randomElement(Education::cases()), + 'religion' => $this->faker->randomElement(Religion::cases()), + ]; + } +} diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 1f97419..7f94887 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -13,7 +13,7 @@ public function up(): void { Schema::create('users', function (Blueprint $table) { $table->id(); - $table->string('name'); + $table->string('name', 150)->index(); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index 750b131..8060bf7 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -11,22 +11,19 @@ */ public function up(): void { - Schema::create('identities', function (Blueprint $table) { + Schema::create('profiles', function (Blueprint $table) { $table->id(); - $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); $table->nullableMorphs('identity'); $table->char('nik', 16)->unique()->nullable(); - $table->string('prefix', 10)->nullable(); - $table->string('fullname', 100); - $table->string('suffix', 10)->nullable(); - $table->char('gender', 1); + $table->string('prefix', 20)->nullable(); + $table->string('suffix', 20)->nullable(); $table->date('birth_date')->nullable(); $table->char('birth_place_code', 4)->nullable(); $table->string('education', 3)->nullable(); $table->unsignedTinyInteger('religion')->nullable(); - $table->string('phone_number', 20)->nullable(); - $table->text('summary')->nullable(); + $table->unsignedSmallInteger('tax_status')->nullable(); + $table->string('tax_id', 16)->nullable(); $table->timestamps(); $table->softDeletes(); @@ -34,7 +31,7 @@ public function up(): void Schema::create('addresses', function (Blueprint $table) { $table->id(); - $table->nullableMorphs('owner'); + $table->nullableMorphs('addressable'); $table->boolean('is_resident'); $table->string('line'); @@ -84,6 +81,6 @@ public function down(): void Schema::dropIfExists('file_attached'); Schema::dropIfExists('file_uploads'); Schema::dropIfExists('addresses'); - Schema::dropIfExists('identities'); + Schema::dropIfExists('profiles'); } }; diff --git a/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php b/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php index c6a21fd..b2d06e9 100644 --- a/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php +++ b/database/migrations/2022_05_10_000002_create_companies_and_personnels_table.php @@ -11,37 +11,41 @@ */ public function up(): void { - Schema::create('companies', function (Blueprint $table) { + Schema::create('businesses', function (Blueprint $table) { $table->id(); - $table->string('code')->unique()->nullable(); - $table->string('name'); + $table->string('name', 150); + $table->string('alias', 50)->nullable(); $table->string('email')->unique()->nullable(); - $table->string('phone_number', 20)->nullable(); - $table->text('summary')->nullable(); + $table->string('phone', 20)->nullable(); + $table->unsignedSmallInteger('tax_status')->nullable(); + $table->string('tax_id', 16)->nullable(); + $table->string('summary', 200)->nullable(); $table->timestamps(); $table->softDeletes(); }); - Schema::create('company_relatives', function (Blueprint $table) { + Schema::create('business_relatives', function (Blueprint $table) { $table->id(); - $table->foreignId('company_id')->constrained()->cascadeOnDelete(); + $table->foreignId('business_id')->constrained()->cascadeOnDelete(); $table->nullableMorphs('stakeholder'); - $table->unsignedSmallInteger('type')->nullable(); $table->boolean('is_internal')->default(false); - $table->text('remark')->nullable(); + $table->string('code')->unique()->nullable(); + $table->unsignedSmallInteger('type')->nullable(); }); Schema::create('personnels', function (Blueprint $table) { $table->id(); + $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); - $table->string('code')->unique()->nullable(); - $table->string('name'); + $table->string('name', 150); + $table->string('alias', 50)->nullable(); $table->string('email')->unique()->nullable(); - $table->string('phone_number', 20)->nullable(); - $table->text('summary')->nullable(); + $table->string('phone', 20)->nullable(); + $table->char('gender', 1); + $table->string('summary', 200)->nullable(); $table->timestamps(); $table->softDeletes(); @@ -52,19 +56,19 @@ public function up(): void $table->foreignId('relative_id')->constrained('personnels')->cascadeOnDelete(); $table->unsignedSmallInteger('status')->nullable(); - $table->text('remark')->nullable(); }); Schema::create('employments', function (Blueprint $table) { - $table->foreignId('company_id')->constrained()->cascadeOnDelete(); + $table->id(); + $table->foreignId('employer_id')->constrained('businesses')->cascadeOnDelete(); $table->foreignId('employee_id')->constrained('personnels')->cascadeOnDelete(); $table->boolean('is_primary')->default(false); + $table->string('code')->unique()->nullable(); $table->unsignedSmallInteger('type')->nullable(); $table->unsignedSmallInteger('status')->nullable(); $table->date('start_date')->nullable(); $table->date('finish_date')->nullable(); - $table->text('remark')->nullable(); }); } @@ -76,7 +80,7 @@ public function down(): void Schema::dropIfExists('employments'); Schema::dropIfExists('personnel_relatives'); Schema::dropIfExists('personnels'); - Schema::dropIfExists('company_relatives'); - Schema::dropIfExists('companies'); + Schema::dropIfExists('business_relatives'); + Schema::dropIfExists('business'); } }; diff --git a/routes/base.php b/routes/base.php index e5f467c..8861cc7 100644 --- a/routes/base.php +++ b/routes/base.php @@ -1,7 +1,7 @@ isInternal()) { continue; } diff --git a/src/Contracts/Employee.php b/src/Contracts/Employee.php index a00d90d..462aa24 100644 --- a/src/Contracts/Employee.php +++ b/src/Contracts/Employee.php @@ -3,9 +3,15 @@ namespace Creasi\Base\Contracts; /** + * @property-read \Creasi\Base\Models\Employment $employment + * @property-read \Illuminate\Database\Eloquent\Collection $employees + * * @mixin \Illuminate\Database\Eloquent\Model */ interface Employee { - // + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Employer + */ + public function employers(); } diff --git a/src/Contracts/Employer.php b/src/Contracts/Employer.php new file mode 100644 index 0000000..56b2bcb --- /dev/null +++ b/src/Contracts/Employer.php @@ -0,0 +1,17 @@ + $employees + * + * @mixin \Illuminate\Database\Eloquent\Model + */ +interface Employer +{ + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Employee + */ + public function employees(); +} diff --git a/src/Contracts/HasIdentity.php b/src/Contracts/HasIdentity.php index 79a9b30..739dd77 100644 --- a/src/Contracts/HasIdentity.php +++ b/src/Contracts/HasIdentity.php @@ -3,12 +3,11 @@ namespace Creasi\Base\Contracts; /** + * @property-read null|\Creasi\Base\Models\Personnel $identity + * * @mixin \Illuminate\Database\Eloquent\Model */ interface HasIdentity { - /** - * @return \Illuminate\Database\Eloquent\Relations\MorphOne|Identity - */ public function identity(); } diff --git a/src/Contracts/HasProfile.php b/src/Contracts/HasProfile.php new file mode 100644 index 0000000..d8626fd --- /dev/null +++ b/src/Contracts/HasProfile.php @@ -0,0 +1,14 @@ +latest(); + $items = Business::query()->latest(); return new CompanyCollection($items->paginate()); } @@ -31,8 +31,8 @@ public function index(Request $request) */ public function store(StoreRequest $request) { - /** @var Company $item */ - $item = Company::create($request->validated()); + /** @var Business $item */ + $item = Business::create($request->validated()); return $this->show($item, $request)->setStatusCode(201); } @@ -40,7 +40,7 @@ public function store(StoreRequest $request) /** * @return CompanyResource */ - public function show(Company $company, Request $request) + public function show(Business $company, Request $request) { return CompanyResource::make($company)->toResponse($request); } @@ -48,7 +48,7 @@ public function show(Company $company, Request $request) /** * @return CompanyResource */ - public function update(UpdateRequest $request, Company $company) + public function update(UpdateRequest $request, Business $company) { $company->update($request->validated()); @@ -58,7 +58,7 @@ public function update(UpdateRequest $request, Company $company) /** * @return \Illuminate\Http\Response */ - public function destroy(Company $company) + public function destroy(Business $company) { $company->delete(); diff --git a/src/Http/Requests/Company/StoreRequest.php b/src/Http/Requests/Company/StoreRequest.php index 24d4534..fc4ddb2 100644 --- a/src/Http/Requests/Company/StoreRequest.php +++ b/src/Http/Requests/Company/StoreRequest.php @@ -12,7 +12,6 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'code' => ['required', 'string'], 'name' => ['required', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], diff --git a/src/Http/Requests/Company/UpdateRequest.php b/src/Http/Requests/Company/UpdateRequest.php index b8b1371..98ac2ea 100644 --- a/src/Http/Requests/Company/UpdateRequest.php +++ b/src/Http/Requests/Company/UpdateRequest.php @@ -12,7 +12,6 @@ class UpdateRequest extends FormRequest public function rules(): array { return [ - 'code' => ['required', 'string'], 'name' => ['required', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], diff --git a/src/Http/Requests/Employee/StoreRequest.php b/src/Http/Requests/Employee/StoreRequest.php index 595d141..715d0fc 100644 --- a/src/Http/Requests/Employee/StoreRequest.php +++ b/src/Http/Requests/Employee/StoreRequest.php @@ -3,6 +3,8 @@ namespace Creasi\Base\Http\Requests\Employee; use Creasi\Base\Http\Requests\FormRequest; +use Creasi\Base\Models\Enums\Gender; +use Illuminate\Validation\Rule; class StoreRequest extends FormRequest { @@ -12,10 +14,10 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'code' => ['required', 'string'], 'name' => ['required', 'string'], 'email' => ['required', 'email'], - 'phone_number' => ['nullable', 'numeric'], + 'phone' => ['nullable', 'numeric'], + 'gender' => ['required', Rule::enum(Gender::class)], 'summary' => ['nullable', 'string'], ]; } diff --git a/src/Http/Requests/Employee/UpdateRequest.php b/src/Http/Requests/Employee/UpdateRequest.php index ca6396a..43b3fca 100644 --- a/src/Http/Requests/Employee/UpdateRequest.php +++ b/src/Http/Requests/Employee/UpdateRequest.php @@ -12,10 +12,9 @@ class UpdateRequest extends FormRequest public function rules(): array { return [ - 'code' => ['required', 'string'], 'name' => ['required', 'string'], 'email' => ['required', 'email'], - 'phone_number' => ['nullable', 'numeric'], + 'phone' => ['nullable', 'numeric'], 'summary' => ['nullable', 'string'], ]; } diff --git a/src/Http/Requests/FileUpload/StoreRequest.php b/src/Http/Requests/FileUpload/StoreRequest.php index ac2cb86..b899f7f 100644 --- a/src/Http/Requests/FileUpload/StoreRequest.php +++ b/src/Http/Requests/FileUpload/StoreRequest.php @@ -19,7 +19,6 @@ public function rules(): array 'name' => ['required', 'string'], 'type' => ['required', Rule::enum(FileUploadType::class)], 'upload' => ['nullable', 'file'], - 'summary' => ['nullable', 'string'], ]; } @@ -33,7 +32,6 @@ public function storeFor(HasFileUploads $entity) $this->file('upload'), $this->name, $this->title, - $this->summary, ); } } diff --git a/src/Http/Requests/FileUpload/UpdateRequest.php b/src/Http/Requests/FileUpload/UpdateRequest.php index e01888a..04a8fcf 100644 --- a/src/Http/Requests/FileUpload/UpdateRequest.php +++ b/src/Http/Requests/FileUpload/UpdateRequest.php @@ -16,7 +16,6 @@ public function rules(): array 'name' => ['required', 'string'], 'path' => ['required', 'string'], 'upload' => ['nullable', 'file'], - 'summary' => ['nullable', 'string'], ]; } } diff --git a/src/Http/Requests/Stakeholder/StoreRequest.php b/src/Http/Requests/Stakeholder/StoreRequest.php index 5947494..9667cc0 100644 --- a/src/Http/Requests/Stakeholder/StoreRequest.php +++ b/src/Http/Requests/Stakeholder/StoreRequest.php @@ -12,7 +12,6 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'code' => ['required', 'string'], 'name' => ['required', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], diff --git a/src/Http/Requests/Stakeholder/UpdateRequest.php b/src/Http/Requests/Stakeholder/UpdateRequest.php index 6611434..ebf512d 100644 --- a/src/Http/Requests/Stakeholder/UpdateRequest.php +++ b/src/Http/Requests/Stakeholder/UpdateRequest.php @@ -12,7 +12,6 @@ class UpdateRequest extends FormRequest public function rules(): array { return [ - 'code' => ['required', 'string'], 'name' => ['required', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], diff --git a/src/Models/Business.php b/src/Models/Business.php new file mode 100644 index 0000000..92f5613 --- /dev/null +++ b/src/Models/Business.php @@ -0,0 +1,71 @@ + $individualRelatives + * @property-read \Illuminate\Database\Eloquent\Collection $companyRelatives + * @property-read \Illuminate\Database\Eloquent\Collection $stakeholders + * @property-read BusinessRelative $stakeholder + * + * @method static \Database\Factories\BusinessFactory factory() + */ +class Business extends Entity implements HasTaxInfo, Employer +{ + use AsEmployer; + use WithTaxInfo; + + protected $fillable = [ + // . + ]; + + protected $casts = [ + // . + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|BusinessRelative + */ + protected function relatives(string $relative, bool $forward = true) + { + $relation = $forward + ? $this->morphedByMany($relative, 'stakeholder', 'business_relatives', 'business_id') + : $this->morphToMany(static::class, 'stakeholder', 'business_relatives', null, 'business_id'); + + return $relation->using(BusinessRelative::class)->withPivot('type', 'is_internal', 'code'); + } + + public function companyRelatives() + { + return $this->relatives(static::class)->as('stakeholder'); + } + + public function individualRelatives() + { + return $this->relatives(Personnel::class)->as('stakeholder'); + } + + public function stakeholders() + { + return $this->hasMany(BusinessRelative::class); + } + + public function addStakeholder( + BusinessRelativeType $type, + Entity $stakeholder, + bool $internal = null, + ): static { + $this->relatives(\get_class($stakeholder))->attach($stakeholder, [ + 'type' => $type, + 'is_internal' => $internal ?? $type->isInternal(), + ]); + + return $this->fresh(); + } +} diff --git a/src/Models/CompanyRelative.php b/src/Models/BusinessRelative.php similarity index 55% rename from src/Models/CompanyRelative.php rename to src/Models/BusinessRelative.php index a0ea35c..1f0dbd2 100644 --- a/src/Models/CompanyRelative.php +++ b/src/Models/BusinessRelative.php @@ -2,23 +2,24 @@ namespace Creasi\Base\Models; -use Creasi\Base\Models\Enums\CompanyRelativeType; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Illuminate\Database\Eloquent\Relations\MorphPivot; /** - * @property int $company_id + * @property ?string $code + * @property int $business_id * @property bool $is_internal - * @property null|CompanyRelativeType $type + * @property null|BusinessRelativeType $type * @property null|string $remark */ -class CompanyRelative extends MorphPivot +class BusinessRelative extends MorphPivot { - protected $table = 'company_relatives'; + protected $table = 'business_relatives'; protected $casts = [ - 'company_id' => 'int', + 'business_id' => 'int', 'is_internal' => 'bool', - 'type' => CompanyRelativeType::class, + 'type' => BusinessRelativeType::class, ]; /** diff --git a/src/Models/Company.php b/src/Models/Company.php deleted file mode 100644 index 0e85fd7..0000000 --- a/src/Models/Company.php +++ /dev/null @@ -1,75 +0,0 @@ - $employees - * @property-read \Illuminate\Database\Eloquent\Collection $individualRelatives - * @property-read \Illuminate\Database\Eloquent\Collection $companyRelatives - * @property-read \Illuminate\Database\Eloquent\Collection $stakeholders - * @property-read Employment $employment - * @property-read CompanyRelative $stakeholder - * - * @method static \Database\Factories\CompanyFactory factory() - */ -class Company extends Entity -{ - protected $casts = [ - // . - ]; - - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Personnel - */ - public function employees() - { - return $this->belongsToMany(Personnel::class, 'employments', 'company_id', 'employee_id') - ->withPivot('is_primary', 'type', 'status', 'start_date', 'finish_date', 'remark') - ->using(Employment::class) - ->as('employment'); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|CompanyRelative - */ - protected function businessRelative(string $relative, bool $forward = true) - { - $relation = $forward - ? $this->morphedByMany($relative, 'stakeholder', 'company_relatives', 'company_id') - : $this->morphToMany(static::class, 'stakeholder', 'company_relatives', null, 'company_id'); - - return $relation->using(CompanyRelative::class)->withPivot('type', 'remark', 'is_internal'); - } - - public function companyRelatives() - { - return $this->businessRelative(static::class)->as('stakeholder'); - } - - public function individualRelatives() - { - return $this->businessRelative(Personnel::class)->as('stakeholder'); - } - - public function stakeholders() - { - return $this->hasMany(CompanyRelative::class); - } - - public function addStakeholder( - CompanyRelativeType $type, - Entity $stakeholder, - bool $internal = null, - string $remark = null, - ): static { - $this->businessRelative(\get_class($stakeholder))->attach($stakeholder, [ - 'type' => $type, - 'remark' => $remark, - 'is_internal' => $internal ?? $type->isInternal(), - ]); - - return $this->fresh(); - } -} diff --git a/src/Models/Concerns/AsEmployee.php b/src/Models/Concerns/AsEmployee.php new file mode 100644 index 0000000..4df5505 --- /dev/null +++ b/src/Models/Concerns/AsEmployee.php @@ -0,0 +1,23 @@ +belongsToMany(Business::class, 'employments', 'employee_id', 'employer_id') + ->withPivot('is_primary', 'type', 'status', 'start_date', 'finish_date') + ->using(Employment::class) + ->as('employment'); + } +} diff --git a/src/Models/Concerns/AsEmployer.php b/src/Models/Concerns/AsEmployer.php new file mode 100644 index 0000000..d740beb --- /dev/null +++ b/src/Models/Concerns/AsEmployer.php @@ -0,0 +1,23 @@ +belongsToMany(Personnel::class, 'employments', 'employer_id', 'employee_id') + ->withPivot('is_primary', 'type', 'status', 'start_date', 'finish_date') + ->using(Employment::class) + ->as('employment'); + } +} diff --git a/src/Models/Concerns/WithAvatar.php b/src/Models/Concerns/WithAvatar.php index 290310a..4ea6bbb 100644 --- a/src/Models/Concerns/WithAvatar.php +++ b/src/Models/Concerns/WithAvatar.php @@ -3,7 +3,6 @@ namespace Creasi\Base\Models\Concerns; use Creasi\Base\Models\Enums\FileUploadType; -use Creasi\Base\Models\Identity; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Http\UploadedFile; @@ -15,7 +14,7 @@ trait WithAvatar { /** - * @return \Illuminate\Database\Eloquent\Relations\MorphOne|Identity + * @return \Illuminate\Database\Eloquent\Relations\MorphOne|\Creasi\Base\Models\FileUpload */ public function avatar(): Attribute { diff --git a/src/Models/Concerns/WithIdentity.php b/src/Models/Concerns/WithIdentity.php deleted file mode 100644 index 4253a7c..0000000 --- a/src/Models/Concerns/WithIdentity.php +++ /dev/null @@ -1,21 +0,0 @@ -morphOne(Identity::class, 'identity'); - } -} diff --git a/src/Models/Concerns/WithProfile.php b/src/Models/Concerns/WithProfile.php new file mode 100644 index 0000000..73a9a14 --- /dev/null +++ b/src/Models/Concerns/WithProfile.php @@ -0,0 +1,21 @@ +morphOne(Profile::class, 'identity'); + } +} diff --git a/src/Models/Employment.php b/src/Models/Employment.php index 51b5ab6..c3cdd67 100644 --- a/src/Models/Employment.php +++ b/src/Models/Employment.php @@ -8,18 +8,21 @@ use Illuminate\Database\Eloquent\Relations\Pivot; /** + * @property int $id + * @property string $code * @property bool $is_primary * @property null|EmploymentType $type * @property null|EmploymentStatus $status * @property null|\Carbon\CarbonImmutable $start_date * @property null|\Carbon\CarbonImmutable $finish_date - * @property null|string $remark * @property null|bool $is_started * @property null|bool $is_finished */ class Employment extends Pivot { protected $casts = [ + 'employer_id' => 'int', + 'employee_id' => 'int', 'is_primary' => 'bool', 'type' => EmploymentType::class, 'status' => EmploymentStatus::class, diff --git a/src/Models/Entity.php b/src/Models/Entity.php index a4f1f4a..b7e62a0 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -10,10 +10,10 @@ use Creasi\Nusa\Models\Concerns\WithAddresses; /** - * @property null|string $code * @property string $name + * @property null|string $alias * @property null|string $email - * @property null|string $phone_number + * @property null|string $phone * @property null|string $summary */ abstract class Entity extends Model implements HasAddresses, HasFileUploads, Stakeholder @@ -25,10 +25,10 @@ abstract class Entity extends Model implements HasAddresses, HasFileUploads, Sta public function getFillable() { return \array_merge($this->fillable, [ - 'code', 'name', + 'alias', 'email', - 'phone_number', + 'phone', 'summary', ]); } diff --git a/src/Models/Enums/CompanyRelativeType.php b/src/Models/Enums/BusinessRelativeType.php similarity index 96% rename from src/Models/Enums/CompanyRelativeType.php rename to src/Models/Enums/BusinessRelativeType.php index c7e722a..80aacbf 100644 --- a/src/Models/Enums/CompanyRelativeType.php +++ b/src/Models/Enums/BusinessRelativeType.php @@ -5,7 +5,7 @@ /** * Typically a stakeholder that has some interests in the company. */ -enum CompanyRelativeType: int +enum BusinessRelativeType: int { use KeyableEnum; diff --git a/src/Models/FileUpload.php b/src/Models/FileUpload.php index 02b8f28..f52cae3 100644 --- a/src/Models/FileUpload.php +++ b/src/Models/FileUpload.php @@ -23,7 +23,7 @@ * @property-read \Illuminate\Database\Eloquent\Collection $revisions * @property-read \Illuminate\Database\Eloquent\Collection $attaches * @property-read null|static $revisionOf - * @property-read \Illuminate\Database\Eloquent\Collection $ownedByCompanies + * @property-read \Illuminate\Database\Eloquent\Collection $ownedByCompanies * @property-read \Illuminate\Database\Eloquent\Collection $ownedByPersonnels * * @method static static store(FileUploadType $type, string|UploadedFile $path, string $name, ?string $title = null, ?string $summary = null, ?string $disk = null) @@ -67,7 +67,7 @@ protected function attachedTo(string $owner) public function ownedByCompanies() { - return $this->attachedTo(Company::class); + return $this->attachedTo(Business::class); } public function ownedByPersonnels() diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index 8d31144..f0543dd 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -3,23 +3,34 @@ namespace Creasi\Base\Models; use Creasi\Base\Contracts\Employee; -use Creasi\Base\Models\Concerns\WithIdentity; +use Creasi\Base\Contracts\HasCredential; +use Creasi\Base\Contracts\HasProfile; +use Creasi\Base\Models\Concerns\AsEmployee; +use Creasi\Base\Models\Concerns\WithCredential; +use Creasi\Base\Models\Concerns\WithProfile; +use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; /** - * @property ?string $photo_path + * @property null|Gender $gender * @property-read ?PersonnelRelative $relative * @property-read \Illuminate\Database\Eloquent\Collection $relatives - * @property-read CompanyRelative $stakeholder + * @property-read BusinessRelative $stakeholder * * @method static \Database\Factories\PersonnelFactory factory() */ -class Personnel extends Entity implements Employee +class Personnel extends Entity implements Employee, HasCredential, HasProfile { - use WithIdentity; + use AsEmployee; + use WithCredential; + use WithProfile; + + protected $fillable = [ + 'gender', + ]; protected $casts = [ - // . + 'gender' => Gender::class, ]; /** @@ -28,26 +39,25 @@ class Personnel extends Entity implements Employee public function relatives() { return $this->belongsToMany(static::class, 'personnel_relatives', 'personnel_id', 'relative_id') - ->withPivot('status', 'remark') + ->withPivot('status') ->using(PersonnelRelative::class) ->as('relative'); } - public function addRelative(Personnel $relative, PersonnelRelativeStatus $status, string $remark = null) + public function addRelative(Personnel $relative, PersonnelRelativeStatus $status) { $this->relatives()->attach($relative, [ 'status' => $status, - 'remark' => $remark, ]); } /** - * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|CompanyRelative + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|BusinessRelative */ public function stakeholders() { - return $this->morphToMany(Company::class, 'stakeholder', 'company_relatives', 'company_id', 'stakeholder_id') - ->withPivot('type', 'remark') + return $this->morphToMany(Business::class, 'stakeholder', 'company_relatives', 'company_id', 'stakeholder_id') + ->withPivot('type') ->as('stakeholder'); } } diff --git a/src/Models/PersonnelRelative.php b/src/Models/PersonnelRelative.php index 8ca55f5..088b2b3 100644 --- a/src/Models/PersonnelRelative.php +++ b/src/Models/PersonnelRelative.php @@ -7,7 +7,6 @@ /** * @property null|PersonnelRelativeStatus $status - * @property null|string $remark */ class PersonnelRelative extends Pivot { diff --git a/src/Models/Identity.php b/src/Models/Profile.php similarity index 52% rename from src/Models/Identity.php rename to src/Models/Profile.php index bf963b7..f33f3be 100644 --- a/src/Models/Identity.php +++ b/src/Models/Profile.php @@ -2,34 +2,35 @@ namespace Creasi\Base\Models; -use Creasi\Base\Contracts\Identity as IdentityContract; +use Creasi\Base\Contracts\HasIdentity; +use Creasi\Base\Contracts\HasTaxInfo; +use Creasi\Base\Models\Concerns\WithTaxInfo; use Creasi\Base\Models\Enums\Education; -use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\Enums\Religion; +use Creasi\Nusa\Models\Concerns\WithRegency; use Creasi\Nusa\Models\Regency; /** - * @property null|int $user_id * @property null|string $nik * @property null|string $prefix - * @property string $fullname * @property null|string $suffix * @property null|\Carbon\CarbonImmutable $birth_date * @property null|int $birth_place_code * @property null|Education $education - * @property null|Gender $gender * @property null|Religion $religion - * @property null|string $photo_path * @property null|string $summary - * @property-read null|User $user * @property-read null|Regency $birthPlace * - * @method static \Database\Factories\IdentityFactory factory() + * @method static \Database\Factories\ProfileFactory factory() */ -class Identity extends Model implements IdentityContract +class Profile extends Model implements HasIdentity, HasTaxInfo { + use WithRegency { + regency as birthPlace; + } + use WithTaxInfo; + protected $fillable = [ - 'user_id', 'nik', 'prefix', 'fullname', @@ -37,10 +38,8 @@ class Identity extends Model implements IdentityContract 'birth_date', 'birth_place_code', 'education', - 'gender', 'religion', 'photo_path', - 'summary', ]; protected $casts = [ @@ -48,31 +47,16 @@ class Identity extends Model implements IdentityContract 'birth_date' => 'immutable_date', 'birth_place_code' => 'int', 'education' => Education::class, - 'gender' => Gender::class, 'religion' => Religion::class, ]; - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|User - */ - public function user() - { - return $this->belongsTo(User::class); - } + protected $regencyKey = 'birth_place_code'; /** - * @return \Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo|Personnel */ - public function profile() + public function identity() { return $this->morphTo('identity'); } - - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|Regency - */ - public function birthPlace() - { - return $this->belongsTo(Regency::class, 'birth_place_code'); - } } diff --git a/src/Models/User.php b/src/Models/User.php index aa88523..3cfb3f5 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -2,6 +2,7 @@ namespace Creasi\Base\Models; +use Creasi\Base\Contracts\HasIdentity; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -18,7 +19,7 @@ * * @method static \Database\Factories\UserFactory factory() */ -class User extends Authenticatable +class User extends Authenticatable implements HasIdentity { use HasFactory; use Notifiable; @@ -39,8 +40,11 @@ public function password(): Attribute return Attribute::set(fn (string $value) => \bcrypt($value)); } - public function profile() + /** + * @return \Illuminate\Database\Eloquent\Relations\HasOne|Personnel + */ + public function identity() { - return $this->hasOne(Identity::class); + return $this->hasOne(Personnel::class); } } diff --git a/src/Repository.php b/src/Repository.php index d1a5d17..9bb2af4 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -3,8 +3,9 @@ namespace Creasi\Base; use Creasi\Base\Contracts\Employee; +use Creasi\Base\Contracts\Employer; use Creasi\Base\Contracts\Stakeholder; -use Creasi\Base\Models\Company; +use Creasi\Base\Models\Business; use Creasi\Base\Models\Entity; use Creasi\Base\Models\Personnel; use Illuminate\Routing\Router; @@ -20,7 +21,7 @@ public function __construct( public function resolveEntity(): Entity { /** @var Entity */ - $entity = app($this->router->is('companies.*') ? Company::class : Personnel::class); + $entity = app($this->router->is('companies.*') ? Business::class : Personnel::class); // For some reason we do need to resolve the binding ourselves // see: https://stackoverflow.com/a/76717314/881743 @@ -32,8 +33,13 @@ public function resolveEmployee(): Employee return app(Personnel::class); } + public function resolveEmployer(): Employer + { + return app(Business::class); + } + public function resolveStakeholder(): Stakeholder { - return app(Company::class); + return app(Business::class); } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 2a6ff0d..8c3b0f8 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,6 +3,7 @@ namespace Creasi\Base; use Creasi\Base\Contracts\Employee; +use Creasi\Base\Contracts\Employer; use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Address; use Creasi\Base\Models\Entity; @@ -107,6 +108,10 @@ protected function registerBindings() return $app->make(Repository::class)->resolveEntity(); }); + $this->app->bind(Employer::class, function ($app) { + return $app->make(Repository::class)->resolveEmployer(); + }); + $this->app->bind(Employee::class, function ($app) { return $app->make(Repository::class)->resolveEmployee(); }); diff --git a/tests/Http/Business/CompanyTest.php b/tests/Http/Business/BusinessTest.php similarity index 84% rename from tests/Http/Business/CompanyTest.php rename to tests/Http/Business/BusinessTest.php index d8209b4..ecfb0a2 100644 --- a/tests/Http/Business/CompanyTest.php +++ b/tests/Http/Business/BusinessTest.php @@ -3,7 +3,7 @@ namespace Creasi\Tests\Http\Business; use Creasi\Base\Models\Address; -use Creasi\Base\Models\Company; +use Creasi\Base\Models\Business; use Creasi\Base\Models\FileUpload; use Creasi\Tests\TestCase; use Illuminate\Http\UploadedFile; @@ -14,7 +14,7 @@ #[Group('api')] #[Group('company')] -class CompanyTest extends TestCase +class BusinessTest extends TestCase { #[Test] public function should_receive_404_when_no_data_available(): void @@ -30,7 +30,7 @@ public function should_receive_404_when_no_data_available(): void public function should_able_to_retrieve_all_data(): void { Sanctum::actingAs($this->user()); - Company::factory(2)->create(); + Business::factory(2)->create(); $response = $this->getJson('base/companies'); @@ -42,7 +42,7 @@ public function should_able_to_store_new_data(): void { Sanctum::actingAs($this->user()); - $data = Company::factory()->raw(); + $data = Business::factory()->raw(); $response = $this->postJson('base/companies', $data); @@ -54,7 +54,7 @@ public function should_able_to_show_existing_data(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->getJson("base/companies/{$model->getRouteKey()}"); @@ -66,7 +66,7 @@ public function should_receive_404_when_no_addresses_available(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->getJson("base/companies/{$model->getRouteKey()}/addresses"); @@ -79,7 +79,7 @@ public function should_able_to_create_new_address(): void Sanctum::actingAs($this->user()); $data = Address::factory()->raw(); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->postJson("base/companies/{$model->getRouteKey()}/addresses", $data); @@ -91,7 +91,7 @@ public function should_able_to_retrieve_all_addresses(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->withAddress()->createOne(); + $model = Business::factory()->withAddress()->createOne(); $response = $this->getJson("base/companies/{$model->getRouteKey()}/addresses"); @@ -103,7 +103,7 @@ public function should_receive_404_when_no_files_available(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); @@ -116,7 +116,7 @@ public function should_able_to_upload_and_store_new_file(): void Storage::fake(); Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $data = FileUpload::factory()->withoutFile()->raw(); $data['upload'] = UploadedFile::fake()->create('file.pdf'); @@ -130,7 +130,7 @@ public function should_able_to_retrieve_all_uploaded_files(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->withFileUpload()->createOne(); + $model = Business::factory()->withFileUpload()->createOne(); $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); @@ -142,7 +142,7 @@ public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->putJson("base/companies/{$model->getRouteKey()}", $model->toArray()); @@ -154,7 +154,7 @@ public function should_able_to_delete_existing_data(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->deleteJson("base/companies/{$model->getRouteKey()}"); diff --git a/tests/Http/Business/CustomerTest.php b/tests/Http/Business/CustomerTest.php index 51d79d7..06fbcdc 100644 --- a/tests/Http/Business/CustomerTest.php +++ b/tests/Http/Business/CustomerTest.php @@ -2,7 +2,7 @@ namespace Creasi\Tests\Http\Business; -use Creasi\Base\Models\Enums\CompanyRelativeType; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Tests\Http\StakeholderTestCase; use PHPUnit\Framework\Attributes\Group; @@ -10,8 +10,8 @@ #[Group('customer')] class CustomerTest extends StakeholderTestCase { - protected function getRelativeType(): CompanyRelativeType + protected function getRelativeType(): BusinessRelativeType { - return CompanyRelativeType::Customer; + return BusinessRelativeType::Customer; } } diff --git a/tests/Http/Business/SupplierTest.php b/tests/Http/Business/SupplierTest.php index 3cbeda9..fe89148 100644 --- a/tests/Http/Business/SupplierTest.php +++ b/tests/Http/Business/SupplierTest.php @@ -2,7 +2,7 @@ namespace Creasi\Tests\Http\Business; -use Creasi\Base\Models\Enums\CompanyRelativeType; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Tests\Http\StakeholderTestCase; use PHPUnit\Framework\Attributes\Group; @@ -10,8 +10,8 @@ #[Group('supplier')] class SupplierTest extends StakeholderTestCase { - protected function getRelativeType(): CompanyRelativeType + protected function getRelativeType(): BusinessRelativeType { - return CompanyRelativeType::Supplier; + return BusinessRelativeType::Supplier; } } diff --git a/tests/Http/Business/VendorTest.php b/tests/Http/Business/VendorTest.php index ea26c7f..3d57007 100644 --- a/tests/Http/Business/VendorTest.php +++ b/tests/Http/Business/VendorTest.php @@ -2,7 +2,7 @@ namespace Creasi\Tests\Http\Business; -use Creasi\Base\Models\Enums\CompanyRelativeType; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Tests\Http\StakeholderTestCase; use PHPUnit\Framework\Attributes\Group; @@ -10,8 +10,8 @@ #[Group('vendor')] class VendorTest extends StakeholderTestCase { - protected function getRelativeType(): CompanyRelativeType + protected function getRelativeType(): BusinessRelativeType { - return CompanyRelativeType::Vendor; + return BusinessRelativeType::Vendor; } } diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php index 6a2e32d..c55a9e1 100644 --- a/tests/Http/StakeholderTestCase.php +++ b/tests/Http/StakeholderTestCase.php @@ -2,8 +2,8 @@ namespace Creasi\Tests\Http; -use Creasi\Base\Models\Company; -use Creasi\Base\Models\Enums\CompanyRelativeType; +use Creasi\Base\Models\Business; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Tests\TestCase; use Illuminate\Contracts\Routing\UrlRoutable; use Laravel\Sanctum\Sanctum; @@ -14,7 +14,7 @@ #[Group('stakeholder')] abstract class StakeholderTestCase extends TestCase { - abstract protected function getRelativeType(): CompanyRelativeType; + abstract protected function getRelativeType(): BusinessRelativeType; final protected function getRoutePath(string|UrlRoutable ...$suffixs): string { @@ -42,7 +42,7 @@ public function should_receive_404_when_no_data_available(): void public function should_able_to_retrieve_all_data(): void { Sanctum::actingAs($this->user()); - Company::factory(2)->create(); + Business::factory(2)->create(); $response = $this->getJson($this->getRoutePath()); @@ -54,7 +54,7 @@ public function should_able_to_store_new_data(): void { Sanctum::actingAs($this->user()); - $data = Company::factory()->raw(); + $data = Business::factory()->raw(); $response = $this->postJson($this->getRoutePath(), $data); @@ -66,7 +66,7 @@ public function should_able_to_show_existing_data(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->getJson($this->getRoutePath($model)); @@ -78,7 +78,7 @@ public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->putJson($this->getRoutePath($model), $model->toArray()); @@ -90,7 +90,7 @@ public function should_able_to_delete_existing_data(): void { Sanctum::actingAs($this->user()); - $model = Company::factory()->createOne(); + $model = Business::factory()->createOne(); $response = $this->deleteJson($this->getRoutePath($model)); diff --git a/tests/Models/BusinessTest.php b/tests/Models/BusinessTest.php new file mode 100644 index 0000000..08b7ca8 --- /dev/null +++ b/tests/Models/BusinessTest.php @@ -0,0 +1,114 @@ +withAddress()->createOne(); + + $this->assertCount(1, $business->addresses); + $this->assertInstanceOf(Address::class, $business->addresses->first()); + } + + #[Test] + public function should_have_avatar_image() + { + $business = Business::factory()->createOne(); + + // $this->assertNull($company->avatar); + + // dump($company->toArray()); + + $avatar = $business->setAvatar( + UploadedFile::fake()->image('logo.png') + ); + + $this->assertInstanceOf(FileUpload::class, $business->avatar); + $this->assertEquals(FileUploadType::Avatar, $business->avatar->type); + $this->assertTrue($business->avatar->is_internal); + } + + #[Test] + public function should_have_employees() + { + $business = Business::factory()->createOne(); + $person = Personnel::factory()->createOne(); + + $business->employees()->attach($person, [ + 'is_primary' => true, + 'type' => EmploymentType::Fulltime, + 'status' => EmploymentStatus::Permanent, + 'start_date' => now()->subDays(3), + ]); + + $employee = $business->employees->first(); + + $this->assertTrue($employee->employment->is_started); + $this->assertNull($employee->employment->is_finished); + $this->assertInstanceOf(EmploymentType::class, $employee->employment->type); + $this->assertInstanceOf(EmploymentStatus::class, $employee->employment->status); + } + + #[Test] + public function should_have_other_company_as_stakeholders() + { + $business = Business::factory()->createOne(['name' => 'Internal Company']); + $external = Business::factory()->createOne(['name' => 'External Company']); + + $business->addStakeholder(BusinessRelativeType::Vendor, $external); + + $this->assertCount(1, $business->companyRelatives); + + $vendor = $business->companyRelatives->first(); + + $this->assertFalse($vendor->stakeholder->is_internal); + $this->assertInstanceOf(BusinessRelativeType::class, $vendor->stakeholder->type); + } + + #[Test] + public function should_have_other_individual_as_stakeholders() + { + $business = Business::factory()->createOne(['name' => 'Some Company']); + $personal = Personnel::factory()->withProfile()->createOne(); + + $business->addStakeholder(BusinessRelativeType::Owner, $personal, true); + + $this->assertCount(1, $business->individualRelatives); + + $owner = $business->individualRelatives->first(); + + $this->assertTrue($owner->stakeholder->is_internal); + $this->assertInstanceOf(BusinessRelativeType::class, $owner->stakeholder->type); + } + + #[Test] + public function should_have_access_to_mixed_stakeholders() + { + $business = Business::factory()->createOne(['name' => 'Internal Company']); + $external = Business::factory()->createOne(['name' => 'External Company']); + $personal = Personnel::factory()->withProfile()->createOne(); + + $business->addStakeholder(BusinessRelativeType::Owner, $personal); + $business->addStakeholder(BusinessRelativeType::Supplier, $external); + + $this->assertCount(2, $business->stakeholders); + } +} diff --git a/tests/Models/CompanyTest.php b/tests/Models/CompanyTest.php deleted file mode 100644 index 378b90d..0000000 --- a/tests/Models/CompanyTest.php +++ /dev/null @@ -1,114 +0,0 @@ -withAddress()->createOne(); - - $this->assertCount(1, $company->addresses); - $this->assertInstanceOf(Address::class, $company->addresses->first()); - } - - #[Test] - public function should_have_avatar_image() - { - $company = Company::factory()->createOne(); - - // $this->assertNull($company->avatar); - - // dump($company->toArray()); - - $avatar = $company->setAvatar( - UploadedFile::fake()->image('logo.png') - ); - - $this->assertInstanceOf(FileUpload::class, $company->avatar); - $this->assertEquals(FileUploadType::Avatar, $company->avatar->type); - $this->assertTrue($company->avatar->is_internal); - } - - #[Test] - public function should_have_employees() - { - $company = Company::factory()->createOne(); - $person = Personnel::factory()->createOne(); - - $company->employees()->attach($person, [ - 'is_primary' => true, - 'type' => EmploymentType::Fulltime, - 'status' => EmploymentStatus::Permanent, - 'start_date' => now()->subDays(3), - ]); - - $employee = $company->employees->first(); - - $this->assertTrue($employee->employment->is_started); - $this->assertNull($employee->employment->is_finished); - $this->assertInstanceOf(EmploymentType::class, $employee->employment->type); - $this->assertInstanceOf(EmploymentStatus::class, $employee->employment->status); - } - - #[Test] - public function should_have_other_company_as_stakeholders() - { - $company = Company::factory()->createOne(['name' => 'Internal Company']); - $external = Company::factory()->createOne(['name' => 'External Company']); - - $company->addStakeholder(CompanyRelativeType::Vendor, $external); - - $this->assertCount(1, $company->companyRelatives); - - $vendor = $company->companyRelatives->first(); - - $this->assertFalse($vendor->stakeholder->is_internal); - $this->assertInstanceOf(CompanyRelativeType::class, $vendor->stakeholder->type); - } - - #[Test] - public function should_have_other_individual_as_stakeholders() - { - $company = Company::factory()->createOne(['name' => 'Some Company']); - $personal = Personnel::factory()->withIdentity()->createOne(); - - $company->addStakeholder(CompanyRelativeType::Owner, $personal, true); - - $this->assertCount(1, $company->individualRelatives); - - $owner = $company->individualRelatives->first(); - - $this->assertTrue($owner->stakeholder->is_internal); - $this->assertInstanceOf(CompanyRelativeType::class, $owner->stakeholder->type); - } - - #[Test] - public function should_have_access_to_mixed_stakeholders() - { - $company = Company::factory()->createOne(['name' => 'Internal Company']); - $external = Company::factory()->createOne(['name' => 'External Company']); - $personal = Personnel::factory()->withIdentity()->createOne(); - - $company->addStakeholder(CompanyRelativeType::Owner, $personal); - $company->addStakeholder(CompanyRelativeType::Supplier, $external); - - $this->assertCount(2, $company->stakeholders); - } -} diff --git a/tests/Models/FileUploadTest.php b/tests/Models/FileUploadTest.php index 11bb471..129a050 100644 --- a/tests/Models/FileUploadTest.php +++ b/tests/Models/FileUploadTest.php @@ -2,7 +2,7 @@ namespace Creasi\Tests\Models; -use Creasi\Base\Models\Company; +use Creasi\Base\Models\Business; use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; @@ -76,7 +76,7 @@ public function could_attached_to_many_personnels() #[Test] public function could_attached_to_company() { - $company = Company::factory()->createOne(); + $company = Business::factory()->createOne(); $files = [ 'first' => UploadedFile::fake()->create('first.pdf'), 'second' => UploadedFile::fake()->create('second.pdf'), @@ -96,7 +96,7 @@ public function could_attached_to_company() #[Test] public function could_attached_to_many_companies() { - $companies = Company::factory(2)->create(); + $companies = Business::factory(2)->create(); $file = UploadedFile::fake()->create('document.pdf'); foreach ($companies as $company) { diff --git a/tests/Models/PersonnelTest.php b/tests/Models/PersonnelTest.php index fa2317d..b3cbf4e 100644 --- a/tests/Models/PersonnelTest.php +++ b/tests/Models/PersonnelTest.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Enums\FileUploadType; +use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; @@ -22,6 +23,7 @@ public function should_have_addresses() $person = Personnel::factory()->withAddress()->createOne(); $this->assertCount(1, $person->addresses); + $this->assertInstanceOf(Gender::class, $person->gender); $this->assertInstanceOf(Address::class, $person->addresses->first()); } diff --git a/tests/Models/IdentityTest.php b/tests/Models/ProfileTest.php similarity index 62% rename from tests/Models/IdentityTest.php rename to tests/Models/ProfileTest.php index f628e9c..5105f3a 100644 --- a/tests/Models/IdentityTest.php +++ b/tests/Models/ProfileTest.php @@ -4,28 +4,26 @@ use Carbon\CarbonImmutable; use Creasi\Base\Models\Enums\Education; -use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\Enums\Religion; -use Creasi\Base\Models\Identity; use Creasi\Base\Models\Personnel; +use Creasi\Base\Models\Profile; use Creasi\Nusa\Models\Regency; use Creasi\Tests\TestCase; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; #[Group('models')] -#[Group('identity')] -class IdentityTest extends TestCase +#[Group('profile')] +class ProfileTest extends TestCase { #[Test] public function should_have_correct_attributes_cast() { - $model = Identity::factory()->createOne(); + $model = Profile::factory()->createOne(); $this->assertModelExists($model); $this->assertInstanceOf(CarbonImmutable::class, $model->birth_date); $this->assertInstanceOf(Education::class, $model->education); - $this->assertInstanceOf(Gender::class, $model->gender); $this->assertInstanceOf(Religion::class, $model->religion); } @@ -33,11 +31,11 @@ public function should_have_correct_attributes_cast() public function should_owned_by_personnel() { $person = Personnel::factory()->createOne(); - $identity = Identity::factory()->createOne(); + $profile = Profile::factory()->createOne(); - $identity->profile()->associate($person)->save(); + $profile->identity()->associate($person)->save(); - $this->assertModelExists($person->identity); - $this->assertInstanceOf(Regency::class, $identity->birthPlace); + $this->assertModelExists($person->profile); + $this->assertInstanceOf(Regency::class, $profile->birthPlace); } } diff --git a/tests/Models/UserTest.php b/tests/Models/UserTest.php index e6b4196..53eef31 100644 --- a/tests/Models/UserTest.php +++ b/tests/Models/UserTest.php @@ -2,7 +2,7 @@ namespace Creasi\Tests\Models; -use Creasi\Base\Models\Identity; +use Creasi\Base\Models\Personnel; use Creasi\Base\Models\User; use Creasi\Tests\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -16,9 +16,9 @@ class UserTest extends TestCase public function it_could_have_profile() { $user = User::factory()->createOne(); - $identity = Identity::factory()->createOne(); + $identity = Personnel::factory()->createOne(); - $user->profile()->save($identity); + $user->identity()->save($identity); $this->assertTrue($identity->user->is($user)); } From 136e9016b4a89f9ba201491625675c9b711f6d7b Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 20 Jul 2023 11:32:29 +0700 Subject: [PATCH 42/71] chore(docs): update missing docs Signed-off-by: Fery Wardiyanto --- database/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/database/README.md b/database/README.md index 7272efb..2f9f4ae 100644 --- a/database/README.md +++ b/database/README.md @@ -115,7 +115,6 @@ erDiagram unsignedBigInt file_upload_id FK morph attached_to } - ``` --- ## Entities @@ -255,7 +254,7 @@ classDiagram } class Personnel { unsignedBigInt id - employer() Business + employers() Business[] } class Employments { unsignedBigInt id From 91fa4f270e604a159536a9cfc9c524b05f68256f Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Thu, 20 Jul 2023 11:59:36 +0700 Subject: [PATCH 43/71] chore(docs): clean up Signed-off-by: Fery Wardiyanto --- database/README.md | 134 ++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/database/README.md b/database/README.md index 2f9f4ae..282f851 100644 --- a/database/README.md +++ b/database/README.md @@ -212,14 +212,14 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `name` | `varchar(150)` | | - | -| `alias` | `varchar(50)`, `nullable` | | - | -| `email` | `varchar`, `nullable` | `unique` | - | -| `phone` | `varchar(20)`, `nullable` | | - | -| `tax_status` | `unsignedSmallInt`, `nullable` | | - | -| `tax_id` | `varchar(16)`, `nullable` | | - | -| `summary` | `text`, `nullable` | | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | | +| `name` | `varchar(150)` | | | +| `alias` | `varchar(50)`, `nullable` | | | +| `email` | `varchar`, `nullable` | `unique` | | +| `phone` | `varchar(20)`, `nullable` | | | +| `tax_status` | `unsignedSmallInt`, `nullable` | | | +| `tax_id` | `varchar(16)`, `nullable` | | | +| `summary` | `text`, `nullable` | | | **Model Attributes** - `timestamps` @@ -229,12 +229,12 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `business_id` | `unsignedBigInt` | `foreign` | - | -| `stakeholder` | `morphs`, `nullable` | | - | -| `is_internal` | `boolean`, `default: false` | | - | -| `code` | `varchar(100)`, `nullable` | `unique` | - | -| `type` | `unsignedSmallInt`, `nullable` | | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | | +| `business_id` | `unsignedBigInt` | `foreign` | | +| `stakeholder` | `morphs`, `nullable` | | | +| `is_internal` | `boolean`, `default: false` | | | +| `code` | `varchar(100)`, `nullable` | `unique` | | +| `type` | `unsignedSmallInt`, `nullable` | | | **Relation Properties** - `business_id` : reference `businesses` @@ -274,15 +274,15 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `employer_id` | `unsignedBigInt` | `foreign` | - | -| `employee_id` | `unsignedBigInt` | `foreign` | - | -| `is_primary` | `boolean`, `default: false` | | - | -| `code` | `varchar(100)`, `nullable` | `unique` | - | -| `type` | `unsignedSmallInt`, `nullable` | | - | -| `status` | `unsignedSmallInt`, `nullable` | | - | -| `start_date` | `date`, `nullable` | | - | -| `finish_date` | `date`, `nullable` | | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | | +| `employer_id` | `unsignedBigInt` | `foreign` | | +| `employee_id` | `unsignedBigInt` | `foreign` | | +| `is_primary` | `boolean`, `default: false` | | | +| `code` | `varchar(100)`, `nullable` | `unique` | | +| `type` | `unsignedSmallInt`, `nullable` | | | +| `status` | `unsignedSmallInt`, `nullable` | | | +| `start_date` | `date`, `nullable` | | | +| `finish_date` | `date`, `nullable` | | | **Relation Properties** - `employer_id` : reference `businesses` @@ -331,14 +331,14 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `user_id` | `unsignedBigInt`, `nullable` | `foreign` | - | -| `name` | `varchar(150)` | | - | -| `alias` | `varchar(50)`, `nullable` | | - | -| `email` | `varchar`, `nullable` | `unique` | - | -| `phone` | `varchar(20)`, `nullable` | | - | -| `gender` | `char(1)` | | - | -| `summary` | `text`, `nullable` | | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | | +| `user_id` | `unsignedBigInt`, `nullable` | `foreign` | | +| `name` | `varchar(150)` | | | +| `alias` | `varchar(50)`, `nullable` | | | +| `email` | `varchar`, `nullable` | `unique` | | +| `phone` | `varchar(20)`, `nullable` | | | +| `gender` | `char(1)` | | | +| `summary` | `text`, `nullable` | | | **Model Attributes** - `timestamps` @@ -351,10 +351,10 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `personnel_id` | `unsignedBigInt` | `foreign` | - | -| `relative_id` | `unsignedBigInt` | `foreign` | - | -| `status` | `unsignedSmallInt`, `nullable` | | - | -| `remark` | `text`, `nullable` | | - | +| `personnel_id` | `unsignedBigInt` | `foreign` | | +| `relative_id` | `unsignedBigInt` | `foreign` | | +| `status` | `unsignedSmallInt`, `nullable` | | | +| `remark` | `text`, `nullable` | | | **Relation Properties** - `personnel_id` : reference `personnels` @@ -375,22 +375,22 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `identity` | `morphs`, `nullable` | | - | -| `nik` | `char(16)`, `nullable` | | - | -| `prefix` | `varchar(10)`, `nullable` | | - | -| `suffix` | `varchar(10)`, `nullable` | | - | -| `birth_date` | `date`, `nullable` | | - | -| `birth_place_code` | `char(4)`, `nullable` | | - | -| `education` | `varchar(3)`, `nullable` | | - | -| `religion` | `unsignedTinyInt`, `nullable` | | - | -| `tax_status` | `unsignedSmallInt`, `nullable` | | - | -| `tax_id` | `varchar(16)`, `nullable` | | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | | +| `identity` | `morphs`, `nullable` | | | +| `nik` | `char(16)`, `nullable` | | | +| `prefix` | `varchar(10)`, `nullable` | | | +| `suffix` | `varchar(10)`, `nullable` | | | +| `birth_date` | `date`, `nullable` | | | +| `birth_place_code` | `char(4)`, `nullable` | | | +| `education` | `varchar(3)`, `nullable` | | | +| `religion` | `unsignedTinyInt`, `nullable` | | | +| `tax_status` | `unsignedSmallInt`, `nullable` | | | +| `tax_id` | `varchar(16)`, `nullable` | | | **Model Attributes** - `timestamps` - `softDeletes` -- + **Profile Educations** - Uneducated - SD @@ -441,18 +441,18 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `unsignedBigInt`, `incrementing` | `primary` | - | -| `addressable` | `morphs`, `nullable` | | - | -| `is_resident` | `boolean` | | - | -| `line` | `varchar` | | - | -| `rt` | `char(3)`, `nullable` | | - | -| `rw` | `char(3)`, `nullable` | | - | -| `village_code` | `char(10)`, `nullable` | | - | -| `district_code` | `char(6)`, `nullable` | | - | -| `regency_code` | `char(4)`, `nullable` | | - | -| `province_code` | `char(2)`, `nullable` | | - | -| `postal_code` | `char(5)`, `nullable` | | - | -| `summary` | `varchar`, `nullable` | | - | +| `id` | `unsignedBigInt`, `incrementing` | `primary` | | +| `addressable` | `morphs`, `nullable` | | | +| `is_resident` | `boolean` | | | +| `line` | `varchar` | | | +| `rt` | `char(3)`, `nullable` | | | +| `rw` | `char(3)`, `nullable` | | | +| `village_code` | `char(10)`, `nullable` | | | +| `district_code` | `char(6)`, `nullable` | | | +| `regency_code` | `char(4)`, `nullable` | | | +| `province_code` | `char(2)`, `nullable` | | | +| `postal_code` | `char(5)`, `nullable` | | | +| `summary` | `varchar`, `nullable` | | | **Model Attributes** - `timestamps` @@ -488,13 +488,13 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `id` | `uuid` | `primary` | - | +| `id` | `uuid` | `primary` | | | `revision_id` | `uuid`, `nullable` | `foreign` | Indicates that this row is actually a revision of parent `id` | -| `title` | `varchar`, `nullable` | | - | -| `name` | `varchar` | | - | -| `path` | `varchar`, `nullable` | | - | -| `drive` | `varchar`, `nullable` | | - | -| `summary` | `varchar`, `nullable` | | - | +| `title` | `varchar`, `nullable` | | | +| `name` | `varchar` | | | +| `path` | `varchar`, `nullable` | | | +| `drive` | `varchar`, `nullable` | | | +| `summary` | `varchar`, `nullable` | | | **Model Attributes** - `timestamps` @@ -507,8 +507,8 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | -| `file_upload_id` | `uuid` | `foreign` | - | -| `attached_to` | `morphs`, `nullable` | | - | +| `file_upload_id` | `uuid` | `foreign` | | +| `attached_to` | `morphs`, `nullable` | | | **Relation Properties** - `file_upload_id` : reference `file_uploads` From a9975b79fa0ea3ce6649dad4237cb97aab8d03e9 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 23 Jul 2023 15:48:57 +0700 Subject: [PATCH 44/71] chore(docs): update table comments for each fields Signed-off-by: Fery Wardiyanto --- database/README.md | 77 +++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/database/README.md b/database/README.md index 282f851..72bc7fa 100644 --- a/database/README.md +++ b/database/README.md @@ -138,12 +138,12 @@ classDiagram varchar~20~ phone varchar~200~ summary } - class Business { + class Business~Entity~ { unsignedBigInt id unsignedSmallInt tax_status varchar~16~ tax_id } - class Personnel { + class Personnel~Entity~ { unsignedBigInt id unsignedBigInt user_id char~1~ gender @@ -194,10 +194,10 @@ classDiagram BusinessRelative --> Personnel : stakeholders Business --> BusinessRelative : businessRelatives - class Business { + class Business~Entity~ { unsignedBigInt id } - class Personnel { + class Personnel~Entity~ { unsignedBigInt id } class BusinessRelative { @@ -213,10 +213,10 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | | -| `name` | `varchar(150)` | | | -| `alias` | `varchar(50)`, `nullable` | | | -| `email` | `varchar`, `nullable` | `unique` | | -| `phone` | `varchar(20)`, `nullable` | | | +| `name` | `varchar(150)` | | Must contain legal name of the business, e.g : `PT. Creasi Tekno Solusi` | +| `alias` | `varchar(50)`, `nullable` | | Must contain alias name from its legal name of the business, e.g : `Creasico` | +| `email` | `varchar`, `nullable` | `unique` | The business primary email address, e.g : `hello@creasi.co` | +| `phone` | `varchar(20)`, `nullable` | | The business primary phone number | | `tax_status` | `unsignedSmallInt`, `nullable` | | | | `tax_id` | `varchar(16)`, `nullable` | | | | `summary` | `text`, `nullable` | | | @@ -232,7 +232,7 @@ classDiagram | `id` | `unsignedBigInt`, `incrementing` | `primary` | | | `business_id` | `unsignedBigInt` | `foreign` | | | `stakeholder` | `morphs`, `nullable` | | | -| `is_internal` | `boolean`, `default: false` | | | +| `is_internal` | `boolean`, `default: false` | | Determine whether the stakeholder is actually from internal business | | `code` | `varchar(100)`, `nullable` | `unique` | | | `type` | `unsignedSmallInt`, `nullable` | | | @@ -258,7 +258,7 @@ classDiagram } class Employments { unsignedBigInt id - unsignedBigInt business_id + unsignedBigInt employer_id unsignedBigInt employee_id boolean is_primary varchar~100~ code @@ -275,11 +275,11 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | | -| `employer_id` | `unsignedBigInt` | `foreign` | | -| `employee_id` | `unsignedBigInt` | `foreign` | | -| `is_primary` | `boolean`, `default: false` | | | -| `code` | `varchar(100)`, `nullable` | `unique` | | -| `type` | `unsignedSmallInt`, `nullable` | | | +| `employer_id` | `unsignedBigInt` | `foreign` | ID of the company | +| `employee_id` | `unsignedBigInt` | `foreign` | ID of the personnel | +| `is_primary` | `boolean`, `default: false` | | Determine whether it's the personnel's primary company | +| `code` | `varchar(100)`, `nullable` | `unique` | An identification that given by the company to employee | +| `type` | `unsignedSmallInt`, `nullable` | | Define the employment type of the personnel in the company | | `status` | `unsignedSmallInt`, `nullable` | | | | `start_date` | `date`, `nullable` | | | | `finish_date` | `date`, `nullable` | | | @@ -300,6 +300,9 @@ classDiagram - Contract - Probation +**Note :** +Field `type` and `status` shouldn't be detachable so we can maintain historical changes of the personnel in the company. + ## Personnel and its Profile Every individuals should have their own identity, it also can helps a business to identify better of their individuals. But there's a circumstance that a business doesn't really care about that, all they need is just a way to communicate with the individuals, and that's it. @@ -425,13 +428,23 @@ classDiagram class Address { unsignedBigInt id + boolean is_resident + string line + char~3~ rt + char~3~ rw + char~10~ village_code + char~6~ district_code + char~4~ regency_code + char~2~ province_code + char~5~ postal_code + string summary addressable() Entity } - class Business { + class Business~Entity~ { unsignedBigInt id addresses() Address[] } - class Personnel { + class Personnel~Entity~ { unsignedBigInt id addresses() Address[] } @@ -447,10 +460,10 @@ classDiagram | `line` | `varchar` | | | | `rt` | `char(3)`, `nullable` | | | | `rw` | `char(3)`, `nullable` | | | -| `village_code` | `char(10)`, `nullable` | | | -| `district_code` | `char(6)`, `nullable` | | | -| `regency_code` | `char(4)`, `nullable` | | | -| `province_code` | `char(2)`, `nullable` | | | +| `village_code` | `char(10)`, `nullable` | `foreign` | | +| `district_code` | `char(6)`, `nullable` | `foreign` | | +| `regency_code` | `char(4)`, `nullable` | `foreign` | | +| `province_code` | `char(2)`, `nullable` | `foreign` | | | `postal_code` | `char(5)`, `nullable` | | | | `summary` | `varchar`, `nullable` | | | @@ -458,6 +471,12 @@ classDiagram - `timestamps` - `softDeletes` +**Relation Properties** +- `village_code` : reference `villages` +- `district_code` : reference `districts` +- `regency_code` : reference `regencies` +- `province_code` : reference `provinces` + ## Uploaded Files Last but not least, either company or individual might also be able to have certain files or documents to runs the business. This functionality can serve any kind of purposes and its might be vary depending of the business. For instance, as simple as both might want to upload their photo e.g an profile avatar or company logo. Meanwhile there's business that want to serve their invoice, quotation or any other document. @@ -468,19 +487,29 @@ It also possible to store an `.xlsx` or `.csv` file that would be used for data classDiagram Business "1" ..> "*" FileAttached : files Personnel "1" ..> "*" FileAttached : files + FileAttached "1" <--> "1" FileUpload : attachments + class FileUpload { unsignedBigInt id + string revision_id + string title + string name + string path + string drive + string summary } - FileAttached "1" <--> "1" FileUpload : attachments class FileAttached { unsignedBigInt id morph attached_to + attachedTo() Entity } - class Business { + class Business~Entity~ { unsignedBigInt id + files() FileUpload[] } - class Personnel { + class Personnel~Entity~ { unsignedBigInt id + files() FileUpload[] } ``` From 154960dadc4b0eaf55e52764deca3dc15e948b18 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 23 Jul 2023 17:05:05 +0700 Subject: [PATCH 45/71] feat: adds ability and flexibility to define address types per-project-basis Signed-off-by: Fery Wardiyanto --- config/creasico.php | 18 ++++++++ database/README.md | 41 +++++++++++++------ database/factories/AddressFactory.php | 5 ++- ...ate_identities_address_and_files_table.php | 2 +- src/Http/Requests/Address/StoreRequest.php | 6 ++- src/Http/Requests/Address/UpdateRequest.php | 5 ++- src/Models/Address.php | 21 +++++++--- src/Models/Enums/AddressType.php | 26 ++++++++++++ 8 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 src/Models/Enums/AddressType.php diff --git a/config/creasico.php b/config/creasico.php index 6c9c6d9..fdd5e29 100644 --- a/config/creasico.php +++ b/config/creasico.php @@ -1,7 +1,25 @@ true, 'routes_prefix' => 'base', + + 'address' => [ + + /* + |-------------------------------------------------------------------------- + | Address Types + |-------------------------------------------------------------------------- + | + | This value defines which address types enum that will be used in the project. + | By default there's only 2 types defined in the `AddressType` enum, which is + | `Legal` and `Recident`. + | + */ + + 'types' => Enums\AddressType::class, + ], ]; diff --git a/database/README.md b/database/README.md index 72bc7fa..2f93cb4 100644 --- a/database/README.md +++ b/database/README.md @@ -78,7 +78,7 @@ erDiagram addresses { unsignedBigInt id PK morph addressable - boolean is_resident + unsignedSmallInt type varchar line char(3) rt char(3) rw @@ -384,7 +384,7 @@ classDiagram | `prefix` | `varchar(10)`, `nullable` | | | | `suffix` | `varchar(10)`, `nullable` | | | | `birth_date` | `date`, `nullable` | | | -| `birth_place_code` | `char(4)`, `nullable` | | | +| `birth_place_code` | `char(4)`, `nullable` | `foreign` | | | `education` | `varchar(3)`, `nullable` | | | | `religion` | `unsignedTinyInt`, `nullable` | | | | `tax_status` | `unsignedSmallInt`, `nullable` | | | @@ -394,6 +394,9 @@ classDiagram - `timestamps` - `softDeletes` +**Relation Properties** +- `birth_place_code` : reference `regencies` + **Profile Educations** - Uneducated - SD @@ -427,18 +430,23 @@ classDiagram Personnel "1" ..> "*" Address : addresses class Address { - unsignedBigInt id - boolean is_resident + int id string line - char~3~ rt - char~3~ rw - char~10~ village_code - char~6~ district_code - char~4~ regency_code - char~2~ province_code - char~5~ postal_code - string summary + null|int type + boolean is_resident + null|string rt + null|string rw + null|int village_code + null|int district_code + null|int regency_code + null|int province_code + null|int postal_code + null|string summary addressable() Entity + village() null|Village + district() null|District + regency() null|Regency + province() null|Province } class Business~Entity~ { unsignedBigInt id @@ -456,7 +464,7 @@ classDiagram | --- | --- | :---: | --- | | `id` | `unsignedBigInt`, `incrementing` | `primary` | | | `addressable` | `morphs`, `nullable` | | | -| `is_resident` | `boolean` | | | +| `type` | `unsignedSmallInt`, `nullable` | | | | `line` | `varchar` | | | | `rt` | `char(3)`, `nullable` | | | | `rw` | `char(3)`, `nullable` | | | @@ -477,6 +485,13 @@ classDiagram - `regency_code` : reference `regencies` - `province_code` : reference `provinces` +**Address Types** +Currently there's only 2 types of address, which is : +- `Legal` : Meaning that the address data is what defined in their legal document, +- `Resident` : Meaning that the address data is actually the place where their live, + +But it can be extended by the config `creasi.base.address.types` value, that said we have flexibility to define the address type per-project-basis. + ## Uploaded Files Last but not least, either company or individual might also be able to have certain files or documents to runs the business. This functionality can serve any kind of purposes and its might be vary depending of the business. For instance, as simple as both might want to upload their photo e.g an profile avatar or company logo. Meanwhile there's business that want to serve their invoice, quotation or any other document. diff --git a/database/factories/AddressFactory.php b/database/factories/AddressFactory.php index 784bfb1..a71f151 100644 --- a/database/factories/AddressFactory.php +++ b/database/factories/AddressFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use Creasi\Base\Models\Address; +use Creasi\Base\Models\Enums\AddressType; use Creasi\Nusa\Models\Village; use Illuminate\Contracts\Database\Query\Builder; use Illuminate\Database\Eloquent\Factories\Factory; @@ -24,8 +25,10 @@ public function definition(): array $query->where('code', 33); })->inRandomOrder()->first(); + $types = config('creasi.base.address.types', AddressType::class); + return [ - 'is_resident' => $this->faker->boolean(), + 'type' => $this->faker->randomElement($types::cases()), 'line' => $this->faker->streetAddress(), 'rt' => $this->faker->numberBetween(1, 15), 'rw' => $this->faker->numerify(1, 10), diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index 8060bf7..f3c498d 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -33,7 +33,7 @@ public function up(): void $table->id(); $table->nullableMorphs('addressable'); - $table->boolean('is_resident'); + $table->unsignedSmallInteger('type')->nullable(); $table->string('line'); $table->char('rt', 3)->nullable(); $table->char('rw', 3)->nullable(); diff --git a/src/Http/Requests/Address/StoreRequest.php b/src/Http/Requests/Address/StoreRequest.php index 6af07eb..2602f68 100644 --- a/src/Http/Requests/Address/StoreRequest.php +++ b/src/Http/Requests/Address/StoreRequest.php @@ -4,7 +4,9 @@ use Creasi\Base\Http\Requests\FormRequest; use Creasi\Base\Models\Address; +use Creasi\Base\Models\Enums\AddressType; use Creasi\Nusa\Contracts\HasAddresses; +use Illuminate\Validation\Rule; class StoreRequest extends FormRequest { @@ -13,8 +15,10 @@ class StoreRequest extends FormRequest */ public function rules(): array { + $types = config('creasi.base.address.types', AddressType::class); + return [ - 'is_resident' => ['required', 'boolean'], + 'type' => ['required', 'numeric', Rule::enum($types)], 'line' => ['required', 'string'], 'rt' => ['required', 'numeric'], 'rw' => ['required', 'numeric'], diff --git a/src/Http/Requests/Address/UpdateRequest.php b/src/Http/Requests/Address/UpdateRequest.php index 7b527f3..869c2f5 100644 --- a/src/Http/Requests/Address/UpdateRequest.php +++ b/src/Http/Requests/Address/UpdateRequest.php @@ -3,6 +3,7 @@ namespace Creasi\Base\Http\Requests\Address; use Creasi\Base\Http\Requests\FormRequest; +use Illuminate\Validation\Rule; class UpdateRequest extends FormRequest { @@ -11,8 +12,10 @@ class UpdateRequest extends FormRequest */ public function rules(): array { + $types = config('creasi.base.address.types', AddressType::class); + return [ - 'is_resident' => ['required', 'boolean'], + 'type' => ['required', 'numeric', Rule::enum($types)], 'line' => ['required', 'string'], 'rt' => ['required', 'numeric'], 'rw' => ['required', 'numeric'], diff --git a/src/Models/Address.php b/src/Models/Address.php index d291c85..63d10b5 100644 --- a/src/Models/Address.php +++ b/src/Models/Address.php @@ -9,7 +9,8 @@ use Illuminate\Support\Str; /** - * @property bool $is_resident + * @property-read bool $is_resident + * @property null|Addres $type * @property null|string $rt * @property null|string $rw * @property null|string $summary @@ -22,15 +23,25 @@ class Address extends NusaAddress use SoftDeletes; protected $fillable = [ - 'is_resident', + 'type', 'rt', 'rw', 'summary', ]; - protected $casts = [ - 'is_resident' => 'bool', - ]; + protected $casts = []; + + public function getCasts() + { + return \array_merge(parent::getCasts(), [ + 'type' => config('creasi.base.address.types', Enums\AddressType::class), + ]); + } + + public function isResident(): Attribute + { + return Attribute::get(fn () => $this->getAttributeValue('type')->isResident()); + } public function rt(): Attribute { diff --git a/src/Models/Enums/AddressType.php b/src/Models/Enums/AddressType.php new file mode 100644 index 0000000..9415ce6 --- /dev/null +++ b/src/Models/Enums/AddressType.php @@ -0,0 +1,26 @@ +value === self::Resident->value; + } +} From 65b911e3e30ce534c6c17fffe53c825f929ac7e5 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 23 Jul 2023 17:43:51 +0700 Subject: [PATCH 46/71] chore(model): utilize parent method instead of completely overwrite it Signed-off-by: Fery Wardiyanto --- src/Models/Entity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Entity.php b/src/Models/Entity.php index b7e62a0..e80f17c 100644 --- a/src/Models/Entity.php +++ b/src/Models/Entity.php @@ -24,7 +24,7 @@ abstract class Entity extends Model implements HasAddresses, HasFileUploads, Sta public function getFillable() { - return \array_merge($this->fillable, [ + return \array_merge(parent::getFillable(), [ 'name', 'alias', 'email', From 9dbb9dc120ee460c1133e25806596f97e64c5b85 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sun, 23 Jul 2023 23:28:16 +0700 Subject: [PATCH 47/71] chore: get rid of `laravel-dusk` macro integration Since it being covered by [`creasico/laravel-dusk-browserstack@v0.2.0`](https://github.com/creasico/laravel-dusk-browserstack/releases/tag/v0.2.0) Signed-off-by: Fery Wardiyanto --- composer.json | 1 - composer.lock | 219 +++++++--------------------------- src/ServiceProvider.php | 24 ---- tests/ServiceProviderTest.php | 60 +--------- 4 files changed, 44 insertions(+), 260 deletions(-) diff --git a/composer.json b/composer.json index 3f99f44..5d226ac 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,6 @@ }, "require-dev": { "composer-runtime-api": "*", - "laravel/dusk": "^7.0", "laravel/pint": "^1.1", "laravel/sanctum": "^3.2", "nunomaduro/collision": "^7.4", diff --git a/composer.lock b/composer.lock index da9fcf4..1965547 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dffa6c26afc73744e753475e067d69ec", + "content-hash": "260133eff7b8a3b8da04a58921da8014", "packages": [ { "name": "brick/math", @@ -5085,81 +5085,6 @@ }, "time": "2020-07-09T08:09:16+00:00" }, - { - "name": "laravel/dusk", - "version": "v7.8.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/dusk.git", - "reference": "82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5", - "reference": "82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-zip": "*", - "illuminate/console": "^9.0|^10.0", - "illuminate/support": "^9.0|^10.0", - "nesbot/carbon": "^2.0", - "php": "^8.0", - "php-webdriver/webdriver": "^1.9.0", - "symfony/console": "^6.0", - "symfony/finder": "^6.0", - "symfony/process": "^6.0", - "vlucas/phpdotenv": "^5.2" - }, - "require-dev": { - "mockery/mockery": "^1.4.2", - "orchestra/testbench": "^7.0|^8.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.10|^10.0.1", - "psy/psysh": "^0.11.12" - }, - "suggest": { - "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Dusk\\DuskServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Dusk\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel Dusk provides simple end-to-end testing and browser automation.", - "keywords": [ - "laravel", - "testing", - "webdriver" - ], - "support": { - "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v7.8.0" - }, - "time": "2023-07-08T21:23:29+00:00" - }, { "name": "laravel/pint", "version": "v1.10.5", @@ -5294,37 +5219,33 @@ }, { "name": "mockery/mockery", - "version": "1.6.2", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191" + "reference": "d1413755e26fe56a63455f7753221c86cbb88f66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/13a7fa2642c76c58fa2806ef7f565344c817a191", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191", + "url": "https://api.github.com/repos/mockery/mockery/zipball/d1413755e26fe56a63455f7753221c86cbb88f66", + "reference": "d1413755e26fe56a63455f7753221c86cbb88f66", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.4 || ^8.0" + "php": ">=7.4,<8.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { "phpunit/phpunit": "^8.5 || ^9.3", - "psalm/plugin-phpunit": "^0.18", - "vimeo/psalm": "^5.9" + "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", + "vimeo/psalm": "^5.13.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.6.x-dev" - } - }, "autoload": { "files": [ "library/helpers.php", @@ -5342,12 +5263,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -5365,10 +5294,13 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.6.2" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2023-06-07T09:07:52+00:00" + "time": "2023-07-19T15:51:02+00:00" }, { "name": "myclabs/deep-copy", @@ -5828,72 +5760,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "php-webdriver/webdriver", - "version": "1.14.0", - "source": { - "type": "git", - "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "3ea4f924afb43056bf9c630509e657d951608563" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3ea4f924afb43056bf9c630509e657d951608563", - "reference": "3ea4f924afb43056bf9c630509e657d951608563", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-zip": "*", - "php": "^7.3 || ^8.0", - "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^5.0 || ^6.0" - }, - "replace": { - "facebook/webdriver": "*" - }, - "require-dev": { - "ergebnis/composer-normalize": "^2.20.0", - "ondram/ci-detector": "^4.0", - "php-coveralls/php-coveralls": "^2.4", - "php-mock/php-mock-phpunit": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^5.0 || ^6.0" - }, - "suggest": { - "ext-SimpleXML": "For Firefox profile creation" - }, - "type": "library", - "autoload": { - "files": [ - "lib/Exception/TimeoutException.php" - ], - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", - "homepage": "https://github.com/php-webdriver/php-webdriver", - "keywords": [ - "Chromedriver", - "geckodriver", - "php", - "selenium", - "webdriver" - ], - "support": { - "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.14.0" - }, - "time": "2023-02-09T12:12:19+00:00" - }, { "name": "phpunit/php-code-coverage", "version": "10.1.2", @@ -6216,16 +6082,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.2.5", + "version": "10.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd" + "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd", - "reference": "15a89f123d8ca9c1e1598d6d87a56a8bf28c72cd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c17815c129f133f3019cc18e8d0c8622e6d9bcd", + "reference": "1c17815c129f133f3019cc18e8d0c8622e6d9bcd", "shasum": "" }, "require": { @@ -6297,7 +6163,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.6" }, "funding": [ { @@ -6313,7 +6179,7 @@ "type": "tidelift" } ], - "time": "2023-07-14T04:18:47+00:00" + "time": "2023-07-17T12:08:28+00:00" }, { "name": "pimple/pimple", @@ -7030,16 +6896,16 @@ }, { "name": "sebastian/global-state", - "version": "6.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "aab257c712de87b90194febd52e4d184551c2d44" + "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44", - "reference": "aab257c712de87b90194febd52e4d184551c2d44", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", "shasum": "" }, "require": { @@ -7079,7 +6945,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" }, "funding": [ { @@ -7087,7 +6954,7 @@ "type": "github" } ], - "time": "2023-02-03T07:07:38+00:00" + "time": "2023-07-19T07:19:23+00:00" }, { "name": "sebastian/lines-of-code", @@ -7494,16 +7361,16 @@ }, { "name": "spatie/laravel-ray", - "version": "1.32.5", + "version": "1.32.6", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "288f30c94c9725dfd78d8a1b82b230521f4d697e" + "reference": "8526dd6c6c838b4bac4e0df2ea7896b688b0d43e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/288f30c94c9725dfd78d8a1b82b230521f4d697e", - "reference": "288f30c94c9725dfd78d8a1b82b230521f4d697e", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/8526dd6c6c838b4bac4e0df2ea7896b688b0d43e", + "reference": "8526dd6c6c838b4bac4e0df2ea7896b688b0d43e", "shasum": "" }, "require": { @@ -7563,7 +7430,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.32.5" + "source": "https://github.com/spatie/laravel-ray/tree/1.32.6" }, "funding": [ { @@ -7575,7 +7442,7 @@ "type": "other" } ], - "time": "2023-06-23T07:04:32+00:00" + "time": "2023-07-19T12:30:16+00:00" }, { "name": "spatie/macroable", diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 8c3b0f8..defd5b8 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -13,7 +13,6 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider as IlluminateServiceProvider; -use Laravel\Dusk\Browser; class ServiceProvider extends IlluminateServiceProvider { @@ -61,10 +60,6 @@ public function register(): void Factory::guessFactoryNamesUsing(function (string $modelName) { return Factory::$namespace.\class_basename($modelName).'Factory'; }); - - if (\class_exists(Browser::class)) { - $this->registerDuskMacroForInertia(); - } } $this->registerBindings(); @@ -121,25 +116,6 @@ protected function registerBindings() }); } - /** - * Register inertia.js helper for dusk testing - * - * @see https://github.com/protonemedia/inertiajs-events-laravel-dusk - */ - private function registerDuskMacroForInertia(): void - { - Browser::macro('waitForInertia', function (int $seconds = null): Browser { - /** @var Browser $this */ - $driver = $this->driver; - - $currentCount = $driver->executeScript('return window.__inertiaNavigatedCount;'); - - return $this->waitUsing($seconds, 100, fn () => $driver->executeScript( - "return window.__inertiaNavigatedCount > {$currentCount};" - ), 'Waited %s seconds for Inertia.js to increase the navigate count.'); - }); - } - private function bootViewComposers(): void { View::composer('*', TranslationsComposer::class); diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index c029bec..669fcf1 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -4,13 +4,10 @@ namespace Creasi\Tests; -use Facebook\WebDriver\Exception\TimeoutException; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Support\Facades\Route; -use Laravel\Dusk\Browser; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; -use SebastianBergmann\CodeCoverage\Driver\Driver; #[Group('serviceProvider')] class ServiceProviderTest extends TestCase @@ -35,61 +32,6 @@ public function should_able_to_disable_routes() /** @var \Countable */ $routes = Route::getRoutes(); - $this->assertCount(4, $routes); - } - - #[Test] - #[Group('dusk')] - public function should_adds_dusk_macros() - { - $this->assertTrue(Browser::hasMacro('waitForInertia')); - } - - #[Test] - #[Group('dusk')] - public function should_throws_an_exception_if_the_count_doesnt_increase() - { - $driver = $this->mock(Driver::class); - - $driver->shouldReceive('executeScript') - ->with('return window.__inertiaNavigatedCount;') - ->once() - ->andReturn(0); - - $driver->shouldReceive('executeScript') - ->with('return window.__inertiaNavigatedCount > 0;') - ->andReturn(0); - - $browser = new Browser($driver); - - try { - $browser->waitForInertia(0.1); // wait for 0.1 seconds - } catch (TimeoutException $e) { - return $this->assertTrue(true); - } - - $this->fail('Should have thrown TimeoutException'); - } - - #[Test] - #[Group('dusk')] - public function should_passes_when_the_count_increases() - { - $driver = $this->mock(Driver::class); - - $driver->shouldReceive('executeScript') - ->with('return window.__inertiaNavigatedCount;') - ->once() - ->andReturn(0); - - $driver->shouldReceive('executeScript') - ->with('return window.__inertiaNavigatedCount > 0;') - ->once() - ->andReturn(1); - - $browser = new Browser($driver); - $browser->waitForInertia(1); - - $this->assertTrue(true); + $this->assertCount(1, $routes); } } From c8574838ed3df0147a6f6cabe78be557940c5dae Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 00:02:29 +0700 Subject: [PATCH 48/71] chore: fix tax status casts Signed-off-by: Fery Wardiyanto --- database/factories/ProfileFactory.php | 5 ++++- src/Contracts/HasTaxInfo.php | 2 +- src/Models/Concerns/WithTaxInfo.php | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/database/factories/ProfileFactory.php b/database/factories/ProfileFactory.php index 8980a03..12294bc 100644 --- a/database/factories/ProfileFactory.php +++ b/database/factories/ProfileFactory.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Enums\Education; use Creasi\Base\Models\Enums\Religion; +use Creasi\Base\Models\Enums\TaxStatus; use Creasi\Base\Models\Profile; use Creasi\Nusa\Models\Regency; use Illuminate\Contracts\Database\Query\Builder; @@ -26,11 +27,13 @@ public function definition(): array })->inRandomOrder()->first(); return [ - 'nik' => $this->faker->nik(null, $birthDate = $this->faker->dateTime()), + 'nik' => $nik = $this->faker->nik(null, $birthDate = $this->faker->dateTime()), 'birth_date' => $birthDate->format('Y-m-d'), 'birth_place_code' => $birthPlace->code, 'education' => $this->faker->randomElement(Education::cases()), 'religion' => $this->faker->randomElement(Religion::cases()), + 'tax_status' => $this->faker->randomElement(TaxStatus::cases()), + 'tax_id' => $nik, ]; } } diff --git a/src/Contracts/HasTaxInfo.php b/src/Contracts/HasTaxInfo.php index 2638248..e7890f1 100644 --- a/src/Contracts/HasTaxInfo.php +++ b/src/Contracts/HasTaxInfo.php @@ -3,7 +3,7 @@ namespace Creasi\Base\Contracts; /** - * @property null|int $tax_status + * @property null|\Creasi\Base\Models\Enums\TaxStatus $tax_status * @property null|string $tax_id * * @mixin \Illuminate\Database\Eloquent\Model diff --git a/src/Models/Concerns/WithTaxInfo.php b/src/Models/Concerns/WithTaxInfo.php index b5a9e3f..1b8d595 100644 --- a/src/Models/Concerns/WithTaxInfo.php +++ b/src/Models/Concerns/WithTaxInfo.php @@ -2,6 +2,8 @@ namespace Creasi\Base\Models\Concerns; +use Creasi\Base\Models\Enums\TaxStatus; + /** * @mixin \Creasi\Base\Contracts\HasTaxInfo */ @@ -13,7 +15,7 @@ trait WithTaxInfo public function initializeWithTaxInfo(): void { $this->mergeCasts([ - 'tax_status' => 'int', + 'tax_status' => TaxStatus::class, ]); $this->mergeFillable([ From 614186d4f084a577619cb4b3d764168411e109b8 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 00:18:09 +0700 Subject: [PATCH 49/71] chore: use `Str::snake()` instead of `Str::slug()` for enums key Signed-off-by: Fery Wardiyanto --- src/Models/Enums/KeyableEnum.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Models/Enums/KeyableEnum.php b/src/Models/Enums/KeyableEnum.php index 429774f..4ae2e23 100644 --- a/src/Models/Enums/KeyableEnum.php +++ b/src/Models/Enums/KeyableEnum.php @@ -16,7 +16,7 @@ public function key(): Stringable { assert($this instanceof \BackedEnum, '"KeyableEnum" should only be used in an emun'); - return str($this->name)->slug(); + return str($this->name)->snake('-'); } /** @@ -24,7 +24,7 @@ public function key(): Stringable */ public function label(): string { - $self = str(static::class)->classBasename()->slug(); + $self = str(static::class)->classBasename()->snake('-'); return trans("creasico::base.{$self}.{$this->key()}"); } From 3666879101356bc9fdb00e7b87219a16a4889165 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 00:44:27 +0700 Subject: [PATCH 50/71] chore: add missing `religion` locale Signed-off-by: Fery Wardiyanto --- resources/lang/en/base.php | 1 + resources/lang/id/base.php | 1 + 2 files changed, 2 insertions(+) diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 212f057..87cdd2f 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -7,6 +7,7 @@ ], 'religion' => [ + 'other' => 'Other Religion', 'islam' => 'Islam', 'christian' => 'Christian', 'catholic' => 'Catholic', diff --git a/resources/lang/id/base.php b/resources/lang/id/base.php index 8e05813..fd69d67 100644 --- a/resources/lang/id/base.php +++ b/resources/lang/id/base.php @@ -7,6 +7,7 @@ ], 'religion' => [ + 'other' => 'Kepercaan Lainnya', 'islam' => 'Islam', 'christian' => 'Kristen', 'catholic' => 'Katolik', From 370093a6a3e02c60594b6dac687f7bcb9185f273 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 01:01:50 +0700 Subject: [PATCH 51/71] feat: adds ability to turn enum cases into an option Signed-off-by: Fery Wardiyanto --- src/Models/Enums/Education.php | 2 ++ src/Models/Enums/KeyableEnum.php | 2 ++ src/Models/Enums/OptionableEnum.php | 35 +++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 src/Models/Enums/OptionableEnum.php diff --git a/src/Models/Enums/Education.php b/src/Models/Enums/Education.php index 64e1705..53c1e27 100644 --- a/src/Models/Enums/Education.php +++ b/src/Models/Enums/Education.php @@ -4,6 +4,8 @@ enum Education: string { + use OptionableEnum; + case Uneducated = '-'; case SD = 'SD'; case SMP = 'SMP'; diff --git a/src/Models/Enums/KeyableEnum.php b/src/Models/Enums/KeyableEnum.php index 4ae2e23..4809744 100644 --- a/src/Models/Enums/KeyableEnum.php +++ b/src/Models/Enums/KeyableEnum.php @@ -9,6 +9,8 @@ */ trait KeyableEnum { + use OptionableEnum; + /** * Retrieve translation key. */ diff --git a/src/Models/Enums/OptionableEnum.php b/src/Models/Enums/OptionableEnum.php new file mode 100644 index 0000000..aa07358 --- /dev/null +++ b/src/Models/Enums/OptionableEnum.php @@ -0,0 +1,35 @@ + $self->value, + ]; + + if (\in_array(KeyableEnum::class, \trait_uses_recursive($self), true)) { + $option['key'] = (string) $self->key(); + $option['label'] = $self->label(); + } else { + $option['key'] = $self->name; + } + + $options[] = $option; + } + + return $options; + } +} From c41652663540c26402018290dd26a6d6867e7c4b Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 01:03:23 +0700 Subject: [PATCH 52/71] feat: add `UserFactory::withIdentity()` method So we can generate new user including its `profile` and `identity` relation Signed-off-by: Fery Wardiyanto --- database/factories/UserFactory.php | 7 +++++++ tests/TestCase.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index c03ac6c..7153cf8 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -2,6 +2,8 @@ namespace Database\Factories; +use Creasi\Base\Models\Enums\Gender; +use Creasi\Base\Models\Personnel; use Creasi\Base\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; @@ -38,4 +40,9 @@ public function unverified(): static 'email_verified_at' => null, ]); } + + public function withIdentity(Gender $gender = null): static + { + return $this->has(Personnel::factory()->withProfile($gender), 'identity'); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index f8758f7..2121e4a 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -32,7 +32,7 @@ protected function getPackageProviders($app): array final protected function user(array|Closure $attrs = []): User { if (! $this->currentUser?->exists) { - $this->currentUser = User::factory()->createOne($attrs); + $this->currentUser = User::factory()->withIdentity()->createOne($attrs); } return $this->currentUser; From 77b91aedf55a25e9a4691f35ff0c52d1214194e1 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 01:09:46 +0700 Subject: [PATCH 53/71] chore: fix translation for `tax-status` Signed-off-by: Fery Wardiyanto --- resources/lang/en/base.php | 16 ++++++++-------- resources/lang/id/base.php | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index 87cdd2f..adfe518 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -31,18 +31,18 @@ ], 'tax-status' => [ - 'tk0' => 'TK/0', - 'tk1' => 'TK/1', - 'tk2' => 'TK/2', - 'tk3' => 'TK/3', + 't-k0' => 'TK/0', + 't-k1' => 'TK/1', + 't-k2' => 'TK/2', + 't-k3' => 'TK/3', 'k0' => 'K/0', 'k1' => 'K/1', 'k2' => 'K/2', 'k3' => 'K/3', - 'ki0' => 'K/I/0', - 'ki1' => 'K/I/1', - 'ki2' => 'K/I/2', - 'ki3' => 'K/I/3', + 'k-i0' => 'K/I/0', + 'k-i1' => 'K/I/1', + 'k-i2' => 'K/I/2', + 'k-i3' => 'K/I/3', ], 'personnel-relative-status' => [ diff --git a/resources/lang/id/base.php b/resources/lang/id/base.php index fd69d67..d10c078 100644 --- a/resources/lang/id/base.php +++ b/resources/lang/id/base.php @@ -31,18 +31,18 @@ ], 'tax-status' => [ - 'tk0' => 'TK/0', - 'tk1' => 'TK/1', - 'tk2' => 'TK/2', - 'tk3' => 'TK/3', + 't-k0' => 'TK/0', + 't-k1' => 'TK/1', + 't-k2' => 'TK/2', + 't-k3' => 'TK/3', 'k0' => 'K/0', 'k1' => 'K/1', 'k2' => 'K/2', 'k3' => 'K/3', - 'ki0' => 'K/I/0', - 'ki1' => 'K/I/1', - 'ki2' => 'K/I/2', - 'ki3' => 'K/I/3', + 'k-i0' => 'K/I/0', + 'k-i1' => 'K/I/1', + 'k-i2' => 'K/I/2', + 'k-i3' => 'K/I/3', ], 'personnel-relative' => [ From 42444649f0035d2866e2a4d7bdea230fe0831663 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 01:43:48 +0700 Subject: [PATCH 54/71] feat: update basic api response and request for profile resource Signed-off-by: Fery Wardiyanto --- src/Http/Controllers/ProfileController.php | 4 +- src/Http/Requests/ProfileRequest.php | 39 ++++++++++++++- src/Http/Resources/ProfileResource.php | 57 +++++++++++++++++++++- src/Models/Profile.php | 1 - tests/Http/ProfileTest.php | 49 +++++++++++++++++-- 5 files changed, 142 insertions(+), 8 deletions(-) diff --git a/src/Http/Controllers/ProfileController.php b/src/Http/Controllers/ProfileController.php index 05407f3..4c40b97 100644 --- a/src/Http/Controllers/ProfileController.php +++ b/src/Http/Controllers/ProfileController.php @@ -15,6 +15,8 @@ public function show(Request $request) { $user = $request->user(); + $user->load('identity.profile'); + return ProfileResource::make($user); } @@ -23,7 +25,7 @@ public function show(Request $request) */ public function update(ProfileRequest $request) { - $user = $request->user(); + $user = $request->fulfill(); return ProfileResource::make($user); } diff --git a/src/Http/Requests/ProfileRequest.php b/src/Http/Requests/ProfileRequest.php index d4a07e8..25408c0 100644 --- a/src/Http/Requests/ProfileRequest.php +++ b/src/Http/Requests/ProfileRequest.php @@ -2,6 +2,10 @@ namespace Creasi\Base\Http\Requests; +use Creasi\Base\Models\Enums\Education; +use Creasi\Base\Models\Enums\TaxStatus; +use Illuminate\Validation\Rule; + class ProfileRequest extends FormRequest { /** @@ -10,7 +14,40 @@ class ProfileRequest extends FormRequest public function rules(): array { return [ - // . + 'fullname' => ['required', 'string'], + 'nickname' => ['nullable', 'string'], + 'phone' => ['required', 'string'], + 'summary' => ['nullable', 'string'], + 'prefix' => ['nullable', 'string'], + 'suffix' => ['nullable', 'string'], + 'education' => ['nullable', Rule::enum(Education::class)], + 'tax_status' => ['nullable', Rule::enum(TaxStatus::class)], + 'tax_id' => ['nullable', 'string'], ]; } + + public function fulfill() + { + $user = $this->user(); + $data = $this->validated(); + + $user->identity()->update([ + 'name' => $data['fullname'], + 'alias' => $data['nickname'], + 'phone' => $data['phone'], + 'summary' => $data['summary'], + ]); + + $user->identity->profile()->update([ + 'prefix' => $data['prefix'], + 'suffix' => $data['suffix'], + 'education' => $this->enum('education', Education::class), + 'tax_status' => $this->enum('tax_status', TaxStatus::class), + 'tax_id' => $data['tax_id'], + ]); + + $user->load('identity.profile'); + + return $user; + } } diff --git a/src/Http/Resources/ProfileResource.php b/src/Http/Resources/ProfileResource.php index 4ca3a28..3f48bdd 100644 --- a/src/Http/Resources/ProfileResource.php +++ b/src/Http/Resources/ProfileResource.php @@ -2,9 +2,14 @@ namespace Creasi\Base\Http\Resources; +use Creasi\Base\Models\Enums\Education; +use Creasi\Base\Models\Enums\TaxStatus; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin \Creasi\Base\Models\User + */ class ProfileResource extends JsonResource { /** @@ -14,6 +19,56 @@ class ProfileResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + $resource = [ + $this->getKeyName() => $this->getKey(), + 'avatar' => null, + 'fullname' => $this->identity->name, + 'nickname' => $this->identity->alias, + 'username' => $this->name, + 'email' => $this->identity->email, + 'phone' => $this->identity->phone, + 'gender' => [ + 'value' => $this->identity->gender?->value, + 'label' => $this->identity->gender?->label(), + ], + 'summary' => $this->identity->summary, + ]; + + if ($avatar = $this->identity->avatar) { + $resource['avatar'] = [ + 'url' => $avatar->url, + 'alt' => $avatar->title, + ]; + } + + if ($profile = $this->identity->profile) { + $resource['nik'] = $profile->nik; + $resource['prefix'] = $profile->prefix; + $resource['suffix'] = $profile->suffix; + $resource['birth_date'] = $profile->birth_date; + $resource['birth_place'] = [ + 'name' => $profile->birthPlace->name, + 'code' => $profile->birthPlace->code, + ]; + $resource['education'] = $profile->education?->value; + $resource['religion'] = [ + 'value' => $profile->religion?->value, + 'label' => $profile->religion?->label(), + ]; + $resource['tax_status'] = [ + 'value' => $profile->tax_status?->value, + 'label' => $profile->tax_status?->label(), + ]; + $resource['tax_id'] = $profile->tax_id; + } + + $this->additional([ + 'meta' => [ + 'educations' => Education::toOptions(), + 'tax_statuses' => TaxStatus::toOptions(), + ], + ]); + + return $resource; } } diff --git a/src/Models/Profile.php b/src/Models/Profile.php index f33f3be..2418319 100644 --- a/src/Models/Profile.php +++ b/src/Models/Profile.php @@ -43,7 +43,6 @@ class Profile extends Model implements HasIdentity, HasTaxInfo ]; protected $casts = [ - 'user_id' => 'int', 'birth_date' => 'immutable_date', 'birth_place_code' => 'int', 'education' => Education::class, diff --git a/tests/Http/ProfileTest.php b/tests/Http/ProfileTest.php index 9fa0420..3b5355c 100644 --- a/tests/Http/ProfileTest.php +++ b/tests/Http/ProfileTest.php @@ -12,6 +12,35 @@ #[Group('profile')] class ProfileTest extends TestCase { + private array $responseStructure = [ + 'data' => [ + 'avatar', + 'fullname', + 'nickname', + 'email', + 'phone', + 'gender' => ['value', 'label'], + 'summary', + 'nik', + 'prefix', + 'suffix', + 'birth_date', + 'birth_place' => ['name', 'code'], + 'education', + 'religion' => ['value', 'label'], + 'tax_status' => ['value', 'label'], + 'tax_id', + ], + 'meta' => [ + 'educations' => [ + ['key', 'value'], + ], + 'tax_statuses' => [ + ['key', 'value', 'label'], + ], + ], + ]; + #[Test] public function should_able_to_retrieve_profile_data(): void { @@ -19,16 +48,28 @@ public function should_able_to_retrieve_profile_data(): void $response = $this->getJson('base/profile'); - $response->assertOk(); + $response->assertOk()->assertJsonStructure($this->responseStructure); } #[Test] public function should_able_to_update_profile_data(): void { - Sanctum::actingAs($this->user()); + Sanctum::actingAs($user = $this->user()); + + $user->load('identity.profile'); - $response = $this->putJson('base/profile'); + $response = $this->putJson('base/profile', [ + 'fullname' => $user->identity->name, + 'nickname' => $user->identity->alias, + 'phone' => $user->identity->phone, + 'summary' => null, + 'prefix' => null, + 'suffix' => null, + 'education' => $user->identity->profile->education->value, + 'tax_status' => $user->identity->profile->tax_status->value, + 'tax_id' => $user->identity->profile->tax_id, + ]); - $response->assertOk(); + $response->assertOk()->assertJsonStructure($this->responseStructure); } } From a7fceca2dfbb381b23990722e2a1b49d95167ffd Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 07:29:05 +0700 Subject: [PATCH 55/71] chore: fix route model binding for the abstracts and interfaces Signed-off-by: Fery Wardiyanto --- database/factories/PersonnelFactory.php | 23 ++++ database/factories/UserFactory.php | 9 +- src/Contracts/Company.php | 44 ++++++++ src/Contracts/Employee.php | 5 +- src/Contracts/Employer.php | 17 --- src/Http/Controllers/CompanyController.php | 20 ++-- src/Http/Controllers/EmployeeController.php | 9 +- .../Controllers/StakeholderController.php | 15 ++- src/Http/Requests/Company/StoreRequest.php | 4 +- src/Http/Requests/Company/UpdateRequest.php | 1 + src/Http/Requests/Employee/StoreRequest.php | 3 +- src/Http/Requests/Employee/UpdateRequest.php | 1 + .../Requests/Stakeholder/StoreRequest.php | 16 ++- .../Requests/Stakeholder/UpdateRequest.php | 1 + src/Models/Business.php | 52 +-------- src/Models/BusinessRelative.php | 2 + src/Models/Concerns/AsCompany.php | 105 ++++++++++++++++++ src/Models/Concerns/AsEmployee.php | 25 ++++- src/Models/Concerns/AsEmployer.php | 23 ---- src/Models/Concerns/WithAvatar.php | 16 ++- src/Models/Profile.php | 2 - src/Repository.php | 49 +++++++- src/ServiceProvider.php | 27 ++++- .../{BusinessTest.php => CompanyTest.php} | 10 +- tests/Http/Business/EmployeeTest.php | 10 -- tests/Http/ProfileTest.php | 6 +- tests/Http/StakeholderTestCase.php | 19 +++- tests/TestCase.php | 5 +- 28 files changed, 367 insertions(+), 152 deletions(-) create mode 100644 src/Contracts/Company.php delete mode 100644 src/Contracts/Employer.php create mode 100644 src/Models/Concerns/AsCompany.php delete mode 100644 src/Models/Concerns/AsEmployer.php rename tests/Http/Business/{BusinessTest.php => CompanyTest.php} (92%) diff --git a/database/factories/PersonnelFactory.php b/database/factories/PersonnelFactory.php index c9ea210..b033dae 100644 --- a/database/factories/PersonnelFactory.php +++ b/database/factories/PersonnelFactory.php @@ -3,10 +3,14 @@ namespace Database\Factories; use Creasi\Base\Models\Address; +use Creasi\Base\Models\Business; +use Creasi\Base\Models\Enums\EmploymentStatus; +use Creasi\Base\Models\Enums\EmploymentType; use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Base\Models\Profile; +use DateTimeInterface; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -47,6 +51,25 @@ public function withProfile(Gender $gender = null): static ]); } + public function withCompany( + bool $primary = null, + EmploymentType $type = null, + EmploymentStatus $status = null, + false|DateTimeInterface $startDate = null, + ): static { + if (null === $startDate) { + $startDate = $this->faker->dateTime(); + } + + return $this->hasAttached(Business::factory(), [ + 'is_primary' => $primary, + 'type' => $type ?? $this->faker->randomElement(EmploymentType::cases()), + 'status' => $status ?? $this->faker->randomElement(EmploymentStatus::cases()), + 'start_date' => $startDate?->format('Y-m-d'), + 'finish_date' => null, + ], 'employers'); + } + public function withAddress(): static { return $this->has(Address::factory()); diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 7153cf8..5c01c73 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -2,7 +2,6 @@ namespace Database\Factories; -use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\Personnel; use Creasi\Base\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -41,8 +40,12 @@ public function unverified(): static ]); } - public function withIdentity(Gender $gender = null): static + public function withIdentity(\Closure $cb = null): static { - return $this->has(Personnel::factory()->withProfile($gender), 'identity'); + if (null === $cb) { + $cb = fn (PersonnelFactory $identity) => $identity->withProfile(); + } + + return $this->has($cb(Personnel::factory()), 'identity'); } } diff --git a/src/Contracts/Company.php b/src/Contracts/Company.php new file mode 100644 index 0000000..146a2c6 --- /dev/null +++ b/src/Contracts/Company.php @@ -0,0 +1,44 @@ + $employees + * @property-read \Illuminate\Database\Eloquent\Collection $individualRelatives + * @property-read \Illuminate\Database\Eloquent\Collection $companyRelatives + * @property-read \Illuminate\Database\Eloquent\Collection $stakeholders + * + * @mixin \Illuminate\Database\Eloquent\Model + */ +interface Company +{ + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Employee + */ + public function employees(); + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|BusinessRelative + */ + public function companyRelatives(); + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|BusinessRelative + */ + public function individualRelatives(); + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative + */ + public function stakeholders(); + + public function addStakeholder( + BusinessRelativeType $type, + Entity $stakeholder, + bool $internal = null, + ): static; +} diff --git a/src/Contracts/Employee.php b/src/Contracts/Employee.php index 462aa24..43d1322 100644 --- a/src/Contracts/Employee.php +++ b/src/Contracts/Employee.php @@ -4,14 +4,15 @@ /** * @property-read \Creasi\Base\Models\Employment $employment - * @property-read \Illuminate\Database\Eloquent\Collection $employees + * @property-read \Illuminate\Database\Eloquent\Collection $employers + * @property-read null|Company $company * * @mixin \Illuminate\Database\Eloquent\Model */ interface Employee { /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Employer + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Company */ public function employers(); } diff --git a/src/Contracts/Employer.php b/src/Contracts/Employer.php deleted file mode 100644 index 56b2bcb..0000000 --- a/src/Contracts/Employer.php +++ /dev/null @@ -1,17 +0,0 @@ - $employees - * - * @mixin \Illuminate\Database\Eloquent\Model - */ -interface Employer -{ - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Employee - */ - public function employees(); -} diff --git a/src/Http/Controllers/CompanyController.php b/src/Http/Controllers/CompanyController.php index b167556..f6d7863 100644 --- a/src/Http/Controllers/CompanyController.php +++ b/src/Http/Controllers/CompanyController.php @@ -2,11 +2,11 @@ namespace Creasi\Base\Http\Controllers; +use Creasi\Base\Contracts\Company; use Creasi\Base\Http\Requests\Company\StoreRequest; use Creasi\Base\Http\Requests\Company\UpdateRequest; use Creasi\Base\Http\Resources\Company\CompanyCollection; use Creasi\Base\Http\Resources\Company\CompanyResource; -use Creasi\Base\Models\Business; use Illuminate\Http\Request; class CompanyController extends Controller @@ -19,9 +19,11 @@ public function __construct() /** * @return CompanyCollection */ - public function index() + public function index(Company $company) { - $items = Business::query()->latest(); + $items = $company->subsidiaries()->latest('id'); + + $items->with('stakeholder'); return new CompanyCollection($items->paginate()); } @@ -29,10 +31,10 @@ public function index() /** * @return CompanyResource */ - public function store(StoreRequest $request) + public function store(StoreRequest $request, Company $company) { - /** @var Business $item */ - $item = Business::create($request->validated()); + /** @var Company $item */ + $item = $company->create($request->validated()); return $this->show($item, $request)->setStatusCode(201); } @@ -40,7 +42,7 @@ public function store(StoreRequest $request) /** * @return CompanyResource */ - public function show(Business $company, Request $request) + public function show(Company $company, Request $request) { return CompanyResource::make($company)->toResponse($request); } @@ -48,7 +50,7 @@ public function show(Business $company, Request $request) /** * @return CompanyResource */ - public function update(UpdateRequest $request, Business $company) + public function update(UpdateRequest $request, Company $company) { $company->update($request->validated()); @@ -58,7 +60,7 @@ public function update(UpdateRequest $request, Business $company) /** * @return \Illuminate\Http\Response */ - public function destroy(Business $company) + public function destroy(Company $company) { $company->delete(); diff --git a/src/Http/Controllers/EmployeeController.php b/src/Http/Controllers/EmployeeController.php index ac3061c..a04f1d0 100644 --- a/src/Http/Controllers/EmployeeController.php +++ b/src/Http/Controllers/EmployeeController.php @@ -2,6 +2,7 @@ namespace Creasi\Base\Http\Controllers; +use Creasi\Base\Contracts\Company; use Creasi\Base\Contracts\Employee; use Creasi\Base\Http\Requests\Employee\StoreRequest; use Creasi\Base\Http\Requests\Employee\UpdateRequest; @@ -19,9 +20,9 @@ public function __construct() /** * @return EmployeeCollection */ - public function index(Employee $employee) + public function index(Company $company) { - $items = $employee->newInstance()->latest(); + $items = $company->employees()->latest(); return new EmployeeCollection($items->paginate()); } @@ -29,10 +30,10 @@ public function index(Employee $employee) /** * @return EmployeeResource */ - public function store(StoreRequest $request, Employee $employee) + public function store(StoreRequest $request, Company $company) { /** @var Employee */ - $item = $employee->create($request->validated()); + $item = $company->employees()->create($request->validated()); return $this->show($item, $request)->setStatusCode(201); } diff --git a/src/Http/Controllers/StakeholderController.php b/src/Http/Controllers/StakeholderController.php index c6e2c69..eb99d73 100644 --- a/src/Http/Controllers/StakeholderController.php +++ b/src/Http/Controllers/StakeholderController.php @@ -2,11 +2,13 @@ namespace Creasi\Base\Http\Controllers; +use Creasi\Base\Contracts\Company; use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Http\Requests\Stakeholder\StoreRequest; use Creasi\Base\Http\Requests\Stakeholder\UpdateRequest; use Creasi\Base\Http\Resources\Stakeholder\StakeholderCollection; use Creasi\Base\Http\Resources\Stakeholder\StakeholderResource; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Illuminate\Http\Request; class StakeholderController extends Controller @@ -19,9 +21,13 @@ public function __construct() /** * @return StakeholderCollection */ - public function index(Stakeholder $stakeholder) + public function index(Company $company, BusinessRelativeType $type) { - $items = $stakeholder->newQuery()->latest(); + $items = $company->stakeholders()->where([ + 'type' => $type, + ])->latest('id'); + + $items->with('stakeholder'); return new StakeholderCollection($items->paginate()); } @@ -29,10 +35,9 @@ public function index(Stakeholder $stakeholder) /** * @return StakeholderResource */ - public function store(StoreRequest $request, Stakeholder $stakeholder) + public function store(StoreRequest $request, Company $company, BusinessRelativeType $type) { - /** @var Stakeholder */ - $item = $stakeholder->create($request->validated()); + $item = $request->fulfill($company, $type); return $this->show($item, $request)->setStatusCode(201); } diff --git a/src/Http/Requests/Company/StoreRequest.php b/src/Http/Requests/Company/StoreRequest.php index fc4ddb2..9bf3ea6 100644 --- a/src/Http/Requests/Company/StoreRequest.php +++ b/src/Http/Requests/Company/StoreRequest.php @@ -3,6 +3,7 @@ namespace Creasi\Base\Http\Requests\Company; use Creasi\Base\Http\Requests\FormRequest; +use Illuminate\Validation\Rule; class StoreRequest extends FormRequest { @@ -13,7 +14,8 @@ public function rules(): array { return [ 'name' => ['required', 'string'], - 'email' => ['required', 'email'], + 'alias' => ['nullable', 'string', Rule::unique('businesses', 'alias')], + 'email' => ['required', 'email', Rule::unique('businesses', 'email')], 'phone_number' => ['nullable', 'numeric'], 'summary' => ['nullable', 'string'], ]; diff --git a/src/Http/Requests/Company/UpdateRequest.php b/src/Http/Requests/Company/UpdateRequest.php index 98ac2ea..660db60 100644 --- a/src/Http/Requests/Company/UpdateRequest.php +++ b/src/Http/Requests/Company/UpdateRequest.php @@ -13,6 +13,7 @@ public function rules(): array { return [ 'name' => ['required', 'string'], + 'alias' => ['nullable', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], 'summary' => ['nullable', 'string'], diff --git a/src/Http/Requests/Employee/StoreRequest.php b/src/Http/Requests/Employee/StoreRequest.php index 715d0fc..58df556 100644 --- a/src/Http/Requests/Employee/StoreRequest.php +++ b/src/Http/Requests/Employee/StoreRequest.php @@ -15,7 +15,8 @@ public function rules(): array { return [ 'name' => ['required', 'string'], - 'email' => ['required', 'email'], + 'alias' => ['nullable', 'string', Rule::unique('personnels', 'alias')], + 'email' => ['required', 'email', Rule::unique('personnels', 'email')], 'phone' => ['nullable', 'numeric'], 'gender' => ['required', Rule::enum(Gender::class)], 'summary' => ['nullable', 'string'], diff --git a/src/Http/Requests/Employee/UpdateRequest.php b/src/Http/Requests/Employee/UpdateRequest.php index 43b3fca..941b2c3 100644 --- a/src/Http/Requests/Employee/UpdateRequest.php +++ b/src/Http/Requests/Employee/UpdateRequest.php @@ -13,6 +13,7 @@ public function rules(): array { return [ 'name' => ['required', 'string'], + 'alias' => ['nullable', 'string'], 'email' => ['required', 'email'], 'phone' => ['nullable', 'numeric'], 'summary' => ['nullable', 'string'], diff --git a/src/Http/Requests/Stakeholder/StoreRequest.php b/src/Http/Requests/Stakeholder/StoreRequest.php index 9667cc0..2aefda0 100644 --- a/src/Http/Requests/Stakeholder/StoreRequest.php +++ b/src/Http/Requests/Stakeholder/StoreRequest.php @@ -2,7 +2,10 @@ namespace Creasi\Base\Http\Requests\Stakeholder; +use Creasi\Base\Contracts\Company; use Creasi\Base\Http\Requests\FormRequest; +use Creasi\Base\Models\Enums\BusinessRelativeType; +use Illuminate\Validation\Rule; class StoreRequest extends FormRequest { @@ -13,9 +16,20 @@ public function rules(): array { return [ 'name' => ['required', 'string'], - 'email' => ['required', 'email'], + 'alias' => ['nullable', 'string', Rule::unique('businesses', 'alias')], + 'email' => ['required', 'email', Rule::unique('businesses', 'email')], 'phone_number' => ['nullable', 'numeric'], 'summary' => ['nullable', 'string'], ]; } + + public function fulfill(Company $company, BusinessRelativeType $type) + { + /** @var \Creasi\Base\Models\Entity */ + $entity = $company->newInstance($this->validated()); + + $company->addStakeholder($type, $entity); + + return $entity; + } } diff --git a/src/Http/Requests/Stakeholder/UpdateRequest.php b/src/Http/Requests/Stakeholder/UpdateRequest.php index ebf512d..af4692f 100644 --- a/src/Http/Requests/Stakeholder/UpdateRequest.php +++ b/src/Http/Requests/Stakeholder/UpdateRequest.php @@ -13,6 +13,7 @@ public function rules(): array { return [ 'name' => ['required', 'string'], + 'alias' => ['nullable', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], 'summary' => ['nullable', 'string'], diff --git a/src/Models/Business.php b/src/Models/Business.php index 92f5613..f85b607 100644 --- a/src/Models/Business.php +++ b/src/Models/Business.php @@ -2,23 +2,19 @@ namespace Creasi\Base\Models; -use Creasi\Base\Contracts\Employer; +use Creasi\Base\Contracts\Company; use Creasi\Base\Contracts\HasTaxInfo; -use Creasi\Base\Models\Concerns\AsEmployer; +use Creasi\Base\Models\Concerns\AsCompany; use Creasi\Base\Models\Concerns\WithTaxInfo; -use Creasi\Base\Models\Enums\BusinessRelativeType; /** - * @property-read \Illuminate\Database\Eloquent\Collection $individualRelatives - * @property-read \Illuminate\Database\Eloquent\Collection $companyRelatives - * @property-read \Illuminate\Database\Eloquent\Collection $stakeholders * @property-read BusinessRelative $stakeholder * * @method static \Database\Factories\BusinessFactory factory() */ -class Business extends Entity implements HasTaxInfo, Employer +class Business extends Entity implements HasTaxInfo, Company { - use AsEmployer; + use AsCompany; use WithTaxInfo; protected $fillable = [ @@ -28,44 +24,4 @@ class Business extends Entity implements HasTaxInfo, Employer protected $casts = [ // . ]; - - /** - * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|BusinessRelative - */ - protected function relatives(string $relative, bool $forward = true) - { - $relation = $forward - ? $this->morphedByMany($relative, 'stakeholder', 'business_relatives', 'business_id') - : $this->morphToMany(static::class, 'stakeholder', 'business_relatives', null, 'business_id'); - - return $relation->using(BusinessRelative::class)->withPivot('type', 'is_internal', 'code'); - } - - public function companyRelatives() - { - return $this->relatives(static::class)->as('stakeholder'); - } - - public function individualRelatives() - { - return $this->relatives(Personnel::class)->as('stakeholder'); - } - - public function stakeholders() - { - return $this->hasMany(BusinessRelative::class); - } - - public function addStakeholder( - BusinessRelativeType $type, - Entity $stakeholder, - bool $internal = null, - ): static { - $this->relatives(\get_class($stakeholder))->attach($stakeholder, [ - 'type' => $type, - 'is_internal' => $internal ?? $type->isInternal(), - ]); - - return $this->fresh(); - } } diff --git a/src/Models/BusinessRelative.php b/src/Models/BusinessRelative.php index 1f0dbd2..43502f1 100644 --- a/src/Models/BusinessRelative.php +++ b/src/Models/BusinessRelative.php @@ -22,6 +22,8 @@ class BusinessRelative extends MorphPivot 'type' => BusinessRelativeType::class, ]; + public $timestamps = false; + /** * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ diff --git a/src/Models/Concerns/AsCompany.php b/src/Models/Concerns/AsCompany.php new file mode 100644 index 0000000..a2c83b8 --- /dev/null +++ b/src/Models/Concerns/AsCompany.php @@ -0,0 +1,105 @@ + $owners + * @property-read \Illuminate\Database\Eloquent\Collection $subsidiaries + * @property-read \Illuminate\Database\Eloquent\Collection $customers + * @property-read \Illuminate\Database\Eloquent\Collection $suppliers + * @property-read \Illuminate\Database\Eloquent\Collection $vendors + * + * @method \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative owners() + * @method \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative subsidiaries() + * @method \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative customers() + * @method \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative suppliers() + * @method \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative vendors() + * + * @mixin Company + */ +trait AsCompany +{ + /** + * Initialize the trait. + */ + final protected static function bootAsCompany(): void + { + foreach (BusinessRelativeType::cases() as $stakeholder) { + static::resolveRelationUsing( + (string) $stakeholder->key()->plural(), + fn (Company $model) => $model->stakeholders()->where([ + 'type' => $stakeholder, + ]) + ); + } + } + + /** + * Initialize the trait. + */ + final protected function initializeAsCompany(): void + { + // . + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Personnel + */ + public function employees() + { + return $this->belongsToMany(Personnel::class, 'employments', 'employer_id', 'employee_id') + ->withPivot('is_primary', 'type', 'status', 'start_date', 'finish_date') + ->using(Employment::class) + ->as('employment'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\MorphToMany|BusinessRelative + */ + protected function relatives(string $relative, bool $forward = true) + { + $relation = $forward + ? $this->morphedByMany($relative, 'stakeholder', 'business_relatives', 'business_id') + : $this->morphToMany(static::class, 'stakeholder', 'business_relatives', null, 'business_id'); + + return $relation->using(BusinessRelative::class)->withPivot('type', 'is_internal', 'code'); + } + + public function companyRelatives() + { + return $this->relatives(static::class)->as('stakeholder'); + } + + public function individualRelatives() + { + return $this->relatives(Personnel::class)->as('stakeholder'); + } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany|BusinessRelative + */ + public function stakeholders() + { + return $this->hasMany(BusinessRelative::class); + } + + public function addStakeholder( + BusinessRelativeType $type, + Entity $stakeholder, + bool $internal = null, + ): static { + $this->relatives(\get_class($stakeholder))->attach($stakeholder, [ + 'type' => $type, + 'is_internal' => $internal ?? $type->isInternal(), + ]); + + return $this->fresh(); + } +} diff --git a/src/Models/Concerns/AsEmployee.php b/src/Models/Concerns/AsEmployee.php index 4df5505..5a8d7ab 100644 --- a/src/Models/Concerns/AsEmployee.php +++ b/src/Models/Concerns/AsEmployee.php @@ -4,12 +4,23 @@ use Creasi\Base\Models\Business; use Creasi\Base\Models\Employment; +use Illuminate\Database\Eloquent\Casts\Attribute; /** - * @mixin \Creasi\Base\Contracts\HasFileUploads + * @mixin \Creasi\Base\Contracts\Employee */ trait AsEmployee { + /** + * Initialize the trait. + */ + final protected function initializeAsEmployee(): void + { + $this->append('company'); + + $this->makeHidden('primaryEmployer'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Business */ @@ -20,4 +31,16 @@ public function employers() ->using(Employment::class) ->as('employment'); } + + public function company(): Attribute + { + $this->loadMissing('primaryEmployer'); + + return Attribute::get(fn () => $this->primaryEmployer?->first()); + } + + public function primaryEmployer() + { + return $this->employers()->wherePivot('is_primary', '=', true); + } } diff --git a/src/Models/Concerns/AsEmployer.php b/src/Models/Concerns/AsEmployer.php deleted file mode 100644 index d740beb..0000000 --- a/src/Models/Concerns/AsEmployer.php +++ /dev/null @@ -1,23 +0,0 @@ -belongsToMany(Personnel::class, 'employments', 'employer_id', 'employee_id') - ->withPivot('is_primary', 'type', 'status', 'start_date', 'finish_date') - ->using(Employment::class) - ->as('employment'); - } -} diff --git a/src/Models/Concerns/WithAvatar.php b/src/Models/Concerns/WithAvatar.php index 4ea6bbb..ffed431 100644 --- a/src/Models/Concerns/WithAvatar.php +++ b/src/Models/Concerns/WithAvatar.php @@ -13,12 +13,24 @@ */ trait WithAvatar { + /** + * Initialize the trait. + */ + final protected function initializeWithAvatar(): void + { + $this->append('avatar'); + + $this->makeHidden('avatarFile'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\MorphOne|\Creasi\Base\Models\FileUpload */ public function avatar(): Attribute { - return Attribute::get(fn () => $this->getAvatarFile?->first()); + $this->loadMissing('avatarFile'); + + return Attribute::get(fn () => $this->avatarFile?->first()); } public function setAvatar(string|UploadedFile $image) @@ -26,7 +38,7 @@ public function setAvatar(string|UploadedFile $image) return $this->storeFile(FileUploadType::Avatar, $image, $this->getRouteKey(), 'Avatar Image'); } - protected function getAvatarFile() + public function avatarFile() { return $this->files()->where('type', FileUploadType::Avatar); } diff --git a/src/Models/Profile.php b/src/Models/Profile.php index 2418319..3b8f48e 100644 --- a/src/Models/Profile.php +++ b/src/Models/Profile.php @@ -33,13 +33,11 @@ class Profile extends Model implements HasIdentity, HasTaxInfo protected $fillable = [ 'nik', 'prefix', - 'fullname', 'suffix', 'birth_date', 'birth_place_code', 'education', 'religion', - 'photo_path', ]; protected $casts = [ diff --git a/src/Repository.php b/src/Repository.php index 9bb2af4..364e41d 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -2,20 +2,35 @@ namespace Creasi\Base; +use Creasi\Base\Contracts\Company; use Creasi\Base\Contracts\Employee; -use Creasi\Base\Contracts\Employer; use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Business; +use Creasi\Base\Models\BusinessRelative; use Creasi\Base\Models\Entity; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Base\Models\Personnel; +use Creasi\Base\Models\User; +use Illuminate\Http\Request; use Illuminate\Routing\Router; class Repository { + protected ?User $user; + + protected array $stakeholders = []; + public function __construct( protected Router $router, + protected Request $request, ) { - // . + $this->user = $request->user(); + + $this->user->load('identity'); + + foreach (BusinessRelativeType::cases() as $stakeholder) { + $this->stakeholders[(string) $stakeholder->key()->plural()] = $stakeholder; + } } public function resolveEntity(): Entity @@ -30,16 +45,40 @@ public function resolveEntity(): Entity public function resolveEmployee(): Employee { + if ($personnel = $this->user->identity) { + $key = $this->router->input('employee'); + + return $key ? $personnel->newInstance()->resolveRouteBinding($key) : $personnel; + } + return app(Personnel::class); } - public function resolveEmployer(): Employer + public function resolveEmployer(): Company { + if ($company = $this->user->identity?->company) { + $key = $this->router->input('company'); + + return $key ? $company->newInstance()->resolveRouteBinding($key) : $company; + } + return app(Business::class); } - public function resolveStakeholder(): Stakeholder + public function resolveBusinessRelativeType(): BusinessRelativeType { - return app(Business::class); + $name = \explode('.', $this->router->currentRouteName())[0]; + + return $this->stakeholders[$name]; + } + + public function resolveStakeholder(Company $company, BusinessRelativeType $type): Stakeholder + { + $relative = BusinessRelative::with('stakeholder')->where([ + 'type' => $type, + 'stakeholder_id' => (int) $this->router->input('stakeholder'), + ]); + + return $relative->first()?->stakeholder ?: $company; } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index defd5b8..db944cb 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,11 +2,12 @@ namespace Creasi\Base; +use Creasi\Base\Contracts\Company; use Creasi\Base\Contracts\Employee; -use Creasi\Base\Contracts\Employer; use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Address; use Creasi\Base\Models\Entity; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Base\View\Composers\TranslationsComposer; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Mail; @@ -100,19 +101,33 @@ protected function defineRoutes(): void protected function registerBindings() { $this->app->bind(Entity::class, function ($app) { - return $app->make(Repository::class)->resolveEntity(); + $repo = $app->make(Repository::class); + + return $app->call([$repo, 'resolveEntity']); }); - $this->app->bind(Employer::class, function ($app) { - return $app->make(Repository::class)->resolveEmployer(); + $this->app->bind(Company::class, function ($app) { + $repo = $app->make(Repository::class); + + return $app->call([$repo, 'resolveEmployer']); }); $this->app->bind(Employee::class, function ($app) { - return $app->make(Repository::class)->resolveEmployee(); + $repo = $app->make(Repository::class); + + return $app->call([$repo, 'resolveEmployee']); }); $this->app->bind(Stakeholder::class, function ($app) { - return $app->make(Repository::class)->resolveStakeholder(); + $repo = $app->make(Repository::class); + + return $app->call([$repo, 'resolveStakeholder']); + }); + + $this->app->bind(BusinessRelativeType::class, function ($app) { + $repo = $app->make(Repository::class); + + return $app->call([$repo, 'resolveBusinessRelativeType']); }); } diff --git a/tests/Http/Business/BusinessTest.php b/tests/Http/Business/CompanyTest.php similarity index 92% rename from tests/Http/Business/BusinessTest.php rename to tests/Http/Business/CompanyTest.php index ecfb0a2..8828bc7 100644 --- a/tests/Http/Business/BusinessTest.php +++ b/tests/Http/Business/CompanyTest.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Business; +use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Base\Models\FileUpload; use Creasi\Tests\TestCase; use Illuminate\Http\UploadedFile; @@ -14,7 +15,7 @@ #[Group('api')] #[Group('company')] -class BusinessTest extends TestCase +class CompanyTest extends TestCase { #[Test] public function should_receive_404_when_no_data_available(): void @@ -29,8 +30,11 @@ public function should_receive_404_when_no_data_available(): void #[Test] public function should_able_to_retrieve_all_data(): void { - Sanctum::actingAs($this->user()); - Business::factory(2)->create(); + Sanctum::actingAs($user = $this->user()); + + $external = Business::factory()->createOne(['name' => 'Internal Company']); + + $user->identity->company->addStakeholder(BusinessRelativeType::Subsidiary, $external); $response = $this->getJson('base/companies'); diff --git a/tests/Http/Business/EmployeeTest.php b/tests/Http/Business/EmployeeTest.php index a9ce567..8d3bf0a 100644 --- a/tests/Http/Business/EmployeeTest.php +++ b/tests/Http/Business/EmployeeTest.php @@ -16,16 +16,6 @@ #[Group('employee')] class EmployeeTest extends TestCase { - #[Test] - public function should_receive_404_when_no_data_available(): void - { - Sanctum::actingAs($this->user()); - - $response = $this->getJson('base/employees'); - - $response->assertNotFound(); - } - #[Test] public function should_able_to_retrieve_all_data(): void { diff --git a/tests/Http/ProfileTest.php b/tests/Http/ProfileTest.php index 3b5355c..0af7b82 100644 --- a/tests/Http/ProfileTest.php +++ b/tests/Http/ProfileTest.php @@ -65,9 +65,9 @@ public function should_able_to_update_profile_data(): void 'summary' => null, 'prefix' => null, 'suffix' => null, - 'education' => $user->identity->profile->education->value, - 'tax_status' => $user->identity->profile->tax_status->value, - 'tax_id' => $user->identity->profile->tax_id, + 'education' => $user->identity->profile?->education->value, + 'tax_status' => $user->identity->profile?->tax_status->value, + 'tax_id' => $user->identity->profile?->tax_id, ]); $response->assertOk()->assertJsonStructure($this->responseStructure); diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php index c55a9e1..a3f4e82 100644 --- a/tests/Http/StakeholderTestCase.php +++ b/tests/Http/StakeholderTestCase.php @@ -41,8 +41,11 @@ public function should_receive_404_when_no_data_available(): void #[Test] public function should_able_to_retrieve_all_data(): void { - Sanctum::actingAs($this->user()); - Business::factory(2)->create(); + Sanctum::actingAs($user = $this->user()); + + $external = Business::factory()->createOne(['name' => 'External Company']); + + $user->identity->company->addStakeholder($this->getRelativeType(), $external); $response = $this->getJson($this->getRoutePath()); @@ -64,10 +67,12 @@ public function should_able_to_store_new_data(): void #[Test] public function should_able_to_show_existing_data(): void { - Sanctum::actingAs($this->user()); + Sanctum::actingAs($user = $this->user()); $model = Business::factory()->createOne(); + $user->identity->company->addStakeholder($this->getRelativeType(), $model); + $response = $this->getJson($this->getRoutePath($model)); $response->assertOk(); @@ -76,10 +81,12 @@ public function should_able_to_show_existing_data(): void #[Test] public function should_able_to_update_existing_data(): void { - Sanctum::actingAs($this->user()); + Sanctum::actingAs($user = $this->user()); $model = Business::factory()->createOne(); + $user->identity->company->addStakeholder($this->getRelativeType(), $model); + $response = $this->putJson($this->getRoutePath($model), $model->toArray()); $response->assertOk(); @@ -88,10 +95,12 @@ public function should_able_to_update_existing_data(): void #[Test] public function should_able_to_delete_existing_data(): void { - Sanctum::actingAs($this->user()); + Sanctum::actingAs($user = $this->user()); $model = Business::factory()->createOne(); + $user->identity->company->addStakeholder($this->getRelativeType(), $model); + $response = $this->deleteJson($this->getRoutePath($model)); $response->assertNoContent(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 2121e4a..d9d6d3d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,6 +6,7 @@ use Creasi\Base\Models\User; use Creasi\Base\ServiceProvider; use Creasi\Nusa\ServiceProvider as NusaServiceProvider; +use Database\Factories\PersonnelFactory; use Illuminate\Contracts\Config\Repository; use Illuminate\Foundation\Testing\RefreshDatabase; use Laravel\Sanctum\SanctumServiceProvider; @@ -32,7 +33,9 @@ protected function getPackageProviders($app): array final protected function user(array|Closure $attrs = []): User { if (! $this->currentUser?->exists) { - $this->currentUser = User::factory()->withIdentity()->createOne($attrs); + $this->currentUser = User::factory() + ->withIdentity(fn (PersonnelFactory $p) => $p->withProfile()->withCompany(true)) + ->createOne($attrs); } return $this->currentUser; From 18662c4ffd8787f9877acb0fea1d17cbb3a5d5df Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 07:37:01 +0700 Subject: [PATCH 56/71] chore: ensure the stakeholder is belongs to current company Signed-off-by: Fery Wardiyanto --- src/Models/BusinessRelative.php | 1 + src/Repository.php | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Models/BusinessRelative.php b/src/Models/BusinessRelative.php index 43502f1..44ee656 100644 --- a/src/Models/BusinessRelative.php +++ b/src/Models/BusinessRelative.php @@ -11,6 +11,7 @@ * @property bool $is_internal * @property null|BusinessRelativeType $type * @property null|string $remark + * @property-read Entity $stakeholder */ class BusinessRelative extends MorphPivot { diff --git a/src/Repository.php b/src/Repository.php index 364e41d..11be2d8 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -6,7 +6,6 @@ use Creasi\Base\Contracts\Employee; use Creasi\Base\Contracts\Stakeholder; use Creasi\Base\Models\Business; -use Creasi\Base\Models\BusinessRelative; use Creasi\Base\Models\Entity; use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Base\Models\Personnel; @@ -74,11 +73,12 @@ public function resolveBusinessRelativeType(): BusinessRelativeType public function resolveStakeholder(Company $company, BusinessRelativeType $type): Stakeholder { - $relative = BusinessRelative::with('stakeholder')->where([ + /** @var \Creasi\Base\Models\BusinessRelative */ + $relative = $company->stakeholders()->newQuery()->with('stakeholder')->where([ 'type' => $type, 'stakeholder_id' => (int) $this->router->input('stakeholder'), - ]); + ])->first(); - return $relative->first()?->stakeholder ?: $company; + return $relative?->stakeholder ?: $company->newInstance(); } } From 80549bb4b43eb0de0e8473bec620854d21e983fe Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 16:53:47 +0700 Subject: [PATCH 57/71] chore: do some clean ups Signed-off-by: Fery Wardiyanto --- src/Models/Business.php | 8 ++------ src/Models/BusinessRelative.php | 1 + src/Models/Employment.php | 5 +++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Models/Business.php b/src/Models/Business.php index f85b607..963d572 100644 --- a/src/Models/Business.php +++ b/src/Models/Business.php @@ -17,11 +17,7 @@ class Business extends Entity implements HasTaxInfo, Company use AsCompany; use WithTaxInfo; - protected $fillable = [ - // . - ]; + protected $fillable = []; - protected $casts = [ - // . - ]; + protected $casts = []; } diff --git a/src/Models/BusinessRelative.php b/src/Models/BusinessRelative.php index 44ee656..2a34434 100644 --- a/src/Models/BusinessRelative.php +++ b/src/Models/BusinessRelative.php @@ -19,6 +19,7 @@ class BusinessRelative extends MorphPivot protected $casts = [ 'business_id' => 'int', + 'stakeholder_id' => 'int', 'is_internal' => 'bool', 'type' => BusinessRelativeType::class, ]; diff --git a/src/Models/Employment.php b/src/Models/Employment.php index c3cdd67..309225c 100644 --- a/src/Models/Employment.php +++ b/src/Models/Employment.php @@ -15,8 +15,9 @@ * @property null|EmploymentStatus $status * @property null|\Carbon\CarbonImmutable $start_date * @property null|\Carbon\CarbonImmutable $finish_date - * @property null|bool $is_started - * @property null|bool $is_finished + * + * @property-reads null|bool $is_started + * @property-reads null|bool $is_finished */ class Employment extends Pivot { From a5076edf97f7d1940b7c000a0052694663b315aa Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 19:55:50 +0700 Subject: [PATCH 58/71] chore(db): rename database migration files to follow `testbench` convention Signed-off-by: Fery Wardiyanto --- database/.gitattributes | 4 +-- ...2_000000_testbench_create_users_table.php} | 0 ...000_testbench_create_failed_jobs_table.php | 32 +++++++++++++++++++ ...sessions_queue_and_notifications_table.php | 11 ------- 4 files changed, 34 insertions(+), 13 deletions(-) rename database/migrations/{2014_10_12_000000_create_users_table.php => 2014_10_12_000000_testbench_create_users_table.php} (100%) create mode 100644 database/migrations/2019_08_19_000000_testbench_create_failed_jobs_table.php diff --git a/database/.gitattributes b/database/.gitattributes index d6c566f..85b6b06 100644 --- a/database/.gitattributes +++ b/database/.gitattributes @@ -1,3 +1,3 @@ # Exclude unused files -2014_10_12_000000_create_users_table.php export-ignore -UserFactory.php export-ignore +*_testbench_*.php export-ignore +UserFactory.php export-ignore diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_testbench_create_users_table.php similarity index 100% rename from database/migrations/2014_10_12_000000_create_users_table.php rename to database/migrations/2014_10_12_000000_testbench_create_users_table.php diff --git a/database/migrations/2019_08_19_000000_testbench_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_testbench_create_failed_jobs_table.php new file mode 100644 index 0000000..d1e64d7 --- /dev/null +++ b/database/migrations/2019_08_19_000000_testbench_create_failed_jobs_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists(config('queue.failed.table')); + } +}; diff --git a/database/migrations/2022_05_10_000000_create_sessions_queue_and_notifications_table.php b/database/migrations/2022_05_10_000000_create_sessions_queue_and_notifications_table.php index b21e45e..58e7f97 100644 --- a/database/migrations/2022_05_10_000000_create_sessions_queue_and_notifications_table.php +++ b/database/migrations/2022_05_10_000000_create_sessions_queue_and_notifications_table.php @@ -55,16 +55,6 @@ public function up(): void $table->timestamp('read_at')->nullable(); $table->timestamps(); }); - - Schema::create(config('queue.failed.table'), function (Blueprint $table) { - $table->id(); - $table->string('uuid')->unique(); - $table->text('connection'); - $table->text('queue'); - $table->longText('payload'); - $table->longText('exception'); - $table->timestamp('failed_at')->useCurrent(); - }); } /** @@ -72,7 +62,6 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists(config('queue.failed.table')); Schema::dropIfExists('notifications'); if (config('queue.default') !== 'database') { From 25336eba91228bf05edd3034aeb881e6b9dc469e Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 20:17:32 +0700 Subject: [PATCH 59/71] refactor: rename field `attached_to` to `attachable` Signed-off-by: Fery Wardiyanto --- ...1_create_identities_address_and_files_table.php | 2 +- src/Models/Concerns/WithFileUploads.php | 2 +- src/Models/FileAttached.php | 14 +++++++------- src/Models/FileUpload.php | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index f3c498d..02e38af 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -69,7 +69,7 @@ public function up(): void Schema::create('file_attached', function (Blueprint $table) { $table->foreignUuid('file_upload_id')->constrained('file_uploads')->cascadeOnDelete(); - $table->nullableMorphs('attached_to'); + $table->nullableMorphs('attachable'); }); } diff --git a/src/Models/Concerns/WithFileUploads.php b/src/Models/Concerns/WithFileUploads.php index ebd2332..49a5283 100644 --- a/src/Models/Concerns/WithFileUploads.php +++ b/src/Models/Concerns/WithFileUploads.php @@ -17,7 +17,7 @@ trait WithFileUploads */ public function files() { - return $this->morphToMany(FileUpload::class, 'attached_to', 'file_attached', null, 'file_upload_id') + return $this->morphToMany(FileUpload::class, 'attachable', 'file_attached', null, 'file_upload_id') ->using(FileAttached::class); } diff --git a/src/Models/FileAttached.php b/src/Models/FileAttached.php index cb0f7d2..99c54a0 100644 --- a/src/Models/FileAttached.php +++ b/src/Models/FileAttached.php @@ -5,10 +5,10 @@ use Illuminate\Database\Eloquent\Relations\MorphPivot; /** - * @property int $file_upload_id - * @property null|int $attached_to_id - * @property null|string $attached_to_type - * @property-read null|\Creasi\Base\Contracts\HasFileUploads $attachedTo + * @property string $file_upload_id + * @property int $attachable_id + * @property string $attachable_type + * @property-read \Creasi\Base\Contracts\HasFileUploads $attachable */ class FileAttached extends MorphPivot { @@ -21,14 +21,14 @@ class FileAttached extends MorphPivot ]; protected $casts = [ - 'attached_to_id' => 'int', + 'attachable_id' => 'int', ]; /** * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ - public function attachedTo() + public function attachable() { - return $this->morphTo('attached_to'); + return $this->morphTo('attachable'); } } diff --git a/src/Models/FileUpload.php b/src/Models/FileUpload.php index f52cae3..6ebb55f 100644 --- a/src/Models/FileUpload.php +++ b/src/Models/FileUpload.php @@ -59,20 +59,20 @@ public function isInternal(): Attribute return Attribute::get(fn ($_, array $attrs) => ! \str_contains($attrs['path'], '://')); } - protected function attachedTo(string $owner) + protected function attachable(string $owner) { - return $this->morphedByMany($owner, 'attached_to', 'file_attached', 'file_upload_id') + return $this->morphedByMany($owner, 'attachable', 'file_attached', 'file_upload_id') ->as('attachments'); } public function ownedByCompanies() { - return $this->attachedTo(Business::class); + return $this->attachable(Business::class); } public function ownedByPersonnels() { - return $this->attachedTo(Personnel::class); + return $this->attachable(Personnel::class); } /** @@ -136,7 +136,7 @@ public function createRevision(string|UploadedFile $path, string $summary = null $this->revisions()->save($revision); foreach ($this->attaches as $model) { - $model->attachedTo->files()->sync($revision); + $model->attachable->files()->sync($revision->getKey()); } $this->refresh(); From a3a06bff90e167db9a35a29de27c3fbe1c18c994 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 22:15:24 +0700 Subject: [PATCH 60/71] refactor: move file `type` field From `file_uploads` table to `file_attached` pivot table Signed-off-by: Fery Wardiyanto --- database/factories/BusinessFactory.php | 7 ++- database/factories/FileUploadFactory.php | 2 - database/factories/PersonnelFactory.php | 7 ++- ...ate_identities_address_and_files_table.php | 2 +- src/Models/Concerns/WithAvatar.php | 2 +- src/Models/Concerns/WithFileUploads.php | 8 ++-- src/Models/FileAttached.php | 4 ++ src/Models/FileUpload.php | 47 +++++++++++-------- tests/Http/Business/CompanyTest.php | 4 +- tests/Http/Business/EmployeeTest.php | 6 +-- tests/Models/BusinessTest.php | 2 - tests/Models/FileUploadTest.php | 14 ++---- tests/Models/PersonnelTest.php | 2 - 13 files changed, 58 insertions(+), 49 deletions(-) diff --git a/database/factories/BusinessFactory.php b/database/factories/BusinessFactory.php index 9bd83a5..aaaa937 100644 --- a/database/factories/BusinessFactory.php +++ b/database/factories/BusinessFactory.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Business; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Illuminate\Database\Eloquent\Factories\Factory; @@ -32,8 +33,10 @@ public function withAddress(): static return $this->has(Address::factory()); } - public function withFileUpload(): static + public function withFileUpload(FileUploadType $type = null): static { - return $this->has(FileUpload::factory(), 'files'); + return $this->hasAttached(FileUpload::factory(), [ + 'type' => $type ?? $this->faker->randomElement(FileUploadType::cases()), + ], 'files'); } } diff --git a/database/factories/FileUploadFactory.php b/database/factories/FileUploadFactory.php index 492043c..5c0a2b7 100644 --- a/database/factories/FileUploadFactory.php +++ b/database/factories/FileUploadFactory.php @@ -2,7 +2,6 @@ namespace Database\Factories; -use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Http\Testing\File; @@ -25,7 +24,6 @@ public function definition(): array 'title' => $title = $this->faker->word(), 'name' => $name = Str::slug($title), 'path' => File::fake()->create($name)->path(), - 'type' => $this->faker->randomElement(FileUploadType::cases()), 'disk' => null, 'summary' => $this->faker->sentence(4), ]; diff --git a/database/factories/PersonnelFactory.php b/database/factories/PersonnelFactory.php index b033dae..905d9f2 100644 --- a/database/factories/PersonnelFactory.php +++ b/database/factories/PersonnelFactory.php @@ -6,6 +6,7 @@ use Creasi\Base\Models\Business; use Creasi\Base\Models\Enums\EmploymentStatus; use Creasi\Base\Models\Enums\EmploymentType; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; @@ -75,8 +76,10 @@ public function withAddress(): static return $this->has(Address::factory()); } - public function withFileUpload(): static + public function withFileUpload(FileUploadType $type = null): static { - return $this->has(FileUpload::factory(), 'files'); + return $this->hasAttached(FileUpload::factory(), [ + 'type' => $type ?? $this->faker->randomElement(FileUploadType::cases()), + ], 'files'); } } diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index 02e38af..eda6075 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -55,7 +55,6 @@ public function up(): void $table->string('title')->nullable(); $table->string('name')->nullable(); $table->string('path'); - $table->unsignedSmallInteger('type')->nullable(); $table->string('disk')->nullable(); $table->string('summary')->nullable(); @@ -70,6 +69,7 @@ public function up(): void Schema::create('file_attached', function (Blueprint $table) { $table->foreignUuid('file_upload_id')->constrained('file_uploads')->cascadeOnDelete(); $table->nullableMorphs('attachable'); + $table->unsignedSmallInteger('type'); }); } diff --git a/src/Models/Concerns/WithAvatar.php b/src/Models/Concerns/WithAvatar.php index ffed431..b1b48a2 100644 --- a/src/Models/Concerns/WithAvatar.php +++ b/src/Models/Concerns/WithAvatar.php @@ -40,6 +40,6 @@ public function setAvatar(string|UploadedFile $image) public function avatarFile() { - return $this->files()->where('type', FileUploadType::Avatar); + return $this->files()->wherePivot('type', '=', FileUploadType::Avatar); } } diff --git a/src/Models/Concerns/WithFileUploads.php b/src/Models/Concerns/WithFileUploads.php index 49a5283..eb9bcd3 100644 --- a/src/Models/Concerns/WithFileUploads.php +++ b/src/Models/Concerns/WithFileUploads.php @@ -18,7 +18,9 @@ trait WithFileUploads public function files() { return $this->morphToMany(FileUpload::class, 'attachable', 'file_attached', null, 'file_upload_id') - ->using(FileAttached::class); + ->using(FileAttached::class) + ->withPivot('type') + ->as('attachment'); } public function storeFile( @@ -29,9 +31,9 @@ public function storeFile( string $summary = null, string $disk = null, ): FileUpload { - $file = FileUpload::store($type, $path, $name, $title, $summary, $disk); + $file = FileUpload::store($path, $name, $title, $summary, $disk); - $this->files()->attach($file); + $this->files()->syncWithPivotValues($file, ['type' => $type], false); return $file; } diff --git a/src/Models/FileAttached.php b/src/Models/FileAttached.php index 99c54a0..f10b358 100644 --- a/src/Models/FileAttached.php +++ b/src/Models/FileAttached.php @@ -2,12 +2,14 @@ namespace Creasi\Base\Models; +use Creasi\Base\Models\Enums\FileUploadType; use Illuminate\Database\Eloquent\Relations\MorphPivot; /** * @property string $file_upload_id * @property int $attachable_id * @property string $attachable_type + * @property null|FileUploadType $type * @property-read \Creasi\Base\Contracts\HasFileUploads $attachable */ class FileAttached extends MorphPivot @@ -17,11 +19,13 @@ class FileAttached extends MorphPivot public $timestamps = false; protected $fillable = [ + 'type', 'file_upload_id', ]; protected $casts = [ 'attachable_id' => 'int', + 'type' => FileUploadType::class, ]; /** diff --git a/src/Models/FileUpload.php b/src/Models/FileUpload.php index 6ebb55f..ab17c75 100644 --- a/src/Models/FileUpload.php +++ b/src/Models/FileUpload.php @@ -2,7 +2,6 @@ namespace Creasi\Base\Models; -use Creasi\Base\Models\Enums\FileUploadType; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Concerns\HasUuids; @@ -14,19 +13,19 @@ * @property null|int $revision_id * @property null|string $title * @property string $name - * @property null|FileUploadType $type - * @property null|string $path + * @property string $path * @property null|string $disk * @property string $url - * @property bool $is_internal + * @property bool $is_internal Determine whether the file is actually stored internally or its an external link. * @property null|string $summary - * @property-read \Illuminate\Database\Eloquent\Collection $revisions * @property-read \Illuminate\Database\Eloquent\Collection $attaches + * @property-read \Illuminate\Database\Eloquent\Collection $revisions * @property-read null|static $revisionOf + * @property-read FileAttached $attachment * @property-read \Illuminate\Database\Eloquent\Collection $ownedByCompanies * @property-read \Illuminate\Database\Eloquent\Collection $ownedByPersonnels * - * @method static static store(FileUploadType $type, string|UploadedFile $path, string $name, ?string $title = null, ?string $summary = null, ?string $disk = null) + * @method static static store(string|UploadedFile $path, string $name, ?string $title = null, ?string $summary = null, ?string $disk = null) * @method static \Database\Factories\FileUploadFactory factory() */ class FileUpload extends Model @@ -38,31 +37,43 @@ class FileUpload extends Model 'title', 'name', 'path', - 'type', 'disk', 'summary', ]; - protected $casts = [ - 'type' => FileUploadType::class, - ]; + protected $casts = []; + + protected $appends = ['url', 'is_internal']; public function url(): Attribute { - return Attribute::get(fn ($_, array $attrs) => $this->is_internal - ? Storage::url($attrs['path']) - : $attrs['path']); + return Attribute::get(function () { + if ($path = $this->getAttributeValue('path')) { + return $this->is_internal ? Storage::url($path) : $path; + } + + return null; + }); } + /** + * Determine whether the file is actually stored internally or its an external link. + */ public function isInternal(): Attribute { - return Attribute::get(fn ($_, array $attrs) => ! \str_contains($attrs['path'], '://')); + return Attribute::get(function () { + if ($path = $this->getAttributeValue('path')) { + return ! \str_contains($path, '://'); + } + + return null; + }); } protected function attachable(string $owner) { return $this->morphedByMany($owner, 'attachable', 'file_attached', 'file_upload_id') - ->as('attachments'); + ->as('attachment'); } public function ownedByCompanies() @@ -101,7 +112,6 @@ public function revisionOf() public function scopeStore( Builder $query, - FileUploadType $type, string|UploadedFile $path, string $name, string $title = null, @@ -111,13 +121,12 @@ public function scopeStore( $name = Str::slug($name); if ($path instanceof UploadedFile) { - $path = $path->store($type->key().'/'.$name, $disk ?? []); + $path = $path->store($name, $disk ?? []); } $instance = $query->newModelInstance([ 'title' => $title, 'disk' => $disk, - 'type' => $type, 'name' => $name, 'path' => $path, 'summary' => $summary, @@ -130,7 +139,7 @@ public function scopeStore( public function createRevision(string|UploadedFile $path, string $summary = null): static { - $revision = static::store($this->type, $path, $this->name, $this->title, $summary, $this->disk); + $revision = static::store($path, $this->name, $this->title, $summary, $this->disk); /** @var static */ $this->revisions()->save($revision); diff --git a/tests/Http/Business/CompanyTest.php b/tests/Http/Business/CompanyTest.php index 8828bc7..4f75d03 100644 --- a/tests/Http/Business/CompanyTest.php +++ b/tests/Http/Business/CompanyTest.php @@ -5,6 +5,7 @@ use Creasi\Base\Models\Address; use Creasi\Base\Models\Business; use Creasi\Base\Models\Enums\BusinessRelativeType; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Creasi\Tests\TestCase; use Illuminate\Http\UploadedFile; @@ -123,6 +124,7 @@ public function should_able_to_upload_and_store_new_file(): void $model = Business::factory()->createOne(); $data = FileUpload::factory()->withoutFile()->raw(); $data['upload'] = UploadedFile::fake()->create('file.pdf'); + $data['type'] = FileUploadType::Document->value; $response = $this->postJson("base/companies/{$model->getRouteKey()}/files", $data); @@ -134,7 +136,7 @@ public function should_able_to_retrieve_all_uploaded_files(): void { Sanctum::actingAs($this->user()); - $model = Business::factory()->withFileUpload()->createOne(); + $model = Business::factory()->withFileUpload(FileUploadType::Document)->createOne(); $response = $this->getJson("base/companies/{$model->getRouteKey()}/files"); diff --git a/tests/Http/Business/EmployeeTest.php b/tests/Http/Business/EmployeeTest.php index 8d3bf0a..25dc3f4 100644 --- a/tests/Http/Business/EmployeeTest.php +++ b/tests/Http/Business/EmployeeTest.php @@ -3,6 +3,7 @@ namespace Creasi\Tests\Http\Business; use Creasi\Base\Models\Address; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; @@ -110,6 +111,7 @@ public function should_able_to_upload_and_store_new_file(): void $model = Personnel::factory()->createOne(); $data = FileUpload::factory()->withoutFile()->raw(); $data['upload'] = UploadedFile::fake()->create('file.pdf'); + $data['type'] = FileUploadType::Document->value; $response = $this->postJson("base/employees/{$model->getRouteKey()}/files", $data); @@ -121,9 +123,7 @@ public function should_able_to_retrieve_all_uploaded_files(): void { Sanctum::actingAs($this->user()); - $model = Personnel::factory()->createOne(); - - $model->files()->saveMany(FileUpload::factory(2)->create()); + $model = Personnel::factory()->withFileUpload(FileUploadType::Document)->createOne(); $response = $this->getJson("base/employees/{$model->getRouteKey()}/files"); diff --git a/tests/Models/BusinessTest.php b/tests/Models/BusinessTest.php index 08b7ca8..03fcb9c 100644 --- a/tests/Models/BusinessTest.php +++ b/tests/Models/BusinessTest.php @@ -7,7 +7,6 @@ use Creasi\Base\Models\Enums\BusinessRelativeType; use Creasi\Base\Models\Enums\EmploymentStatus; use Creasi\Base\Models\Enums\EmploymentType; -use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; @@ -42,7 +41,6 @@ public function should_have_avatar_image() ); $this->assertInstanceOf(FileUpload::class, $business->avatar); - $this->assertEquals(FileUploadType::Avatar, $business->avatar->type); $this->assertTrue($business->avatar->is_internal); } diff --git a/tests/Models/FileUploadTest.php b/tests/Models/FileUploadTest.php index 129a050..7600093 100644 --- a/tests/Models/FileUploadTest.php +++ b/tests/Models/FileUploadTest.php @@ -20,7 +20,7 @@ public function should_have_revisions() { $file = UploadedFile::fake()->create('original.pdf'); - $original = FileUpload::store(FileUploadType::Document, $file, 'document'); + $original = FileUpload::store($file, 'document'); $revision = $original->createRevision( UploadedFile::fake()->create('revision.pdf') @@ -56,21 +56,13 @@ public function could_attached_to_personnel() public function could_attached_to_many_personnels() { $people = Personnel::factory(2)->create(); - $file = FileUpload::factory()->createOne([ - 'path' => 'path/to/file.pdf', - ]); + $file = UploadedFile::fake()->create('document.pdf'); foreach ($people as $person) { - $person->files()->attach($file); + $person->storeFile(FileUploadType::Document, $file, 'document'); $this->assertCount(1, $person->files); } - - $this->assertCount($file->attaches()->count(), $file->ownedByPersonnels); - - $revision = $file->createRevision('path/to/revision.pdf'); - - $this->assertCount($revision->attaches()->count(), $revision->ownedByPersonnels); } #[Test] diff --git a/tests/Models/PersonnelTest.php b/tests/Models/PersonnelTest.php index b3cbf4e..0bbd9f4 100644 --- a/tests/Models/PersonnelTest.php +++ b/tests/Models/PersonnelTest.php @@ -3,7 +3,6 @@ namespace Creasi\Tests\Models; use Creasi\Base\Models\Address; -use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\Enums\Gender; use Creasi\Base\Models\Enums\PersonnelRelativeStatus; use Creasi\Base\Models\FileUpload; @@ -37,7 +36,6 @@ public function should_have_avatar_image() ); $this->assertInstanceOf(FileUpload::class, $person->avatar); - $this->assertEquals(FileUploadType::Avatar, $person->avatar->type); $this->assertTrue($person->avatar->is_internal); } From 2331c6fb09c4a25f931baf2b301025ce6d6e9ad4 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Mon, 24 Jul 2023 22:21:18 +0700 Subject: [PATCH 61/71] chore(docs): update database structure documentation regarding latest changes Signed-off-by: Fery Wardiyanto --- database/README.md | 126 ++++++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 54 deletions(-) diff --git a/database/README.md b/database/README.md index 2f93cb4..03de7d4 100644 --- a/database/README.md +++ b/database/README.md @@ -105,7 +105,7 @@ erDiagram varchar title varchar name varchar path - varchar drive + varchar disk varchar summary } businesses }o..|| file_attached : files @@ -113,7 +113,8 @@ erDiagram file_attached ||--|| file_uploads : attachments file_attached { unsignedBigInt file_upload_id FK - morph attached_to + morph attachable + unsignedSmallInt type } ``` --- @@ -132,28 +133,28 @@ classDiagram Personnel "1" ..> "1" User : credential class Entity { - varchar~150~ name - varchar~50~ alias - varchar email - varchar~20~ phone - varchar~200~ summary + string name + null|string alias + string email + null|string phone + null|string summary } class Business~Entity~ { - unsignedBigInt id - unsignedSmallInt tax_status - varchar~16~ tax_id + int id + null|int tax_status + null|string tax_id } class Personnel~Entity~ { - unsignedBigInt id - unsignedBigInt user_id - char~1~ gender + int id + null|int user_id + string gender credential() User } class User { - unsignedBigInt id - varchar~150~ name - varchar email - varchar password + int id + string name + string email + string password identity(): Personnel } ``` @@ -195,16 +196,22 @@ classDiagram Business --> BusinessRelative : businessRelatives class Business~Entity~ { - unsignedBigInt id + int id + owners() Entity[] + subsidiaries() Entity[] + customers() Entity[] + suppliers() Entity[] + vendors() Entity[] } class Personnel~Entity~ { - unsignedBigInt id + int id } class BusinessRelative { - unsignedBigInt id + int id boolean is_internal - varchar~100~ code - stakeholders() Entity[] + string code + int type + stakeholder() Entity } ``` @@ -232,7 +239,7 @@ classDiagram | `id` | `unsignedBigInt`, `incrementing` | `primary` | | | `business_id` | `unsignedBigInt` | `foreign` | | | `stakeholder` | `morphs`, `nullable` | | | -| `is_internal` | `boolean`, `default: false` | | Determine whether the stakeholder is actually from internal business | +| `is_internal` | `boolean` | | Determine whether the stakeholder is actually from internal business, default: `false` | | `code` | `varchar(100)`, `nullable` | `unique` | | | `type` | `unsignedSmallInt`, `nullable` | | | @@ -249,24 +256,26 @@ classDiagram Business --> Employments : employments class Business { - unsignedBigInt id + int id employees() Personnel[] } class Personnel { - unsignedBigInt id + int id + company() null|Business employers() Business[] } class Employments { - unsignedBigInt id - unsignedBigInt employer_id - unsignedBigInt employee_id + int id + int employer_id + int employee_id boolean is_primary - varchar~100~ code - unsignedSmallInt type - unsignedSmallInt status - date start_date - date finish_date - stakeholders() Entity[] + null|string code + int type + int status + null|date start_date + null|date finish_date + null|bool is_started + null|bool is_finished } ``` @@ -277,7 +286,7 @@ classDiagram | `id` | `unsignedBigInt`, `incrementing` | `primary` | | | `employer_id` | `unsignedBigInt` | `foreign` | ID of the company | | `employee_id` | `unsignedBigInt` | `foreign` | ID of the personnel | -| `is_primary` | `boolean`, `default: false` | | Determine whether it's the personnel's primary company | +| `is_primary` | `boolean` | | Determine whether it's the personnel's primary company, default: `false` | | `code` | `varchar(100)`, `nullable` | `unique` | An identification that given by the company to employee | | `type` | `unsignedSmallInt`, `nullable` | | Define the employment type of the personnel in the company | | `status` | `unsignedSmallInt`, `nullable` | | | @@ -316,17 +325,17 @@ classDiagram PersonnelRelative --> Personnel : personnel class Personnel { - unsignedBigInt id - profile() Profile + int id + profile() null|Profile } class Profile { - unsignedBigInt id + int id identity() Personnel } class PersonnelRelative { - unsignedBigInt personnel_id - unsignedBigInt relative_id - unsignedSmallInt status + int personnel_id + int relative_id + int status } ``` @@ -449,11 +458,11 @@ classDiagram province() null|Province } class Business~Entity~ { - unsignedBigInt id + int id addresses() Address[] } class Personnel~Entity~ { - unsignedBigInt id + int id addresses() Address[] } ``` @@ -502,28 +511,31 @@ It also possible to store an `.xlsx` or `.csv` file that would be used for data classDiagram Business "1" ..> "*" FileAttached : files Personnel "1" ..> "*" FileAttached : files - FileAttached "1" <--> "1" FileUpload : attachments + FileUpload "1" ..> "*" FileUpload : revisions + FileAttached <--> FileUpload : attachment class FileUpload { - unsignedBigInt id + int id string revision_id string title string name string path - string drive - string summary + string disk + null|string summary + revisions() FileUpload[] } class FileAttached { - unsignedBigInt id - morph attached_to - attachedTo() Entity + int id + morph attachable + int type + attachable() Entity } class Business~Entity~ { - unsignedBigInt id + int id files() FileUpload[] } class Personnel~Entity~ { - unsignedBigInt id + int id files() FileUpload[] } ``` @@ -537,7 +549,7 @@ classDiagram | `title` | `varchar`, `nullable` | | | | `name` | `varchar` | | | | `path` | `varchar`, `nullable` | | | -| `drive` | `varchar`, `nullable` | | | +| `disk` | `varchar`, `nullable` | | | | `summary` | `varchar`, `nullable` | | | **Model Attributes** @@ -552,7 +564,13 @@ classDiagram | Field | Attribute | Key | Description | | --- | --- | :---: | --- | | `file_upload_id` | `uuid` | `foreign` | | -| `attached_to` | `morphs`, `nullable` | | | +| `attachable` | `morphs`, `nullable` | | | +| `type` | `insignedSmallInt` | | | **Relation Properties** - `file_upload_id` : reference `file_uploads` + +**File Attached Types** +- Avatar +- Image +- Document From f899c62275e6e7009b60c283f5e720e20c72000f Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Tue, 25 Jul 2023 05:59:51 +0700 Subject: [PATCH 62/71] feat: add general `index` and `store` routes for `files` and `addresses` That will only returns files and address for the current user Signed-off-by: Fery Wardiyanto --- routes/base.php | 6 ++-- src/Repository.php | 53 +++++++++++++++++------------------ tests/Http/AddressTest.php | 44 +++++++++++++++++++++++++++-- tests/Http/FileUploadTest.php | 49 +++++++++++++++++++++++++++++--- 4 files changed, 117 insertions(+), 35 deletions(-) diff --git a/routes/base.php b/routes/base.php index 8861cc7..5e402c8 100644 --- a/routes/base.php +++ b/routes/base.php @@ -18,6 +18,8 @@ Route::middleware('auth:sanctum')->group(function () { Route::apiResource('companies', Controllers\CompanyController::class); Route::apiResource('employees', Controllers\EmployeeController::class); + Route::apiResource('addresses', Controllers\AddressController::class); + Route::apiResource('files', Controllers\FileUploadController::class); Route::apiSingleton('profile', Controllers\ProfileController::class); Route::apiSingleton('setting', Controllers\SettingController::class); @@ -29,8 +31,8 @@ "{$entity}.addresses" => Controllers\AddressController::class, "{$entity}.files" => Controllers\FileUploadController::class, ], [ - 'parameters' => [$entity => 'entity'], - 'shallow' => true, + 'only' => ['index', 'store'], + // 'parameters' => [$entity => 'entity'], ]); } diff --git a/src/Repository.php b/src/Repository.php index 11be2d8..9e6fb1d 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -5,10 +5,8 @@ use Creasi\Base\Contracts\Company; use Creasi\Base\Contracts\Employee; use Creasi\Base\Contracts\Stakeholder; -use Creasi\Base\Models\Business; use Creasi\Base\Models\Entity; use Creasi\Base\Models\Enums\BusinessRelativeType; -use Creasi\Base\Models\Personnel; use Creasi\Base\Models\User; use Illuminate\Http\Request; use Illuminate\Routing\Router; @@ -32,43 +30,32 @@ public function __construct( } } - public function resolveEntity(): Entity - { - /** @var Entity */ - $entity = app($this->router->is('companies.*') ? Business::class : Personnel::class); - - // For some reason we do need to resolve the binding ourselves - // see: https://stackoverflow.com/a/76717314/881743 - return $entity->resolveRouteBinding($this->router->input('entity')) ?: $entity; - } - public function resolveEmployee(): Employee { - if ($personnel = $this->user->identity) { - $key = $this->router->input('employee'); + /** @var Employee */ + $entity = $this->user->identity; + $key = $this->router->input('employee'); - return $key ? $personnel->newInstance()->resolveRouteBinding($key) : $personnel; - } - - return app(Personnel::class); + return $key ? $entity->resolveRouteBinding($key) : $entity; } public function resolveEmployer(): Company { - if ($company = $this->user->identity?->company) { - $key = $this->router->input('company'); - - return $key ? $company->newInstance()->resolveRouteBinding($key) : $company; - } + /** @var Company */ + $entity = $this->user->identity->company; + $key = $this->router->input('company'); - return app(Business::class); + return $key ? $entity->resolveRouteBinding($key) : $entity; } - public function resolveBusinessRelativeType(): BusinessRelativeType + public function resolveEntity(Company $company, Employee $employee): Entity { - $name = \explode('.', $this->router->currentRouteName())[0]; + $entity = $this->currentRoutePrefix('companies') ? $company : $employee; + $key = $this->router->input('entity'); - return $this->stakeholders[$name]; + // For some reason we do need to resolve the binding ourselves + // see: https://stackoverflow.com/a/76717314/881743 + return $entity->resolveRouteBinding($key) ?: $entity; } public function resolveStakeholder(Company $company, BusinessRelativeType $type): Stakeholder @@ -81,4 +68,16 @@ public function resolveStakeholder(Company $company, BusinessRelativeType $type) return $relative?->stakeholder ?: $company->newInstance(); } + + public function resolveBusinessRelativeType(): BusinessRelativeType + { + return $this->stakeholders[$this->currentRoutePrefix()]; + } + + private function currentRoutePrefix(string $prefix = null) + { + $name = \explode('.', $this->router->currentRouteName())[0]; + + return $prefix ? $name === $prefix : $name; + } } diff --git a/tests/Http/AddressTest.php b/tests/Http/AddressTest.php index 1a0edb0..3b62db6 100644 --- a/tests/Http/AddressTest.php +++ b/tests/Http/AddressTest.php @@ -13,10 +13,47 @@ class AddressTest extends TestCase { #[Test] - public function should_able_to_show_existing_data(): void + public function should_receive_404_when_no_data_available(): void { Sanctum::actingAs($this->user()); + + $response = $this->getJson('base/addresses'); + + $response->assertNotFound(); + } + + #[Test] + public function should_able_to_retrieve_all_data(): void + { + Sanctum::actingAs($user = $this->user()); + $model = Address::factory()->createOne(); + $user->identity->addresses()->save($model); + + $response = $this->getJson('base/addresses'); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + + $data = Address::factory()->raw(); + + $response = $this->postJson('base/addresses', $data); + + $response->assertCreated(); + } + + #[Test] + public function should_able_to_show_existing_data(): void + { + Sanctum::actingAs($user = $this->user()); + + $model = Address::factory()->createOne(); + $user->identity->addresses()->save($model); $response = $this->getJson("base/addresses/{$model->getRouteKey()}"); @@ -27,6 +64,7 @@ public function should_able_to_show_existing_data(): void public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); + $model = Address::factory()->createOne(); $response = $this->putJson("base/addresses/{$model->getRouteKey()}", $model->toArray()); @@ -37,8 +75,10 @@ public function should_able_to_update_existing_data(): void #[Test] public function should_able_to_delete_existing_data(): void { - Sanctum::actingAs($this->user()); + Sanctum::actingAs($user = $this->user()); + $model = Address::factory()->createOne(); + $user->identity->addresses()->save($model); $response = $this->deleteJson("base/addresses/{$model->getRouteKey()}"); diff --git a/tests/Http/FileUploadTest.php b/tests/Http/FileUploadTest.php index 0399b39..d7dcb4b 100644 --- a/tests/Http/FileUploadTest.php +++ b/tests/Http/FileUploadTest.php @@ -2,8 +2,10 @@ namespace Creasi\Tests\Http; +use Creasi\Base\Models\Enums\FileUploadType; use Creasi\Base\Models\FileUpload; use Creasi\Tests\TestCase; +use Illuminate\Http\UploadedFile; use Laravel\Sanctum\Sanctum; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; @@ -13,10 +15,47 @@ class FileUploadTest extends TestCase { #[Test] - public function should_able_to_show_existing_data(): void + public function should_receive_404_when_no_data_available(): void { Sanctum::actingAs($this->user()); - $model = FileUpload::factory()->createOne(); + + $response = $this->getJson('base/files'); + + $response->assertNotFound(); + } + + #[Test] + public function should_able_to_retrieve_all_data(): void + { + Sanctum::actingAs($user = $this->user()); + + $user->identity->storeFile(FileUploadType::Document, '/doc/file.pdf', 'document'); + + $response = $this->getJson('base/files'); + + $response->assertOk(); + } + + #[Test] + public function should_able_to_store_new_data(): void + { + Sanctum::actingAs($this->user()); + + $data = FileUpload::factory()->raw(); + $data['upload'] = UploadedFile::fake()->create('file.pdf'); + $data['type'] = FileUploadType::Document->value; + + $response = $this->postJson('base/files', $data); + + $response->assertCreated(); + } + + #[Test] + public function should_able_to_show_existing_data(): void + { + Sanctum::actingAs($user = $this->user()); + + $model = $user->identity->storeFile(FileUploadType::Document, '/doc/file.pdf', 'document'); $response = $this->getJson("base/files/{$model->getRouteKey()}"); @@ -27,6 +66,7 @@ public function should_able_to_show_existing_data(): void public function should_able_to_update_existing_data(): void { Sanctum::actingAs($this->user()); + $model = FileUpload::factory()->createOne(); $response = $this->putJson("base/files/{$model->getRouteKey()}", $model->toArray()); @@ -37,8 +77,9 @@ public function should_able_to_update_existing_data(): void #[Test] public function should_able_to_delete_existing_data(): void { - Sanctum::actingAs($this->user()); - $model = FileUpload::factory()->createOne(); + Sanctum::actingAs($user = $this->user()); + + $model = $user->identity->storeFile(FileUploadType::Document, '/doc/file.pdf', 'document'); $response = $this->deleteJson("base/files/{$model->getRouteKey()}"); From 2e26de27901f428dca6f290fc32df2fe497810b8 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Fri, 4 Aug 2023 15:50:59 +0700 Subject: [PATCH 63/71] chore: ugly tricks to make `addresses` and `fileUploads` api works Signed-off-by: Fery Wardiyanto --- src/Http/Controllers/AddressController.php | 18 ++++++++++++------ src/Http/Controllers/FileUploadController.php | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Http/Controllers/AddressController.php b/src/Http/Controllers/AddressController.php index 2de0494..ec0053b 100644 --- a/src/Http/Controllers/AddressController.php +++ b/src/Http/Controllers/AddressController.php @@ -28,7 +28,7 @@ public function index(Entity $entity) } /** - * @return AddressResource + * @return \Illuminate\Http\Response */ public function store(StoreRequest $request, Entity $entity) { @@ -38,18 +38,22 @@ public function store(StoreRequest $request, Entity $entity) } /** - * @return AddressResource + * @return \Illuminate\Http\Response */ - public function show(Address $address, Request $request) + public function show(Address $model, Request $request, int $address = null) { + $address = $model->exists ? $model : $model->newQuery()->findOrFail($address); + return AddressResource::make($address)->toResponse($request); } /** - * @return AddressResource + * @return \Illuminate\Http\Response */ - public function update(UpdateRequest $request, Address $address) + public function update(UpdateRequest $request, Address $model, int $address = null) { + $address = $model->newQuery()->findOrFail($address); + $address->update($request->validated()); return $this->show($address, $request); @@ -58,8 +62,10 @@ public function update(UpdateRequest $request, Address $address) /** * @return \Illuminate\Http\Response */ - public function destroy(Address $address) + public function destroy(Address $model, int $address = null) { + $address = $model->newQuery()->findOrFail($address); + $address->delete(); return response()->noContent(); diff --git a/src/Http/Controllers/FileUploadController.php b/src/Http/Controllers/FileUploadController.php index df1aabb..434a6f4 100644 --- a/src/Http/Controllers/FileUploadController.php +++ b/src/Http/Controllers/FileUploadController.php @@ -28,7 +28,7 @@ public function index(Entity $entity) } /** - * @return FileUploadResource + * @return \Illuminate\Http\Response */ public function store(StoreRequest $request, Entity $entity) { @@ -38,18 +38,22 @@ public function store(StoreRequest $request, Entity $entity) } /** - * @return FileUploadResource + * @return \Illuminate\Http\Response */ - public function show(FileUpload $file, Request $request) + public function show(FileUpload $model, Request $request, string $file = null) { + $file = $model->exists ? $model : $model->newQuery()->findOrFail($file); + return FileUploadResource::make($file)->toResponse($request); } /** - * @return FileUploadResource + * @return \Illuminate\Http\Response */ - public function update(UpdateRequest $request, FileUpload $file) + public function update(UpdateRequest $request, FileUpload $model, string $file = null) { + $file = $model->newQuery()->findOrFail($file); + $file->update($request->validated()); return $this->show($file, $request); @@ -58,8 +62,10 @@ public function update(UpdateRequest $request, FileUpload $file) /** * @return \Illuminate\Http\Response */ - public function destroy(FileUpload $file) + public function destroy(FileUpload $model, string $file = null) { + $file = $model->newQuery()->findOrFail($file); + $file->delete(); return response()->noContent(); From d49076a63e89c8ef2211598ae0be9e37558c0c54 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 5 Aug 2023 02:42:07 +0700 Subject: [PATCH 64/71] feat: add 2 methods in `HasFileUploads` interface Signed-off-by: Fery Wardiyanto --- src/Contracts/HasFileUploads.php | 10 ++++++++++ src/Models/Concerns/WithFileUploads.php | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/Contracts/HasFileUploads.php b/src/Contracts/HasFileUploads.php index 5eae841..f09172a 100644 --- a/src/Contracts/HasFileUploads.php +++ b/src/Contracts/HasFileUploads.php @@ -26,4 +26,14 @@ public function storeFile( string $summary = null, string $disk = null, ): FileUpload; + + /** + * Retrieve attachable key attribute value + */ + public function getAttachableKey(): mixed; + + /** + * Retrieve attachable key attribute name. + */ + public function getAttachableKeyName(): string; } diff --git a/src/Models/Concerns/WithFileUploads.php b/src/Models/Concerns/WithFileUploads.php index eb9bcd3..9389ba4 100644 --- a/src/Models/Concerns/WithFileUploads.php +++ b/src/Models/Concerns/WithFileUploads.php @@ -37,4 +37,14 @@ public function storeFile( return $file; } + + public function getAttachableKey(): mixed + { + return $this->getAttributeValue($this->getAttachableKeyName()); + } + + public function getAttachableKeyName(): string + { + return 'name'; + } } From 5a9f141ddd1d83a52fc9d02283ac424b008216ea Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 5 Aug 2023 03:29:52 +0700 Subject: [PATCH 65/71] fix: fix extra slash in `FileUpload::` attribute Signed-off-by: Fery Wardiyanto --- src/Models/FileUpload.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Models/FileUpload.php b/src/Models/FileUpload.php index ab17c75..bbc7fbe 100644 --- a/src/Models/FileUpload.php +++ b/src/Models/FileUpload.php @@ -48,11 +48,20 @@ class FileUpload extends Model public function url(): Attribute { return Attribute::get(function () { - if ($path = $this->getAttributeValue('path')) { - return $this->is_internal ? Storage::url($path) : $path; + $path = $this->getAttributeValue('path'); + + if (! $path) { + return null; } - return null; + if (! $this->is_internal) { + return $path; + } + + /** @var \Illuminate\Contracts\Filesystem\Cloud */ + $storage = Storage::disk($this->disk); + + return $storage->url(\ltrim($path, '/')); }); } From 4f60c9cdc59a38f71d312cd85db8d0286689b26f Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 5 Aug 2023 03:30:40 +0700 Subject: [PATCH 66/71] fix: fix `Address::` property type docblock Signed-off-by: Fery Wardiyanto --- src/Models/Address.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/Address.php b/src/Models/Address.php index 63d10b5..49dea91 100644 --- a/src/Models/Address.php +++ b/src/Models/Address.php @@ -10,7 +10,7 @@ /** * @property-read bool $is_resident - * @property null|Addres $type + * @property null|Enums\AddressType $type * @property null|string $rt * @property null|string $rw * @property null|string $summary From 0c4975a8fe89ea7969ef4986388dc57267ce6fee Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 5 Aug 2023 03:44:02 +0700 Subject: [PATCH 67/71] feat: add `Arrayable` support for `OptionableEnum` Signed-off-by: Fery Wardiyanto --- src/Models/Enums/OptionableEnum.php | 37 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Models/Enums/OptionableEnum.php b/src/Models/Enums/OptionableEnum.php index aa07358..ddfcd15 100644 --- a/src/Models/Enums/OptionableEnum.php +++ b/src/Models/Enums/OptionableEnum.php @@ -7,6 +7,11 @@ */ trait OptionableEnum { + /** + * Retrieve `cases` of enum as array options. + * + * @return array + */ public static function toOptions() { static $options = []; @@ -16,20 +21,28 @@ public static function toOptions() } foreach (static::cases() as $self) { - $option = [ - 'value' => $self->value, - ]; - - if (\in_array(KeyableEnum::class, \trait_uses_recursive($self), true)) { - $option['key'] = (string) $self->key(); - $option['label'] = $self->label(); - } else { - $option['key'] = $self->name; - } - - $options[] = $option; + $options[] = $self->toArray(); } return $options; } + + /** + * Retrieve `key` and `value` of enum as an array. + */ + public function toArray(): array + { + $arr = [ + 'value' => $this->value, + ]; + + if (\in_array(KeyableEnum::class, \trait_uses_recursive($this), true)) { + $arr['key'] = (string) $this->key(); + $arr['label'] = $this->label(); + } else { + $arr['key'] = $this->name; + } + + return $arr; + } } From da70b386a99af73117dc44fb10f9717a7461b371 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 5 Aug 2023 07:53:43 +0700 Subject: [PATCH 68/71] feat: normalize api responses Signed-off-by: Fery Wardiyanto --- resources/lang/en/base.php | 14 ++- resources/lang/id/base.php | 16 ++- src/Contracts/Company.php | 4 +- src/Contracts/Employee.php | 5 +- src/Contracts/HasProfile.php | 2 + src/Contracts/Stakeholder.php | 2 +- .../Resources/Address/AddressCollection.php | 11 ++- .../Resources/Address/AddressResource.php | 17 +++- src/Http/Resources/AsAddress.php | 42 ++++++++ src/Http/Resources/AsEntity.php | 99 +++++++++++++++++++ .../Resources/Company/CompanyCollection.php | 9 +- .../Resources/Company/CompanyResource.php | 18 +++- .../Resources/Employee/EmployeeCollection.php | 9 +- .../Resources/Employee/EmployeeResource.php | 18 +++- .../FileUpload/FileUploadCollection.php | 6 +- .../FileUpload/FileUploadResource.php | 32 +++++- src/Http/Resources/ProfileResource.php | 65 ++++-------- .../Stakeholder/StakeholderCollection.php | 9 +- .../Stakeholder/StakeholderResource.php | 18 +++- src/Models/Concerns/WithProfile.php | 2 - src/Models/Personnel.php | 1 - tests/Http/AddressTest.php | 35 ++++++- tests/Http/Business/CompanyTest.php | 31 +++++- tests/Http/Business/EmployeeTest.php | 31 +++++- tests/Http/StakeholderTestCase.php | 37 +++++-- 25 files changed, 444 insertions(+), 89 deletions(-) create mode 100644 src/Http/Resources/AsAddress.php create mode 100644 src/Http/Resources/AsEntity.php diff --git a/resources/lang/en/base.php b/resources/lang/en/base.php index adfe518..1d0e720 100644 --- a/resources/lang/en/base.php +++ b/resources/lang/en/base.php @@ -6,6 +6,11 @@ 'female' => 'Female', ], + 'address-type' => [ + 'resident' => 'Resident', + 'legal' => 'Legal', + ], + 'religion' => [ 'other' => 'Other Religion', 'islam' => 'Islam', @@ -16,6 +21,13 @@ 'confucianism' => 'Confucianism', ], + 'file-upload-type' => [ + 'avatar' => 'Avatar', + 'logo' => 'Logo', + 'image' => 'Image', + 'document' => 'Document', + ], + 'employment-type' => [ 'unemployeed' => 'Unemploeed', 'fulltime' => 'Full-time', @@ -57,7 +69,7 @@ 'cousin' => 'Cousin', ], - 'stakeholder' => [ + 'business-relative-type' => [ 'owner' => 'Owner', 'subsidiary' => 'Subsidiary', 'customer' => 'Customer', diff --git a/resources/lang/id/base.php b/resources/lang/id/base.php index d10c078..2446a13 100644 --- a/resources/lang/id/base.php +++ b/resources/lang/id/base.php @@ -6,6 +6,11 @@ 'female' => 'Perempuan', ], + 'address-type' => [ + 'resident' => 'Domisili', + 'legal' => 'Legal', + ], + 'religion' => [ 'other' => 'Kepercaan Lainnya', 'islam' => 'Islam', @@ -16,6 +21,13 @@ 'confucianism' => 'Konghuchu', ], + 'file-upload-type' => [ + 'avatar' => 'Avatar', + 'logo' => 'Logo', + 'image' => 'Image', + 'document' => 'Document', + ], + 'employment-type' => [ 'unemployeed' => 'Tidak Dipekerjakan', 'fulltime' => 'Full-time', @@ -45,7 +57,7 @@ 'k-i3' => 'K/I/3', ], - 'personnel-relative' => [ + 'personnel-relative-status' => [ 'child' => 'Anak', 'spouse' => 'Pasangan', 'sibling' => 'Saudara Kandung', @@ -57,7 +69,7 @@ 'cousin' => 'Sepupu', ], - 'stakeholder' => [ + 'business-relative-type' => [ 'owner' => 'Pemilik', 'subsidiary' => 'Anak Perusahaan', 'customer' => 'Pelanggan', diff --git a/src/Contracts/Company.php b/src/Contracts/Company.php index 146a2c6..2154278 100644 --- a/src/Contracts/Company.php +++ b/src/Contracts/Company.php @@ -11,10 +11,8 @@ * @property-read \Illuminate\Database\Eloquent\Collection $individualRelatives * @property-read \Illuminate\Database\Eloquent\Collection $companyRelatives * @property-read \Illuminate\Database\Eloquent\Collection $stakeholders - * - * @mixin \Illuminate\Database\Eloquent\Model */ -interface Company +interface Company extends Stakeholder { /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Employee diff --git a/src/Contracts/Employee.php b/src/Contracts/Employee.php index 43d1322..e4a2f37 100644 --- a/src/Contracts/Employee.php +++ b/src/Contracts/Employee.php @@ -3,13 +3,12 @@ namespace Creasi\Base\Contracts; /** + * @property null|\Creasi\Base\Models\Enums\Gender $gender * @property-read \Creasi\Base\Models\Employment $employment * @property-read \Illuminate\Database\Eloquent\Collection $employers * @property-read null|Company $company - * - * @mixin \Illuminate\Database\Eloquent\Model */ -interface Employee +interface Employee extends Stakeholder { /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|Company diff --git a/src/Contracts/HasProfile.php b/src/Contracts/HasProfile.php index d8626fd..89f71f0 100644 --- a/src/Contracts/HasProfile.php +++ b/src/Contracts/HasProfile.php @@ -3,6 +3,8 @@ namespace Creasi\Base\Contracts; /** + * @property-read null|\Creasi\Base\Models\Profile $profile + * * @mixin \Illuminate\Database\Eloquent\Model */ interface HasProfile diff --git a/src/Contracts/Stakeholder.php b/src/Contracts/Stakeholder.php index 826daf7..d35eeba 100644 --- a/src/Contracts/Stakeholder.php +++ b/src/Contracts/Stakeholder.php @@ -3,7 +3,7 @@ namespace Creasi\Base\Contracts; /** - * @mixin \Illuminate\Database\Eloquent\Model + * @mixin \Creasi\Base\Models\Entity */ interface Stakeholder { diff --git a/src/Http/Resources/Address/AddressCollection.php b/src/Http/Resources/Address/AddressCollection.php index 0f27b56..57e354a 100644 --- a/src/Http/Resources/Address/AddressCollection.php +++ b/src/Http/Resources/Address/AddressCollection.php @@ -2,11 +2,17 @@ namespace Creasi\Base\Http\Resources\Address; +use Creasi\Base\Http\Resources\AsAddress; use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; +/** + * @property-read \Illuminate\Support\Collection $collection + */ class AddressCollection extends Collection { + use AsAddress; + /** * Transform the resource collection into an array. * @@ -14,6 +20,9 @@ class AddressCollection extends Collection */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + 'data' => $this->collection, + 'meta' => $this->addressMeta(), + ]; } } diff --git a/src/Http/Resources/Address/AddressResource.php b/src/Http/Resources/Address/AddressResource.php index 66b5207..0a73e85 100644 --- a/src/Http/Resources/Address/AddressResource.php +++ b/src/Http/Resources/Address/AddressResource.php @@ -2,11 +2,26 @@ namespace Creasi\Base\Http\Resources\Address; +use Creasi\Base\Http\Resources\AsAddress; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @property-read \Creasi\Base\Models\Address $resource + */ class AddressResource extends JsonResource { + use AsAddress; + + public function __construct($resource) + { + parent::__construct($resource); + + $this->additional([ + 'meta' => $this->addressMeta(), + ]); + } + /** * Transform the resource into an array. * @@ -14,6 +29,6 @@ class AddressResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + return $this->forAddress($this->resource); } } diff --git a/src/Http/Resources/AsAddress.php b/src/Http/Resources/AsAddress.php new file mode 100644 index 0000000..af17c9c --- /dev/null +++ b/src/Http/Resources/AsAddress.php @@ -0,0 +1,42 @@ + AddressType::toOptions(), + ]; + } + + final protected function forAddress(Address $address): array + { + if (! $address->exists) { + return []; + } + + return [ + $address->getKeyName() => $address->getKey(), + 'type' => $address->type ? [ + 'value' => $address->type?->value, + 'label' => $address->type?->label(), + ] : null, + 'line' => $address->line, + 'rt' => $address->rt, + 'rw' => $address->rw, + 'village' => $address->village?->only('code', 'name'), + 'district' => $address->district?->only('code', 'name'), + 'regency' => $address->regency?->only('code', 'name'), + 'province' => $address->province?->only('code', 'name'), + 'postal_code' => $address->postal_code, + 'summary' => $address->summary, + 'created_at' => $address->created_at, + 'updated_at' => $address->updated_at, + ]; + } +} diff --git a/src/Http/Resources/AsEntity.php b/src/Http/Resources/AsEntity.php new file mode 100644 index 0000000..0c82a06 --- /dev/null +++ b/src/Http/Resources/AsEntity.php @@ -0,0 +1,99 @@ +getKeyName() => $entity->getKey(), + 'avatar' => $entity?->avatar?->only('url', 'title'), + 'fullname' => $entity?->name, + 'nickname' => $entity?->alias, + 'gender' => $entity?->gender?->toArray(), + 'email' => $entity?->email, + 'phone' => $entity?->phone, + 'summary' => $entity?->summary, + ]; + + if ($showProfile && $profile = $entity?->profile) { + $arr = \array_merge($arr, $this->forProfile($profile)); + } + + return $arr; + } + + /** + * @param \Creasi\Base\Models\Business|null $entity + */ + final protected function forCompany(Company $entity = null): array + { + $arr = [ + $entity->getKeyName() => $entity->getKey(), + 'avatar' => $entity?->avatar?->only('url', 'title'), + 'legalname' => $entity?->name, + 'aliasname' => $entity?->alias, + 'email' => $entity?->email, + 'phone' => $entity?->phone, + 'summary' => $entity?->summary, + ]; + + return $arr; + } + + final protected function forStakeholder(BusinessRelative|Stakeholder $entity): array + { + if ($entity instanceof BusinessRelative) { + return \array_merge([ + 'type' => $entity->type?->toArray(), + ], $this->forStakeholder($entity->stakeholder)); + } + + $arr = $entity instanceof Company + ? $this->forCompany($entity) + : $this->forPersonnel($entity, false); + + // $arr['type'] = \class_basename($entity); + + return $arr; + } + + final protected function forProfile(Profile $profile): array + { + if (empty($profile)) { + return []; + } + + return [ + 'nik' => $profile->nik, + 'prefix' => $profile->prefix, + 'suffix' => $profile->suffix, + 'birth_date' => $profile->birth_date, + 'birth_place' => $profile->birthPlace?->only('code', 'name'), + 'education' => $profile->education?->value, + 'religion' => $profile->religion?->toArray(), + 'tax_status' => $profile->tax_status?->toArray(), + 'tax_id' => $profile->tax_id, + ]; + } +} diff --git a/src/Http/Resources/Company/CompanyCollection.php b/src/Http/Resources/Company/CompanyCollection.php index 33a0f1c..ea3d566 100644 --- a/src/Http/Resources/Company/CompanyCollection.php +++ b/src/Http/Resources/Company/CompanyCollection.php @@ -2,11 +2,14 @@ namespace Creasi\Base\Http\Resources\Company; +use Creasi\Base\Http\Resources\AsEntity; use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; class CompanyCollection extends Collection { + use AsEntity; + /** * Transform the resource collection into an array. * @@ -14,6 +17,10 @@ class CompanyCollection extends Collection */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + 'data' => $this->collection, + 'meta' => $this->meta(), + 'links' => $this->links(), + ]; } } diff --git a/src/Http/Resources/Company/CompanyResource.php b/src/Http/Resources/Company/CompanyResource.php index a404438..013a7f6 100644 --- a/src/Http/Resources/Company/CompanyResource.php +++ b/src/Http/Resources/Company/CompanyResource.php @@ -2,11 +2,27 @@ namespace Creasi\Base\Http\Resources\Company; +use Creasi\Base\Http\Resources\AsEntity; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @property-read \Creasi\Base\Models\BusinessRelative $resource + */ class CompanyResource extends JsonResource { + use AsEntity; + + public function __construct($resource) + { + parent::__construct($resource); + + $this->additional([ + 'meta' => $this->meta(), + 'links' => $this->links(), + ]); + } + /** * Transform the resource into an array. * @@ -14,6 +30,6 @@ class CompanyResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + return $this->forStakeholder($this->resource); } } diff --git a/src/Http/Resources/Employee/EmployeeCollection.php b/src/Http/Resources/Employee/EmployeeCollection.php index 9ed37fc..5a0ea9f 100644 --- a/src/Http/Resources/Employee/EmployeeCollection.php +++ b/src/Http/Resources/Employee/EmployeeCollection.php @@ -2,11 +2,14 @@ namespace Creasi\Base\Http\Resources\Employee; +use Creasi\Base\Http\Resources\AsEntity; use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; class EmployeeCollection extends Collection { + use AsEntity; + /** * Transform the resource collection into an array. * @@ -14,6 +17,10 @@ class EmployeeCollection extends Collection */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + 'data' => $this->collection, + 'meta' => $this->meta(), + 'links' => $this->links(), + ]; } } diff --git a/src/Http/Resources/Employee/EmployeeResource.php b/src/Http/Resources/Employee/EmployeeResource.php index 5877464..315fe80 100644 --- a/src/Http/Resources/Employee/EmployeeResource.php +++ b/src/Http/Resources/Employee/EmployeeResource.php @@ -2,11 +2,27 @@ namespace Creasi\Base\Http\Resources\Employee; +use Creasi\Base\Http\Resources\AsEntity; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @property-read \Creasi\Base\Models\Personnel $resource + */ class EmployeeResource extends JsonResource { + use AsEntity; + + public function __construct($resource) + { + parent::__construct($resource); + + $this->additional([ + 'meta' => $this->meta(), + 'links' => $this->links(), + ]); + } + /** * Transform the resource into an array. * @@ -14,6 +30,6 @@ class EmployeeResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + return $this->forPersonnel($this->resource, $this->resource->relationLoaded('profile')); } } diff --git a/src/Http/Resources/FileUpload/FileUploadCollection.php b/src/Http/Resources/FileUpload/FileUploadCollection.php index 8c658a5..bbf53ee 100644 --- a/src/Http/Resources/FileUpload/FileUploadCollection.php +++ b/src/Http/Resources/FileUpload/FileUploadCollection.php @@ -14,6 +14,10 @@ class FileUploadCollection extends Collection */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + 'data' => $this->collection, + 'meta' => [], + 'links' => [], + ]; } } diff --git a/src/Http/Resources/FileUpload/FileUploadResource.php b/src/Http/Resources/FileUpload/FileUploadResource.php index 2d48112..3a56740 100644 --- a/src/Http/Resources/FileUpload/FileUploadResource.php +++ b/src/Http/Resources/FileUpload/FileUploadResource.php @@ -2,11 +2,25 @@ namespace Creasi\Base\Http\Resources\FileUpload; +use Creasi\Base\Models\FileAttached; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @property-read \Creasi\Base\Models\FileUpload $resource + */ class FileUploadResource extends JsonResource { + public function __construct($resource) + { + parent::__construct($resource); + + $this->additional([ + 'meta' => [], + 'links' => [], + ]); + } + /** * Transform the resource into an array. * @@ -14,6 +28,22 @@ class FileUploadResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + $this->resource->getKeyName() => $this->resource->getKey(), + 'title' => $this->resource->title, + 'name' => $this->resource->name, + 'path' => $this->resource->path, + 'disk' => $this->resource->disk, + 'summary' => $this->resource->summary, + 'url' => $this->resource->url, + 'attaches' => $this->resource->attaches->map(fn (FileAttached $a) => [ + $a->attachable->getKeyName() => $a->attachable->getKey(), + $a->attachable->getAttachableKeyName() => $a->attachable->getAttachableKey(), + 'type' => $a->type?->toArray(), + ]), + 'is_internal' => $this->resource->is_internal, + 'created_at' => $this->resource->created_at, + 'updated_at' => $this->resource->updated_at, + ]; } } diff --git a/src/Http/Resources/ProfileResource.php b/src/Http/Resources/ProfileResource.php index 3f48bdd..78e4afc 100644 --- a/src/Http/Resources/ProfileResource.php +++ b/src/Http/Resources/ProfileResource.php @@ -12,62 +12,31 @@ */ class ProfileResource extends JsonResource { - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - $resource = [ - $this->getKeyName() => $this->getKey(), - 'avatar' => null, - 'fullname' => $this->identity->name, - 'nickname' => $this->identity->alias, - 'username' => $this->name, - 'email' => $this->identity->email, - 'phone' => $this->identity->phone, - 'gender' => [ - 'value' => $this->identity->gender?->value, - 'label' => $this->identity->gender?->label(), - ], - 'summary' => $this->identity->summary, - ]; + use AsEntity; - if ($avatar = $this->identity->avatar) { - $resource['avatar'] = [ - 'url' => $avatar->url, - 'alt' => $avatar->title, - ]; - } - - if ($profile = $this->identity->profile) { - $resource['nik'] = $profile->nik; - $resource['prefix'] = $profile->prefix; - $resource['suffix'] = $profile->suffix; - $resource['birth_date'] = $profile->birth_date; - $resource['birth_place'] = [ - 'name' => $profile->birthPlace->name, - 'code' => $profile->birthPlace->code, - ]; - $resource['education'] = $profile->education?->value; - $resource['religion'] = [ - 'value' => $profile->religion?->value, - 'label' => $profile->religion?->label(), - ]; - $resource['tax_status'] = [ - 'value' => $profile->tax_status?->value, - 'label' => $profile->tax_status?->label(), - ]; - $resource['tax_id'] = $profile->tax_id; - } + public function __construct($resource) + { + parent::__construct($resource); $this->additional([ 'meta' => [ 'educations' => Education::toOptions(), 'tax_statuses' => TaxStatus::toOptions(), ], + 'links' => $this->links(), ]); + } + + /** + * Transform the resource into an array. + * + * @return array + */ + public function toArray(Request $request): array + { + $resource = $this->forPersonnel($this->identity); + + $resource[$this->getKeyName()] = $this->getKey(); return $resource; } diff --git a/src/Http/Resources/Stakeholder/StakeholderCollection.php b/src/Http/Resources/Stakeholder/StakeholderCollection.php index a89a8b2..6ee24e6 100644 --- a/src/Http/Resources/Stakeholder/StakeholderCollection.php +++ b/src/Http/Resources/Stakeholder/StakeholderCollection.php @@ -2,11 +2,14 @@ namespace Creasi\Base\Http\Resources\Stakeholder; +use Creasi\Base\Http\Resources\AsEntity; use Creasi\Base\Http\Resources\Collection; use Illuminate\Http\Request; class StakeholderCollection extends Collection { + use AsEntity; + /** * Transform the resource collection into an array. * @@ -14,6 +17,10 @@ class StakeholderCollection extends Collection */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + 'data' => $this->collection, + 'meta' => $this->meta(), + 'links' => $this->links(), + ]; } } diff --git a/src/Http/Resources/Stakeholder/StakeholderResource.php b/src/Http/Resources/Stakeholder/StakeholderResource.php index 7d4d22c..0734b4d 100644 --- a/src/Http/Resources/Stakeholder/StakeholderResource.php +++ b/src/Http/Resources/Stakeholder/StakeholderResource.php @@ -2,11 +2,27 @@ namespace Creasi\Base\Http\Resources\Stakeholder; +use Creasi\Base\Http\Resources\AsEntity; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @property-read \Creasi\Base\Models\BusinessRelative $resource + */ class StakeholderResource extends JsonResource { + use AsEntity; + + public function __construct($resource) + { + parent::__construct($resource); + + $this->additional([ + 'meta' => $this->meta(), + 'links' => $this->links(), + ]); + } + /** * Transform the resource into an array. * @@ -14,6 +30,6 @@ class StakeholderResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + return $this->forStakeholder($this->resource); } } diff --git a/src/Models/Concerns/WithProfile.php b/src/Models/Concerns/WithProfile.php index 73a9a14..e633d2b 100644 --- a/src/Models/Concerns/WithProfile.php +++ b/src/Models/Concerns/WithProfile.php @@ -5,8 +5,6 @@ use Creasi\Base\Models\Profile; /** - * @property-read ?Profile $profile - * * @mixin \Creasi\Base\Contracts\HasProfile */ trait WithProfile diff --git a/src/Models/Personnel.php b/src/Models/Personnel.php index f0543dd..6dfc227 100644 --- a/src/Models/Personnel.php +++ b/src/Models/Personnel.php @@ -12,7 +12,6 @@ use Creasi\Base\Models\Enums\PersonnelRelativeStatus; /** - * @property null|Gender $gender * @property-read ?PersonnelRelative $relative * @property-read \Illuminate\Database\Eloquent\Collection $relatives * @property-read BusinessRelative $stakeholder diff --git a/tests/Http/AddressTest.php b/tests/Http/AddressTest.php index 3b62db6..9fbdadf 100644 --- a/tests/Http/AddressTest.php +++ b/tests/Http/AddressTest.php @@ -12,6 +12,20 @@ #[Group('address')] class AddressTest extends TestCase { + private array $dataStructure = [ + 'id', + 'type', + 'line', + 'rt', + 'rw', + 'village' => ['code', 'name'], + 'district' => ['code', 'name'], + 'regency' => ['code', 'name'], + 'province' => ['code', 'name'], + 'postal_code', + 'summary', + ]; + #[Test] public function should_receive_404_when_no_data_available(): void { @@ -32,7 +46,11 @@ public function should_able_to_retrieve_all_data(): void $response = $this->getJson('base/addresses'); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => [$this->dataStructure], + 'links' => [], + 'meta' => ['types'], + ]); } #[Test] @@ -44,7 +62,10 @@ public function should_able_to_store_new_data(): void $response = $this->postJson('base/addresses', $data); - $response->assertCreated(); + $response->assertCreated()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => ['types'], + ]); } #[Test] @@ -57,7 +78,10 @@ public function should_able_to_show_existing_data(): void $response = $this->getJson("base/addresses/{$model->getRouteKey()}"); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => ['types'], + ]); } #[Test] @@ -69,7 +93,10 @@ public function should_able_to_update_existing_data(): void $response = $this->putJson("base/addresses/{$model->getRouteKey()}", $model->toArray()); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => ['types'], + ]); } #[Test] diff --git a/tests/Http/Business/CompanyTest.php b/tests/Http/Business/CompanyTest.php index 4f75d03..ee5f9d1 100644 --- a/tests/Http/Business/CompanyTest.php +++ b/tests/Http/Business/CompanyTest.php @@ -18,6 +18,16 @@ #[Group('company')] class CompanyTest extends TestCase { + private array $dataStructure = [ + 'id', + 'avatar', + 'legalname', + 'aliasname', + 'email', + 'phone', + 'summary', + ]; + #[Test] public function should_receive_404_when_no_data_available(): void { @@ -39,7 +49,11 @@ public function should_able_to_retrieve_all_data(): void $response = $this->getJson('base/companies'); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => [$this->dataStructure], + 'links' => [], + 'meta' => [], + ]); } #[Test] @@ -51,7 +65,10 @@ public function should_able_to_store_new_data(): void $response = $this->postJson('base/companies', $data); - $response->assertCreated(); + $response->assertCreated()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] @@ -63,7 +80,10 @@ public function should_able_to_show_existing_data(): void $response = $this->getJson("base/companies/{$model->getRouteKey()}"); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] @@ -152,7 +172,10 @@ public function should_able_to_update_existing_data(): void $response = $this->putJson("base/companies/{$model->getRouteKey()}", $model->toArray()); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] diff --git a/tests/Http/Business/EmployeeTest.php b/tests/Http/Business/EmployeeTest.php index 25dc3f4..0dc94f7 100644 --- a/tests/Http/Business/EmployeeTest.php +++ b/tests/Http/Business/EmployeeTest.php @@ -17,6 +17,16 @@ #[Group('employee')] class EmployeeTest extends TestCase { + private array $dataStructure = [ + 'id', + 'avatar', + 'fullname', + 'nickname', + 'email', + 'phone', + 'summary', + ]; + #[Test] public function should_able_to_retrieve_all_data(): void { @@ -25,7 +35,11 @@ public function should_able_to_retrieve_all_data(): void $response = $this->getJson('base/employees'); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => [$this->dataStructure], + 'links' => [], + 'meta' => [], + ]); } #[Test] @@ -36,7 +50,10 @@ public function should_able_to_store_new_data(): void $response = $this->postJson('base/employees', $data); - $response->assertCreated(); + $response->assertCreated()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] @@ -48,7 +65,10 @@ public function should_able_to_show_existing_data(): void $response = $this->getJson("base/employees/{$model->getRouteKey()}"); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] @@ -139,7 +159,10 @@ public function should_able_to_update_existing_data(): void $response = $this->putJson("base/employees/{$model->getRouteKey()}", $model->toArray()); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php index a3f4e82..5f0036c 100644 --- a/tests/Http/StakeholderTestCase.php +++ b/tests/Http/StakeholderTestCase.php @@ -4,6 +4,7 @@ use Creasi\Base\Models\Business; use Creasi\Base\Models\Enums\BusinessRelativeType; +use Creasi\Base\Models\Personnel; use Creasi\Tests\TestCase; use Illuminate\Contracts\Routing\UrlRoutable; use Laravel\Sanctum\Sanctum; @@ -14,6 +15,14 @@ #[Group('stakeholder')] abstract class StakeholderTestCase extends TestCase { + private array $dataStructure = [ + 'id', + 'avatar', + 'email', + 'phone', + 'summary', + ]; + abstract protected function getRelativeType(): BusinessRelativeType; final protected function getRoutePath(string|UrlRoutable ...$suffixs): string @@ -43,13 +52,20 @@ public function should_able_to_retrieve_all_data(): void { Sanctum::actingAs($user = $this->user()); - $external = Business::factory()->createOne(['name' => 'External Company']); + $company = Business::factory()->createOne(['name' => 'External Company']); + $personal = Personnel::factory()->createOne(['name' => 'External Personal']); - $user->identity->company->addStakeholder($this->getRelativeType(), $external); + $user->identity->company->addStakeholder($this->getRelativeType(), $company); + $user->identity->company->addStakeholder($this->getRelativeType(), $personal); $response = $this->getJson($this->getRoutePath()); - $response->assertOk(); + $response->dump(); + $response->assertOk()->assertJsonStructure([ + 'data' => [$this->dataStructure], + 'links' => [], + 'meta' => [], + ]); } #[Test] @@ -61,7 +77,10 @@ public function should_able_to_store_new_data(): void $response = $this->postJson($this->getRoutePath(), $data); - $response->assertCreated(); + $response->assertCreated()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] @@ -75,7 +94,10 @@ public function should_able_to_show_existing_data(): void $response = $this->getJson($this->getRoutePath($model)); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] @@ -89,7 +111,10 @@ public function should_able_to_update_existing_data(): void $response = $this->putJson($this->getRoutePath($model), $model->toArray()); - $response->assertOk(); + $response->assertOk()->assertJsonStructure([ + 'data' => $this->dataStructure, + 'meta' => [], + ]); } #[Test] From dcadde88a00dffcc6049b79c9051c28974257d41 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Sat, 12 Aug 2023 11:36:29 +0700 Subject: [PATCH 69/71] feat: every `summary` field should accept less or equal 200 char Signed-off-by: Fery Wardiyanto --- ...05_10_000001_create_identities_address_and_files_table.php | 4 ++-- src/Http/Requests/Address/StoreRequest.php | 2 +- src/Http/Requests/Address/UpdateRequest.php | 2 +- src/Http/Requests/Company/StoreRequest.php | 2 +- src/Http/Requests/Company/UpdateRequest.php | 2 +- src/Http/Requests/Employee/StoreRequest.php | 2 +- src/Http/Requests/Employee/UpdateRequest.php | 2 +- src/Http/Requests/ProfileRequest.php | 2 +- src/Http/Requests/Stakeholder/StoreRequest.php | 2 +- src/Http/Requests/Stakeholder/UpdateRequest.php | 2 +- tests/Http/StakeholderTestCase.php | 1 - 11 files changed, 11 insertions(+), 12 deletions(-) diff --git a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php index eda6075..885038c 100644 --- a/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php +++ b/database/migrations/2022_05_10_000001_create_identities_address_and_files_table.php @@ -42,7 +42,7 @@ public function up(): void $table->char('regency_code', 4)->nullable(); $table->char('province_code', 2)->nullable(); $table->char('postal_code', 5)->nullable(); - $table->string('summary')->nullable(); + $table->string('summary', 200)->nullable(); $table->timestamps(); $table->softDeletes(); @@ -56,7 +56,7 @@ public function up(): void $table->string('name')->nullable(); $table->string('path'); $table->string('disk')->nullable(); - $table->string('summary')->nullable(); + $table->string('summary', 200)->nullable(); $table->timestamps(); $table->softDeletes(); diff --git a/src/Http/Requests/Address/StoreRequest.php b/src/Http/Requests/Address/StoreRequest.php index 2602f68..90660e9 100644 --- a/src/Http/Requests/Address/StoreRequest.php +++ b/src/Http/Requests/Address/StoreRequest.php @@ -27,7 +27,7 @@ public function rules(): array 'regency_code' => ['required', 'numeric'], 'province_code' => ['required', 'numeric'], 'postal_code' => ['required', 'numeric'], - 'summary' => ['string', 'nullable'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } diff --git a/src/Http/Requests/Address/UpdateRequest.php b/src/Http/Requests/Address/UpdateRequest.php index 869c2f5..b2dc0af 100644 --- a/src/Http/Requests/Address/UpdateRequest.php +++ b/src/Http/Requests/Address/UpdateRequest.php @@ -24,7 +24,7 @@ public function rules(): array 'regency_code' => ['required', 'numeric'], 'province_code' => ['required', 'numeric'], 'postal_code' => ['required', 'numeric'], - 'summary' => ['string', 'nullable'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } } diff --git a/src/Http/Requests/Company/StoreRequest.php b/src/Http/Requests/Company/StoreRequest.php index 9bf3ea6..fa85e05 100644 --- a/src/Http/Requests/Company/StoreRequest.php +++ b/src/Http/Requests/Company/StoreRequest.php @@ -17,7 +17,7 @@ public function rules(): array 'alias' => ['nullable', 'string', Rule::unique('businesses', 'alias')], 'email' => ['required', 'email', Rule::unique('businesses', 'email')], 'phone_number' => ['nullable', 'numeric'], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } } diff --git a/src/Http/Requests/Company/UpdateRequest.php b/src/Http/Requests/Company/UpdateRequest.php index 660db60..eac8881 100644 --- a/src/Http/Requests/Company/UpdateRequest.php +++ b/src/Http/Requests/Company/UpdateRequest.php @@ -16,7 +16,7 @@ public function rules(): array 'alias' => ['nullable', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } } diff --git a/src/Http/Requests/Employee/StoreRequest.php b/src/Http/Requests/Employee/StoreRequest.php index 58df556..0cb2f38 100644 --- a/src/Http/Requests/Employee/StoreRequest.php +++ b/src/Http/Requests/Employee/StoreRequest.php @@ -19,7 +19,7 @@ public function rules(): array 'email' => ['required', 'email', Rule::unique('personnels', 'email')], 'phone' => ['nullable', 'numeric'], 'gender' => ['required', Rule::enum(Gender::class)], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } } diff --git a/src/Http/Requests/Employee/UpdateRequest.php b/src/Http/Requests/Employee/UpdateRequest.php index 941b2c3..0aa9d24 100644 --- a/src/Http/Requests/Employee/UpdateRequest.php +++ b/src/Http/Requests/Employee/UpdateRequest.php @@ -16,7 +16,7 @@ public function rules(): array 'alias' => ['nullable', 'string'], 'email' => ['required', 'email'], 'phone' => ['nullable', 'numeric'], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } } diff --git a/src/Http/Requests/ProfileRequest.php b/src/Http/Requests/ProfileRequest.php index 25408c0..5301010 100644 --- a/src/Http/Requests/ProfileRequest.php +++ b/src/Http/Requests/ProfileRequest.php @@ -17,7 +17,7 @@ public function rules(): array 'fullname' => ['required', 'string'], 'nickname' => ['nullable', 'string'], 'phone' => ['required', 'string'], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], 'prefix' => ['nullable', 'string'], 'suffix' => ['nullable', 'string'], 'education' => ['nullable', Rule::enum(Education::class)], diff --git a/src/Http/Requests/Stakeholder/StoreRequest.php b/src/Http/Requests/Stakeholder/StoreRequest.php index 2aefda0..2d9a90a 100644 --- a/src/Http/Requests/Stakeholder/StoreRequest.php +++ b/src/Http/Requests/Stakeholder/StoreRequest.php @@ -19,7 +19,7 @@ public function rules(): array 'alias' => ['nullable', 'string', Rule::unique('businesses', 'alias')], 'email' => ['required', 'email', Rule::unique('businesses', 'email')], 'phone_number' => ['nullable', 'numeric'], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } diff --git a/src/Http/Requests/Stakeholder/UpdateRequest.php b/src/Http/Requests/Stakeholder/UpdateRequest.php index af4692f..b5f3a0b 100644 --- a/src/Http/Requests/Stakeholder/UpdateRequest.php +++ b/src/Http/Requests/Stakeholder/UpdateRequest.php @@ -16,7 +16,7 @@ public function rules(): array 'alias' => ['nullable', 'string'], 'email' => ['required', 'email'], 'phone_number' => ['nullable', 'numeric'], - 'summary' => ['nullable', 'string'], + 'summary' => ['nullable', 'string', 'max:200'], ]; } } diff --git a/tests/Http/StakeholderTestCase.php b/tests/Http/StakeholderTestCase.php index 5f0036c..eee1bb8 100644 --- a/tests/Http/StakeholderTestCase.php +++ b/tests/Http/StakeholderTestCase.php @@ -60,7 +60,6 @@ public function should_able_to_retrieve_all_data(): void $response = $this->getJson($this->getRoutePath()); - $response->dump(); $response->assertOk()->assertJsonStructure([ 'data' => [$this->dataStructure], 'links' => [], From af5367a78f913b1e8034f63cf8f9d40b4419d981 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 30 Aug 2023 21:37:59 +0700 Subject: [PATCH 70/71] chore(docs): update documentation Signed-off-by: Fery Wardiyanto --- README.md | 11 +++++++++-- composer.json | 4 ++-- database/README.md | 33 ++++++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c38fa4c..63c861d 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,16 @@ [![License](https://img.shields.io/github/license/creasico/laravel-base?style=flat-square)](https://github.com/creasico/laravel-base/blob/main/LICENSE) [![Actions Status](https://img.shields.io/github/actions/workflow/status/creasico/laravel-base/test.yml?branch=main&style=flat-square)](https://github.com/creasico/laravel-base/actions) -# Laravel Account +# Creasi Base -Account Management Package for All of Our Projects +Laravel Package that aims to provide basic organization directory structures for most of our projects. + +## Requirements + +- PHP `>=v8.1` +- Laravel `>=10.0` + +## Why? ## Installation diff --git a/composer.json b/composer.json index 5d226ac..57f10e9 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "creasi/laravel-base", - "description": "Accounts Management Package", - "keywords": ["laravel", "accounts", "package"], + "description": "Laravel Package that aims to provide basic organization directory structures.", + "keywords": ["laravel", "package", "base", "accountability", "organization", "directory"], "license": "MIT", "type": "library", "authors": [ diff --git a/database/README.md b/database/README.md index 03de7d4..e36f629 100644 --- a/database/README.md +++ b/database/README.md @@ -12,6 +12,9 @@ erDiagram unsignedSmallInt tax_status varchar(16) tax_id varchar(200) summary + timestamp created_at + timestamp updated_at + timestamp deleted_at } business_relatives ||--|| businesses : stakeholder @@ -48,6 +51,9 @@ erDiagram varchar(20) phone char(1) gender varchar(200) summary + timestamp created_at + timestamp updated_at + timestamp deleted_at } personnels ||..|| profiles : identity @@ -63,6 +69,9 @@ erDiagram unsignedSmallInt religion unsignedSmallInt tax_status varchar(16) tax_id + timestamp created_at + timestamp updated_at + timestamp deleted_at } personnels ||..o{ personnel_relatives : relatives @@ -87,7 +96,10 @@ erDiagram char(4) regency_code char(2) province_code char(5) postal_code - varchar summary + varchar(200) summary + timestamp created_at + timestamp updated_at + timestamp deleted_at } users ||..|| personnels : credential @@ -106,8 +118,12 @@ erDiagram varchar name varchar path varchar disk - varchar summary + varchar(200) summary + timestamp created_at + timestamp updated_at + timestamp deleted_at } + businesses }o..|| file_attached : files personnels }o..|| file_attached : files file_attached ||--|| file_uploads : attachments @@ -120,7 +136,7 @@ erDiagram --- ## Entities -Either a companies or an individuals are tent to have share some similarities, which is they must have a way for externals to communicate with them. The most common ways are by `email` or `phone`. In that regard, another similarity is they must have a name, but there's case that differenciate how we describe the way we call them. A businesses are commonly use term `legal_name` and `alias_name`, while an individuals are commonly using `full_name` and `nick_name`. Either of them serve the same purposes. +Either a companies or an individuals are tent to shares some similarities, which is they must have a way for externals to communicate with them. The most common ways are by `email` or `phone`. In that regard, another similarity is they must have a name, but there's case that differenciate how we describe the way we call them. A businesses are commonly use term `legal_name` and `alias_name`, while an individuals are commonly using `full_name` and `nick_name`. Either of them serve the same purposes. Despite those similarities they must have some differences, including : - An individual does have gender, while a company doesn't @@ -161,7 +177,7 @@ classDiagram ## Businesses -A business must have some short of [relationship](https://www.investopedia.com/terms/b/business-relations.asp) to the externals, either to another business or individuals regardless of its size. The most common term to describe it is [stakeholder](https://www.investopedia.com/terms/s/stakeholder.asp). +A business must have some sort of [relationship](https://www.investopedia.com/terms/b/business-relations.asp) to the externals, either to another business or individuals regardless of its size. The most common term to describe it is [stakeholder](https://www.investopedia.com/terms/s/stakeholder.asp). > A stakeholder is a person or group with an interest in an enterprise - [What Are Stakeholders: Definition, Types, and Examples](https://www.investopedia.com/terms/s/stakeholder.asp) @@ -226,7 +242,7 @@ classDiagram | `phone` | `varchar(20)`, `nullable` | | The business primary phone number | | `tax_status` | `unsignedSmallInt`, `nullable` | | | | `tax_id` | `varchar(16)`, `nullable` | | | -| `summary` | `text`, `nullable` | | | +| `summary` | `varchar(200)`, `nullable` | | | **Model Attributes** - `timestamps` @@ -350,7 +366,7 @@ classDiagram | `email` | `varchar`, `nullable` | `unique` | | | `phone` | `varchar(20)`, `nullable` | | | | `gender` | `char(1)` | | | -| `summary` | `text`, `nullable` | | | +| `summary` | `varchar(200)`, `nullable` | | | **Model Attributes** - `timestamps` @@ -366,7 +382,6 @@ classDiagram | `personnel_id` | `unsignedBigInt` | `foreign` | | | `relative_id` | `unsignedBigInt` | `foreign` | | | `status` | `unsignedSmallInt`, `nullable` | | | -| `remark` | `text`, `nullable` | | | **Relation Properties** - `personnel_id` : reference `personnels` @@ -482,7 +497,7 @@ classDiagram | `regency_code` | `char(4)`, `nullable` | `foreign` | | | `province_code` | `char(2)`, `nullable` | `foreign` | | | `postal_code` | `char(5)`, `nullable` | | | -| `summary` | `varchar`, `nullable` | | | +| `summary` | `varchar(200)`, `nullable` | | | **Model Attributes** - `timestamps` @@ -550,7 +565,7 @@ classDiagram | `name` | `varchar` | | | | `path` | `varchar`, `nullable` | | | | `disk` | `varchar`, `nullable` | | | -| `summary` | `varchar`, `nullable` | | | +| `summary` | `varchar(200)`, `nullable` | | | **Model Attributes** - `timestamps` From 2777efbb9c6aa2b2a665b2c6468ea38dc028da55 Mon Sep 17 00:00:00 2001 From: Fery Wardiyanto Date: Wed, 30 Aug 2023 21:40:40 +0700 Subject: [PATCH 71/71] chore(ci): get rid of coverage output while running test Signed-off-by: Fery Wardiyanto --- .github/workflows/test.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8e2165..60a5d51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,7 +88,7 @@ jobs: command: composer update --prefer-dist --no-interaction --no-progress - name: Run tests - run: composer test + run: composer test -- --coverage - name: Generate reports for CodeClimate env: diff --git a/composer.json b/composer.json index 57f10e9..343640f 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "pint --preset laravel" ], "test": [ - "testbench package:test --coverage --ansi" + "testbench package:test --ansi" ], "testbench": [ "testbench"