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

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).

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

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.