Accounting
How MPH WMS sends invoices to your accounting system and what happens when something cannot be booked
MPH WMS is the source for your sales accounting. It sends invoices to your accounting system, one per order, and then reconciles the money that actually arrives, payout by payout. Your ledger receives two things from MPH: an invoice when you sell, and a voucher when the money lands on the bank.
Fortnox is the supported accounting system today.
Connecting
- Go to Settings → Integrations → Add integration and choose Fortnox.
- Approve the access in Fortnox.
- You are returned to MPH WMS with the connection active. It now appears as a set of books under Settings → Accounting, where the account mapping, payment methods and general policy live.
Fortnox access tokens are refreshed automatically every night. If Fortnox is unreachable during a refresh, the next refresh picks it up; if the connection has genuinely expired, the app asks you to reconnect.
What a sale becomes
Every order that comes in, or changes, is turned into an accounting document in the background. Building it takes four steps, and anything the system cannot decide with certainty stops the document instead of guessing:
- Classify the tax. Domestic VAT, OSS (EU distance selling), or export, from the destination and the customer.
- Map the accounts. Each combination of tax regime, destination country, VAT rate and line type is looked up under Settings → Accounting → Account mapping. A missing combination holds the document.
- Reconcile. The lines are checked against the order total. A difference up to the rounding tolerance becomes a visible rounding line; anything bigger holds the document.
- Decide the payment handling from the order's payment method. See Payment methods below.
The grace period
A document waits (30 minutes by default, under Settings → Accounting → General) before it is sent. It is rebuilt from the current order data immediately before sending, so a cancellation or an address change in the first minutes never reaches the ledger. Once sent, a document is immutable; a later change to the order produces an adjustment or a credit note instead.
What is created in Fortnox
- A customer per e-mail address, created or updated on first use.
- An article per SKU. Fortnox articles are not stock-keeping; MPH remains the stock master.
- The invoice, in the order's own currency. A currency Fortnox does not know blocks the push until it is added to the currency register there.
If MPH loses contact with Fortnox after the invoice was created but before the reference was saved, the retry finds the existing invoice and adopts it rather than creating a second one.
Payment methods
Settings → Accounting → Payment methods holds one row per payment method the sales channel sends (for example shopify_payments, klarna, manual). Each row decides what happens to the document:
| Setting | Effect |
|---|---|
| Hold for review | The document stays in the review queue until someone approves it. Use for invoice or prepayment orders, which are often business purchases. |
| Post automatically | Book the invoice in Fortnox rather than leaving it as an unposted draft. |
| Mark as paid | Register the payment on the invoice straight away, against the payment account, instead of leaving it open. |
Post automatically and mark as paid are separate on purpose: an invoice can be posted automatically while still sitting open in the ledger. An unmapped payment method defaults to review, never to a guess.
For a payment provider that pays you in batches, such as Shopify Payments, there are two ways to get the invoice marked as paid: here, at invoice time, or when the payout arrives. Payouts explains the difference and why you should pick one.
The review queue
Finance → Review queue lists every document that was held back before reaching the ledger, grouped by what is blocking it. The button offered depends on the cause, and only appears where it can succeed:
- Approve & send lifts a hold that a payment method's Hold for review raised. Someone has looked at the order and says it is fine to book.
- Retry covers the rest. A document that failed on the way to Fortnox, for example during an outage, is simply sent again. A data hold (a missing account mapping, an amount that did not reconcile, a missing exchange rate) is built again from the order, so fix the cause first: add the mapping, add the rate, correct the order. Retrying without fixing the cause puts the document straight back in the queue.
Retry all at the top of the queue does the same for every document at once, for the common case where one fix, such as a new account mapping, unblocks many documents.
Several sets of books
An organization can hold more than one connected set of books, for separate legal entities. Each has its own chart of accounts, its own voucher series and its own OSS report. An order is booked into the ledger that its sales channel points to; channels with no explicit ledger use the default.
Reports
- Finance → OSS report summarises EU distance sales per destination country and VAT rate for the period, from the documents that reached the ledger.
- Finance → Payouts lists every payout and its status. See Payouts.
Permissions
| Permission | Allows |
|---|---|
accounting:read | View accounting documents, payouts and VAT reports |
accounting:manage | Edit account mappings, payment methods and payout settings, and post to the ledger |
What a ledger plugin declares
Every accounting system is a plugin under plugins/accounting-*, built on @mph-wms/plugin-sdk/accounting. The app never names a ledger: the settings pages, the connect flow and the checks on the account mapping read what the plugin's manifest says about itself, and the sentences around those facts stay in MPH's own translations.
| Manifest field | What it drives |
|---|---|
auth | How a company connects. oauth2 names the config keys for the app's client id and secret, and the Connect button goes straight to the provider. api_key lists the fields the merchant pastes (stored encrypted under apiKey, apiSecret or accessToken), and the app renders a generic connect form from them. |
vatRates | The VAT rates a fresh company in that ledger knows. The account mapping flags a row whose rate is not in the list. Leave it out if the ledger accepts any rate. |
links | Help pages in the ledger's own documentation for vatRates, chartOfAccounts and vatAccounts. They appear under the matching warnings in the account mapping. |
icon | A generic word for the card on the add-integration page, such as ledger. |
connectPath | Where the connect flow starts, when it is not the default for the auth type. |
docsUrl | The provider's setup guide, linked from the connect form. |
Adding a ledger
- Write the manifest with
auth, andvatRates,links,iconandconnectPathwhere they apply. - Implement the connector. An
api_keyledger must implementverifyCredentials, so a mistyped key fails on the form rather than at the first push. - Implement
fetchCompanyif the ledger can report the company's base currency. Without it, the operator sets the base currency on the connection's settings tab. - Add a config group in
core/config/src/schema.tsfor every key the manifest names (clientIdKeyandclientSecretKeyfor OAuth,appKeysfor an API-key ledger with an app identity of its own). Then runpnpm --filter @mph-wms/config genso.env.examplefollows. - Add a catalogue line to
INTEGRATION_PROVIDERSincore/shared, withcategory: "accounting", so the ledger is offered on the add-integration page. - Import the plugin in
core/plugin-host/src/register.ts. - Run
pnpm --filter @mph-wms/plugin-host test. It fails when a shipped plugin has no catalogue line, or declares a capability its connector does not implement.
The web app and the worker may not import a ledger plugin package directly. Lint fails the build if they do, because everything they need comes through the manifest.