AMS Studio ERP — Invoicing & Accounting

Description

AMS Studio ERP turns WordPress into the back office of a small company. Quotes, invoices,
purchase orders, stock, deals, documents, treasury and accounting all live in your
own database, on your own server. There is no service to subscribe to and no second
password to remember.

The document chain

Nothing is retyped from one step to the next. A quote becomes an invoice, the invoice
calls for its payment, the payment produces its ledger entry, and every document keeps
the link back to the one it came from.

  • Quotes sent by email with their PDF, then signed online by the customer
  • Invoices numbered in a continuous, chronological series
  • Payments entered by hand or matched to an imported bank statement
  • Ledger entries posted to the right journal and financial year

What holds up under audit

These rules live in the engine, and the plugin refuses whatever breaks them.

  • Numbering stays continuous: a prefix already in use cannot change, and a reopened
    document keeps its number
  • An approved document is sealed by a fingerprint over its amounts, its party and its
    number; a mistake is corrected by a credit note, never by editing
  • VAT is broken down by rate, as the law requires on the document itself
  • Factur-X (PDF/A-3) and Peppol BIS Billing 3.0; a non-compliant invoice refuses to go
    out, in print, by email and on the public page
  • Attachments are encrypted with AES-256-GCM, outside the public media library
  • Exact decimal arithmetic throughout, never floating point

What this plugin does

  • Customers: records, addresses, contacts, categories
  • Catalogue: products and services, purchase and sale prices, margins, VAT rates
  • Quotes: drafted, sent by email with their PDF, signed online by the customer
  • Customer invoices and credit notes: continuous numbering, deposits, payment terms,
    what is paid and what remains due
  • Factur-X (PDF/A-3) and Peppol BIS Billing 3.0 on every invoice
  • Encrypted attachments, exact decimal arithmetic, an audit trail on every document

What it does not do

Purchasing, stock, deals, treasury, accounting, the document library and the
WooCommerce bridge are not part of this plugin. They come with AMS Studio Pro, a
separate add-on installed alongside it and distributed from https://www.ams-studio.lu/

Nothing here is capped, time-limited or waiting for a key: what you install is
complete. The modules above are simply not in this package — the menu shows them so
you know they exist, and says where to get them.

Languages

French and English. The plugin follows your site’s language, or its own if you ask it to —
an English site can run a French ERP.

Third-party libraries

Producing an invoice as a PDF needs an HTML-to-PDF engine, and WordPress core
has none. Four libraries are bundled, unmodified except where noted, each with
its own licence file in its directory — all GPL-compatible:

  • dompdf/dompdf 3.1.6 — LGPL-2.1 — https://github.com/dompdf/dompdf
  • masterminds/html5 — MIT — https://github.com/Masterminds/html5-php
  • sabberworm/php-css-parser — MIT — https://github.com/MyIntervals/PHP-CSS-Parser
  • php-font-lib — LGPL-2.1 — https://github.com/dompdf/php-font-lib

They are bundled rather than pulled by Composer because the plugin must install
from the WordPress admin, on shared hosting, with no command line. Their
namespaces are prefixed under AMSBM\Vendor\ with PHP-Scoper, so that two
plugins bundling dompdf in different versions cannot break each other; the
prefixing is reproducible with bin/scoper.sh. Every other deviation from the
upstream code is marked in place and listed in lib/pdf/ECARTS.md: a direct-access guard on the two files that carry code
outside a class, embedding a Factur-X XML attachment into a PDF/A-3 file which
dompdf does not support upstream, heredoc blocks rewritten as concatenation,
and the removal of SVG rendering.

dompdf’s fifth dependency, php-svg-lib, is deliberately not bundled. It
exists only to draw SVG images inside a PDF, and drawing one means parsing XML
that a site user supplied. The plugin does not need it — logos are PNG, JPEG,
GIF or WebP, and an SVG is refused at upload — so the smaller attack surface is
worth more than the feature. The three places where dompdf reached for it are
neutralised in place, and an SVG is simply not a recognised image type any
more.

One more deviation is replayed automatically rather than by hand: in
php-font-lib, glyf::toHTML() — a font-debugging method that builds an HTML
page with inline scripts — is emptied. Neither dompdf nor the plugin calls it.
bin/pdf-patches.php applies that change after every copy of the libraries
(bin/vendor-pdf.sh) and again after prefixing (bin/scoper.sh), and the
package build (build-zip.sh) refuses to proceed if it is missing.

Compiled admin interface

The admin interface is a React/TypeScript application, compiled with Vite. The
compiled, minified bundle is in app/build/; its unminified sources ship with
the plugin
, in app/src/, along with everything needed to rebuild it:
app/package.json, app/package-lock.json, app/tsconfig.json,
app/tsconfig.node.json, app/vite.config.ts, app/tailwind.config.js and
app/postcss.config.js.

The same sources are publicly available at https://github.com/SanMat11/wp-erpx

To rebuild the bundle — Node.js 18 or 20 and later, verified with Node.js 22:

cd app && npm ci && npm run build

npm ci installs the exact dependency versions recorded in
app/package-lock.json. `npm run build` type-checks the sources with TypeScript

5.9, then builds them with Vite 5.4 into app/build/. The file
app/build/morceaux/vendor-[hash].js is the npm dependencies listed in
app/package.json — React, Ant Design and the others — bundled together by
Vite; each package’s name and exact version are in app/package-lock.json.

Vite writes the entry point as amsbm-app-[hash].js and amsbm-app-[hash].css,
with the code split into chunks under app/build/morceaux/. The hash in the
filename is the version: a new build produces a new name, so a cached file is
never a stale one. app/build/manifeste.json records the current pair, and the
plugin reads it rather than guessing.

Nothing about the plugin requires npm at runtime. The bundle is committed and
shipped because the target audience installs from the WordPress admin, on shared
hosting, with no command line.

External services

VIES — EU VAT number validation (European Commission)

Used only when you click “check” beside an EU VAT number, to confirm that the number
exists. What is sent: the country code and the VAT number, in the request URL. Nothing
else — no company name, no site address, no identifier.
Service: https://ec.europa.eu/taxation_customs/vies/
Terms and privacy: https://ec.europa.eu/taxation_customs/vies/#/help

Stripe — card payment of your invoices (Stripe, Inc.)

Used only if you enter your own Stripe API keys in the settings. It lets a customer of
yours pay a shared invoice by card. What is sent, when your customer clicks “pay”: the
amount, the currency, the invoice reference and the return address. The plugin never
sends your customer list or your documents.
Service: https://stripe.com/
Terms: https://stripe.com/legal/ssa
Privacy: https://stripe.com/privacy

AMS Studio licence server (AMS Studio, Luxembourg)

Only reached if you click “Upgrade to Pro” in the application header. That button is a
link: your browser opens it, the plugin itself opens no connection and sends nothing on
its own. What travels in that link: an installation identifier drawn at random on your
site, a one-way hash of a secret drawn with it, your site address and your admin email
address — so that the licence you buy is tied to this site and fills itself in when you
install the paid package, with nothing to copy by hand. The hash cannot be reversed, and
the secret itself never appears in the link.
Nothing is sent if you never click the button, and nothing is sent on a schedule.
Service: https://licences.ams-studio.lu/
Terms and privacy: https://www.ams-studio.lu/

The plugin contacts nothing else. No telemetry, no update server, no licence check.

Screenshots

Installation

  1. Install and activate the plugin as usual.
  2. Open AMS ERP in the admin menu and fill in your company details.
  3. Optionally install the demo data set to look around before entering your own.

No command line is required, and no account anywhere: the plugin ships its own autoloader and its own PDF engine. Two optional features do contact an outside service when you use them — see “External services” below.

FAQ

Is my data sent anywhere?

Your business data stays put. Documents, customers, articles and ledger entries live
in your own database and are never sent anywhere.

Two features do reach outside, and only when you ask them to:

  • VAT number validation. When you click “check” next to an EU VAT number, that
    number and its country code are sent to VIES, the European Commission’s service.
    Nothing else. If you never click, nothing is ever sent.
  • Card payment of an invoice. If — and only if — you enter your own Stripe keys,
    your customer can pay a shared invoice by card. Stripe then receives the amount, the
    currency and the invoice reference, at the moment your customer clicks “pay”.

Both are documented in full under “External services” below.

Is my data deleted when I uninstall?

No. Dropping the tables requires adding define( 'AMSBM_REMOVE_ALL_DATA', true ); to
your wp-config.php. Accounting retention obligations — six years in France, up to ten
elsewhere in Europe — outlast the lifetime of any plugin.

Does it need InnoDB?

Yes. Without a transactional engine, operations cannot be made atomic. The built-in
health check reports it.

Are any features locked or limited?

No. Every feature in this package works, with no cap on the number of customers,
quotes, invoices or articles, and no trial period.

Some modules — purchasing, stock, deals, treasury, accounting, the document library
and the WooCommerce bridge — are not in this package at all. They ship with a separate
add-on. The menu lists them so you know they exist; they are not disabled code waiting
for a key.

Do I need Composer or npm to install this?

No. The plugin ships its own autoloader, its own PDF engine and its compiled
admin interface. composer.json and the JavaScript sources are included so that
the build is reproducible and the dependencies are auditable, not because
anything has to be run.

What happens to my data if I deactivate the plugin?

Nothing. Deactivating stops the code from running; the tables, the documents and
the ledger stay exactly as they are, and reactivating picks up where you left
off.

Which chart of accounts is supported?

The French plan comptable général and the Luxembourg plan comptable normalisé 2020.
Accounts, journals and financial years are all editable.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“AMS Studio ERP — Invoicing & Accounting” is open source software. The following people have contributed to this plugin.

Contributors

Changelog

0.5.25

  • On a site without the stock, WooCommerce or accounting modules, the admin
    interface no longer requests routes that do not exist there: the dashboard
    only asks for stock alerts when stock is installed, the VAT settings only list
    accounting accounts when accounting is, and the Warehouses and Store settings
    tabs only appear when their module does.
  • Demo data no longer writes purchase orders and supplier invoices on a site
    that has no purchasing module.

0.5.24

  • Plugin Check reports nothing left in the plugin’s own code. The hook fired when
    the installation fingerprint is renewed is now fired by its literal, prefixed
    name. The one call it discourages, load_plugin_textdomain(), says why it
    stays: the plugin ships its own English catalogue, which WordPress only finds
    through that call.

0.5.23

  • The print page of a document is no longer echoed as one string. Its template
    is included directly in the response, and escapes every value at the exact
    place where it is output.
  • No inline JavaScript left: the print button, the confirmation before a status
    change, the setup fields to copy and the “copy backup codes” button now use a
    script enqueued with wp_enqueue_script(), or printed with
    wp_print_inline_script_tag() on the standalone print page. The “copy backup
    codes” button, which copied nothing, now copies.
  • PDF downloads, accounting exports and stored attachments are sent with
    X-Content-Type-Options: nosniff.
  • Bundled php-font-lib: a font-debugging method that built HTML with inline
    scripts, and that nothing calls, is emptied — see “Third-party libraries”.

0.5.22

  • The sources of the admin interface shipped in app/src/ can be rebuilt from
    the plugin package alone again. A screen that is not part of this package was
    imported unconditionally, so npm run build stopped on a missing file; it is
    now loaded only where its file exists.
  • The same sources are published at https://github.com/SanMat11/wp-erpx, and the
    readme gives the Node.js version, the exact command and where the output goes.

0.5.21

  • English is translated again. The English catalogue was deleted during the
    0.5.7 rename and never rebuilt: since then every string written by the server
    — API refusals shown on screen, e-mails, PDFs, meta boxes — came out in French
    on English sites, while the interface itself switched. All 971 strings the
    code asks for are now translated and shipped as
    languages/ams-studio-erp-invoicing-accounting-en_US.mo.
  • A quote could not be accepted in English. The wording a customer copies
    out to accept a quote was compared against the French bon pour accord, while
    the refusal message told English customers to type “Agreed and accepted”. They
    typed exactly what was asked and were turned away. The expected wording now
    follows the language of the site, and French stays accepted so agreements
    given before a language change remain valid.
  • Four screen strings still quoted the French wording inside English sentences,
    and two were left in French outright. The screen, the refusal message and the
    check now say the same thing.

0.5.20

  • The text domain is now ams-studio-erp-invoicing-accounting, matching the
    slug registered on WordPress.org. It was ams-studio-business-management on
    1155 i18n calls, which prevented the plugin from being translated through
    translate.wordpress.org. The PHP prefix amsbm_, the option, table, post
    type and REST namespace names are unchanged, as is the main file name.
  • Internal: the translation catalogue built by bin/i18n/ was named
    amsbm-.po, which matched no text domain — WordPress would never
    have loaded it. It now carries the domain, as the format requires.

0.5.19

  • Internal: the Peppol reader no longer emits a PHP warning when an incoming
    invoice omits optional elements — payment terms, legal entity, trading name
    or tax scheme. Absent elements read back as an empty string, as intended.
  • Internal: the test asserting that every document type is reachable by a role
    performed no assertion at all: it returned on the first role covering
    everything. It now checks the business roles, and was seen to fail when a
    document type is unwired from them.

0.5.18

  • Internal: the bundled PDF libraries — dompdf, php-font-lib, masterminds/html5
    and sabberworm/php-css-parser — now live under the AMSBM\Vendor\ namespace,
    prefixed with PHP-Scoper. Two plugins bundling dompdf in different versions
    can no longer break each other. A document renders byte-for-byte as before.

0.5.17

  • Internal: the document editor’s article catalogue no longer writes its
    tag by hand. It goes through wp_print_inline_script_tag(), which
    carries the page’s Content-Security-Policy nonce where there is one. It stays
    a JSON data block, not executable code. Nothing changes on screen.

0.5.16

  • Security: SVG rendering has been removed from the PDF engine, and the
    php-svg-lib library with it. Drawing an SVG meant parsing user-supplied XML
    inside the printing chain, for a format the plugin never needed: logos are
    PNG, JPEG, GIF or WebP. An SVG is now refused when the logo is uploaded, and
    refused again before it reaches the PDF. Documents without an SVG render
    byte-for-byte as before.

0.5.15

  • Internal: every SQL query now goes through wpdb::prepare(), with no exception.
    Table and column names are bound with the %i placeholder instead of being
    interpolated into the query string, and the plugin’s own table names are checked
    against a hard-coded list before any query is built. Nothing changes on screen:
    the 132 read routes return byte-for-byte identical answers.

0.5.14

  • Internal: the public quote and invoice page no longer writes its script tags by hand.
    They go through the WordPress loader, which now also carries the page’s
    Content-Security-Policy nonce. Nothing changes on screen.

0.5.13

  • Internal: four blocks in the bundled PDF libraries were rewritten from heredoc to
    string concatenation, so that the whole package passes the directory’s automated
    check. The generated PDFs are byte-for-byte identical. Every deviation from the
    upstream libraries is now listed in lib/pdf/ECARTS.md.

0.5.12

  • The “Upgrade to Pro” link in the header now offers to subscribe, in so many words:
    the dialog it opens says “Subscribe” instead of “Learn more” and states the price —
    the same one the website quotes. It no longer names an empty module when it was not
    opened from a particular screen.

0.5.11

  • Internal: this package now refuses any update whose archive does not come from where
    it says it does. Nothing changes for you here — the free edition is updated by
    WordPress.org as it should be.

0.5.10

  • Removing the demo dataset and installing it again left you with no customers and no
    products at all — while reporting it had created twenty. Removing deleted the
    WordPress posts but not the plugin’s own rows, and the next install reused a post id
    that pointed at nothing.
  • A company named “Roussel & Fils” read “Roussel & Fils” in the demo dataset.
  • An IBAN broke into six stacked pieces in the treasury list, and an IBAN entered with
    spaces was regrouped wrongly. A cash account shows a dash instead of an empty box.

0.5.9

  • This installation now has an identity of its own — an identifier drawn at random on
    your site, and a secret drawn with it. Nothing is sent anywhere: they exist so that a
    licence bought later belongs to this site and fills itself in, instead of being copied
    by hand. See “External services” for the one moment anything leaves, and what it is.
  • The activate button no longer sits flush against the licence field.

0.5.8

  • Screens that spoke French on an English site. The public invoice page a customer
    opens to pay, the goods receipt screen and the online payment settings carried their
    wording inside the code instead of in the catalogues: 87 sentences that no
    translation could reach. They are now in both catalogues, English included.
  • The subscription screen showed its translation keys instead of its labels — its
    whole set of 80 keys was in neither catalogue. It also named the four licence states
    it had never been given a word for, starting with a site that simply has no key.

0.5.7

  • This package now contains exactly what it runs. Purchasing, stock, deals, treasury,
    accounting, the document library and the WooCommerce bridge moved to a separate
    add-on — their code is no longer shipped here. Nothing is capped or locked: what is
    present works, in full.
  • New name. The plugin was called ERP-X; it is now AMS Studio ERP.
    Everything it declares was renamed with it — options, tables, custom post types,
    capabilities, hooks and the REST namespace. Nothing carries over from the old
    names, so this version is an installation, not an upgrade.
  • Nothing in this package is capped, time-limited or tied to a key. What is here
    is installed and open; what is not here is not shipped at all.
  • Electronic invoicing follows. Factur-X (PDF/A-3) and Peppol BIS Billing 3.0
    used to be conditional; they now apply to every invoice, which is what a legal
    obligation ought to be.
  • The plugin no longer looks anywhere but here for its updates.

0.5.6

  • A document the WooCommerce bridge found wrong now says so before you click. The
    bridge marks it, approval refuses it — but nothing showed the reason, so the refusal
    arrived out of nowhere.
  • The supplier invoice screens stop offering what the server will refuse: five write
    actions in the list, and approving a credit note, are now hidden from roles that do
    not have the right — and the reason is stated, with who to ask.
  • A supplier credit note table headed two different columns “Amount excl. VAT”.
  • The credit note route names the capability it needs instead of relying on the global
    write right.

0.5.5

  • The plugin’s own header is translated at last. Its name and description were in no
    catalogue, so an English site read the description in French on its plugins screen —
    the one place never reread. The extractor now covers the header, as xgettext does.
  • One header dropped: the plugin address and the author address may not be the same,
    and the site at ams-studio.lu presents the plugin.

0.5.4

  • Every PHP file the plugin ships now refuses a direct HTTP call. On shared hosting
    wp-content is often served without going through WordPress, and a file reached that
    way ran outside it — printing the server’s absolute path in the fatal error that
    followed. A test counts them all and fails on the next file shipped without a guard.
  • The two-factor QR script goes through the WordPress script queue.
  • Repository compliance: exemptions declared for the directory’s own database sniff,
    which is stricter than the WordPress coding standard, and one core function dropped
    in favour of a plain expression.

0.5.3

  • The plugin is named ERP-X throughout, with a new mark drawn in one place and used on
    the admin menu, the sidebar and the sign-in screen.

0.5.2

  • fail2ban on the API, installation blocking, and an allow list on the administration.

0.5.1

  • Sealing: an invoice or credit note the WooCommerce bridge found wrong can no longer be
    approved by any path.
  • Electronic invoicing: a non-compliant invoice no longer goes out by email or on the
    public page either.
  • Purchasing: only what has been received is invoiced; invoicing ahead must be asked for.

0.5.0

  • Two-factor authentication and the per-module permission matrix.

0.2.0

  • The sales cycle end to end: items, parties, quotes, invoices.
  • Multi-rate VAT rounded per rate at the foot of the document.
  • Quote to invoice transformation, with a line-by-line trace.

0.1.0

  • Exact decimal arithmetic, with no silent overflow.
  • Numbering by atomic UPDATE, refusing documents backdated below the last period used.
  • Migration lock through a MySQL GET_LOCK, genuinely atomic.