Skip to content

Commit e6a8303

Browse files
Copilotnielsdrost7
andcommitted
Refactor PEPPOL to use enums, remove JSON columns, use key-value config, and logging trait
Co-authored-by: nielsdrost7 <[email protected]>
1 parent 8acc617 commit e6a8303

25 files changed

+690
-260
lines changed

Modules/Clients/Database/Migrations/2025_10_02_000004_add_peppol_validation_fields_to_relations_table.php renamed to Modules/Clients/Database/Migrations/2025_10_02_000007_add_peppol_validation_fields_to_relations_table.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66

77
return new class extends Migration
88
{
9-
/**
10-
* Run the migrations.
11-
*/
129
public function up(): void
1310
{
14-
Schema::table('relations', function (Blueprint $table) {
11+
Schema::table('relations', function (Blueprint $table): void {
1512
$table->string('peppol_scheme', 50)->nullable()->after('peppol_id')
1613
->comment('Peppol endpoint scheme (e.g., BE:CBE, DE:VAT)');
1714

@@ -26,12 +23,9 @@ public function up(): void
2623
});
2724
}
2825

29-
/**
30-
* Reverse the migrations.
31-
*/
3226
public function down(): void
3327
{
34-
Schema::table('relations', function (Blueprint $table) {
28+
Schema::table('relations', function (Blueprint $table): void {
3529
$table->dropColumn(['peppol_scheme', 'peppol_validation_status', 'peppol_validation_message', 'peppol_validated_at']);
3630
});
3731
}

Modules/Clients/Models/Relation.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Modules\Core\Models\User;
1818
use Modules\Core\Traits\BelongsToCompany;
1919
use Modules\Expenses\Models\Expense;
20+
use Modules\Invoices\Enums\PeppolValidationStatus;
2021
use Modules\Invoices\Models\CustomerPeppolValidationHistory;
2122
use Modules\Invoices\Models\Invoice;
2223
use Modules\Payments\Models\Payment;
@@ -41,7 +42,7 @@
4142
* @property string|null $peppol_scheme
4243
* @property string|null $peppol_format
4344
* @property bool $enable_e_invoicing
44-
* @property string|null $peppol_validation_status
45+
* @property PeppolValidationStatus|null $peppol_validation_status
4546
* @property string|null $peppol_validation_message
4647
* @property Carbon|null $peppol_validated_at
4748
* @property Carbon $registered_at
@@ -73,6 +74,7 @@ class Relation extends Model
7374
'relation_type' => RelationType::class,
7475
'relation_status' => RelationStatus::class,
7576
'enable_e_invoicing' => 'boolean',
77+
'peppol_validation_status' => PeppolValidationStatus::class,
7678
'peppol_validated_at' => 'datetime',
7779
];
7880

@@ -198,7 +200,7 @@ public function getCustomerEmailAttribute()
198200
public function hasPeppolIdValidated(): bool
199201
{
200202
return $this->enable_e_invoicing
201-
&& $this->peppol_validation_status === 'valid'
203+
&& $this->peppol_validation_status === PeppolValidationStatus::VALID
202204
&& $this->peppol_id !== null;
203205
}
204206

Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,24 @@
66

77
return new class extends Migration
88
{
9-
/**
10-
* Run the migrations.
11-
*/
129
public function up(): void
1310
{
14-
Schema::create('peppol_integrations', function (Blueprint $table) {
11+
Schema::create('peppol_integrations', function (Blueprint $table): void {
1512
$table->id();
1613
$table->unsignedBigInteger('company_id');
17-
$table->string('provider_name', 50)->comment('e.g., storecove, e_invoice_be');
14+
$table->string('provider_name', 50)->comment('e.g., e_invoice_be, storecove');
1815
$table->text('encrypted_api_token')->nullable()->comment('Encrypted API credentials');
19-
$table->json('config')->nullable()->comment('Provider-specific configuration');
2016
$table->string('test_connection_status', 20)->default('untested')->comment('untested, success, failed');
2117
$table->text('test_connection_message')->nullable()->comment('Last test connection result message');
2218
$table->timestamp('test_connection_at')->nullable();
2319
$table->boolean('enabled')->default(false)->comment('Whether integration is active');
24-
$table->timestamps();
2520

2621
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
2722
$table->index(['company_id', 'enabled']);
2823
$table->index('provider_name');
2924
});
3025
}
3126

32-
/**
33-
* Reverse the migrations.
34-
*/
3527
public function down(): void
3628
{
3729
Schema::dropIfExists('peppol_integrations');
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('peppol_integration_config', function (Blueprint $table): void {
12+
$table->id();
13+
$table->unsignedBigInteger('integration_id');
14+
$table->string('config_key', 100);
15+
$table->text('config_value');
16+
17+
$table->foreign('integration_id')->references('id')->on('peppol_integrations')->onDelete('cascade');
18+
$table->index(['integration_id', 'config_key']);
19+
});
20+
}
21+
22+
public function down(): void
23+
{
24+
Schema::dropIfExists('peppol_integration_config');
25+
}
26+
};

Modules/Invoices/Database/Migrations/2025_10_02_000002_create_peppol_transmissions_table.php renamed to Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66

77
return new class extends Migration
88
{
9-
/**
10-
* Run the migrations.
11-
*/
129
public function up(): void
1310
{
14-
Schema::create('peppol_transmissions', function (Blueprint $table) {
11+
Schema::create('peppol_transmissions', function (Blueprint $table): void {
1512
$table->id();
1613
$table->unsignedBigInteger('invoice_id');
1714
$table->unsignedBigInteger('customer_id');
@@ -25,11 +22,11 @@ public function up(): void
2522
$table->string('stored_pdf_path')->nullable()->comment('Path to stored PDF file');
2623
$table->text('last_error')->nullable()->comment('Last error message if failed');
2724
$table->string('error_type', 20)->nullable()->comment('TRANSIENT, PERMANENT, UNKNOWN');
28-
$table->json('provider_response')->nullable()->comment('Last provider response data');
2925
$table->timestamp('sent_at')->nullable();
3026
$table->timestamp('acknowledged_at')->nullable();
3127
$table->timestamp('next_retry_at')->nullable();
32-
$table->timestamps();
28+
$table->timestamp('created_at')->nullable();
29+
$table->timestamp('updated_at')->nullable();
3330

3431
$table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
3532
$table->foreign('customer_id')->references('id')->on('relations')->onDelete('cascade');
@@ -42,9 +39,6 @@ public function up(): void
4239
});
4340
}
4441

45-
/**
46-
* Reverse the migrations.
47-
*/
4842
public function down(): void
4943
{
5044
Schema::dropIfExists('peppol_transmissions');
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('peppol_transmission_responses', function (Blueprint $table): void {
12+
$table->id();
13+
$table->unsignedBigInteger('transmission_id');
14+
$table->string('response_key', 100);
15+
$table->text('response_value');
16+
17+
$table->foreign('transmission_id')->references('id')->on('peppol_transmissions')->onDelete('cascade');
18+
$table->index(['transmission_id', 'response_key']);
19+
});
20+
}
21+
22+
public function down(): void
23+
{
24+
Schema::dropIfExists('peppol_transmission_responses');
25+
}
26+
};

Modules/Invoices/Database/Migrations/2025_10_02_000003_create_customer_peppol_validation_history_table.php renamed to Modules/Invoices/Database/Migrations/2025_10_02_000005_create_customer_peppol_validation_history_table.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66

77
return new class extends Migration
88
{
9-
/**
10-
* Run the migrations.
11-
*/
129
public function up(): void
1310
{
14-
Schema::create('customer_peppol_validation_history', function (Blueprint $table) {
11+
Schema::create('customer_peppol_validation_history', function (Blueprint $table): void {
1512
$table->id();
1613
$table->unsignedBigInteger('customer_id');
1714
$table->unsignedBigInteger('integration_id')->nullable()->comment('Which integration was used for validation');
@@ -20,9 +17,8 @@ public function up(): void
2017
$table->string('peppol_id', 100);
2118
$table->string('validation_status', 20)->comment('valid, invalid, not_found, error');
2219
$table->text('validation_message')->nullable();
23-
$table->json('provider_response')->nullable()->comment('Full provider response for audit');
24-
$table->json('request_payload')->nullable()->comment('Request sent to provider');
25-
$table->timestamps();
20+
$table->timestamp('created_at')->nullable();
21+
$table->timestamp('updated_at')->nullable();
2622

2723
$table->foreign('customer_id')->references('id')->on('relations')->onDelete('cascade');
2824
$table->foreign('integration_id')->references('id')->on('peppol_integrations')->onDelete('set null');
@@ -33,9 +29,6 @@ public function up(): void
3329
});
3430
}
3531

36-
/**
37-
* Reverse the migrations.
38-
*/
3932
public function down(): void
4033
{
4134
Schema::dropIfExists('customer_peppol_validation_history');
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('customer_peppol_validation_responses', function (Blueprint $table): void {
12+
$table->id();
13+
$table->unsignedBigInteger('validation_history_id');
14+
$table->string('response_key', 100);
15+
$table->text('response_value');
16+
17+
$table->foreign('validation_history_id', 'fk_peppol_validation_responses')
18+
->references('id')->on('customer_peppol_validation_history')->onDelete('cascade');
19+
$table->index(['validation_history_id', 'response_key'], 'idx_validation_responses');
20+
});
21+
}
22+
23+
public function down(): void
24+
{
25+
Schema::dropIfExists('customer_peppol_validation_responses');
26+
}
27+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Modules\Invoices\Enums;
4+
5+
use Modules\Core\Contracts\LabeledEnum;
6+
use Modules\Core\Traits\HasOptions;
7+
8+
enum PeppolConnectionStatus: string implements LabeledEnum
9+
{
10+
use HasOptions;
11+
12+
case UNTESTED = 'untested';
13+
case SUCCESS = 'success';
14+
case FAILED = 'failed';
15+
16+
public function label(): string
17+
{
18+
return match ($this) {
19+
self::UNTESTED => 'Untested',
20+
self::SUCCESS => 'Success',
21+
self::FAILED => 'Failed',
22+
};
23+
}
24+
25+
public function color(): string
26+
{
27+
return match ($this) {
28+
self::UNTESTED => 'gray',
29+
self::SUCCESS => 'green',
30+
self::FAILED => 'red',
31+
};
32+
}
33+
34+
public function icon(): string
35+
{
36+
return match ($this) {
37+
self::UNTESTED => 'heroicon-o-question-mark-circle',
38+
self::SUCCESS => 'heroicon-o-check-circle',
39+
self::FAILED => 'heroicon-o-x-circle',
40+
};
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Modules\Invoices\Enums;
4+
5+
use Modules\Core\Contracts\LabeledEnum;
6+
use Modules\Core\Traits\HasOptions;
7+
8+
enum PeppolErrorType: string implements LabeledEnum
9+
{
10+
use HasOptions;
11+
12+
case TRANSIENT = 'TRANSIENT';
13+
case PERMANENT = 'PERMANENT';
14+
case UNKNOWN = 'UNKNOWN';
15+
16+
public function label(): string
17+
{
18+
return match ($this) {
19+
self::TRANSIENT => 'Transient Error',
20+
self::PERMANENT => 'Permanent Error',
21+
self::UNKNOWN => 'Unknown Error',
22+
};
23+
}
24+
25+
public function color(): string
26+
{
27+
return match ($this) {
28+
self::TRANSIENT => 'yellow',
29+
self::PERMANENT => 'red',
30+
self::UNKNOWN => 'gray',
31+
};
32+
}
33+
34+
public function icon(): string
35+
{
36+
return match ($this) {
37+
self::TRANSIENT => 'heroicon-o-arrow-path',
38+
self::PERMANENT => 'heroicon-o-x-circle',
39+
self::UNKNOWN => 'heroicon-o-question-mark-circle',
40+
};
41+
}
42+
}

0 commit comments

Comments
 (0)