Repository
OCA/l10n-romania · module folder · Try on Runboat
Module version
1.0.0
Category
Localization
Folder size
0.26 MB
License
AGPL-3
Application
No
Auto-installable
No
Website
https://github.com/OCA/l10n-romania
Last tracking update
2026-09-18 11:33:44
Authors
Odoo Community Association (OCA), NextERP Romania, Dakai Soft SRL
Maintainers
Odoo Community Association (OCA), NextERP Romania, Dakai Soft SRL
Committers
Mihai Fekete, OCA-git-bot, oca-ci
Odoo dependencies
Python dependencies
None
System dependencies
None
Required by
None
Description
# Romania - Retail Price Change (Proces Verbal de Schimbare Pret)

Adds the price change document to the retail merchandise accounting of
`l10n_ro_stock_account_retail`.

`l10n.ro.retail.price.change` is a persistent document numbered
`PVSP/YYYY/00000` out of a sequence of its own company. It captures the
warehouse, the date, the products on hand and the old versus new shelf
price (PVA, VAT included) per line, with the markup (378) and deferred
VAT (4428) split.

The old side is what the stock carries — cost, markup and deferred VAT
per unit, read from the markup ledger — and it is read again when the
document is posted, so the delta always measures the gap that exists at
the moment the entry is made. A posted document is final: it is revoked
by posting another one, never reset, cancelled or deleted.

There are three flows:

1. **Manual** - create a draft, load the products on hand, edit the new
   prices, then post. Posting writes the new prices on the warehouse
   retail pricelist, books the revaluation and records it in the markup
   ledger, so the next sale releases the new markup and not the old one.
2. **Automatic** - a draft document is raised for each affected retail
   warehouse whenever a shelf price moves, and the user reviews it and
   posts it. What decided the price does not matter: a fixed rule, a rule
   over a category or the whole shop, any term of a formula, a change on
   another pricelist the shop derives from, or the product's own sale
   price. The price before and the price after are compared per product
   and per shop, so a rule that names a whole range raises a document
   holding the labels that actually moved.

   A shop has at most one open automatic document, topped up as prices
   keep moving, so it always quotes the price that is on the label.

   Posting writes a fixed rule back on the pricelist only where the
   pricelist does not already answer with the price decided, which is
   what keeps a shop priced by formula priced by formula.

3. **Reconciled** - a daily cron, *Retail: Reconcile Shelf Prices*,
   compares what the markup ledger says each unit on the shelf carries
   with what the pricelist says the label reads, and raises a draft
   wherever the two have parted company. That catches the prices which
   move with nobody writing anything: the day a dated promotion opens, a
   shelf price computed over a cost that a reception has moved, a change
   of VAT rate. Stock the ledger does not yet account for is left to the
   opening balance wizard.

## Report

The document prints as *Proces-verbal privind modificarea pretului de
vanzare cu amanuntul*, listing quantity, old and new price, old and new
value and the difference per product, with the markup and VAT split
shown separately.

## Price history

Every posted document is a dated record of the shelf price of the
products it covers. The *Price History* button on a product shows the
lines that concern it, oldest first, so the sequence of shelf prices and
the document that decided each of them are visible in one place.

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
report_retail_price_change report_retail_price_change ir.ui.view qweb New
view_product_template_retail_price_change_form product.template.form.l10n.ro.retail.price.change product.template form Inherits product.product_template_form_view
view_retail_price_change_form l10n.ro.retail.price.change.form l10n.ro.retail.price.change form New
view_retail_price_change_line_history_list l10n.ro.retail.price.change.line.history.list l10n.ro.retail.price.change.line list New
view_retail_price_change_list l10n.ro.retail.price.change.list l10n.ro.retail.price.change list New
view_retail_price_change_search l10n.ro.retail.price.change.search l10n.ro.retail.price.change search New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (7)

New fields (1)
  • price_change_id Many2one → l10n.ro.retail.price.change
    index='btree_not_null' ondelete='set null' string='Price Change' args: 'l10n.ro.retail.price.change'
Public methods (0)

No public methods.

New fields (12)
  • account_move_id Many2one → account.move
    copy=False readonly=True args: 'account.move'
  • auto_created Boolean
    copy=False help='True when this document was generated automatically from a pricelist change.' readonly=True
  • company_id Many2one → res.company
    default=<expr> readonly=True required=True args: 'res.company'
  • date Date
    default=fields.Date.context_today required=True tracking=True
  • journal_id Many2one → account.journal
    compute='_compute_journal_id' domain="[('company_id', '=', company_id)]" readonly=False store=True args: 'account.journal'
  • line_ids One2many → l10n.ro.retail.price.change.line
    copy=True string='Lines' args: 'l10n.ro.retail.price.change.line', 'document_id'
  • markup_line_ids One2many → l10n.ro.retail.markup.line
    readonly=True string='Markup Ledger' args: 'l10n.ro.retail.markup.line', 'price_change_id'
  • name Char
    copy=False default='/' index=True readonly=True tracking=True
  • notes Html
  • pricelist_id Many2one → product.pricelist
    compute='_compute_pricelist_id' readonly=False store=True args: 'product.pricelist'
  • state Selection
    default='draft' required=True tracking=True args: [('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Cancelled')]
  • warehouse_id Many2one → stock.warehouse
    domain="[('l10n_ro_retail', '=', True), ('company_id', '=', company_id)]" required=True tracking=True args: 'stock.warehouse'
Public methods (5)
  • action_cancel(self)
    Drop a document that was never posted. Only from draft, and there is no way back from done. A posted Proces Verbal wrote the shelf prices, posted an entry and moved what the stock carries; undoing it in place would leave the three out of step with each other and the printed document out of step with the books. A price decision that turned out wrong is revoked the way it was made, by posting another one.
  • action_load_products(self)
  • action_post(self)
  • action_view_move(self)
  • create(self, vals_list)
    @api.model_create_multi
    Number each document out of the sequence of its own company. ``next_by_code`` reads ``self.env.company``, which is not necessarily the company of the document being created: the pricelist hook raises one document per retail warehouse, in sudo, and a warehouse can belong to another company of the group. With a sequence per company that would hand a document the series of a different firm.

New fields (18)
  • company_id Many2one
    related='document_id.company_id' store=True
  • cost_unit Monetary
    compute='_compute_carried' help='Cost the stock on hand carries, from the markup ledger.' store=True string='Cost / Unit'
  • currency_id Many2one
    readonly=True related='company_id.currency_id'
  • document_date Date
    index=True related='document_id.date' store=True string='Date'
  • document_id Many2one → l10n.ro.retail.price.change
    index=True ondelete='cascade' required=True args: 'l10n.ro.retail.price.change'
  • location_id Many2one → stock.location
    required=True args: 'stock.location'
  • markup_diff_total Monetary
    compute='_compute_splits' store=True string='Markup Delta'
  • new_markup_unit Monetary
    compute='_compute_splits' store=True string='New Markup / Unit'
  • new_price_with_vat Monetary
    help='New retail price including VAT.' string='New PVA'
  • new_vat_unit Monetary
    compute='_compute_splits' store=True string='New VAT / Unit'
  • old_markup_unit Monetary
    compute='_compute_carried' help='Markup the stock carries on 378 per unit, as recorded.' store=True string='Old Markup / Unit'
  • old_price_with_vat Monetary
    compute='_compute_old_price' help='What the stock on the shelf carries per unit, VAT included - the cost plus the markup and deferred VAT recorded against it. This is what 371 holds, which is not always what the pricelist says.' store=True string='Old PVA'
  • old_vat_unit Monetary
    compute='_compute_carried' help='Deferred VAT the stock carries on 4428 per unit, as recorded.' store=True string='Old VAT / Unit'
  • product_id Many2one → product.product
    required=True args: 'product.product'
  • quantity Float
    digits='Product Unit of Measure' readonly=True
  • state Selection
    related='document_id.state' store=False
  • vat_diff_total Monetary
    compute='_compute_splits' store=True string='VAT Delta'
  • warehouse_id Many2one
    related='document_id.warehouse_id' store=True string='Warehouse'
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (3)
  • create(self, vals_list)
    @api.model_create_multi
    A new rule has no price from before to be compared against. So the comparison is made against what the goods on the shelf carry - account 371 against the label, the invariant itself. Comparing against nothing instead, as though every price had moved up from zero, put every article the rule reaches on a document: harmless once posted, because a line whose two sides agree books no entry, but a rule covering a whole category handed the shop a draft listing its entire assortment to read through.
  • unlink(self)
    Deleting a rule moves the shelf price too. The label then shows whatever answers next - the rule underneath, or the sale price - while 371 still carries the price the deleted rule set. That is the same divergence a price edit causes, and it deserves the same Proces Verbal. The prices after have to be read once the rule is gone, so the targets are carried over from before the delete.
  • write(self, vals)

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (1)
  • l10n_ro_retail_price_change_count Integer
    compute='_compute_l10n_ro_retail_price_change_count' string='Retail Price Changes'
Public methods (2)
  • action_l10n_ro_retail_price_history(self)
    Every shelf price this product has had, and the document that decided each of them.
  • write(self, vals)
    A shop priced off the sale price re-prices its shelves here. ``base='list_price'`` is the default of a formula rule and the most common way a shop is set up: the shelf price is the sale price, or the sale price less a discount. For that shop the repricing action is editing the product, and nothing at all is written on the pricelist - so watching only ``product.pricelist.item`` saw none of it, and 371 stayed on the old price with no document raised. The cost is the other base a formula can have, and it is deliberately not watched here. ``standard_price`` is written by the valuation on every reception under average cost, which is the hot path of every goods movement in the database, and hanging a price snapshot off it would make every receipt pay for a check that almost never finds anything. A shelf price that follows the cost is caught by the nightly reconciliation instead, which is where the other prices that move without anyone writing them are caught too.

New fields (0)

No new fields.

Public methods (1)
  • create(self, vals_list)
    @api.model_create_multi