Skip to content

l10n_ro_stock_account: stiva FIFO per locație ignoră transferurile interne între gestiuni valorizate → COGS greșit pe ieșiri #1546

Description

@dhongu

[19.0][BUG] l10n_ro_stock_account: stiva FIFO per locație ignoră transferurile interne între gestiuni valorizate → COGS greșit pe ieșiri

Modul

l10n_ro_stock_account 19.0.0.24.0 / 19.0.0.25.0 (funcționalitatea fifo_per_location, models/fifo/)

Mediu

  • Odoo 19.0 Enterprise (Odoo Online / SaaS), bază de producție migrată de la 18.0 pe 09.07.2026
  • Companie românească → company.fifo_per_location = True (default calculat pentru country_code == "RO")
  • Categorie de produs cu property_cost_method = "fifo", fără valorizare pe loturi

Fluxul de business care declanșează bugul

Depozitul folosește un flux intern în doi pași, uzual pentru marfă care necesită
procesare/etichetare înainte de vânzare:

Furnizori ──(recepție, is_in=True)──▶ WH/Stock/Neprocesate ──(transfer intern)──▶ WH/Stock/Procesate ──(livrare)──▶ Clienți

Toate recepțiile de la furnizori intră în Neprocesate. Marfa trece în Procesate printr-un
transfer intern simplu, iar livrările către clienți pleacă din Procesate.

Pași de reproducere (minim)

  1. Companie RO, produs FIFO, două locații interne (valorizate) A și B.
  2. Cu ani în urmă: o ajustare de inventar a creat 1 bucată direct în B, la un cost istoric mare
    (ex. 176,79). Bucata a fost vândută ulterior — la nivel de companie stratul e complet consumat.
  3. Se recepționează 2 bucăți @ ~54,00 de la furnizor în A.
  4. Transfer intern A → B cu cele 2 bucăți.
  5. Se livrează 1 bucată din B către client.

Comportament observat

Mișcarea de ieșire este valorizată la 176,79 — costul ajustării de inventar de la pasul 2,
un strat consumat fizic demult, fără nicio legătură cu stocul aflat pe mână.

Pe baza noastră de producție au fost afectate 382 de produse (COGS / valoare de stoc
supraevaluate cu ~12.400 RON), toate produse care trec prin fluxul în doi pași
Neprocesate → Procesate. Problema a fost sesizată prima dată de client ca o marjă negativă
inexplicabilă (preț de vânzare 132,52, cost 176,79) la un produs al cărui cost curent e ~53.

Comportament așteptat

Ieșirea ar trebui valorizată din costul purtat efectiv de bucățile aflate pe mână în locația
sursă — ~54,00 (recepția recentă de la furnizor, adusă de transferul intern).

Analiza cauzei

product.product._run_fifo_get_stack() (models/fifo/product.py), când primește location,
construiește stiva per locație cu domeniul:

moves_domain &= Domain([("location_dest_id", "=", location.id)])
...
moves_domain &= Domain([("is_in", "=", True)])

is_in este câmpul stocat din core Odoo care marchează mișcările ce aduc valoare în
companie
(recepții de la furnizor, retururi de la client…). Un transfer simplu între două
locații interne valorizate are is_in = False stocat, deci:

  • Pentru locația B (locația de livrare), transferul intern A → B este invizibil pentru
    constructorul stivei.
  • Singura mișcare is_in=True care a țintit vreodată direct B este vechea ajustare de
    inventar, deci stiva lui B conține doar acel strat vechi, iar fiecare livrare din B îl
    consumă — la nesfârșit (qty_available la B e pozitiv, iar parcurgerea stivei refolosește
    singura mișcare pe care o găsește, indiferent de câte ori i-a fost deja consumată cantitatea la
    nivel de companie).

Doi factori agravanți:

  1. Inconsistență între is_in stocat și _is_in() runtime. Override-ul RO din modulul de
    bază (models/stock_move.py::_get_in_move_lines) extinde liniile de intrare la transferurile
    intern→intern pentru înregistrările RO, deci move._is_in() întoarce True la runtime pentru
    transfer — dar coloana stocată is_in (calculată de core, store=True) este False
    (pe această bază, probabil calculată în timpul migrării 18→19, înainte ca override-ul RO să fie
    în registry). Căutarea stivei folosește coloana stocată, deci transferul rămâne invizibil, deși
    modulul „intenționa" să-l trateze ca intrare. Exemplu din producție:

    mișcare referință is_in stocat _is_in() runtime
    transfer intern A→B nepr_proce05751 False True
  2. Transferul intern însuși nu poartă o valoare FIFO fiabilă. Chiar acolo unde transferul e
    vizibil, value nu este costul FIFO consumat din locația sursă (în datele noastre conținea un
    fragment rezidual de landed cost: value = 10,06 pentru 2 bucăți a căror recepție a costat
    107,71), deci stiva destinației tot ar valoriza greșit marfa.

De ce FIFO la nivel de companie dă rezultatul corect

Cu fifo_per_location = False totul trece prin _run_fifo_get_stack din core, stiva este pe
toată compania, recepțiile sunt mereu vizibile, iar revalorizarea ieșirilor afectate produce exact
costurile așteptate (verificat pe o copie a bazei de producție: 176,79 → 53,86/53,01). Acesta e
workaround-ul pe care l-am aplicat.

Direcții de fix sugerate

Pentru ca stiva per locație să fie corectă, e nevoie de ambele jumătăți:

  1. În _run_fifo_get_stack, când se primește location (internă, valorizată), să fie acceptate
    ca intrări în stivă nu doar mișcările is_in = True, ci și transferurile sosite dintr-o altă
    locație internă/tranzit valorizată:

    moves_domain &= Domain([("is_in", "=", True)]) | Domain([
        ("location_id.is_valued_internal", "=", True),
        ("location_id", "!=", location.id),
    ])
  2. În _set_value, transferurile interne de acest tip să primească o valoare reală: costul FIFO
    consumat din stiva locației sursă (azi ele nu sunt valorizate nici pe ramura
    _is_out(), nici pe is_in din override-urile fifo/, iar ramura
    l10n_ro_move_type == "internal_transfer" din modulul de bază cade pe _get_value(), care
    ajunge la standard_price).

Suplimentar, valorile stocate is_in/is_out pe bazele migrate nu corespund override-urilor RO
_is_in()/_is_out() și probabil au nevoie de un recompute la migrare.

Putem furniza scripturile complete de reproducere și putem testa un fix candidat pe copia noastră
a bazei de producție afectate.

Evaluarea impactului pe cazul nostru de producție

  • 11.841 produse cu stoc pe mână; 487 semnalate inițial, dintre care 382 confirmate greșite
    sub FIFO per locație (celelalte 105 sunt stoc vechi autentic, valorizat corect).
  • Fiecare livrare a unui produs afectat postează o notă COGS supraevaluată (607 / 371).
  • Descoperit pe: LCD Huawei P40 Pro (ref. internă 64314) — vândut pe 15.07.2026 și 20.07.2026 la
    cost 176,79, în timp ce costul FIFO corect era 53,86/53,01.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions