Table of Contents
- Description
- Installation
- Configuration
- Features
- Architecture and Extension Points
- Point 0 Status
- Scripts
- Contributing
- License
Description
The ERP module provides Laraplate’s accounting and operations domain: multi-company, chart of accounts, journal entries, fiscal calendar, tax codes, commercial scaffolding (customers, quotations, projects), and related Filament admin resources. The module is optional and can be enabled or disabled via modules_statuses.json like other Laraplate modules.
The package evolves with product requirements; treat public APIs as unstable until a stable release is declared.
Point 0 Status
The reconciled implementation baseline is documented in docs/STATUS.md. All approved mandatory ERP work that does not require external /api/v1 exposure is implemented. External-source importers are tracked separately and are not part of this baseline.
Architecture and Extension Points
The module's boundaries, invariants, supported service-container replacements, outbox event contracts, and change checklist are documented in docs/VISION.md. ERP currently exposes contract bindings for chart of accounts, currency conversion, and e-invoicing; Core exposes the integration outbox publisher. There are no container-tagged ERP plugin hooks today.
Installation
If you want to add this module to your project, you can use the joshbrw/laravel-module-installer package.
In a full Laraplate application you depend on Core first, then CRM (the ERP requires it: the legal identity of every party lives on the CRM counterparty), then add ERP. Add the repositories to your composer.json file (adjust URLs if you use forks or private registries):
"repositories": [
{
"type": "composer",
"url": "https://github.com/getlaraplate/laracore.git"
},
{
"type": "composer",
"url": "https://github.com/getlaraplate/laraerp.git"
}
]
composer require joshbrw/laravel-module-installer getlaraplate/laracore getlaraplate/laraerp
Then install and activate the modules:
php artisan module:install Core
php artisan module:install ERP
Ensure modules_statuses.json lists ERP as enabled when you want the module loaded.
Configuration
When the module is active, configuration is published under the erp key (file: Modules/ERP/config/config.php).
// Example
config('erp.name'); // "ERP"
config('erp.einvoice.driver'); // "stub", "fatturapa", or "aruba"
E-invoice provider configuration is read through Laravel config/env:
ERP_EINVOICE_DRIVER:stubby default; supported values arestub,fatturapa,arubaERP_EINVOICE_ARUBA_BASE_URL: Aruba API base URL, for example the contracted demo/production web-service hostERP_EINVOICE_ARUBA_AUTH_BASE_URL: optional Aruba auth base URL when using username/password token acquisitionERP_EINVOICE_ARUBA_UPLOAD_PATH: upload endpoint path, default/services/invoice/uploadERP_EINVOICE_ARUBA_NOTIFICATIONS_PATH: notifications polling endpoint path, default/api/v2/invoices-out/notificationsERP_EINVOICE_ARUBA_TOKEN: bearer token used by the Aruba adapter; if missing, username/password auth is attemptedERP_EINVOICE_ARUBA_USERNAME/ERP_EINVOICE_ARUBA_PASSWORD: optional credentials for Aruba/auth/signinERP_EINVOICE_ARUBA_CALLBACK_API_KEY: optional static API key expected on Aruba callback requestsERP_PAYMENT_REQUEST_DRIVER: payment-request provider binding (stubin the current module)ERP_PAYMENT_REQUEST_STUB_CALLBACK_API_KEY: enables/authenticates the external stub callback endpoint; callbacks are disabled when emptyERP_EINVOICE_ARUBA_SIGNATURE_CREDENTIAL/ERP_EINVOICE_ARUBA_SIGNATURE_DOMAIN: optional Aruba signature parametersERP_EINVOICE_ARUBA_SENDER_PIVA: optional sender VAT override; defaults to company fiscal country + tax idERP_EINVOICE_ARUBA_SKIP_EXTRA_SCHEMA/ERP_EINVOICE_ARUBA_DRY_RUN: optional upload flagsERP_STOCK_RESERVATION_SOFT_TTL: minutes a soft stock reservation holds stock back before it lapses (erp.stock_reservation.soft_ttl), default1440; read by whoever creates the soft hold, so keep it well above the payment window
Per-company ERP settings are stored in companies.settings (JSON). Use ErpCompanySettings to read dotted keys (e.g. erp.three_way_match.price_tolerance_percent).
Features
Requirements
- PHP >= 8.5
- Laravel 12.0+
- Required: Laraplate Core for users, permissions, and shared infrastructure, and CRM for counterparties, contacts, leads and opportunities
Quality toolchain (declared by the application)
The module declares no require-dev of its own — it was removed on 2026-09-15, together with its
phpunit.xml, pint.json, rector.php and peck.json. The packages below are installed and
configured once in the root composer.json, and the module is analysed and formatted from there:
- pestphp/pest and Laravel / type-coverage plugins
- laravel/pint
- larastan/larastan
- rector/rector and driftingly/rector-laravel
- filament/filament (v5) for admin UI
- nunomaduro/phpinsights, peckphp/peck
Module metadata
- Priority:
999inmodule.json(load order consistent with other feature modules) - Namespace:
Modules\ERP\... - Composer package:
getlaraplate/laraerp - Web / API routes:
Modules/ERP/routes/web.php,Modules/ERP/routes/api.php
M0 — Multi-tenancy & Multi-currency Foundation
companiestable with functional currencyBelongsToCompanytrait + global scope for tenant isolation- Dual-currency columns via
ERPMigrateUtils::moneyColumns()(amount_doc / currency_doc / amount_local / fx_rate) CurrencyConverterservice (no-op in M0, pluggable for live FX)
M0 — Versioning
HasVersionstrait withVersionStrategy::DIFFon accounting models- Immutable version strategy on contabile models (cannot be overridden via settings)
- Core Settings hides historical
version_strategy_erp_*rows for those class-forced DIFF models; the rows are retained but cannot mislead administrators
M1 — Accounting Backbone
- Chart of Accounts (
accountstable,Accountmodel,ChartOfAccountsProviderinterface, Italian PDC default) - Journal Entries (
JournalEntry/JournalEntryLine,JournalPostingService::post/reverse, immutability after posting) JournalEntry,JournalEntryLine,VatRegisterEntry,StockCostLayer,StockLevelandStockMovementimplement CoreRestrictsCrudWrites(viaDeniesGenericCrudWrites): generic CRUD writes on them answer403; they change only through their owning services- Fiscal Calendar (
FiscalYear,FiscalPeriod,FiscalPeriodCloserwith re-open audit) - Document Sequences (
DocumentSequence,DocumentNumberAllocator,DocumentTypeenum with gap policy)
M2 — Tax & Invoice Foundation
- Tax Codes (
TaxCode,TaxKindenum, immutable fiscal snapshot,TaxLineCalculator,TaxCodeSupersessionService) ItalianTaxCodesSeederbaseline- Invoice / InvoiceLine stub with tax snapshot columns
M3.1 — CRM
- Leads, opportunities, contacts and pipeline stages moved to the CRM module (see its README); the ERP keeps
QuotationObserver(an accepted quotation makes its party a customer and calls the CRMOpportunityLifecycleService) Partyis the relationship between a CRMCounterpartyand oneCompanyCompany.counterparty_id(optional,Company::ensureCounterparty()): the company as a CRM subject. A party whose counterparty belongs to another company of the group is intercompany (Party::isIntercompany(), party list filter); twin documents and consolidation eliminations are not built yet
M3.2 — Sales Order Processing
SalesOrder/SalesOrderLinewith lock-chain progressionSalesOrderEvasionService(delivery + invoice qty tracking, status management)SalesOrderAmendmentService(creates amendment SO from confirmed / partially evaded orders)- Lock propagation: confirming SO locks linked Quotation
M3.3 — Inventory Management
- Items, Warehouses, StockLevels
StockMovement/stock_cost_layerstablesStockMovementServicewith FIFO and weighted-average costing- COGS calculation integrated with delivery posting
StockReservation/StockReservationService:available = on hand - live reservations, item-level and company-wide. A sales order hard-reserves its item lines best effort when confirmed (never blocking the confirm), releases them on cancel or amend, and consumes the reserved part at delivery; MES reserves BOM components for released production orders through the same service. Seedocs/rag/MODULE.md§ Stock Reservation and Availability (ATP)erp:stock-reservations:expire(scheduled hourly) releases soft reservations whoseexpires_athas passed; availability already ignores an expired soft on read, so the sweep is housekeeping
M3.4 — Delivery Notes
DeliveryNote/DeliveryNoteLinewith SO line linkingDeliveryNoteInventoryService(stock posting + rollback)DeliveryNoteCogsJournalService(COGS journal on posting, full reversal on unpost)- Automatic SO qty_delivered updates
M3.5 — Invoice Posting & DDT Integration
InvoicePostingServicewith full journal post / unpost cycle (observer-driven viaposted_at)DocumentNumberAllocatorintegration at posting time (reference assigned on post, cleared on unpost)- Tax snapshot on invoice lines (tax_code, tax_rate, tax_label frozen at posting)
- SO qty_invoiced tracking via
SalesOrderEvasionService - Pivot
erp_invoice_line_delivery_note_linefor flexible Invoice↔DDT linking, mapped byModels\Pivot\InvoiceLineHasDeliveryNoteLinewith covered quantity and timestamps InvoiceDeliveryNoteValidationService— optional DDT linkage rules at posting (posted DDT, qty caps, SO line consistency)InvoiceCompactionService(compact / expand invoice lines by item)- Filament Post / Unpost actions on invoice edit page and list (no manual
posted_atediting) - Posting is refused with
PostingToClosedFiscalPeriodExceptionwhen the posting date falls in a closed fiscal period:InvoicePostingServiceresolves the covering period of the company andJournalPostingServicerejects it. Reopen the period (Filament Reopen action or thereopendomain action) before posting force_postis a separate ability that applies only to unposted purchase invoices: it shows the Force three-way match checkbox on the Post action, which lets posting proceed past a price/quantity tolerance breach recorded asMatchStatus::Forced- See
docs/ERP_GUIDA_SEMPLICE.md§4.7 anddocs/rag/MODULE.md§ Invoice posting workflow for end-to-end flows
M3.6 — Purchasing Cycle
- Unified
Partyentity (replacesCustomer) withis_customer/is_supplierboolean flags - Party type validation on all models (saving callback) + filtered Filament dropdowns
PurchaseOrder/PurchaseOrderLine,GoodsReceipt/GoodsReceiptLineThreeWayMatchServicewith configurable price / quantity tolerances (wired inInvoicePostingServiceon purchase posting)MatchStatusenum (matched, tolerance, forced, unmatched) persisted oninvoice_lineswithmatch_discrepancyJSON- Filament Force three-way match checkbox on purchase invoice Post action
- Per-company settings via
ErpCompanySettingsoncompanies.settingsJSON (erp.three_way_match.*, default 0%)
M3+ — Database Lock Triggers
- MySQL/MariaDB and PostgreSQL triggers protect locked quotations, sales orders, and projects from raw updates/deletes
- Confirmed/partially/fully evaded sales orders lock their linked quotation and project; DDT lines lock their source sales-order line
- Locked order lines reject commercial changes (
qty_ordered, item, description, source and price) and deletion, while operational counters/status remain writable - SQLite and Oracle use the equivalent Eloquent guards; database triggers remain defense in depth where installed
M5.1 — Payment Schedule & Receivables
PaymentTermmodel (rate_lines JSON with days / percent / payment_method)PaymentScheduleLine(auto-generated at invoice posting)Payment/PaymentAllocation(allocation to schedule lines)PaymentScheduleGeneratorService(integrated in InvoicePostingService)PaymentAllocationService(allocate / deallocate with status tracking)AgingReportService(AR / AP aging by 30 / 60 / 90 / 120+ day buckets)PartyBankAccount,PaymentRun, andPaymentRunLinefor supplier payment runsPaymentRunBuilderServicebuilds draft outbound supplier payment batches from open purchase schedule linesSepaPain001Exporterexports approved supplier payment runs as auditable SEPA SCTpain.001XML files- Payment execution is file generation only: no direct bank/API submission, no CBI/Ri.Ba/SDD in v1
M5.2 — Credit & Debit Notes
InvoiceTypeenum (invoice, credit_note, debit_note)- Separate document numbering (SalesCreditNote, PurchaseCreditNote, SalesDebitNote, PurchaseDebitNote)
CreditNoteService(create CN from posted invoice, line copying, total validation)- Inverted journal entries on credit note posting
- Filament "Create Credit Note" action on invoice page
- Credit/debit notes are fiscal corrections: natural prices come from source invoice lines, not from orders.
M5.3 — VAT Registers & Settlement (Italian Compliance)
VatRegisterEntrywith progressive protocol numbers per register / yearVatRegisterService(auto-registration at invoice posting, one entry per tax code)VatSettlement(IVA debito − IVA credito − credito precedente)VatSettlementService(compute + confirm with carry-forward)- Filament read-only register and settlement pages
M5.4 — Financial Statements
TrialBalanceService(debit / credit balance per account at date)BalanceSheetService(assets = liabilities + equity + net income)IncomeStatementService(revenue − expenses for date range)- Filament pages with Blade templates for tabular reports
E-Invoice Interface
EInvoiceProvidercontract (prepare, submit, validateXml, remoteStatus)EInvoiceSubmissionmodel +EInvoiceSubmissionStatusenum- Stub provider binding and deterministic local submission workflow
- Filament actions for posted sale invoice submit / status refresh
- Phase 2C FatturaPA readiness: fiscal schema fields on company/party/invoice,
FatturaPaAnagraphicMapper,FatturaPaXmlBuilder, vendored official FPR12 v1.2.3 XSD resources, and localfatturapadriver validation arubadriver: production-oriented adapter for Aruba v2-style upload (dataFile), notifications polling, optional auth signin, provider callbacks, and conservation availability metadata inresponse_payloadPOST /api/v1/erp/einvoice/aruba/callbacksapplies signed provider callbacks whenERP_EINVOICE_ARUBA_CALLBACK_API_KEYis configurederp:einvoice:refresh-statusespolls open submissions for the configured provider and supports--company,--provider,--limit, and--dry-runerp:health-check --format=table|jsonperforms read-only installation and operational checks for the default company, chart of accounts, fiscal calendar, domain permissions, current-year sequences, and e-invoice configurationerp:sequences:audit --company=ID --year=YYYY --format=table|jsoncross-checks persisted invoice/order references against sequence formatters, counters, duplicates, and gap policy without repairing dataerp:bank-statements:import --bank-account=ID --path=FILE_OR_DIR --format=auto --dry-runvalidates or imports CSV, CAMT.053, and MT940 files idempotently by SHA-256 checksum;--archive-pathexplicitly enables post-import file moveserp:vat-settlements:compute --company=ID --year=YYYY --period=YYYY-N --dry-runpreviews or computes draft VAT settlements for open fiscal periods; confirmed settlements and closed periods are never modified- Current runtime is still local by default: the
stubdriver remains default;fatturapagenerates and validates XML without delivery - Remaining go-live work: verify the configured Aruba tenant/contract, callback accreditation/IP allowlist, production credentials, and legal retention obligations with the provider
M6.1 — Bank Reconciliation
BankAccount,BankStatement, andBankStatementLinemodels- Bank statement import from CSV, CAMT.053, and a minimal MT940 transaction subset
- Manual reconciliation service with ranked payment suggestions
- Match-with-difference service and Filament workflow for fees, rounding, and write-offs
BankDifferenceJournalServiceposts balanced journals againstbank_cashand the selected difference accountBankStatementLine.difference_journal_entry_idkeeps audit linkage to the accounting adjustment
M6.2 — Returns Management
ReturnOrderfor customer returns andSupplierReturnfor supplier returns- Customer returns generate/link inbound DDTs for physical stock receipts
- Supplier returns generate/link outbound DDTs for physical stock shipments
- DDT lines remain quantity/source-link only; no prices or costs on bolle
- Return completion tracks returned quantities on source invoice / sales order / purchase order / goods receipt lines
- Manual follow-up actions create and link credit/debit note drafts; optional setting
erp.returns.auto_create_notes_on_completecreates NC/ND drafts during return completion - Customer return credit notes use
ReturnOrderLine.invoice_line_idand the source salesInvoiceLine.unit_price;ReturnOrderLine.unit_priceis an optional manual override. - Supplier return debit notes use
SupplierReturnLine.invoice_line_idand the source purchaseInvoiceLine.unit_price;purchase_order_line_idandgoods_receipt_line_idremain logistics references. - Supplier return debit-note creation is blocked when the source purchase invoice line is missing. Returning goods physically can happen from PO/GR, but fiscal correction must reference the purchase invoice.
M7.1 — Advanced Pricelists & Party Rules
PriceList/PriceListItemwith validity windows and Filament resource- Each price-list row targets exactly one direct
Itemor oneActivitytaxonomy; direct item prices take precedence and taxonomy prices are the fallback PartyPriceRulewith percent, fixed, and override discountsParty::price_rules()relation and Party Filament relation managerPriceResolverServiceandInvoiceLinePricingServiceapply commercial pricing to quotation / sales order / invoice linesActivityis the concrete ERP taxonomy used for activity-based price rules; do not point UI directly at abstract CoreTaxonomy.
Integration Outbox
- Posted invoices, exact/difference payment matches, completed customer returns, and completed supplier returns write durable Core outbox events in the same database transaction.
- Event types are
erp.invoice.posted,erp.payment.matched,erp.customer-return.completed, anderp.supplier-return.completed. - Core queues publication after commit. Its default publisher is a no-I/O stub; an application transport binding is required for external delivery.
Cash Movements and Journal
Movementis a cash-entry adapter, not a parallel accounting ledger. It stores positive document amount, date, currency, one ofincome,expense,contribution, orwithdrawal, and an explicit economic counterparty account.MovementPostingServiceposts exactly one balancedJournalEntry: income debitsbank_cashand credits Revenue; expense debits Expense and creditsbank_cash; contribution debitsbank_cashand credits Liability; withdrawal debits Liability and creditsbank_cash.- Document/local amount and FX rate are frozen on posting. Repeated posting is idempotent through
posted_journal_entry_id. CashBalanceServicederives the balance from posted journal lines on accounts carryingmeta.erp_role=bank_cash; no mutable balance table is updated.erp:migrate-movements-to-journal [--company=ID] [--dry-run]posts only unlinked movements and can be rerun safely.- The Filament Cash Movements resource offers create/list/detail only. Creation and journal posting share one transaction; posted movements have no edit route.
Partner Pools and Settle-up
PartnerPoolgroups Core users within one company and currency; membership is tracked by thePartnerPoolHasUserpivot.- An expense
Movementcan be split throughMovementAllocation:owed_amountis the participant's share andpaid_amountis what they advanced. Both totals must exactly equal the movement amount. PartnerPoolSettlementServicederives each balance as paid minus owed, adjusted by confirmedPoolTransactiontransfers. No mutable pool balance is stored.suggestSettleUp()proposes debtor-to-creditor transfers;settle()validates current balances under a transaction and records the confirmed internal transfer.- The Partner Pools Filament resource manages membership, expense splits, and settle-up. Pool balances are an internal subledger and never replace journal-derived company cash.
External ERP imports
php artisan erp:import --importer='Vendor\\Importer'runs only importers implementingModules\ERP\Import\Contracts\BulkImporterInterface.--bootstrap, repeated--arg,--limit,--dry-run,--no-search, and--index-batchuse the shared Core import contract.- Source adapters remain external runtime plugins. They map source records explicitly to a company, Core users, accounts, pool, currency, and categories, then call
ExternalCashMovementImportServiceandExternalExpenseAllocationService; ERP contains no Symfony, SPLID, or Tricount schema knowledge. - Every source record uses a stable
(source_key, external_id)plus a lowercase SHA-256 fingerprint of normalized fields. Unchanged reruns skip, changed unposted records may be replaced, and changed posted movements throwPostedImportConflictinstead of rewriting a journal. - Import boundaries accept decimal strings normalized to scale 4 and never floats. Expense allocations preserve both owed and paid totals; dated contributions, withdrawals, and reimbursements remain separate cash events rather than allocation rows.
Payment Requests
PaymentRequestasks one Party or one Core user to pay an amount; it is not itself aPayment, journal entry, or pool settlement.PaymentRequestProvideris replaceable. The built-instubcreates a deterministic non-payable checkout URL for workflow tests without network I/O.PaymentRequestServicesends drafts transactionally and applies authenticated, idempotent provider callbacks. Paid/cancelled states cannot regress.POST /api/v1/erp/payment-requests/{provider}/callbacksis for external providers and is unavailable unless that provider has a callback API key configured.- Filament exposes create/list/edit and a Send action. Real Stripe/PayPal/Satispay adapters and accounting reconciliation remain future provider work.
- API switch. Every
/apiroute, these callbacks included, answers403while the Coreexpose_apisetting is off (the default): an installation that wants provider callbacks must turn the API on. The two callback routes (erp/einvoice/{provider}/callbacks,erp/payment-requests/{provider}/callbacks) are markedSelfAuthenticatedApiRoute: the provider sends its own secret asAuthorization: Bearer, Laraplate does not read it as an API token, the request runs as the anonymous user, and the controller's key check authenticates it. They stay subject to the per-addressapirate limit (core.api.rate_limit_per_minute, default 600 a minute). SeeModules/Core/docs/rag/API_ACCESS_CONTROL.md.
Task Calendar Export
TaskIcsExportergenerates one RFC 5545VEVENTwith stable UID, UTCDTSTART/optionalDTEND, escaped text, CRLF endings, and 75-byte line folding.SUMMARYcomes from the Activity taxonomy (with project/task fallback);LOCATIONcomes fromTask -> Site -> Core Placepostal fields.- Task validity columns are explicitly cast as immutable datetimes and the original nullable project/site FK declarations are cross-driver safe.
TaskResourceprovides create/list/edit and Export calendar, downloadingerp-task-{id}.icsastext/calendar.- This is a portable file export, not calendar subscription, bidirectional sync, reminder management, or attendee invitation delivery.
Quotation Revisions and Project Locks
QuotationRevisionServicecreates a commercial snapshot as a new draft, copies all quotation lines, incrementsversion, and links the immediate predecessor throughrevises_quotation_id.- Revision history is linear: revise the latest quotation; a source with an existing successor cannot branch. Editable unlocked drafts are changed directly rather than revised.
- Filament exposes Create revision for locked or non-draft quotations and redirects to the new draft.
- A project linked to a confirmed/partially/fully evaded sales order is locked automatically. Model validation blocks update/delete and Filament hides edit/delete access.
- DDT linkage locks the source sales-order line. Commercial values become immutable, while delivery, invoice and return counters and operational status can still progress.
- Application locking is portable; MySQL/MariaDB and PostgreSQL add equivalent database trigger defense against raw SQL bypasses.
Spec 2 Phase 2A/2B — Domain Actions & Commercial UX
- State-aware
ERPModelPolicyguards domain actions on top of Core CRUD permissions. - Domain abilities seeded in
ERPDatabaseSeederinclude posting/unposting, forced posting, e-invoice submit/refresh, quotation unlock, document-sequence reset/reserve, tax-code supersession, and company context switch. - Filament edit/list actions call services; they do not implement business mutations inline.
- Implemented Phase 2B items: Party price rules UI, PriceList resource, quotation unlock, document sequence reset, return line fiscal override contract, and optional auto NC/ND on return complete.
- Implemented Phase 2B items also include supplier payment runs with SEPA
pain.001and CBI bonifici exports. - Implemented Phase 2B banking items also include bank difference journals, match-with-difference UI, and CAMT.053 / MT940 statement import.
- Implemented Phase 2B reporting items also include CSV export actions and immutable CSV/PDF snapshot archives for trial balance, balance sheet, and income statement.
- Implemented Phase 2B operational dashboard items also include Sales Pipeline filters/KPIs/CSV export and Stock Valuation warehouse filter/KPIs/CSV export.
Supplier Payment Runs
- Supplier bank coordinates live on
PartyBankAccount, not on invoices or payment lines. PaymentRunBuilderServiceselects open/partial purchase invoice schedule lines and creates a draft payment run.PaymentRunLinestores a beneficiary snapshot: name, IBAN, BIC, amount, due date, and remittance text.- Payment runs move through
draft -> approved -> exported;cancelledis available before export. - Exported payment runs are immutable and store file name, export timestamp, and SHA-256 checksum.
- Supported supplier payment file formats are SEPA Credit Transfer
pain.001and CBI bonifici text export; bank submission remains outside ERP.
Filament Admin UI
- Resources: Party, Contact, Site, Quotation, Project, Lead, Opportunity, SalesOrder, DeliveryNote, Invoice, PurchaseOrder, GoodsReceipt, ReturnOrder, SupplierReturn, PaymentTerm, Payment, PaymentRun, PaymentRequest, PartnerPool, BankAccount, BankStatement, VatRegister (read-only), VatSettlement (read-only)
- ERP
Siterecords select a canonical CorePlace; address, city, province, postcode and country are not duplicated in ERP. Site validity uses the standard CoreHasValiditycontract. - Core accounting: Company, Account, JournalEntry (with view page), FiscalYear, FiscalPeriod, DocumentSequence, TaxCode
- Report pages: Trial Balance, Balance Sheet, Income Statement, Sales Pipeline, Stock Valuation. Financial and operational report pages expose CSV export actions; financial reports can be archived through immutable CSV/PDF snapshots.
Current ERP Status
| Area | Status | Notes |
|---|---|---|
| M3.6 Purchasing | Implemented / cleanup only | Purchase invoice posting and 3-way match are present; keep regression coverage focused. |
| M4 Permissions & reporting | Implemented v1 + CSV/PDF snapshots | Domain permissions, accounting/operational reports, CSV exports, immutable report snapshots, read-only pages, Filament actions, and registered internal HTTP actions are present. |
| M5.1 Payment execution | Implemented v1 + Italian bank exports | Supplier bank coordinates, payment runs, SEPA pain.001 XML export, CBI bonifici export, checksum metadata, and Filament resource are present. Ri.Ba/SDD generators cover customer receivable schedules; direct bank submission is a separate transport concern. |
| M6.1 Bank reconciliation | Implemented v1 + differences + bank formats | CSV, CAMT.053, and minimal MT940 import, manual match, suggestions, match-with-difference UI, and difference journal entries are present. |
| M6.2 Returns management | Implemented v1 + fiscal override + optional auto notes | Customer/supplier returns, DDT integration, returned-quantity tracking, manual NC/ND follow-up actions, optional auto NC/ND on completion, and invoice-line-based fiscal pricing are present. |
| M6.3 E-invoice stub | Implemented v1 + Phase 2C + Aruba operations | Provider binding, deterministic stub submission workflow, invoice actions, FatturaPA readiness fields, local XML/XSD validation, Aruba upload/polling/callback adapter, and polling command are present. |
| M7.1 Advanced pricelists | Implemented v1 + UI | Direct item and taxonomy fallback prices, validity windows, party rules, percent/fixed/override discounts, resolver, document-line integrations, PriceList resource, and Party price-rule UI are present. |
| Spec 2 Phase 2A | Implemented | Domain actions, state-aware policies, and Filament service-backed actions are present. |
| Spec 2 Phase 2B | Implemented | 2B-01/02/03/04/05/06/07/08/09/10/11/12/13 are done. |
| Spec 2 Phase 2C | Implemented | FatturaPA schema/readiness fields (2C-05), SDI/FatturaPA mapping (2C-02), FPR12 XML/XSD validation (2C-01), Aruba upload/polling/callback adapter (2C-03), polling command (6-03), and extended admin permissions (2C-04) are present. |
| Phase 3 internal actions | Implemented | Stateful actions registered by ERP use Core's authenticated /app dispatcher and ERP policy authorization. External /api/v1 exposure remains deliberately deferred. |
| Phase 4 commercial depth | Required scope implemented | Cash sharing, Payment Requests, canonical Site/Place locations, Task UI, and RFC 5545 ICS export are present. Gantt (4-08) and mobile API (4-13) are optional and unapproved. |
| Phase 5 enterprise depth | Implemented | Return reversal, e-invoice operations, Italian bank formats, report snapshots, FX/revaluation, Money, and analytic dimensions are present. |
| Phase 6 integration architecture | Implemented | Commands, outbox integration, extension contracts, and architecture documentation are present. |
Point 0 Go-Live and Future Boundaries
- E-invoice defaults to the deterministic
stubworkflow. The optionalfatturapadriver generates and XSD-validates ordinary FPR12 XML locally, but it does not deliver to SDI. - The optional
arubadriver now follows the Aruba v2-style upload and notifications polling shape, supports optional auth signin, exposes a signed callback route, and records polling/callback/conservation metadata inEInvoiceSubmission.response_payload. It still needs verification against the contracted Aruba tenant before production go-live. - FatturaPA / SDI readiness fields now exist on company, party, and invoice records, sale e-invoice submit validates mandatory data,
FatturaPaAnagraphicMappermaps those fields into a FatturaPA-shaped neutral payload, andFatturaPaXmlBuildergenerates XML from it. Edge-case fiscal mappings still need dedicated fields and rules. - Legal retention is tracked only as provider metadata such as
pddAvailable; full conservazione governance, retrieval/esibizione workflows, and contract-specific retention checks remain go-live/backlog work. - Supplier payment execution exports SEPA SCT
pain.001and CBI bonifici files. Ri.Ba and SDD CORE generators exist for customer receivable schedule lines, with SDD mandate validation onPartyBankAccount. Direct bank/API submission and bank-specific certification remain outside ERP. - Bank import supports CSV, CAMT.053, and a minimal MT940 subset; it is not full bank API sync and it does not auto-confirm matches without the reconciliation workflow.
- Financial report snapshots store immutable payload, CSV, PDF content, hash, and parameters through
erp_report_snapshots; the built-in PDF renderer is intentionally simple and dependency-free. - Multi-currency now has
ExchangeRate,DatabaseCurrencyConverter, andFxRevaluationServicefor historical/inverse FX rates and unrealized revaluation journals on open foreign-currency schedules. External FX feed imports and realized FX settlement automation remain later enhancements. - Money math now has a
Moneyvalue object built onDecimal; analytic accounting has company dimensions, dimension values, and a journal-line pivot with allocation percentage. - Registered domain HTTP actions are available on the authenticated internal
/appsurface. External/api/v1actions and ERP-specific resources remain deferred until a concrete consumer and governance contract are approved. - Processed returns can be safely reversed before any linked credit/debit note exists: the generated DDT is unposted, returned quantities are restored, and the return goes back to approved. Linked fiscal notes must be handled explicitly first.
- DDT/bolle lines intentionally do not carry prices or costs. Fiscal corrections price from source invoice lines, not from DDTs, orders, goods receipts, or current price lists.
- Reports remain live-query in Filament, but financial report snapshots now archive immutable payload/CSV/simple-PDF rows. Rich paginated PDF design and operational report snapshot scheduling remain enhancements.
Moneyexists for decimal-safe amount/currency arithmetic, and journal lines support analytic dimensions. Full refactoring of all legacy money helpers and analytic reporting cubes remains future work.- Application lock-chain guards cover all supported databases. MySQL/MariaDB and PostgreSQL add DB triggers; SQLite and Oracle rely on the application layer for this specific defense.
- MES remains a separate vertical. External-source importers are a separate workstream. Gantt and mobile API are optional; calendar/ICS export and partner-pool settlement are implemented. External money transfer remains separate.
Roadmap
- ERP Point 0: approved mandatory non-external-API scope is implemented; see docs/STATUS.md.
- A new implementation wave starts only after explicitly approving external API governance, optional Gantt/mobile scope, or another independent requirement.
Scripts
The ERP module exposes the same Composer script conventions as Cms and Core:
Test fixtures and the demo dataset
Twelve model factories live in database/factories/, resolved by newFactory() on the model:
Company, Party, Item, Warehouse, FiscalYear, FiscalPeriod, Account, SalesOrder,
PurchaseOrder, DeliveryNote, Invoice and their lines.
$company = Company::factory()->create();
$invoice = Invoice::factory()->for($company)->withLines(3)->create();
$year = FiscalYear::factory()->for($company)->withPeriods()->create();
$order = SalesOrder::factory()->for($company)->confirmed()->withLines()->create();
Three rules they keep, because the domain enforces them:
- No factory writes a document number.
referenceis allocated byDocumentNumberAllocatorat posting time (and at the creation page for orders), so a factory-made document has none.->numbered('INV-2026-0001')exists for the tests that need a fixed value. - A scoped factory reuses the company it was given.
->for($company)keeps the party, the items and the lines inside that company: the models refuse a party or an item from another one. - Enum columns take their backing value. Validation runs before the cast and stringifies.
php artisan db:seed --class="Modules\\ERP\\Database\\Seeders\\DevERPDatabaseSeeder" (or the
root DevDatabaseSeeder, which discovers it) produces the CRM taxonomies plus one demo company
carrying both commercial flows: a customer and a supplier, a warehouse, three articles
(DEMO-ITEM-001..003), sales order to delivery note to invoice, and purchase order to goods
receipt to invoice. Identifiers are stable and the seeder is idempotent, so running it twice
changes nothing.
The module has no runner of its own: its tests extend the application's TestCase
and it has no vendor/, so everything below runs from the application root.
Code quality and testing
php artisan test --compact Modules/ERP/tests # the whole module
php artisan test --compact Modules/ERP/tests/Unit # one suite of it
php artisan test --compact --testsuite=Unit # the fast suite, every module
composer test # Full test and quality pipeline
composer test:type-coverage # Type coverage (target: 100%)
composer test:typos # Peck typo check
composer test:lint # Pint + Rector dry-run
composer test:types # PHPStan
composer test:refactor # Rector
Document-number concurrency has two isolated checks: the regular feature test forks 8 workers, while the opt-in stress test forks 50 workers against a temporary SQLite WAL database. The stress test never uses the configured application database and is skipped unless both pcntl and the explicit flag are available:
RUN_ERP_STRESS_TESTS=1 php artisan test --group=stress
Keep the flag disabled in ordinary CI jobs; run it as a dedicated release or infrastructure gate on a host that supports process forking.
Maintenance
composer lint # Rector, Pint, IDE helpers
composer check # PHPStan
composer fix # PHPStan with fix
composer refactor # Rector
composer update:requirements # composer bump + npm-check-updates
Versioning
Releases are cut by the maintainers with private tooling; do not bump versions or edit CHANGELOG.md by hand.
Repository rename (from laraplate-business)
If you still have a clone using the old remote:
git remote set-url origin git@github.com:getlaraplate/laraerp.git
# or HTTPS: https://github.com/getlaraplate/laraerp.git
In the parent Laraplate app, update .gitmodules to path = Modules/ERP and url / pushurl for laraerp, then run git submodule sync and composer update.
Contributing
If you want to contribute to this project, follow these steps:
- Fork the repository.
- Create a new branch for your feature or fix.
- Open a pull request.
License
ERP module is open-sourced software licensed under the GNU AGPL v3.
Copyright (c) 2025 Marco P. The full licence text is in LICENSE.
TODO
- M6.1 — Bank reconciliation and statement import
- M6.2 — Returns management (customer and supplier)
- M6.2/2B-07 — Invoice-line-based return credit/debit note pricing contract
- M6.2/2B-06 — Optional automatic NC/ND creation on return completion
- M6.3 — E-invoice stub workflow
- M7.1 — Advanced pricelists with party-specific pricing
- M4 — Policies, permissions, and reporting pages
- Spec 2 Phase 2A — State-aware policies and Filament domain actions
- Spec 2 Phase 2B — Party pricing UI, PriceList UI, quotation unlock, document sequence reset, return fiscal override contract, optional auto NC/ND, banking depth, financial CSV export, operational dashboard polish
- Spec 2 Phase 2C — FatturaPA / SDI schema, mapper, XML/XSD validation, configurable Aruba adapter, and extended admin permissions
- Phase 3 internal domain actions — authenticated Core
/appdispatcher with ERP policy authorization - External
/api/v1action/resources governance — deliberately deferred until a consumer contract is approved - Optional Gantt/mobile capabilities (
4-08,4-13) — not approved for implementation - Comprehensive accounting golden-master tests
- Export CSV for financial reports