Skip to main content
Odoo Community Association

Romania - Stock Accounting

Mature License: AGPL-3 OCA/l10n-romania Translate me on Weblate Try me on Runboat

This module provides Romanian-specific stock accounting features that align with Romanian accounting standards and regulations. Below are the configuration options available.

Overview

The module extends Odoo’s standard stock accounting to meet Romanian accounting requirements, providing:

  • Location-specific accounting configurations

  • Romanian-specific stock valuation accounts

  • Product category stock account customization

  • Warehouse fiscal position management

  • Specialized accounts for stock operations

  • Per-location FIFO valuation (vs. Odoo’s default company-wide FIFO)

  • Automatic negative stock compensation for FIFO products

  • Stock valuation entries for dropship deliveries, symmetric with regular deliveries

Per-location FIFO

For products with cost_method=fifo, the module values outgoing moves against the FIFO stack of the source location rather than the company-wide stack. Each outgoing move is automatically split into one stock.move per FIFO layer (e.g. an outgoing of 4 units from a location with IN 2@10 + IN 3@8 produces a 2x10 move + a 2x8 move).

Internal transfers (internal → internal or via transit) are treated as both is_in and is_out on the same move:

  • they consume from the FIFO stack of the source location,

  • they enter the FIFO stack of the destination location with the value pulled from the source,

  • they generate an accounting entry through the location’s valuation account (or the company-level transfer account if locations share an account).

Controlled by res.company.fifo_per_location (defaults to True for Romanian companies, computed from country_id, editable per company).

Negative stock compensation

When a FIFO outgoing move happens before the corresponding incoming move (i.e. the location FIFO stack is empty), the outgoing value falls back to the product’s standard_price. The pending quantity is tracked on the outgoing move via fifo_neg_pending_qty / fifo_neg_origin_value.

When the next matching incoming move is posted, the module:

  1. allocates the new incoming value across pending outgoing moves (FIFO);

  2. updates stock.move.value on the outgoing moves to reflect the real purchase price;

  3. emits an account.move correction that debits the variation/COGS account and credits the stock valuation account for the delta;

  4. links that correction back to the originating IN move via account.move.fifo_neg_origin_move_id for traceability.

Compensation is idempotent — if _set_value is re-invoked on the same IN move (e.g. when the supplier invoice is posted), the existing compensation is detected and not duplicated.

Controlled by res.company.fifo_location_negative_compensation (defaults to True for Romanian companies, editable per company).

Dropship valuation

A dropship move (supplier location straight to a customer location, goods never entering the company’s own stock) is now valued and accounted for the same way a regular delivery is: the vendor bill still debits the stock valuation account on receipt, and the module now credits that same account and debits the expense account when the goods leave to the customer, leaving no residual balance. Without this, the stock valuation account accumulated a balance that no longer corresponded to any goods on hand, and the cost of the dropshipped goods was never recognised as an expense.

This applies to dropship moves validated after the fix is installed; historical dropship moves keep their original (missing) accounting entries and require a separate, deliberate regularisation if that balance needs to be cleared.

Dropship accounting entries

The company never physically holds the dropshipped goods (the supplier ships straight to the customer), but under Romanian accounting rules stock is recognised at the transfer of risks and rewards (OMFP 1802/2014, pt. 283 para. 1), not at physical possession — the same principle behind accounts 327 “Goods in transit” and 357 “Goods held by third parties” for stock the company owns without holding. Routing a dropship purchase through the stock valuation account rather than expensing it directly at the vendor bill also keeps a purchase-invoice/sale-invoice timing mismatch (e.g. vendor bill in December, customer invoice in January) from misstating the period result, per the accrual principle (pt. 53).

Forward move (goods leave to the customer):

Account

Debit

Credit

Expense (607)

value

Stock valuation (371)

value

Return move (dropshipped_return), storno convention — same accounts as the forward move, amount in red, not a debit/credit swap:

Account

Debit

Credit

Expense (607)

−value

Stock valuation (371)

−value

408 “Suppliers - invoices not received” never applies to a dropship move: that account is a pivot for goods physically received into a warehouse before the vendor bill arrives, and a dropship move never has that physical-receipt leg. If the vendor bill is missing at the time of the customer sale, the correct counterpart is 327 (a stock-in-transit account), not 408.

For the entry above to stay correct in practice: the credit to 371 must happen in the same accounting period as the debit from the vendor bill and for the same amount, so the account nets to zero for dropship traffic at period end; and dropship quantities should flow through a distinct location so they never mix into a real warehouse’s physical inventory count.

Dropshipping a product does not affect the value of that same product’s real stock held elsewhere, on either costing method (FIFO or average cost). Core Odoo’s own average-cost engine folds dropship moves into the same moving-average pool as real purchases by design, which would otherwise retroactively reprice unrelated stock on hand purely because the same product was also dropshipped; this module routes dropship moves around that recompute entirely for Romanian-accounted companies.

Performance notes

  • Partial composite index on stock_move(product_id, location_dest_id, date DESC, id DESC) WHERE state='done' AND is_in=true powers the FIFO stack lookup with index-only scans on large histories.

  • Partial index on stock_move(...) WHERE fifo_neg_pending_qty > 0 keeps the compensation search constant-time even with millions of historical moves.

  • A request-scoped cache (context['fifo_stack_cache']) reuses the same _run_fifo_get_stack result across batched lookups in product._compute_value and stock.quant._compute_value — large Inventory Valuation reports drop from O(N) queries to O(distinct (product, location)) queries.

Table of contents

Changelog

19.0.1.11.0

  • Fix an internal transfer expensing the goods instead of handing them over to the destination warehouse, whenever the source location carries its own expense account (l10n_ro_property_account_expense_location_id) and the product category has l10n_ro_stock_account_change set. The transfer entry is built from two legs, Dr <transfer> / Cr <source valuation> and Dr <destination valuation> / Cr <transfer>, and the destination valuation account reaches the entry builder under the expense key, which _get_product_accounts resolves for internal_transfer from the destination location. That resolution was overwritten a few lines below by the source location’s expense account - which for an internal transfer is always the one read, the source being internal, so the location accounts are taken from it. The second leg therefore debited the expense account instead of the destination’s stock valuation account: the value left the source warehouse and was recognised as a cost, the destination warehouse never received it, and the stock ledger drifted away from the stock account by the transferred amount for good. The same override also defeated the guard that suppresses the entry altogether when both ends resolve to one valuation account (expense == stock_valuation), so a transfer between two locations of the same warehouse - which must produce no accounting at all - booked that spurious expense too. The account resolved for internal_transfer is now kept; every other move type keeps reading the location’s expense account exactly as before. It is also resolved for every category, not only for the ones carrying l10n_ro_stock_account_change: without that flag the location accounts are ignored altogether, so both legs land on the product’s own valuation account and the entry is dropped as a whole, where before every single internal transfer of such a product - the default configuration - debited the product’s expense account and credited the stock account. The existing coverage never reproduced this: the direct transfer cases only transfer into the warehouse that has its own accounts, the sub-location one uses locations that share the product’s account, and every category in the test fixture has the flag set. Added regression tests for both cost methods - the reverse direction as a case in each CSV suite, and tests/test_internal_transfer_expense_account.py asserting the entry leg by leg, including a FIFO transfer split over two price layers, a transfer inside one warehouse and a transfer of a category without location accounts, both of which must post nothing.

  • List the extra accounting entries on a picking’s Journal Items button. A stock.move carries two kinds of entries: the valuation entry on account_move_id and the ones in l10n_ro_extra_account_move_ids - the receivable/income entry of a delivery on notice, the off-balance entry of a usage giving, and the entries added by l10n_ro_stock_account_landed_cost. The button listed only the first, so the rest of what the transfer posted was unreachable from the picking, and for a move whose only entry is an extra one the button opened nothing at all. Both sets are listed now, covered by a test on a usage giving.

19.0.1.8.0

  • Fix dropship moves retroactively repricing unrelated real stock of the same product, for average-cost (AVCO) products. Core stock_account’s _set_value() adds a move’s product to products_to_recompute whenever is_dropship or is_in is true, regardless of whether the move ends up contributing any value there, and later recomputes the average cost for every product in that set. Core’s own averaging engine (product._run_average_batch) explicitly includes is_dropship moves in the same moving-average pool as real purchases, and since Odoo 19 derives average-cost quant values live from standard_price, that recompute retroactively changes the reported value of a product’s real stock held elsewhere, purely because the same product was also dropshipped — even though the dropship transaction never touched that stock. Confirmed this reproduces in plain Odoo (no Romanian accounting involved) as well as on a Romanian-accounted company before this fix. Dropship moves now bypass core’s _set_value() entirely on Romanian-accounted companies (this module already sets their value itself, see the 19.0.1.7.0 entry below) so they never enter that recompute. FIFO products were never affected: core’s FIFO branch reprices existing stock strictly from its own total_value/qty_available, neither of which a dropship move ever touches.

19.0.1.7.0

  • Fix missing stock valuation entries for dropshipped stock moves. A move from a supplier location straight to a customer location (dropship) never gets stock.move.value populated by core stock_account: _action_done routes it through the same moves_in._set_value() call used for regular incoming moves (the filter is is_in or is_dropship), but the assignment move.value = move._get_value() inside _set_value only runs when is_in is true, never for a pure dropship move. On top of that, _get_l10n_ro_move_type_account_list() mapped "dropshipped" and "dropshipped_return" to an empty account list, so even a correctly valued move would have produced no accounting line. Combined, the vendor bill kept debiting the stock valuation account (371) on receipt, the customer invoice kept crediting the sale account (707) as usual, but the stock side was never credited back and the cost of goods sold expense account (607) was never debited — the stock valuation account accumulated an ever-growing balance with no goods behind it, and gross margin was permanently overstated by the un-recognised cost. Dropship moves now get value populated the same way internal_transfer moves already do in this module, and use the same account mapping as delivery/ delivery_return (debit expense, credit stock valuation, symmetric on the return). This only affects new moves going forward; historical dropship moves already validated before this fix keep their original (missing) accounting and need a separate, deliberate regularisation entry if the accumulated balance is to be cleared.

19.0.1.5.0

  • Keep the standard valuation for an internal transfer whose source warehouse holds a negative balance with goods still on hand, a state left behind by earlier mis-valuations. The per-warehouse cost is the balance over the quantity, so such a warehouse yields a negative cost; valuing the move at it made the move value negative, which ran the whole entry backwards: the source warehouse came out debited instead of credited, so the transfer deepened its negative balance instead of relieving it.

19.0.1.3.0

  • Value an internal transfer at the cost the source warehouse actually holds for the product, instead of the product’s global average. Both legs of the transfer entry are built from a single stock.move.value, so they can never diverge on this series - what was wrong is the amount itself: a transfer between two warehouses took out of the source warehouse the average computed over all warehouses. When that average was higher than what the source warehouse held, the warehouse was left with value and no quantity to carry it (and the other way round when it was lower), which is what the storage sheet shows per warehouse. The accounting stayed balanced throughout, only the per-warehouse valuation was off.

  • The new _l10n_ro_get_source_account_unit_cost rebuilds that cost from the done moves in and out of the locations sharing the source valuation account. It is plugged into _get_value_from_std_price, the last step of the standard valuation chain, so a value coming from a bill, a quotation, a return or a landed cost keeps priority exactly as before; only the fallback to the product’s global cost is replaced. FIFO and lot valued products are left alone - there the cost already comes from the layers or from the lot - and so are source locations without a valuation account of their own, where there is no per-warehouse cost to follow.

  • Same defect as the one fixed on 18.0 by _l10n_ro_get_source_account_unit_cost in 18.0.1.29.0, but with a different mechanism: on this series the valuation layer is gone and the value lives on the move.

19.0.1.1.1

  • Fix silent over-delivery in stock_move._split_for_fifo_assignment: it walked the per-location FIFO stack for product_uom_qty/product_qty - the ordered demand - instead of quantity, the amount actually being shipped on this transfer. Reducing quantity below the ordered demand so the remainder backorders is the normal Odoo workflow (core’s own _create_backorder compares quantity against product_uom_qty for exactly this); product_uom_qty is supposed to stay at the full order. Consuming/valuing FIFO layers against the full order instead of the actual shipped quantity meant that whenever satisfying the (wrongly inflated) target required more than one price layer, the split created an extra stock.move for the difference and shipped it too - delivering the full original demand regardless of what was actually picked, with no backorder. When a single layer happened to cover the full order the bug was silent (wrong valuation, same visible outcome). The split now walks the stack for quantity (what’s actually shipping), matching what core already uses for its own backorder decision; a consistency check raises a clear error instead of silently completing the transfer if the amount accounted for by the split still doesn’t match.

19.0.1.0.0

  • Recognise the exchange rate difference on the 408 pivot (Furnizori - facturi nesosite) when a reception on notice (picking.l10n_ro_notice) comes from a purchase order in a foreign currency. Until now the 408 leg of the notice entry was booked in company currency only, so the order currency was lost and nothing could compute the rate delta: it stayed as a silent balance on 408, a manual reconciliation could not clear it, and the stock ledger drifted away from the stock account by that same delta.

  • The 408 leg now keeps the order currency, since the estimated liability is a monetary item, while the stock leg stays in company currency at the reception rate. When the invoice is posted, the rate delta on the quantity already received is booked as Dr 408 / Cr 765 (favourable) or Cr 408 / Dr 665 (unfavourable) - per OMFP 1802/2014 the function of account 408 lists exactly these differences as “recorded when the invoice is received”. The lines are cogs lines on the bill, so the invoice total is untouched, they stay out of the e-invoice and they are removed when the bill is reset to draft.

  • Account 408 therefore does not need to be reconcilable: the pivot closes by document, not by matching amounts. No reconciliation is attempted.

  • _get_value_from_bill keeps the reception rate for the quantity received on notice, so inventory is no longer revalued for exchange rate movements (IAS 21 / OMFP 1802: a non-monetary asset is not retranslated) and the stock ledger agrees with the stock account. Only the quantity invoiced beyond the reception - a genuine price difference, whose liability arises at the invoice date - is taken at the invoice rate.

19.0.0.26.1

  • Expose the stock.move.l10n_ro_move_type selection as the module level MOVE_TYPE constant, so other modules can reuse it instead of duplicating the list. Up to 18.0 the same list was available as VALUED_TYPE on stock.valuation.layer. No functional change.

19.0.0.25.1

  • Fix IndexError: list index out of range in stock_move._l10n_ro_process_fifo_split when validating an outgoing move. An incoming move with nothing left to consume (its valued quantity is zero, for instance a reception corrected to 0 after validation) still entered the per-location FIFO stack; _split returns no values for a quantity that is zero at the UoM rounding, so indexing its result crashed the transfer. Such moves no longer enter the stack, zero-quantity slices are skipped by the outgoing split, and the quantities are compared with the UoM rounding instead of raw floats.

19.0.0.19.1

  • Fix TypeError: '<' not supported between instances of 'bool' and 'str' in stock_move._compute_account when sorting the account move lines. A journal item without an account (or whose account has no code) returns False for account_id.code, which cannot be compared against the string codes of the other lines. The sort key now falls back to an empty string, and the subsequent code[0] check guards against an empty/falsy code.

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed feedback.

Do not contact contributors directly about support or help with technical issues.

Credits

Authors

  • NextERP Romania

  • Dorin Hongu

  • Forest and Biomass Romania

Contributors

Do not contact contributors directly about support or help with technical issues.

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

Current maintainers:

dhongu feketemihai

This module is part of the OCA/l10n-romania project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file odoo_addon_l10n_ro_stock_account-19.0.1.13.0-py3-none-any.whl.

File metadata

File hashes

Hashes for odoo_addon_l10n_ro_stock_account-19.0.1.13.0-py3-none-any.whl
Algorithm Hash digest
SHA256 423470f492c5e50e662c2eb860e9283040af16265bc3240e9c850f60b3ac34ed
MD5 0949393b2d1920d2bf1a0062d8575857
BLAKE2b-256 730546796ae00b47dabe2659c4dd8770db7754fca020878dabe7c1da3d1551b0

See more details on using hashes here.

Release history Release notifications | RSS feed

19.0.1.15.0

1 file

19.0.1.14.0

1 file

This release

19.0.1.13.0 This release

1 file

19.0.1.12.0

1 file

19.0.1.11.0

1 file

19.0.1.10.0

1 file

19.0.1.9.0

1 file

19.0.1.8.0

1 file

19.0.1.6.0

1 file

19.0.1.4.0.1

1 file

19.0.1.4.0

1 file

19.0.1.2.0.1

1 file

19.0.1.2.0

1 file

19.0.1.1.0

1 file

19.0.0.27.0

1 file

19.0.0.26.0

1 file

19.0.0.25.0.1

1 file

19.0.0.25.0

1 file

19.0.0.24.0

1 file

19.0.0.23.0

1 file

19.0.0.22.0

1 file

19.0.0.20.0

1 file

19.0.0.19.0.1

1 file

19.0.0.19.0

1 file

19.0.0.18.0

1 file

19.0.0.17.0

1 file

19.0.0.16.0

1 file

19.0.0.15.0

1 file

19.0.0.14.0

1 file

19.0.0.13.2.1

1 file

19.0.0.13.1.1

1 file

19.0.0.13.0

1 file

19.0.0.12.0

1 file

19.0.0.11.0.1

1 file

19.0.0.11.0

1 file

19.0.0.10.0.1

1 file

19.0.0.10.0

1 file

19.0.0.9.0.1

1 file

19.0.0.9.0

1 file

19.0.0.8.0.2

1 file

19.0.0.8.0

1 file

19.0.0.7.0

1 file

19.0.0.6.0.1

1 file

19.0.0.6.0

1 file

19.0.0.5.0

1 file

19.0.0.4.0

1 file

19.0.0.3.0

1 file

19.0.0.2.0

1 file

19.0.0.1.0

1 file

18.0.1.33.0

1 file

18.0.1.31.0

1 file

18.0.1.30.0

1 file

18.0.1.28.0

1 file

18.0.1.27.0

1 file

18.0.1.26.1.1

1 file

18.0.1.26.0

1 file

18.0.1.25.0

1 file

18.0.1.24.0

1 file

18.0.1.23.0

1 file

18.0.1.22.0

1 file

18.0.1.21.0

1 file

18.0.1.20.0.1

1 file

18.0.1.20.0

1 file

18.0.1.19.0.1

1 file

18.0.1.19.0

1 file

18.0.1.18.0

1 file

18.0.1.17.0

1 file

18.0.1.16.0.1

1 file

18.0.1.16.0

1 file

18.0.1.15.0

1 file

18.0.1.14.0.1

1 file

18.0.1.14.0

1 file

18.0.1.13.0

1 file

18.0.1.12.0

1 file

18.0.1.11.0

1 file

17.0.1.36.0

1 file

17.0.1.35.0.1

1 file

17.0.1.35.0

1 file

17.0.1.34.0

1 file

17.0.1.33.0

1 file

17.0.1.32.0

1 file

17.0.1.31.0

1 file

17.0.1.30.0

1 file

17.0.1.29.0.1

1 file

17.0.1.29.0

1 file

17.0.1.28.0

1 file

17.0.1.27.0

1 file

17.0.1.26.0

1 file

17.0.1.25.0

1 file

17.0.1.24.0

1 file

17.0.1.23.0

1 file

17.0.1.22.0

1 file

17.0.1.21.0

1 file

17.0.1.20.0.1

1 file

17.0.1.20.0

1 file

17.0.1.19.0

1 file

17.0.1.18.0.1

1 file

17.0.1.18.0

1 file

17.0.1.17.0

1 file

17.0.1.16.0

1 file

17.0.1.15.0

1 file

17.0.1.14.0

1 file

17.0.1.13.0.1

1 file

17.0.1.13.0

1 file

17.0.1.12.0

1 file

17.0.1.11.0

1 file

17.0.1.10.1

1 file

17.0.1.10.0

1 file

17.0.1.9.0

1 file

17.0.1.8.0

1 file

17.0.1.7.0

1 file

17.0.1.6.0

1 file

17.0.1.5.0.1

1 file

17.0.1.5.0

1 file

17.0.1.4.0

1 file

17.0.1.3.0

1 file

17.0.1.2.0

1 file

17.0.1.1.1

1 file

17.0.1.1.0

1 file

16.0.1.28.0.2

1 file

16.0.1.28.0.1

1 file

16.0.1.28.0

1 file

16.0.1.27.0

1 file

16.0.1.26.0

1 file

16.0.1.25.0

1 file

16.0.1.24.2.1

1 file

16.0.1.24.1

1 file

16.0.1.24.0.1

1 file

16.0.1.24.0

1 file

16.0.1.23.0

1 file

16.0.1.22.0

1 file

16.0.1.21.0

1 file

16.0.1.20.0

1 file

16.0.1.19.0

1 file

16.0.1.17.0

1 file

16.0.1.16.0

1 file

16.0.1.15.0

1 file

16.0.1.14.0

1 file

16.0.1.13.0

1 file

16.0.1.12.0

1 file

16.0.1.11.0

1 file

16.0.1.10.0

1 file

16.0.1.9.0.1

1 file

16.0.1.9.0

1 file

16.0.1.8.0

1 file

16.0.1.7.0

1 file

16.0.1.6.0

1 file

16.0.1.5.0.2

1 file

16.0.1.5.0.1

1 file

16.0.1.5.0

1 file

16.0.1.4.0.2

1 file

16.0.1.4.0.1

1 file

16.0.1.4.0

1 file

16.0.1.3.0.1

1 file

16.0.1.3.0

1 file

16.0.1.2.0

1 file

16.0.1.1.0.1

1 file

16.0.1.1.0

1 file

15.0.3.41.0

1 file

15.0.3.40.0

1 file

15.0.3.39.0

1 file

15.0.3.38.0

1 file

15.0.3.36.0

1 file

15.0.3.35.0

1 file

15.0.3.34.0

1 file

15.0.3.33.0

1 file

15.0.3.32.0

1 file

15.0.3.31.0.1

1 file

15.0.3.31.0

1 file

15.0.3.30.0

1 file

15.0.3.29.0

1 file

15.0.3.28.0.1

1 file

15.0.3.28.0

1 file

15.0.3.27.0

1 file

15.0.3.26.0

1 file

15.0.3.25.0.1

1 file

15.0.3.25.0

1 file

15.0.3.24.0.1

1 file

15.0.3.24.0

1 file

15.0.3.23.0.1

1 file

15.0.3.23.0

1 file

15.0.3.22.0.1

1 file

15.0.3.22.0

1 file

15.0.3.21.0

1 file

15.0.3.20.0

1 file

15.0.3.19.0.1

1 file

15.0.3.19.0

1 file

15.0.3.18.0

1 file

15.0.3.17.0

1 file

15.0.3.16.0

1 file

15.0.3.15.0

1 file

15.0.3.14.0.1

1 file

15.0.3.14.0

1 file

15.0.3.13.0

1 file

15.0.3.12.0.2

1 file

15.0.3.12.0

1 file

15.0.3.11.0

1 file

15.0.3.10.0.8

1 file

15.0.3.10.0.1

1 file

15.0.3.10.0

1 file

15.0.3.9.0

1 file

15.0.3.8.0

1 file

15.0.3.7.0

1 file

15.0.3.6.0

1 file

15.0.3.5.0

1 file

15.0.3.4.0

1 file

15.0.3.3.0

1 file

15.0.3.2.0

1 file

15.0.3.1.1

1 file

15.0.3.1.0

1 file

15.0.3.0.0.1

1 file

15.0.3.0.0

1 file

15.0.2.0.0

1 file

15.0.1.6.0.2

1 file

15.0.1.6.0

1 file

15.0.1.5.0

1 file

15.0.1.4.0

1 file

15.0.1.3.0.1

1 file

15.0.1.1.0.1

1 file

15.0.1.1.0

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page