-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Implement PEPPOL e-invoicing with dynamic provider architecture #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implement PEPPOL e-invoicing with dynamic provider architecture #104
Conversation
Co-authored-by: nielsdrost7 <[email protected]>
…anagement Co-authored-by: nielsdrost7 <[email protected]>
…documentation Co-authored-by: nielsdrost7 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot lots of Refactoring needed:
- I prefer to use file logs instead of logs stuffed in a json column in the database
- I don't use ->timestamps() in my migrations and models
- I use lots and lots of enums instead of constants in the models
- I use configuration in a separate table with key/value pairs (similar to a "settings" table)
- I use very extensive API BaseClients and Sub Clients for API endpoints and a decorated ExternalApiClient with a request() function to deal with API calls
Make sure it looks like it was programmed by 1 single person in 1 single day
Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php
Outdated
Show resolved
Hide resolved
Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php
Show resolved
Hide resolved
…ig, and logging trait Co-authored-by: nielsdrost7 <[email protected]>
Co-authored-by: nielsdrost7 <[email protected]>
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
♻️ Duplicate comments (1)
Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php (1)
255-260: Remove the empty PDF placeholder before shippingPersisting a zero-byte PDF guarantees downstream provider checks or later downloads fail, so the job “succeeds” while delivering corrupted artifacts. Wire up the real invoice PDF generator (or fail fast until it exists); just writing an empty string isn’t safe. This was already called out previously and still needs to be addressed.
🧹 Nitpick comments (15)
PEPPOL_ARCHITECTURE.md (3)
100-104: Add language identifier to state machine diagram.The fenced code block should specify a language for proper rendering. For state diagrams, consider using
mermaidortext.Apply this diff:
-``` +```text pending → queued → processing → sent → accepted ↘ rejected ↘ failed → retrying → (back to processing or dead)--- `150-154`: **Add language identifier to storage structure.** The fenced code block should specify a language for proper rendering. Apply this diff: ```diff -``` +```text peppol/{integration_id}/{year}/{month}/{transmission_id}/ - invoice.xml - invoice.pdf--- `373-420`: **Add language identifier to file structure tree.** The fenced code block should specify a language for proper rendering. Apply this diff: ```diff -``` +```text Modules/Invoices/ ├── Models/ │ ├── PeppolIntegration.php ...</blockquote></details> <details> <summary>Modules/Invoices/Events/Peppol/PeppolTransmissionPrepared.php (1)</summary><blockquote> `11-22`: **Consider removing the empty line for consistency.** The implementation is correct. However, line 14 has an empty line before the `parent::__construct()` call that's absent in sibling event classes (e.g., `PeppolIntegrationCreated`, `PeppolTransmissionSent`). Removing it would maintain visual consistency across all PEPPOL event constructors. Apply this diff if you'd like to align the style: ```diff public function __construct(PeppolTransmission $transmission) { $this->transmission = $transmission; - parent::__construct([ 'transmission_id' => $transmission->id, 'invoice_id' => $transmission->invoice_id, 'format' => $transmission->format, 'xml_path' => $transmission->stored_xml_path, 'pdf_path' => $transmission->stored_pdf_path, ]); }Modules/Invoices/Events/Peppol/PeppolTransmissionSent.php (1)
11-21: Consider removing the empty line for consistency.The implementation is correct. However, line 14 has an empty line before the
parent::__construct()call that's absent inPeppolIntegrationCreated. Removing it would maintain visual consistency across all PEPPOL event constructors.Apply this diff if you'd like to align the style:
public function __construct(PeppolTransmission $transmission) { $this->transmission = $transmission; - parent::__construct([ 'transmission_id' => $transmission->id, 'invoice_id' => $transmission->invoice_id, 'external_id' => $transmission->external_id, 'status' => $transmission->status, ]); }Modules/Invoices/Events/Peppol/PeppolIntegrationTested.php (1)
12-23: Consider removing the empty line for consistency.The implementation is correct and appropriately handles the optional
$messageparameter. However, line 16 has an empty line before theparent::__construct()call that's absent inPeppolIntegrationCreated. Removing it would maintain visual consistency across all PEPPOL event constructors.Apply this diff if you'd like to align the style:
public function __construct(PeppolIntegration $integration, bool $success, ?string $message = null) { $this->integration = $integration; $this->success = $success; - parent::__construct([ 'integration_id' => $integration->id, 'provider_name' => $integration->provider_name, 'success' => $success, 'message' => $message, ]); }Modules/Invoices/Enums/PeppolValidationStatus.php (1)
27-35: Consider distinct colors for INVALID vs ERROR states.Both
INVALIDandERRORmap to 'red', which reduces visual distinction. Users may benefit from differentiating validation failures (business rule violations) from system errors (technical failures).Consider this alternative color mapping:
public function color(): string { return match ($this) { self::VALID => 'green', self::INVALID => 'red', self::NOT_FOUND => 'orange', - self::ERROR => 'red', + self::ERROR => 'purple', }; }Alternatively, you could use 'gray' for ERROR to indicate a technical/unknown state distinct from business validation failures.
Modules/Invoices/Console/Commands/RetryFailedPeppolTransmissionsCommand.php (2)
19-34: Consider async job dispatch for scheduled commands.Line 24 uses
RetryFailedTransmissions::dispatch(), which dispatches the job synchronously by default. For a command scheduled to run every minute (as noted in the docblock), consider usingRetryFailedTransmissions::dispatch()->onQueue('peppol')to avoid blocking the scheduler if the job is long-running or processes multiple transmissions.Apply this diff to dispatch asynchronously to a dedicated queue:
try { - RetryFailedTransmissions::dispatch(); + RetryFailedTransmissions::dispatch()->onQueue('peppol'); $this->info('Retry job dispatched successfully.');
29-32: Enhance error logging with stack trace.The catch block logs only the exception message. For better troubleshooting, include the exception class and stack trace.
Apply this diff:
} catch (\Exception $e) { - $this->error('Failed to dispatch retry job: ' . $e->getMessage()); + $this->error('Failed to dispatch retry job: ' . get_class($e) . ' - ' . $e->getMessage()); + \Log::error('Retry job dispatch failed', ['exception' => $e]); return self::FAILURE; }Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php (1)
16-16: Consider using an enum type for connection status.Line 16 defines
test_connection_statusasstring(20)with a comment listing allowed values ('untested, success, failed'). The PR description emphasizes using enums throughout. While Laravel will cast this to aPeppolConnectionStatusenum in the model, consider whether the database itself should use an ENUM type for stronger constraints and clarity.If you prefer database-level enforcement, apply this diff:
-$table->string('test_connection_status', 20)->default('untested')->comment('untested, success, failed'); +$table->enum('test_connection_status', ['untested', 'success', 'failed'])->default('untested');Otherwise, ensure the
PeppolIntegrationmodel includes this cast:protected $casts = [ 'test_connection_status' => PeppolConnectionStatus::class, ];Modules/Invoices/Console/Commands/TestPeppolIntegrationCommand.php (1)
30-40: Validate array keys before access.Lines 32 and 34 access
$result['ok']and$result['message']without verifying these keys exist. IftestConnection()returns an unexpected structure, this could trigger undefined array key warnings.Apply this diff for safer access:
$result = $service->testConnection($integration); -if ($result['ok']) { +if ($result['ok'] ?? false) { $this->info('✓ Connection test successful!'); - $this->line($result['message']); + $this->line($result['message'] ?? 'No message provided'); return self::SUCCESS; } else { $this->error('✗ Connection test failed.'); - $this->error($result['message']); + $this->error($result['message'] ?? 'No error message provided'); return self::FAILURE; }Modules/Invoices/Listeners/Peppol/LogPeppolEventToAudit.php (2)
64-77: Refactor audit type detection for maintainability.Lines 68-74 use
str_contains()to infer audit type from the event name. This is brittle—if event naming conventions change, the logic breaks silently. Consider adding agetAuditType(): stringmethod to thePeppolEventbase class, allowing each event to declare its own audit type explicitly.Add to
PeppolEvent:public function getAuditType(): string { return 'peppol_event'; // default, override in subclasses }Then in
PeppolTransmissionCreated,PeppolTransmissionFailed, etc.:public function getAuditType(): string { return 'peppol_transmission'; }Update this listener:
-$auditType = $this->getAuditType($event); +$auditType = $event->getAuditType();Remove the protected
getAuditType()helper.
32-32: Handle json_encode errors gracefully.Line 32 calls
json_encode()without checking for errors. If the payload contains non-UTF8 strings or circular references, encoding may fail silently. Consider validating the result or usingJSON_THROW_ON_ERROR.Apply this diff:
-'info' => json_encode($event->getAuditPayload()), +'info' => json_encode($event->getAuditPayload(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),The outer try-catch will handle
JsonExceptionif encoding fails.Modules/Invoices/Jobs/Peppol/PeppolStatusPoller.php (1)
33-38: Eager load integrations to avoid N+1 queriesEach iteration ends up lazy-loading
$transmission->integration, so the poller fires 1+N queries every run. Please join the integration up front to keep the job O(1) queries per batch.- $transmissions = PeppolTransmission::where('status', PeppolTransmissionStatus::SENT) + $transmissions = PeppolTransmission::with('integration') + ->where('status', PeppolTransmissionStatus::SENT)Modules/Invoices/Config/config.php (1)
60-214: Deduplicate the country scheme mappingLines 60‑74 and Lines 169‑183 now carry identical country→scheme mappings. Maintaining two sources invites drift and inconsistent behaviour. Please define the array once (e.g., in a local variable) and reference it in both config entries.
-<?php - -return [ +<?php + +$countrySchemeMapping = [ + 'BE' => 'BE:CBE', + 'DE' => 'DE:VAT', + 'FR' => 'FR:SIRENE', + 'IT' => 'IT:VAT', + 'ES' => 'ES:VAT', + 'NL' => 'NL:KVK', + 'NO' => 'NO:ORGNR', + 'DK' => 'DK:CVR', + 'SE' => 'SE:ORGNR', + 'FI' => 'FI:OVT', + 'AT' => 'AT:VAT', + 'CH' => 'CH:UIDB', + 'GB' => 'GB:COH', +]; + +return [ @@ - 'endpoint_scheme_by_country' = [ - 'BE' => 'BE:CBE', - 'DE' => 'DE:VAT', - 'FR' => 'FR:SIRENE', - 'IT' => 'IT:VAT', - 'ES' => 'ES:VAT', - 'NL' => 'NL:KVK', - 'NO' => 'NO:ORGNR', - 'DK' => 'DK:CVR', - 'SE' => 'SE:ORGNR', - 'FI' => 'FI:OVT', - 'AT' => 'AT:VAT', - 'CH' => 'CH:UIDB', - 'GB' => 'GB:COH', - ], + 'endpoint_scheme_by_country' => $countrySchemeMapping, @@ - 'country_scheme_mapping' => [ - 'BE' => 'BE:CBE', - 'DE' => 'DE:VAT', - 'FR' => 'FR:SIRENE', - 'IT' => 'IT:VAT', - 'ES' => 'ES:VAT', - 'NL' => 'NL:KVK', - 'NO' => 'NO:ORGNR', - 'DK' => 'DK:CVR', - 'SE' => 'SE:ORGNR', - 'FI' => 'FI:OVT', - 'AT' => 'AT:VAT', - 'CH' => 'CH:UIDB', - 'GB' => 'GB:COH', - ], + 'country_scheme_mapping' => $countrySchemeMapping,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
Modules/Clients/Database/Migrations/2025_10_02_000007_add_peppol_validation_fields_to_relations_table.php(1 hunks)Modules/Clients/Models/Relation.php(5 hunks)Modules/Invoices/Config/config.php(1 hunks)Modules/Invoices/Console/Commands/PollPeppolStatusCommand.php(1 hunks)Modules/Invoices/Console/Commands/RetryFailedPeppolTransmissionsCommand.php(1 hunks)Modules/Invoices/Console/Commands/TestPeppolIntegrationCommand.php(1 hunks)Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php(1 hunks)Modules/Invoices/Database/Migrations/2025_10_02_000002_create_peppol_integration_config_table.php(1 hunks)Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php(1 hunks)Modules/Invoices/Database/Migrations/2025_10_02_000004_create_peppol_transmission_responses_table.php(1 hunks)Modules/Invoices/Database/Migrations/2025_10_02_000005_create_customer_peppol_validation_history_table.php(1 hunks)Modules/Invoices/Database/Migrations/2025_10_02_000006_create_customer_peppol_validation_responses_table.php(1 hunks)Modules/Invoices/Enums/PeppolConnectionStatus.php(1 hunks)Modules/Invoices/Enums/PeppolErrorType.php(1 hunks)Modules/Invoices/Enums/PeppolTransmissionStatus.php(1 hunks)Modules/Invoices/Enums/PeppolValidationStatus.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolAcknowledgementReceived.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolEvent.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolIdValidationCompleted.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolIntegrationCreated.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolIntegrationTested.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolTransmissionCreated.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolTransmissionDead.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolTransmissionFailed.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolTransmissionPrepared.php(1 hunks)Modules/Invoices/Events/Peppol/PeppolTransmissionSent.php(1 hunks)Modules/Invoices/Jobs/Peppol/PeppolStatusPoller.php(1 hunks)Modules/Invoices/Jobs/Peppol/RetryFailedTransmissions.php(1 hunks)Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php(1 hunks)Modules/Invoices/Listeners/Peppol/LogPeppolEventToAudit.php(1 hunks)Modules/Invoices/Models/CustomerPeppolValidationHistory.php(1 hunks)Modules/Invoices/Models/CustomerPeppolValidationResponse.php(1 hunks)Modules/Invoices/Models/PeppolIntegration.php(1 hunks)Modules/Invoices/Models/PeppolIntegrationConfig.php(1 hunks)Modules/Invoices/Models/PeppolTransmission.php(1 hunks)Modules/Invoices/Models/PeppolTransmissionResponse.php(1 hunks)Modules/Invoices/Peppol/Contracts/ProviderInterface.php(1 hunks)Modules/Invoices/Peppol/FormatHandlers/FormatHandlerFactory.php(1 hunks)Modules/Invoices/Peppol/Providers/BaseProvider.php(1 hunks)Modules/Invoices/Peppol/Providers/EInvoiceBe/EInvoiceBeProvider.php(1 hunks)Modules/Invoices/Peppol/Providers/ProviderFactory.php(1 hunks)Modules/Invoices/Peppol/Providers/Storecove/StorecoveProvider.php(1 hunks)Modules/Invoices/Peppol/Services/PeppolManagementService.php(1 hunks)Modules/Invoices/Peppol/Services/PeppolTransformerService.php(1 hunks)Modules/Invoices/Traits/LogsPeppolActivity.php(1 hunks)PEPPOL_ARCHITECTURE.md(1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
PEPPOL_ARCHITECTURE.md
100-100: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
150-150: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
373-373: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 PHPMD (2.15.0)
Modules/Invoices/Peppol/Services/PeppolTransformerService.php
47-47: Avoid unused parameters such as '$invoice'. (undefined)
(UnusedFormalParameter)
Modules/Invoices/Peppol/Providers/EInvoiceBe/EInvoiceBeProvider.php
47-47: Avoid unused parameters such as '$config'. (undefined)
(UnusedFormalParameter)
53-53: Avoid unused local variables such as '$data'. (undefined)
(UnusedLocalVariable)
Modules/Invoices/Peppol/Providers/Storecove/StorecoveProvider.php
25-25: Avoid unused parameters such as '$config'. (undefined)
(UnusedFormalParameter)
34-34: Avoid unused parameters such as '$scheme'. (undefined)
(UnusedFormalParameter)
34-34: Avoid unused parameters such as '$id'. (undefined)
(UnusedFormalParameter)
43-43: Avoid unused parameters such as '$transmissionData'. (undefined)
(UnusedFormalParameter)
55-55: Avoid unused parameters such as '$externalId'. (undefined)
(UnusedFormalParameter)
64-64: Avoid unused parameters such as '$externalId'. (undefined)
(UnusedFormalParameter)
Modules/Invoices/Peppol/Providers/BaseProvider.php
53-53: Avoid unused parameters such as '$url'. (undefined)
(UnusedFormalParameter)
53-53: Avoid unused parameters such as '$secret'. (undefined)
(UnusedFormalParameter)
65-65: Avoid unused parameters such as '$since'. (undefined)
(UnusedFormalParameter)
74-74: Avoid unused parameters such as '$responseBody'. (undefined)
(UnusedFormalParameter)
Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php
318-318: Avoid unused parameters such as '$responseBody'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (15)
Modules/Invoices/Database/Migrations/2025_10_02_000006_create_customer_peppol_validation_responses_table.php (1)
11-20: Consider adding a unique constraint on (validation_history_id, response_key).The current schema allows multiple rows with the same
validation_history_idandresponse_keycombination. This could lead to data integrity issues if duplicate keys are accidentally inserted.If key-value uniqueness per validation history is intended, apply this diff:
$table->foreign('validation_history_id', 'fk_peppol_validation_responses') ->references('id')->on('customer_peppol_validation_history')->onDelete('cascade'); - $table->index(['validation_history_id', 'response_key'], 'idx_validation_responses'); + $table->unique(['validation_history_id', 'response_key'], 'idx_validation_responses');Otherwise, verify that duplicate keys are intentionally permitted and document the rationale.
Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php (1)
11-39: LGTM - Well-designed transmission tracking table.The migration correctly implements:
- Proper foreign keys with cascade deletion to maintain referential integrity
- Strategic indexes on
status,external_id, andnext_retry_atfor query performance- Unique constraint on
idempotency_keyto prevent duplicate transmissions- Descriptive comments on columns
- Appropriate nullable fields for optional/temporal data
The string columns for
statusanderror_typewill be cast to enums at the model level, which is the standard Laravel pattern mentioned in past reviews.PEPPOL_ARCHITECTURE.md (1)
1-447: Excellent comprehensive documentation.This architecture document provides exceptional coverage of the PEPPOL implementation:
- Clear component breakdown with implementation status tracking
- Concrete usage examples with runnable code
- Architecture patterns and design rationale well-articulated
- Production readiness considerations (security, performance, monitoring)
- Helpful file structure map and next steps guidance
The document will serve as a valuable reference for developers working with the PEPPOL integration.
Modules/Invoices/Peppol/FormatHandlers/FormatHandlerFactory.php (1)
137-152: LGTM - Clean convenience method following existing patterns.The
make()method provides a useful string-based entry point for format handler creation. The implementation:
- Properly delegates to the enum-based
create()method- Converts
ValueErrortoRuntimeExceptionwith contextual error message- Follows the error handling pattern established in
createForInvoice()- Includes clear documentation
Modules/Invoices/Events/Peppol/PeppolTransmissionDead.php (1)
1-28: LGTM - Well-structured event following Peppol event patterns.The
PeppolTransmissionDeadevent is correctly implemented:
- Extends
PeppolEventbase class appropriately- Includes comprehensive payload with transmission context (
transmission_id,invoice_id,attempts,last_error,reason)- Provides public access to the
$transmissionmodel for event handlers- Implements
getEventName()with consistent naming convention- Optional
$reasonparameter allows contextual explanation of why transmission was marked deadThis event enables proper audit logging and downstream processing when transmissions reach their terminal failure state.
Modules/Invoices/Database/Migrations/2025_10_02_000002_create_peppol_integration_config_table.php (1)
11-19: Consider adding a unique constraint for config key-value integrity.Similar to the
customer_peppol_validation_responsestable, this schema allows multiple rows with the sameintegration_idandconfig_keycombination. This could lead to ambiguity when retrieving configuration values.If each config key should appear only once per integration, apply this diff:
$table->foreign('integration_id')->references('id')->on('peppol_integrations')->onDelete('cascade'); - $table->index(['integration_id', 'config_key']); + $table->unique(['integration_id', 'config_key']);Additionally, ensure consistency across all key-value tables in this PR (e.g.,
customer_peppol_validation_responsesandpeppol_transmission_responsesif they follow the same pattern).Modules/Invoices/Console/Commands/PollPeppolStatusCommand.php (1)
19-34: LGTM - Command follows Laravel conventions.The
handle()method is correctly implemented:
- Provides clear user feedback via
info()anderror()messages- Properly dispatches the
PeppolStatusPollerjob asynchronously- Includes exception handling to prevent command crashes
- Returns appropriate exit codes (
SUCCESS/FAILURE)- Suitable for scheduled execution as documented in the docblock
Modules/Invoices/Events/Peppol/PeppolIntegrationCreated.php (1)
7-25: LGTM! Clean event implementation.The event class follows Laravel conventions and the PEPPOL architecture patterns. The constructor properly initializes the payload with essential integration data, and the event name is appropriately namespaced.
Modules/Invoices/Database/Migrations/2025_10_02_000004_create_peppol_transmission_responses_table.php (1)
11-19: Verify whether duplicate keys per transmission should be prevented.The schema allows multiple rows with the same
(transmission_id, response_key)combination. For a key-value store, this can lead to ambiguous lookups when retrieving a single value by key. Typically, key-value tables enforce uniqueness on(parent_id, key)to ensure deterministic retrieval.If duplicate keys are not intended, apply this diff to add a unique constraint:
$table->foreign('transmission_id')->references('id')->on('peppol_transmissions')->onDelete('cascade'); - $table->index(['transmission_id', 'response_key']); + $table->unique(['transmission_id', 'response_key']);If duplicate keys are intentional (e.g., storing multiple error messages under the same key), please clarify the use case in comments or documentation to guide future maintainers.
Modules/Invoices/Models/CustomerPeppolValidationResponse.php (1)
15-27: LGTM! Model aligns with project patterns.The model correctly implements the key-value store pattern with
$timestamps = false, uses$guarded = []as specified in project standards, and establishes the appropriatebelongsTorelationship. The PHPDoc annotations are helpful for IDE support.Modules/Invoices/Enums/PeppolErrorType.php (1)
8-42: LGTM! Well-structured enum with appropriate UI metadata.The enum correctly implements the
LabeledEnumpattern with semantically appropriate labels, colors, and icons for each error type. Thematchexpressions are exhaustive and the metadata choices (transient→yellow, permanent→red, unknown→gray) provide clear visual distinction for users.Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php (1)
15-15: Encryption handled in model.getApiTokenAttributeandsetApiTokenAttributeencrypt/decryptencrypted_api_tokenappropriately.Modules/Clients/Database/Migrations/2025_10_02_000007_add_peppol_validation_fields_to_relations_table.php (1)
15-16: No changes required for peppol_validation_status
Verified that the Relation model casts this field toPeppolValidationStatus.Modules/Invoices/Events/Peppol/PeppolTransmissionFailed.php (1)
15-22: Enum serialization handled via model casts. ThePeppolTransmissionmodel castsstatusanderror_typeto native PHP enums, which Laravel automatically serializes to their underlying values in arrays/JSON.Modules/Invoices/Events/Peppol/PeppolTransmissionCreated.php (1)
15-22: Ensurestatusenum serializes in payload. ThePeppolTransmissionmodel castsstatustoPeppolTransmissionStatus; verify thattoArray()(and the JSON payload) yields its backing value, not an enum object—otherwise use$transmission->status->value.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Docstrings generation was requested by @nielsdrost7. * #104 (comment) The following files were modified: * `Modules/Clients/Database/Migrations/2025_10_02_000007_add_peppol_validation_fields_to_relations_table.php` * `Modules/Clients/Models/Relation.php` * `Modules/Invoices/Console/Commands/PollPeppolStatusCommand.php` * `Modules/Invoices/Console/Commands/RetryFailedPeppolTransmissionsCommand.php` * `Modules/Invoices/Console/Commands/TestPeppolIntegrationCommand.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000002_create_peppol_integration_config_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000004_create_peppol_transmission_responses_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000005_create_customer_peppol_validation_history_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000006_create_customer_peppol_validation_responses_table.php` * `Modules/Invoices/Enums/PeppolConnectionStatus.php` * `Modules/Invoices/Enums/PeppolErrorType.php` * `Modules/Invoices/Enums/PeppolTransmissionStatus.php` * `Modules/Invoices/Enums/PeppolValidationStatus.php` * `Modules/Invoices/Events/Peppol/PeppolAcknowledgementReceived.php` * `Modules/Invoices/Events/Peppol/PeppolEvent.php` * `Modules/Invoices/Events/Peppol/PeppolIdValidationCompleted.php` * `Modules/Invoices/Events/Peppol/PeppolIntegrationCreated.php` * `Modules/Invoices/Events/Peppol/PeppolIntegrationTested.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionCreated.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionDead.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionFailed.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionPrepared.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionSent.php` * `Modules/Invoices/Jobs/Peppol/PeppolStatusPoller.php` * `Modules/Invoices/Jobs/Peppol/RetryFailedTransmissions.php` * `Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php` * `Modules/Invoices/Listeners/Peppol/LogPeppolEventToAudit.php` * `Modules/Invoices/Models/CustomerPeppolValidationHistory.php` * `Modules/Invoices/Models/CustomerPeppolValidationResponse.php` * `Modules/Invoices/Models/PeppolIntegration.php` * `Modules/Invoices/Models/PeppolIntegrationConfig.php` * `Modules/Invoices/Models/PeppolTransmission.php` * `Modules/Invoices/Models/PeppolTransmissionResponse.php` * `Modules/Invoices/Peppol/Contracts/ProviderInterface.php` * `Modules/Invoices/Peppol/FormatHandlers/FormatHandlerFactory.php` * `Modules/Invoices/Peppol/Providers/BaseProvider.php` * `Modules/Invoices/Peppol/Providers/EInvoiceBe/EInvoiceBeProvider.php` * `Modules/Invoices/Peppol/Providers/ProviderFactory.php` * `Modules/Invoices/Peppol/Providers/Storecove/StorecoveProvider.php` * `Modules/Invoices/Peppol/Services/PeppolManagementService.php` * `Modules/Invoices/Peppol/Services/PeppolTransformerService.php` * `Modules/Invoices/Traits/LogsPeppolActivity.php`
Docstrings generation was requested by @nielsdrost7. * #104 (comment) The following files were modified: * `Modules/Clients/Database/Migrations/2025_10_02_000007_add_peppol_validation_fields_to_relations_table.php` * `Modules/Clients/Models/Relation.php` * `Modules/Invoices/Console/Commands/PollPeppolStatusCommand.php` * `Modules/Invoices/Console/Commands/RetryFailedPeppolTransmissionsCommand.php` * `Modules/Invoices/Console/Commands/TestPeppolIntegrationCommand.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000002_create_peppol_integration_config_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000004_create_peppol_transmission_responses_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000005_create_customer_peppol_validation_history_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000006_create_customer_peppol_validation_responses_table.php` * `Modules/Invoices/Enums/PeppolConnectionStatus.php` * `Modules/Invoices/Enums/PeppolErrorType.php` * `Modules/Invoices/Enums/PeppolTransmissionStatus.php` * `Modules/Invoices/Enums/PeppolValidationStatus.php` * `Modules/Invoices/Events/Peppol/PeppolAcknowledgementReceived.php` * `Modules/Invoices/Events/Peppol/PeppolEvent.php` * `Modules/Invoices/Events/Peppol/PeppolIdValidationCompleted.php` * `Modules/Invoices/Events/Peppol/PeppolIntegrationCreated.php` * `Modules/Invoices/Events/Peppol/PeppolIntegrationTested.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionCreated.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionDead.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionFailed.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionPrepared.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionSent.php` * `Modules/Invoices/Jobs/Peppol/PeppolStatusPoller.php` * `Modules/Invoices/Jobs/Peppol/RetryFailedTransmissions.php` * `Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php` * `Modules/Invoices/Listeners/Peppol/LogPeppolEventToAudit.php` * `Modules/Invoices/Models/CustomerPeppolValidationHistory.php` * `Modules/Invoices/Models/CustomerPeppolValidationResponse.php` * `Modules/Invoices/Models/PeppolIntegration.php` * `Modules/Invoices/Models/PeppolIntegrationConfig.php` * `Modules/Invoices/Models/PeppolTransmission.php` * `Modules/Invoices/Models/PeppolTransmissionResponse.php` * `Modules/Invoices/Peppol/Contracts/ProviderInterface.php` * `Modules/Invoices/Peppol/FormatHandlers/FormatHandlerFactory.php` * `Modules/Invoices/Peppol/Providers/BaseProvider.php` * `Modules/Invoices/Peppol/Providers/EInvoiceBe/EInvoiceBeProvider.php` * `Modules/Invoices/Peppol/Providers/ProviderFactory.php` * `Modules/Invoices/Peppol/Providers/Storecove/StorecoveProvider.php` * `Modules/Invoices/Peppol/Services/PeppolManagementService.php` * `Modules/Invoices/Peppol/Services/PeppolTransformerService.php` * `Modules/Invoices/Traits/LogsPeppolActivity.php` Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
✅ UTG Post-Process Complete No new issues were detected in the generated code and all check runs have completed. The unit test generation process has completed successfully. |
✅ Actions performedUnit Test Generation started... |
…sting documentation (#107) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
@coderabbitai resolve |
✅ Actions performedComments resolved. |
|
@coderabbitai fix title and summary |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 9782 🏁 Script executed: Length of output: 56 @nielsdrost7, I've updated the PR title and summary! Changes made: Title:
The new title follows conventional commit format and provides a clear, professional description of the feature. Summary:
The PR description now provides a professional overview while maintaining all the technical details about the implementation. |
…tecture-components
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Comment |
* Initial plan * Add core PEPPOL database migrations, models, providers, and events Co-authored-by: nielsdrost7 <[email protected]> * Add Jobs, Services, and enhanced configuration for PEPPOL lifecycle management Co-authored-by: nielsdrost7 <[email protected]> * Add audit listener, console commands, and comprehensive architecture documentation Co-authored-by: nielsdrost7 <[email protected]> * Refactor PEPPOL to use enums, remove JSON columns, use key-value config, and logging trait Co-authored-by: nielsdrost7 <[email protected]> * Implement dynamic provider discovery by scanning Providers directory Co-authored-by: nielsdrost7 <[email protected]> * Update Modules/Invoices/Models/CustomerPeppolValidationHistory.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * 📝 Add docstrings to `copilot/add-peppol-architecture-components` (#105) Docstrings generation was requested by @nielsdrost7. * #104 (comment) The following files were modified: * `Modules/Clients/Database/Migrations/2025_10_02_000007_add_peppol_validation_fields_to_relations_table.php` * `Modules/Clients/Models/Relation.php` * `Modules/Invoices/Console/Commands/PollPeppolStatusCommand.php` * `Modules/Invoices/Console/Commands/RetryFailedPeppolTransmissionsCommand.php` * `Modules/Invoices/Console/Commands/TestPeppolIntegrationCommand.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000001_create_peppol_integrations_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000002_create_peppol_integration_config_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000003_create_peppol_transmissions_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000004_create_peppol_transmission_responses_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000005_create_customer_peppol_validation_history_table.php` * `Modules/Invoices/Database/Migrations/2025_10_02_000006_create_customer_peppol_validation_responses_table.php` * `Modules/Invoices/Enums/PeppolConnectionStatus.php` * `Modules/Invoices/Enums/PeppolErrorType.php` * `Modules/Invoices/Enums/PeppolTransmissionStatus.php` * `Modules/Invoices/Enums/PeppolValidationStatus.php` * `Modules/Invoices/Events/Peppol/PeppolAcknowledgementReceived.php` * `Modules/Invoices/Events/Peppol/PeppolEvent.php` * `Modules/Invoices/Events/Peppol/PeppolIdValidationCompleted.php` * `Modules/Invoices/Events/Peppol/PeppolIntegrationCreated.php` * `Modules/Invoices/Events/Peppol/PeppolIntegrationTested.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionCreated.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionDead.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionFailed.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionPrepared.php` * `Modules/Invoices/Events/Peppol/PeppolTransmissionSent.php` * `Modules/Invoices/Jobs/Peppol/PeppolStatusPoller.php` * `Modules/Invoices/Jobs/Peppol/RetryFailedTransmissions.php` * `Modules/Invoices/Jobs/Peppol/SendInvoiceToPeppolJob.php` * `Modules/Invoices/Listeners/Peppol/LogPeppolEventToAudit.php` * `Modules/Invoices/Models/CustomerPeppolValidationHistory.php` * `Modules/Invoices/Models/CustomerPeppolValidationResponse.php` * `Modules/Invoices/Models/PeppolIntegration.php` * `Modules/Invoices/Models/PeppolIntegrationConfig.php` * `Modules/Invoices/Models/PeppolTransmission.php` * `Modules/Invoices/Models/PeppolTransmissionResponse.php` * `Modules/Invoices/Peppol/Contracts/ProviderInterface.php` * `Modules/Invoices/Peppol/FormatHandlers/FormatHandlerFactory.php` * `Modules/Invoices/Peppol/Providers/BaseProvider.php` * `Modules/Invoices/Peppol/Providers/EInvoiceBe/EInvoiceBeProvider.php` * `Modules/Invoices/Peppol/Providers/ProviderFactory.php` * `Modules/Invoices/Peppol/Providers/Storecove/StorecoveProvider.php` * `Modules/Invoices/Peppol/Services/PeppolManagementService.php` * `Modules/Invoices/Peppol/Services/PeppolTransformerService.php` * `Modules/Invoices/Traits/LogsPeppolActivity.php` Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * CodeRabbit Generated Unit Tests: Add PEPPOL PHPUnit test suite and testing documentation (#107) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Summary
Complete implementation of PEPPOL e-invoicing system with fully refactored architecture aligned to InvoicePlane v2 standards.
Key Features
Architecture Highlights
Comprehensive Style Alignment
Dynamic Provider Discovery
Key-Value Architecture
peppol_integration_configtable for provider settingspeppol_transmission_responsestable for provider responsescustomer_peppol_validation_responsestable for validation dataEnums with Rich Functionality
PeppolTransmissionStatus- 9 states with labels/colors/iconsPeppolErrorType- TRANSIENT/PERMANENT/UNKNOWN with metadataPeppolValidationStatus- 4 states with full UI supportPeppolConnectionStatus- 3 states for integration testingLogging Standardization
LogsPeppolActivitytrait used everywherelogPeppolInfo(),logPeppolError(),logPeppolWarning(),logPeppolDebug()HTTP Client Pattern
ApiClientwith singlerequest()methodRequestMethodenum (not split into get/post/etc)HttpClientExceptionHandlerStatistics
Database:
Models:
$guarded = []Code Quality:
Project Pattern Compliance
Every aspect matches InvoicePlane v2 coding standards:
LabeledEnuminterface$guarded = []in models->timestamps()in config migrationsrequest()methodDocumentation
Complete architecture documentation included covering:
Original Design Prompt
Below is the complete, implementation-agnostic architecture and step-by-step lifecycle from admin configuration to transmission, acknowledgement handling, retries and audit.
Components (actors & services)
Key Data Records
PeppolIntegration: provider_name, encrypted_api_token, config, test_connection_status, enabled
Customer: e_invoicing_enabled, peppol_scheme, peppol_id, peppol_validation_status, peppol_validation_message, peppol_validated_at
PeppolTransmission: id, invoice_id, customer_id, integration_id, format, status, attempts, idempotency_key, external_id, stored_xml_path, stored_pdf_path, last_error
PeppolEvent/AuditLog: Logged events determined by unique IDs in log files
Transmission State Machine
States:
Provider Contract
Every provider must implement:
testConnection(config)→ { ok: bool, message }validatePeppolId(peppol_scheme, peppol_id)→ { present: bool, details }sendInvoice(transmissionDto)→ { accepted: bool, external_id?, status_code, message }getTransmissionStatus(external_id)→ { status, ack_payload? }registerWebhookCallback(url, secret)(optional)fetchAcknowledgements(since)→ list[ack] (optional)All providers return normalized errors/codes mapped to: TRANSIENT, PERMANENT, UNKNOWN