Browse the docs

LaraERP v2.1.0

Laraplate ERP module: multi-company accounting, sales, purchases, invoicing, inventory and projects.

Open plans.

PlanModuleProgressStartedLast activity
ERP external-source importersERP
16 / 73 steps
2026-08-032026-10-01

Reference.

From the README.

Imported from laraerp/README.md

Table of Contents

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: stub by default; supported values are stub, fatturapa, aruba
  • ERP_EINVOICE_ARUBA_BASE_URL: Aruba API base URL, for example the contracted demo/production web-service host
  • ERP_EINVOICE_ARUBA_AUTH_BASE_URL: optional Aruba auth base URL when using username/password token acquisition
  • ERP_EINVOICE_ARUBA_UPLOAD_PATH: upload endpoint path, default /services/invoice/upload
  • ERP_EINVOICE_ARUBA_NOTIFICATIONS_PATH: notifications polling endpoint path, default /api/v2/invoices-out/notifications
  • ERP_EINVOICE_ARUBA_TOKEN: bearer token used by the Aruba adapter; if missing, username/password auth is attempted
  • ERP_EINVOICE_ARUBA_USERNAME / ERP_EINVOICE_ARUBA_PASSWORD: optional credentials for Aruba /auth/signin
  • ERP_EINVOICE_ARUBA_CALLBACK_API_KEY: optional static API key expected on Aruba callback requests
  • ERP_PAYMENT_REQUEST_DRIVER: payment-request provider binding (stub in the current module)
  • ERP_PAYMENT_REQUEST_STUB_CALLBACK_API_KEY: enables/authenticates the external stub callback endpoint; callbacks are disabled when empty
  • ERP_EINVOICE_ARUBA_SIGNATURE_CREDENTIAL / ERP_EINVOICE_ARUBA_SIGNATURE_DOMAIN: optional Aruba signature parameters
  • ERP_EINVOICE_ARUBA_SENDER_PIVA: optional sender VAT override; defaults to company fiscal country + tax id
  • ERP_EINVOICE_ARUBA_SKIP_EXTRA_SCHEMA / ERP_EINVOICE_ARUBA_DRY_RUN: optional upload flags
  • ERP_STOCK_RESERVATION_SOFT_TTL: minutes a soft stock reservation holds stock back before it lapses (erp.stock_reservation.soft_ttl), default 1440; 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:

Module metadata

  • Priority: 999 in module.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

  • companies table with functional currency
  • BelongsToCompany trait + global scope for tenant isolation
  • Dual-currency columns via ERPMigrateUtils::moneyColumns() (amount_doc / currency_doc / amount_local / fx_rate)
  • CurrencyConverter service (no-op in M0, pluggable for live FX)

M0 — Versioning

  • HasVersions trait with VersionStrategy::DIFF on 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 (accounts table, Account model, ChartOfAccountsProvider interface, Italian PDC default)
  • Journal Entries (JournalEntry / JournalEntryLine, JournalPostingService::post/reverse, immutability after posting)
  • JournalEntry, JournalEntryLine, VatRegisterEntry, StockCostLayer, StockLevel and StockMovement implement Core RestrictsCrudWrites (via DeniesGenericCrudWrites): generic CRUD writes on them answer 403; they change only through their owning services
  • Fiscal Calendar (FiscalYear, FiscalPeriod, FiscalPeriodCloser with re-open audit)
  • Document Sequences (DocumentSequence, DocumentNumberAllocator, DocumentType enum with gap policy)

M2 — Tax & Invoice Foundation

  • Tax Codes (TaxCode, TaxKind enum, immutable fiscal snapshot, TaxLineCalculator, TaxCodeSupersessionService)
  • ItalianTaxCodesSeeder baseline
  • 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 CRM OpportunityLifecycleService)
  • Party is the relationship between a CRM Counterparty and one Company
  • Company.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 / SalesOrderLine with lock-chain progression
  • SalesOrderEvasionService (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_layers tables
  • StockMovementService with 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. See docs/rag/MODULE.md § Stock Reservation and Availability (ATP)
  • erp:stock-reservations:expire (scheduled hourly) releases soft reservations whose expires_at has passed; availability already ignores an expired soft on read, so the sweep is housekeeping

M3.4 — Delivery Notes

  • DeliveryNote / DeliveryNoteLine with SO line linking
  • DeliveryNoteInventoryService (stock posting + rollback)
  • DeliveryNoteCogsJournalService (COGS journal on posting, full reversal on unpost)
  • Automatic SO qty_delivered updates

M3.5 — Invoice Posting & DDT Integration

  • InvoicePostingService with full journal post / unpost cycle (observer-driven via posted_at)
  • DocumentNumberAllocator integration 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_line for flexible Invoice↔DDT linking, mapped by Models\Pivot\InvoiceLineHasDeliveryNoteLine with 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_at editing)
  • Posting is refused with PostingToClosedFiscalPeriodException when the posting date falls in a closed fiscal period: InvoicePostingService resolves the covering period of the company and JournalPostingService rejects it. Reopen the period (Filament Reopen action or the reopen domain action) before posting
  • force_post is 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 as MatchStatus::Forced
  • See docs/ERP_GUIDA_SEMPLICE.md §4.7 and docs/rag/MODULE.md § Invoice posting workflow for end-to-end flows

M3.6 — Purchasing Cycle

  • Unified Party entity (replaces Customer) with is_customer / is_supplier boolean flags
  • Party type validation on all models (saving callback) + filtered Filament dropdowns
  • PurchaseOrder / PurchaseOrderLine, GoodsReceipt / GoodsReceiptLine
  • ThreeWayMatchService with configurable price / quantity tolerances (wired in InvoicePostingService on purchase posting)
  • MatchStatus enum (matched, tolerance, forced, unmatched) persisted on invoice_lines with match_discrepancy JSON
  • Filament Force three-way match checkbox on purchase invoice Post action
  • Per-company settings via ErpCompanySettings on companies.settings JSON (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

  • PaymentTerm model (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, and PaymentRunLine for supplier payment runs
  • PaymentRunBuilderService builds draft outbound supplier payment batches from open purchase schedule lines
  • SepaPain001Exporter exports approved supplier payment runs as auditable SEPA SCT pain.001 XML files
  • Payment execution is file generation only: no direct bank/API submission, no CBI/Ri.Ba/SDD in v1

M5.2 — Credit & Debit Notes

  • InvoiceType enum (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)

  • VatRegisterEntry with progressive protocol numbers per register / year
  • VatRegisterService (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

  • EInvoiceProvider contract (prepare, submit, validateXml, remoteStatus)
  • EInvoiceSubmission model + EInvoiceSubmissionStatus enum
  • 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 local fatturapa driver validation
  • aruba driver: production-oriented adapter for Aruba v2-style upload (dataFile), notifications polling, optional auth signin, provider callbacks, and conservation availability metadata in response_payload
  • POST /api/v1/erp/einvoice/aruba/callbacks applies signed provider callbacks when ERP_EINVOICE_ARUBA_CALLBACK_API_KEY is configured
  • erp:einvoice:refresh-statuses polls open submissions for the configured provider and supports --company, --provider, --limit, and --dry-run
  • erp:health-check --format=table|json performs read-only installation and operational checks for the default company, chart of accounts, fiscal calendar, domain permissions, current-year sequences, and e-invoice configuration
  • erp:sequences:audit --company=ID --year=YYYY --format=table|json cross-checks persisted invoice/order references against sequence formatters, counters, duplicates, and gap policy without repairing data
  • erp:bank-statements:import --bank-account=ID --path=FILE_OR_DIR --format=auto --dry-run validates or imports CSV, CAMT.053, and MT940 files idempotently by SHA-256 checksum; --archive-path explicitly enables post-import file moves
  • erp:vat-settlements:compute --company=ID --year=YYYY --period=YYYY-N --dry-run previews 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 stub driver remains default; fatturapa generates 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, and BankStatementLine models
  • 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
  • BankDifferenceJournalService posts balanced journals against bank_cash and the selected difference account
  • BankStatementLine.difference_journal_entry_id keeps audit linkage to the accounting adjustment

M6.2 — Returns Management

  • ReturnOrder for customer returns and SupplierReturn for 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_complete creates NC/ND drafts during return completion
  • Customer return credit notes use ReturnOrderLine.invoice_line_id and the source sales InvoiceLine.unit_price; ReturnOrderLine.unit_price is an optional manual override.
  • Supplier return debit notes use SupplierReturnLine.invoice_line_id and the source purchase InvoiceLine.unit_price; purchase_order_line_id and goods_receipt_line_id remain 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 / PriceListItem with validity windows and Filament resource
  • Each price-list row targets exactly one direct Item or one Activity taxonomy; direct item prices take precedence and taxonomy prices are the fallback
  • PartyPriceRule with percent, fixed, and override discounts
  • Party::price_rules() relation and Party Filament relation manager
  • PriceResolverService and InvoiceLinePricingService apply commercial pricing to quotation / sales order / invoice lines
  • Activity is the concrete ERP taxonomy used for activity-based price rules; do not point UI directly at abstract Core Taxonomy.

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, and erp.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

  • Movement is a cash-entry adapter, not a parallel accounting ledger. It stores positive document amount, date, currency, one of income, expense, contribution, or withdrawal, and an explicit economic counterparty account.
  • MovementPostingService posts exactly one balanced JournalEntry: income debits bank_cash and credits Revenue; expense debits Expense and credits bank_cash; contribution debits bank_cash and credits Liability; withdrawal debits Liability and credits bank_cash.
  • Document/local amount and FX rate are frozen on posting. Repeated posting is idempotent through posted_journal_entry_id.
  • CashBalanceService derives the balance from posted journal lines on accounts carrying meta.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

  • PartnerPool groups Core users within one company and currency; membership is tracked by the PartnerPoolHasUser pivot.
  • An expense Movement can be split through MovementAllocation: owed_amount is the participant's share and paid_amount is what they advanced. Both totals must exactly equal the movement amount.
  • PartnerPoolSettlementService derives each balance as paid minus owed, adjusted by confirmed PoolTransaction transfers. 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 implementing Modules\ERP\Import\Contracts\BulkImporterInterface. --bootstrap, repeated --arg, --limit, --dry-run, --no-search, and --index-batch use 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 ExternalCashMovementImportService and ExternalExpenseAllocationService; 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 throw PostedImportConflict instead 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

  • PaymentRequest asks one Party or one Core user to pay an amount; it is not itself a Payment, journal entry, or pool settlement.
  • PaymentRequestProvider is replaceable. The built-in stub creates a deterministic non-payable checkout URL for workflow tests without network I/O.
  • PaymentRequestService sends drafts transactionally and applies authenticated, idempotent provider callbacks. Paid/cancelled states cannot regress.
  • POST /api/v1/erp/payment-requests/{provider}/callbacks is 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 /api route, these callbacks included, answers 403 while the Core expose_api setting 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 marked SelfAuthenticatedApiRoute: the provider sends its own secret as Authorization: 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-address api rate limit (core.api.rate_limit_per_minute, default 600 a minute). See Modules/Core/docs/rag/API_ACCESS_CONTROL.md.

Task Calendar Export

  • TaskIcsExporter generates one RFC 5545 VEVENT with stable UID, UTC DTSTART/optional DTEND, escaped text, CRLF endings, and 75-byte line folding.
  • SUMMARY comes from the Activity taxonomy (with project/task fallback); LOCATION comes from Task -> Site -> Core Place postal fields.
  • Task validity columns are explicitly cast as immutable datetimes and the original nullable project/site FK declarations are cross-driver safe.
  • TaskResource provides create/list/edit and Export calendar, downloading erp-task-{id}.ics as text/calendar.
  • This is a portable file export, not calendar subscription, bidirectional sync, reminder management, or attendee invitation delivery.

Quotation Revisions and Project Locks

  • QuotationRevisionService creates a commercial snapshot as a new draft, copies all quotation lines, increments version, and links the immediate predecessor through revises_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 ERPModelPolicy guards domain actions on top of Core CRUD permissions.
  • Domain abilities seeded in ERPDatabaseSeeder include 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.001 and 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.
  • PaymentRunBuilderService selects open/partial purchase invoice schedule lines and creates a draft payment run.
  • PaymentRunLine stores a beneficiary snapshot: name, IBAN, BIC, amount, due date, and remittance text.
  • Payment runs move through draft -> approved -> exported; cancelled is 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.001 and 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 Site records select a canonical Core Place; address, city, province, postcode and country are not duplicated in ERP. Site validity uses the standard Core HasValidity contract.
  • 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 stub workflow. The optional fatturapa driver generates and XSD-validates ordinary FPR12 XML locally, but it does not deliver to SDI.
  • The optional aruba driver 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 in EInvoiceSubmission.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, FatturaPaAnagraphicMapper maps those fields into a FatturaPA-shaped neutral payload, and FatturaPaXmlBuilder generates 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.001 and CBI bonifici files. Ri.Ba and SDD CORE generators exist for customer receivable schedule lines, with SDD mandate validation on PartyBankAccount. 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, and FxRevaluationService for 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 Money value object built on Decimal; 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 /app surface. External /api/v1 actions 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.
  • Money exists 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. reference is allocated by DocumentNumberAllocator at 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:

  1. Fork the repository.
  2. Create a new branch for your feature or fix.
  3. 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 /app dispatcher with ERP policy authorization
  • External /api/v1 action/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
100%