TIP: You can type at any time to perform a new search.
Romania - Stock Accounting
l10n_ro_stock_account · OCA/l10n-romania
🛠 Migration considerations
- Raw `cr.execute()` INSERT/UPDATE/DELETE bypasses the ORM (no compute/constrains/tracking/mail): `UPDATE stock_move sm SET l10n_ro_transfer_account_id = (sl.l10n_ro_property_stock_valuation_account_id->>'sm.company_id')::integer FROM stock_location sl, stock_location sld WHERE sm.location_id = sl.id AND sm.location_dest_id = sld.id AND sl.usage = 'internal' AND sld.usage = 'internal' AND sl.l10n_ro_property_stock_valuation_account_id IS NOT NULL` - re-check the table/column names still match after upgrading. migration-raw-sql-write · source
Found by automated static analysis: patterns worth a look before/after upgrading this module to a newer Odoo version.
- Repository
- OCA/l10n-romania · module folder · Try on Runboat
- Module version
- Category
- Localization
- Folder size
- 0.41 MB
- License
- AGPL-3
- Application
- No
- Auto-installable
- No
- Website
- https://github.com/OCA/l10n-romania
- Last tracking update
- 2026-08-19 05:33:10
- Authors
- Odoo Community Association (OCA), Forest and Biomass Romania, NextERP Romania, Dorin Hongu
- Maintainers
- Odoo Community Association (OCA), Forest and Biomass Romania, NextERP Romania, Dorin Hongu
- Committers
- GitHub, Mihai Fekete, Dorin Hongu, Weblate, OCA-git-bot, oca-ci, Sima Elisabeta, Flavia0320, VoicuStefan2001, IoanaComaniciu00
- Odoo dependencies
- Python dependencies
- None
- System dependencies
- None
- Required by
- l10n_ro_nondeductible_vat, l10n_ro_stock_account_date, l10n_ro_stock_account_tracking, l10n_ro_stock_picking_comment_template, l10n_ro_stock_report
- Description
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.
Code Analysis ⓘ
Views touched (8)
| XML ID | Name | Model | Type | Status |
|---|---|---|---|---|
product_template_form_view |
product.normal.form.inherit.stock | product.template | form | Inherits account.product_template_form_view |
view_account_form |
account.account.form | account.account | form | Inherits account.view_account_form |
view_company_form_fifo_location |
res.company.form.fifo.location | res.company | form | Inherits base.view_company_form |
view_location_form_inherit |
view_location_romania_form | stock.location | form | Inherits stock_account.view_location_form_inherit |
view_picking_form |
view_picking_form | stock.picking | form | Inherits stock.view_picking_form |
view_product_category_add_romania_form |
view_product_category_add_romania_form | product.category | form | Inherits stock_account.view_category_property_form |
view_stock_move_form_fifo_neg |
stock.move.form.fifo.neg | stock.move | form | Inherits stock.view_move_form |
view_warehouse |
stock.warehouse.form | stock.warehouse | form | Inherits stock.view_warehouse |
HTTP endpoints (0)
No HTTP endpoints found for this module.
Models touched (13)
New fields (2)
-
l10n_ro_reception_in_progress_account_idMany2one → account.accountstring='Romania - Reception in Progress Account' args: 'account.account' -
l10n_ro_stock_consume_account_idMany2one → account.accountcompany_dependent=Truedomain=ACCOUNT_DOMAINhelp='Account used for stock consume and usage giving operations'string='Consume Account' args: 'account.account'
No public methods.
New fields (2)
-
fifo_neg_origin_move_idMany2one → stock.movecopy=Falsehelp='The incoming stock move that triggered this FIFO negative stock compensation accounting entry.'index='btree_not_null'readonly=Truestring='Source IN move for negative stock compensation' args: 'stock.move' -
l10n_ro_extra_stock_move_idMany2one → stock.movereadonly=Truestring='Romania - Extra Stock Move' args: 'stock.move'
No public methods.
New fields (0)
No new fields.
Public methods (0)No public methods.
New fields (1)
-
l10n_ro_stock_account_changeBooleanhelp='Only for Romania, to change the accounts to the ones defined on stock locations'string='Allow stock account change from locations'
No public methods.
New fields (0)
No new fields.
Public methods (0)No public methods.
New fields (1)
-
l10n_ro_property_stock_valuation_account_idMany2one → account.accountcompany_dependent=Truedomain=ACCOUNT_DOMAINhelp='In Romania accounting is only one account for valuation/input/output. If this value is set, we will use it, otherwise will use the category value. 'ondelete='restrict'string='Stock Valuation Account' args: 'account.account'
No public methods.
New fields (2)
-
fifo_location_negative_compensationBooleancompute='_compute_fifo_location_negative_compensation'help='When a FIFO outgoing move happens before the corresponding incoming move (negative stock on the location), its initial value is the standard_price. On the next incoming move, the module emits a correction accounting entry to align the outgoing value with the actual incoming price. Defaults to True for Romanian companies; editable per company.'readonly=Falsestore=Truestring='FIFO Negative Stock Compensation' -
fifo_per_locationBooleancompute='_compute_fifo_per_location'help='Enable FIFO valuation at the source location level (instead of the company level). Outgoing moves for products with cost_method=fifo are automatically split into one stock.move per FIFO layer. Defaults to True for Romanian companies; editable per company.'readonly=Falsestore=Truestring='FIFO per Location'
No public methods.
New fields (3)
-
l10n_ro_property_account_expense_location_idMany2one → account.accountcompany_dependent=Truedomain=ACCOUNT_DOMAINhelp='This account will overwrite the expense accounts from product or category.'string='Expense Account' args: 'account.account' -
l10n_ro_property_account_income_location_idMany2one → account.accountcompany_dependent=Truedomain=ACCOUNT_DOMAINhelp='This account will overwrite the income accounts from product or category.'string='Income Account' args: 'account.account' -
l10n_ro_property_stock_valuation_account_idMany2one → account.accountcompany_dependent=Truedomain=ACCOUNT_DOMAINstring='Stock Valuation Account Romania' args: 'account.account'
-
propagate_account(self)
New fields (4)
-
l10n_ro_account_idMany2one → account.accountcompute='_compute_account'store=Truestring='Romania - Valuation Account' args: 'account.account' -
l10n_ro_extra_account_move_idsOne2many → account.movecopy=Falsereadonly=Truestring='Romania - Extra Account Moves' args: 'account.move', 'l10n_ro_extra_stock_move_id' -
l10n_ro_move_typeSelectioncompute='_compute_l10n_ro_move_type'help='Specify the type of stock move for Romanian localization.'store=Truestring='Romanian - Move Type' args: MOVE_TYPE -
l10n_ro_transfer_account_idMany2one → account.accountcompute='_compute_account'store=Truestring='Romania - Transfer Valuation Account' args: 'account.account'
No public methods.
New fields (3)
-
fifo_neg_compensation_move_idsOne2many → account.movereadonly=Truestring='Negative Stock Compensation Entries' args: 'account.move', 'fifo_neg_origin_move_id' -
fifo_neg_origin_valueMonetarycopy=Falsecurrency_field='company_currency_id'readonly=Truestring='Negative Stock Initial Value' -
fifo_neg_pending_qtyFloatcopy=Falsehelp='Quantity from a FIFO outgoing move that was valued at standard_price because the location stack was empty. Compensated FIFO on the next incoming move for the same (product, location) pair.'readonly=Truestring='Negative Stock Pending Qty'
-
search_remaining_qty(self, operator, value)For companies with ``fifo_per_location``, iterate per location so the search result matches the per-location ``remaining_qty`` computed by ``_compute_remaining_qty``. Falls back to the base behavior for companies that use company-wide FIFO.
New fields (2)
-
l10n_ro_noticeBoolean -
l10n_ro_reception_in_progressBoolean
-
action_l10n_ro_view_account_moves(self)
New fields (0)
No new fields.
Public methods (0)No public methods.
New fields (1)
-
l10n_ro_fiscal_position_idMany2one → account.fiscal.positionstring='Fiscal Position' args: 'account.fiscal.position'
No public methods.
Loading…
Loading…
Loading…
Loading…
Loading…
Loading…