padding acc
This commit is contained in:
@@ -45,6 +45,8 @@ de negoce physique:
|
||||
- `modules/purchase_trade/docs/template-rules.md`
|
||||
- Catalogue des proprietes templates:
|
||||
- `modules/purchase_trade/docs/template-properties.md`
|
||||
- Padding facture provisoire vente / validation comptable:
|
||||
- `modules/purchase_trade/docs/padding-invoice-accounting.md`
|
||||
|
||||
## 4) Invariants metier a preserver
|
||||
|
||||
@@ -131,6 +133,19 @@ de negoce physique:
|
||||
- a la validation d'une `sale.line`, si un lot virtuel est cree et qu'aucun
|
||||
matching purchase n'existe, il faut lancer `generate_from_sale_line()` pour
|
||||
alimenter le PnL sale-first
|
||||
- En padding de facture provisoire vente:
|
||||
- le padding est saisi globalement dans `lot.invoice`
|
||||
- il est uniquement applique aux factures provisoires cote vente
|
||||
- il est reparti par lot et stocke dans `lot.sale_invoice_padding`
|
||||
- la ligne facture affiche `Inc. padding` depuis le lot
|
||||
- le padding augmente `account.invoice.line.quantity`, donc le move principal
|
||||
de facture inclut deja le montant padding
|
||||
- les comptes de padding viennent de `account.configuration`:
|
||||
`Default Sale Padding` et `Default Accrual Padding`
|
||||
- la provisoire doit passer le couple sale/accrual, et la finale doit passer
|
||||
l'inverse pour le montant exact comptabilise en provisoire
|
||||
- preferer stocker ce montant au moment de la provisoire plutot que le
|
||||
recalculer depuis la finale
|
||||
|
||||
## 5) Conventions de modification
|
||||
|
||||
@@ -174,6 +189,9 @@ de negoce physique:
|
||||
- attribution du `number`
|
||||
- `Post` ne doit pas reintroduire une difference de session/fresh login cote
|
||||
client
|
||||
- Pour le padding provisoire vente, ne pas changer la quantite physique du lot:
|
||||
l'ecart doit rester porte par `lot.sale_invoice_padding` et visible sur la
|
||||
facture via `Inc. padding`.
|
||||
|
||||
## 7) Definition of done (module `purchase_trade`)
|
||||
|
||||
|
||||
@@ -190,9 +190,11 @@ def register():
|
||||
dimension.AnalyticDimensionAssignment,
|
||||
weight_report.WeightReport,
|
||||
module='purchase', type_='model')
|
||||
Pool.register(
|
||||
account.PhysicalTradeIFRS,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
account.Configuration,
|
||||
account.ConfigurationDefaultAccount,
|
||||
account.PhysicalTradeIFRS,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
invoice.Invoice,
|
||||
invoice.InvoiceLine,
|
||||
|
||||
@@ -1,12 +1,63 @@
|
||||
# account.py
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
|
||||
__all__ = ['PhysicalTradeIFRS']
|
||||
__all__ = [
|
||||
'Configuration',
|
||||
'ConfigurationDefaultAccount',
|
||||
'PhysicalTradeIFRS',
|
||||
]
|
||||
__metaclass__ = PoolMeta
|
||||
|
||||
|
||||
class Configuration(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration'
|
||||
|
||||
default_sale_padding_account = fields.MultiValue(fields.Many2One(
|
||||
'account.account', "Default Sale Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.revenue', '=', True),
|
||||
('company', '=', Eval('context', {}).get('company', -1)),
|
||||
]))
|
||||
default_accrual_padding_account = fields.MultiValue(fields.Many2One(
|
||||
'account.account', "Default Accrual Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.statement', '=', 'balance'),
|
||||
('company', '=', Eval('context', {}).get('company', -1)),
|
||||
]))
|
||||
|
||||
@classmethod
|
||||
def multivalue_model(cls, field):
|
||||
pool = Pool()
|
||||
if field in {
|
||||
'default_sale_padding_account',
|
||||
'default_accrual_padding_account',
|
||||
}:
|
||||
return pool.get('account.configuration.default_account')
|
||||
return super().multivalue_model(field)
|
||||
|
||||
|
||||
class ConfigurationDefaultAccount(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration.default_account'
|
||||
|
||||
default_sale_padding_account = fields.Many2One(
|
||||
'account.account', "Default Sale Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.revenue', '=', True),
|
||||
('company', '=', Eval('company', -1)),
|
||||
])
|
||||
default_accrual_padding_account = fields.Many2One(
|
||||
'account.account', "Default Accrual Padding",
|
||||
domain=[
|
||||
('closed', '!=', True),
|
||||
('type.statement', '=', 'balance'),
|
||||
('company', '=', Eval('company', -1)),
|
||||
])
|
||||
|
||||
|
||||
class PhysicalTradeIFRS(ModelSQL, ModelView):
|
||||
'Physical Trade - IFRS Adjustment'
|
||||
__name__ = 'account.physical_trade_ifrs'
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="account_configuration_view_form">
|
||||
<field name="model">account.configuration</field>
|
||||
<field name="inherit" ref="account_product.configuration_view_form"/>
|
||||
<field name="name">account_configuration_form</field>
|
||||
</record>
|
||||
|
||||
<record model="res.group" id="group_physical_trade_ifrs">
|
||||
<field name="name">Physical Trade IFRS</field>
|
||||
</record>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Business Rules - Purchase Trade
|
||||
|
||||
Statut: `draft`
|
||||
Version: `v0.6`
|
||||
Derniere mise a jour: `2026-04-23`
|
||||
Version: `v0.7`
|
||||
Derniere mise a jour: `2026-04-26`
|
||||
Owner metier: `a completer`
|
||||
Owner technique: `a completer`
|
||||
|
||||
@@ -426,11 +426,16 @@ Owner technique: `a completer`
|
||||
- deux lots factures ensemble avec un padding global de `1000` recoivent
|
||||
chacun `500` de padding
|
||||
- la ligne facture affiche la quantite augmentee
|
||||
- la ligne facture expose `Included padding`
|
||||
- la ligne facture expose `Inc. padding`
|
||||
- le lot conserve sa part de padding dans `sale_invoice_padding`
|
||||
- Hors scope:
|
||||
- les ecritures comptables specifiques au padding seront traitees dans un
|
||||
second temps
|
||||
- Validation comptable:
|
||||
- au `Validate`, le move principal de facture inclut deja le padding car il
|
||||
est integre a `account.invoice.line.quantity`
|
||||
- la provisoire doit utiliser le couple de comptes configure
|
||||
`Default Sale Padding` / `Default Accrual Padding`
|
||||
- la finale doit passer l'ecriture inverse pour exactement le montant padding
|
||||
comptabilise lors de la provisoire
|
||||
- voir `modules/purchase_trade/docs/padding-invoice-accounting.md`
|
||||
- Priorite:
|
||||
- `importante`
|
||||
|
||||
|
||||
294
modules/purchase_trade/docs/padding-invoice-accounting.md
Normal file
294
modules/purchase_trade/docs/padding-invoice-accounting.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# Padding facture provisoire vente - notes comptables
|
||||
|
||||
Statut: `draft`
|
||||
Derniere mise a jour: `2026-04-26`
|
||||
Scope: `lot.invoice` -> `account.invoice` -> validation comptable.
|
||||
|
||||
## 1) Objectif metier
|
||||
|
||||
Le padding sert a augmenter temporairement la quantite facturee sur une facture
|
||||
provisoire cote vente, afin de constituer une provision avant la facture finale.
|
||||
|
||||
Exemple:
|
||||
|
||||
- 2 lots factures ensemble
|
||||
- padding global saisi dans le wizard: `1000`
|
||||
- repartition actuelle: `500` par lot
|
||||
- chaque ligne facture affiche:
|
||||
- `quantity = quantite reelle du lot + padding du lot`
|
||||
- `Inc. padding = padding du lot`
|
||||
|
||||
Le padding ne modifie pas la quantite physique du lot. Il doit rester visible et
|
||||
traçable comme ecart entre la facture provisoire et le lot.
|
||||
|
||||
## 2) Donnees creees ou utilisees
|
||||
|
||||
### `lot.lot`
|
||||
|
||||
- Champ: `sale_invoice_padding`
|
||||
- Role: memoire de la part de padding appliquee au lot pour la facture
|
||||
provisoire vente.
|
||||
- UI: readonly sur le lot.
|
||||
|
||||
### `account.invoice.line`
|
||||
|
||||
- Champ fonctionnel: `included_padding`
|
||||
- Label UI: `Inc. padding`
|
||||
- Role: affichage de `line.lot.sale_invoice_padding` sur la ligne facture.
|
||||
- La valeur n'est pas stockee sur la ligne; elle lit le padding du lot.
|
||||
|
||||
### Wizard `lot.invoice`
|
||||
|
||||
- Champ: `sale_padding` (`Global padding`)
|
||||
- Visible seulement pour:
|
||||
- `type = sale`
|
||||
- `action = prov`
|
||||
- Repartition actuelle:
|
||||
- padding global / nombre de lots selectionnes
|
||||
|
||||
## 3) Flux de creation de facture avec padding
|
||||
|
||||
1. L'utilisateur ouvre le wizard `lot.invoice` depuis les lots physiques.
|
||||
2. Le wizard prepare deux listes:
|
||||
- `lot_p` pour achat
|
||||
- `lot_s` pour vente
|
||||
3. En mode vente provisoire, l'utilisateur saisit `Global padding`.
|
||||
4. Au clic `Invoice`, le wizard:
|
||||
- calcule la part de padding par lot
|
||||
- ecrit cette part dans `lot.sale_invoice_padding`
|
||||
- appelle `sale.sale._process_invoice(..., action='prov')`
|
||||
5. `sale.line.get_invoice_line(lots, action)` cree une ligne facture par lot
|
||||
physique.
|
||||
6. Pour chaque ligne positive `out` / `Pro forma`, le module ajoute le padding
|
||||
du lot a `invoice_line.quantity`.
|
||||
7. L'amount de la ligne augmente naturellement via:
|
||||
- `quantity * unit_price`
|
||||
|
||||
## 4) Ce qui se passe actuellement lors de `Validate` d'une facture
|
||||
|
||||
Bouton UI: `Validate` sur `account.invoice`.
|
||||
|
||||
Code principal:
|
||||
|
||||
- `modules/account_invoice/invoice.py`
|
||||
- `Invoice.validate_invoice`
|
||||
- `Invoice.get_move`
|
||||
- `InvoiceLine.get_move_lines`
|
||||
- `Invoice.do_lot_invoicing`
|
||||
- `Invoice.cleanMoves`
|
||||
|
||||
### Etapes de `validate_invoice`
|
||||
|
||||
1. Verifie les taxes avec `_check_taxes`.
|
||||
2. Attribue le numero via `set_number`.
|
||||
3. Stocke les caches de montants via `_store_cache`.
|
||||
4. Pour chaque facture:
|
||||
- appelle `invoice.get_move()`
|
||||
- affecte le move a `invoice.move` s'il est nouveau
|
||||
- appelle `invoice.do_lot_invoicing()`
|
||||
5. Sauvegarde les moves crees.
|
||||
6. Nettoie certaines lignes de move via `cleanMoves`.
|
||||
7. Sauvegarde les factures.
|
||||
|
||||
Note importante deja actee dans le projet:
|
||||
|
||||
- `Validate` cree deja le `account.move` et attribue le `number` aussi pour les
|
||||
factures client (`type = out`).
|
||||
- `Post` ne doit plus etre le premier moment ou ces donnees apparaissent cote
|
||||
client.
|
||||
|
||||
### `get_move()`
|
||||
|
||||
`get_move()` construit le move comptable principal de la facture:
|
||||
|
||||
- appelle `update_taxes(exception=True)`
|
||||
- parcourt `invoice.lines`
|
||||
- pour chaque ligne, appelle `line.get_move_lines()`
|
||||
- ajoute les lignes de taxes via `tax.get_move_lines()`
|
||||
- ajoute la ligne tiers / receivable-payable via `_get_move_line`
|
||||
- ajoute eventuellement une ligne de change via `_get_exchange_move_line`
|
||||
- cree un `account.move` avec:
|
||||
- `journal = invoice.journal`
|
||||
- `period` trouve depuis la date comptable
|
||||
- `origin = invoice`
|
||||
- `lines = move_lines`
|
||||
|
||||
### `account.invoice.line.get_move_lines()`
|
||||
|
||||
Pour une ligne facture `type = line`:
|
||||
|
||||
- cree une ligne `account.move.line`
|
||||
- rattache `line.lot = self.invoice.lines[0].lot`
|
||||
- attention: comportement existant, pas forcement la ligne courante
|
||||
- convertit le montant si devise differente
|
||||
- pour une facture client (`type = out`):
|
||||
- montant positif => credit du compte de revenu de la ligne
|
||||
- montant negatif => debit
|
||||
- met `line.account = self.account`
|
||||
- met `line.origin = self`
|
||||
- calcule les lignes de taxe
|
||||
|
||||
Pour le padding actuel, comme il augmente `invoice_line.quantity`, il est deja
|
||||
inclus dans le montant de revenu du move principal.
|
||||
|
||||
### `do_lot_invoicing()`
|
||||
|
||||
`do_lot_invoicing()` traite des ajustements lies aux lots:
|
||||
|
||||
- groupe les lignes facture par `i.lot`
|
||||
- calcule:
|
||||
- `var_price`
|
||||
- `var_qt`
|
||||
- retrouve le mouvement stock:
|
||||
- fournisseur pour `type = in`
|
||||
- client pour `type = out`
|
||||
- declenche des actions stock / shipment si necessaire
|
||||
- calcule le COG du lot
|
||||
- prepare des lignes d'ajustement via `_get_move_lines`
|
||||
- actuellement, le move d'ajustement n'est cree/poste que si:
|
||||
- `adjust_move_lines` existe
|
||||
- `self.type == 'in'`
|
||||
|
||||
Consequence importante pour le padding:
|
||||
|
||||
- les ajustements de quantite/prix existants sont principalement effectifs cote
|
||||
achat (`type = in`)
|
||||
- une facture client provisoire avec padding augmente deja le move principal de
|
||||
vente, mais ne genere pas encore d'ecritures specifiques padding separees
|
||||
|
||||
### `_get_move_lines(gl, amount, drop, IsUsd=False, stock_move=None)`
|
||||
|
||||
Helper existant pour creer des paires de lignes de move liees au stock/COG:
|
||||
|
||||
- rattache `lot`, `fee`, `origin`
|
||||
- gere la conversion devise / second currency
|
||||
- choisit les comptes stock / stock out / stock in / COG selon le signe, fee et
|
||||
contexte
|
||||
- peut creer une variante `drop`
|
||||
|
||||
Ce helper est un candidat possible pour reutilisation partielle, mais il est
|
||||
aujourd'hui pense pour des ajustements stock/COG, pas pour isoler un padding de
|
||||
facture provisoire vente.
|
||||
|
||||
## 5) Question comptable a trancher pour le padding
|
||||
|
||||
Le besoin restant est: au `Validate` d'une facture provisoire vente avec padding,
|
||||
generer des ecritures propres a ce padding.
|
||||
|
||||
Points a definir avant implementation:
|
||||
|
||||
- Le move padding doit-il etre:
|
||||
- integre dans le move principal de facture ?
|
||||
- cree comme `additional_move` ?
|
||||
- cree comme move separe avec `origin = invoice` ?
|
||||
- Quels comptes utiliser ?
|
||||
- compte de revenu temporaire / provision ?
|
||||
- compte de contrepartie padding ?
|
||||
- compte lie au produit ?
|
||||
- compte de configuration dedie ?
|
||||
- Faut-il neutraliser une partie du revenu principal deja augmente par la
|
||||
quantity, ou seulement ajouter une ecriture analytique/stock parallele ?
|
||||
- Le padding doit-il impacter:
|
||||
- revenu client ?
|
||||
- stock out ?
|
||||
- COG ?
|
||||
- PnL / valuation ?
|
||||
- Que doit faire la facture finale ?
|
||||
- reprendre le padding provisoire ?
|
||||
- le contrepasser ?
|
||||
- ne rien faire si la finale facture la vraie quantite ?
|
||||
- Le montant padding a utiliser est:
|
||||
- `lot.sale_invoice_padding * invoice_line.unit_price`
|
||||
- dans la devise de facture
|
||||
- a convertir si devise societe differente selon la logique standard
|
||||
|
||||
## 5.1) Decision comptable cible
|
||||
|
||||
Comptes de configuration ajoutes sur `account.configuration`:
|
||||
|
||||
- `default_sale_padding_account`
|
||||
- label UI: `Default Sale Padding`
|
||||
- compte attendu metier: `80021`
|
||||
- nature: revenu
|
||||
- `default_accrual_padding_account`
|
||||
- label UI: `Default Accrual Padding`
|
||||
- compte attendu metier: `42021`
|
||||
- nature: bilan / accrual
|
||||
|
||||
Facture provisoire vente:
|
||||
|
||||
- condition:
|
||||
- `invoice.type = out`
|
||||
- `invoice.reference = Provisional`
|
||||
- `invoice_line.description = Pro forma`
|
||||
- `invoice_line.lot.sale_invoice_padding > 0`
|
||||
- montant:
|
||||
- `padding_amount = lot.sale_invoice_padding * invoice_line.unit_price`
|
||||
- le prix est celui de la facture provisoire, pas un prix recalcule plus tard
|
||||
- ecriture attendue:
|
||||
- couple `80021 / 42021`
|
||||
- rattachement au lot obligatoire sur les lignes si possible
|
||||
|
||||
Facture finale vente:
|
||||
|
||||
- condition:
|
||||
- lot deja facture en provisoire avec padding
|
||||
- la finale doit inverser le padding deja provisionne
|
||||
- montant:
|
||||
- exactement le montant comptabilise lors de la provisoire
|
||||
- ne pas recalculer avec le prix de la finale
|
||||
- ecriture attendue:
|
||||
- couple inverse `42021 / 80021`
|
||||
|
||||
Decision technique retenue:
|
||||
|
||||
- ne pas ajouter de nouveau champ montant pour l'instant
|
||||
- utiliser le lien deja ecrit sur le lot:
|
||||
- `lot.sale_invoice_line_prov`
|
||||
- au moment de la finale, retrouver la ligne provisoire via ce champ
|
||||
- calculer le montant a extourner avec:
|
||||
- `lot.sale_invoice_padding * lot.sale_invoice_line_prov.unit_price`
|
||||
- utiliser la devise, le taux et la date de la facture provisoire pour obtenir
|
||||
le meme montant societe que l'ecriture initiale
|
||||
- garder le rattachement comptable par `account.move.line.lot`
|
||||
|
||||
Raison:
|
||||
|
||||
- evite les ecarts si le prix final change
|
||||
- reutilise le lien direct deja maintenu entre le lot et la ligne provisoire
|
||||
- rend la reprise finale audit-friendly
|
||||
- garde le pivot comptable lot coherent
|
||||
|
||||
## 6) Invariants a preserver
|
||||
|
||||
- Le padding ne change pas la quantite physique du lot.
|
||||
- Le padding est uniquement pour facture provisoire vente:
|
||||
- `invoice.type = out`
|
||||
- `invoice.reference = Provisional`
|
||||
- lignes `description = Pro forma`
|
||||
- L'utilisateur doit voir clairement l'ecart:
|
||||
- `Quantity`
|
||||
- `Inc. padding`
|
||||
- Le lien principal reste:
|
||||
- `account.invoice.line.lot`
|
||||
- `lot.sale_invoice_padding`
|
||||
- Les futures ecritures doivent etre rattachees au lot autant que possible pour
|
||||
rester visibles dans le pivot comptable du lot.
|
||||
|
||||
## 7) Tests a prevoir pour la suite
|
||||
|
||||
- Validation d'une facture provisoire vente avec padding:
|
||||
- move principal existe
|
||||
- montant facture inclut le padding
|
||||
- ecritures specifiques padding creees selon la regle retenue
|
||||
- lignes rattachees au bon lot
|
||||
- Facture provisoire vente sans padding:
|
||||
- aucun move padding specifique
|
||||
- Facture finale vente:
|
||||
- aucun nouveau padding applique automatiquement
|
||||
- comportement de reprise/contrepassation selon decision metier
|
||||
- Facture achat:
|
||||
- aucun impact padding vente
|
||||
- Multi-lots:
|
||||
- padding reparti correctement
|
||||
- ecritures detaillees par lot si la regle comptable l'exige
|
||||
@@ -15,6 +15,180 @@ from trytond.modules.purchase.purchase import (
|
||||
class Invoice(metaclass=PoolMeta):
|
||||
__name__ = 'account.invoice'
|
||||
|
||||
def do_lot_invoicing(self):
|
||||
super().do_lot_invoicing()
|
||||
self._create_sale_padding_moves()
|
||||
|
||||
@classmethod
|
||||
def _post(cls, invoices):
|
||||
pool = Pool()
|
||||
Move = pool.get('account.move')
|
||||
|
||||
super()._post(invoices)
|
||||
|
||||
padding_moves = []
|
||||
for invoice in invoices:
|
||||
padding_moves.extend(invoice._create_sale_padding_moves())
|
||||
padding_moves.extend([
|
||||
move for move in (invoice.additional_moves or [])
|
||||
if move.description in {
|
||||
invoice._get_sale_padding_move_description(False),
|
||||
invoice._get_sale_padding_move_description(True),
|
||||
}
|
||||
and move.state != 'posted'
|
||||
])
|
||||
if padding_moves:
|
||||
cls.save(invoices)
|
||||
Move.post([m for m in padding_moves if m.state != 'posted'])
|
||||
|
||||
def _get_sale_padding_accounts(self):
|
||||
Configuration = Pool().get('account.configuration')
|
||||
config = Configuration(1)
|
||||
sale_account = config.get_multivalue(
|
||||
'default_sale_padding_account', company=self.company.id)
|
||||
accrual_account = config.get_multivalue(
|
||||
'default_accrual_padding_account', company=self.company.id)
|
||||
if not sale_account or not accrual_account:
|
||||
raise UserError(
|
||||
'Default Sale Padding and Default Accrual Padding '
|
||||
'accounts must be configured.')
|
||||
return sale_account, accrual_account
|
||||
|
||||
def _has_sale_padding_move(self, reversal=False):
|
||||
description = self._get_sale_padding_move_description(reversal)
|
||||
return any(
|
||||
move.description == description
|
||||
for move in (self.additional_moves or []))
|
||||
|
||||
@staticmethod
|
||||
def _get_sale_padding_move_description(reversal=False):
|
||||
if reversal:
|
||||
return 'Sale padding reversal'
|
||||
return 'Sale padding accrual'
|
||||
|
||||
def _get_padding_company_amount(self, invoice_line, padding):
|
||||
Currency = Pool().get('currency.currency')
|
||||
invoice = invoice_line.invoice
|
||||
amount = Decimal(str(padding or 0)) * Decimal(
|
||||
str(invoice_line.unit_price or 0))
|
||||
amount = invoice.currency.round(amount)
|
||||
if invoice.currency == invoice.company.currency:
|
||||
return amount, amount
|
||||
if invoice.rate:
|
||||
company_amount = invoice.company.currency.round(
|
||||
amount / invoice.rate)
|
||||
else:
|
||||
with Transaction().set_context(date=invoice.currency_date):
|
||||
company_amount = Currency.compute(
|
||||
invoice.currency, amount, invoice.company.currency)
|
||||
return amount, company_amount
|
||||
|
||||
def _get_sale_padding_entries(self):
|
||||
if self.type != 'out':
|
||||
return []
|
||||
|
||||
entries = []
|
||||
for line in self.lines or []:
|
||||
if getattr(line, 'type', None) != 'line':
|
||||
continue
|
||||
lot = getattr(line, 'lot', None)
|
||||
if not lot:
|
||||
continue
|
||||
padding = Decimal(str(
|
||||
getattr(lot, 'sale_invoice_padding', 0) or 0))
|
||||
if padding <= 0:
|
||||
continue
|
||||
|
||||
if self.reference == 'Provisional' and line.description == 'Pro forma':
|
||||
entries.append((lot, line, padding, False))
|
||||
elif self.reference == 'Final' and line.description == 'Final':
|
||||
provisional_line = getattr(lot, 'sale_invoice_line_prov', None)
|
||||
if provisional_line:
|
||||
entries.append((lot, provisional_line, padding, True))
|
||||
return entries
|
||||
|
||||
def _get_sale_padding_move_lines(self, entries):
|
||||
MoveLine = Pool().get('account.move.line')
|
||||
sale_account, accrual_account = self._get_sale_padding_accounts()
|
||||
|
||||
move_lines = []
|
||||
for lot, invoice_line, padding, reversal in entries:
|
||||
padding_amount, company_amount = self._get_padding_company_amount(
|
||||
invoice_line, padding)
|
||||
if not company_amount:
|
||||
continue
|
||||
|
||||
sale_line = MoveLine()
|
||||
accrual_line = MoveLine()
|
||||
for move_line in (sale_line, accrual_line):
|
||||
move_line.lot = lot
|
||||
move_line.origin = invoice_line
|
||||
if move_line.account and move_line.account.party_required:
|
||||
move_line.party = self.party
|
||||
|
||||
if not reversal:
|
||||
sale_line.account = sale_account
|
||||
sale_line.debit = company_amount
|
||||
sale_line.credit = Decimal(0)
|
||||
accrual_line.account = accrual_account
|
||||
accrual_line.debit = Decimal(0)
|
||||
accrual_line.credit = company_amount
|
||||
else:
|
||||
accrual_line.account = accrual_account
|
||||
accrual_line.debit = company_amount
|
||||
accrual_line.credit = Decimal(0)
|
||||
sale_line.account = sale_account
|
||||
sale_line.debit = Decimal(0)
|
||||
sale_line.credit = company_amount
|
||||
|
||||
if self.currency != self.company.currency:
|
||||
sale_line.second_currency = self.currency
|
||||
accrual_line.second_currency = self.currency
|
||||
sale_line.amount_second_currency = padding_amount.copy_sign(
|
||||
sale_line.debit - sale_line.credit)
|
||||
accrual_line.amount_second_currency = padding_amount.copy_sign(
|
||||
accrual_line.debit - accrual_line.credit)
|
||||
|
||||
if sale_line.account.party_required:
|
||||
sale_line.party = self.party
|
||||
if accrual_line.account.party_required:
|
||||
accrual_line.party = self.party
|
||||
move_lines.extend([sale_line, accrual_line])
|
||||
return move_lines
|
||||
|
||||
def _create_sale_padding_moves(self):
|
||||
pool = Pool()
|
||||
Move = pool.get('account.move')
|
||||
Period = pool.get('account.period')
|
||||
Date = pool.get('ir.date')
|
||||
|
||||
entries = self._get_sale_padding_entries()
|
||||
if not entries:
|
||||
return []
|
||||
reversal = entries[0][3]
|
||||
if self._has_sale_padding_move(reversal):
|
||||
return []
|
||||
|
||||
move_lines = self._get_sale_padding_move_lines(entries)
|
||||
if not move_lines:
|
||||
return []
|
||||
|
||||
with Transaction().set_context(company=self.company.id):
|
||||
today = Date.today()
|
||||
accounting_date = self.accounting_date or self.invoice_date or today
|
||||
period = Period.find(self.company, date=accounting_date)
|
||||
move = Move()
|
||||
move.journal = self.journal
|
||||
move.period = period
|
||||
move.date = accounting_date
|
||||
move.origin = self
|
||||
move.company = self.company
|
||||
move.description = self._get_sale_padding_move_description(reversal)
|
||||
move.lines = move_lines
|
||||
Move.save([move])
|
||||
self.additional_moves = tuple(self.additional_moves or ()) + (move,)
|
||||
return [move]
|
||||
|
||||
@staticmethod
|
||||
def _format_report_number(value, digits='0.0000', keep_trailing_decimal=False,
|
||||
strip_trailing_zeros=True):
|
||||
|
||||
@@ -106,7 +106,7 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||
lot = fields.Many2One('lot.lot',"Lot")
|
||||
fee = fields.Many2One('fee.fee',"Fee")
|
||||
included_padding = fields.Function(
|
||||
fields.Numeric("Included padding", digits=(1, 5)),
|
||||
fields.Numeric("Inc. padding", digits=(1, 5)),
|
||||
'get_included_padding')
|
||||
|
||||
def get_included_padding(self, name):
|
||||
|
||||
10
modules/purchase_trade/view/account_configuration_form.xml
Normal file
10
modules/purchase_trade/view/account_configuration_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/form/field[@name='default_category_account_revenue']"
|
||||
position="after">
|
||||
<label name="default_sale_padding_account"/>
|
||||
<field name="default_sale_padding_account"/>
|
||||
<label name="default_accrual_padding_account"/>
|
||||
<field name="default_accrual_padding_account"/>
|
||||
</xpath>
|
||||
</data>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/tree/field[@name='quantity']" position="after">
|
||||
<field name="included_padding" optional="1"/>
|
||||
<field name="included_padding" symbol="unit"/>
|
||||
</xpath>
|
||||
</data>
|
||||
|
||||
@@ -94,3 +94,19 @@ Scope: templates Relatorio + ponts `report_*` Python.
|
||||
- `operator` filtre sur `OPERATOR`.
|
||||
- les quotas/pricings doivent fallback sur `quantity` si `quantity_theorical` est vide.
|
||||
- `sale.line` / `purchase.line`: en mode `basis`, si aucun `price_component` n'est defini, le prix et la progression doivent remonter depuis la ligne `Summary` / `pricing.summary` sans component.
|
||||
|
||||
## 6) Session 2026-04-26 - Padding facture provisoire vente
|
||||
|
||||
- Le wizard `lot.invoice` peut appliquer un padding global uniquement sur les
|
||||
factures provisoires cote vente.
|
||||
- Le padding global est reparti par lot et conserve sur `lot.lot` dans
|
||||
`sale_invoice_padding`.
|
||||
- La ligne facture affiche ce montant via `Inc. padding`, avec la meme unite que
|
||||
`Quantity`.
|
||||
- La quantite facturee est augmentee du padding, mais la quantite physique du
|
||||
lot ne doit pas changer.
|
||||
- Au `Validate` d'une facture, le move principal inclut deja le padding car il
|
||||
est integre dans `account.invoice.line.quantity`.
|
||||
- Les ecritures comptables specifiques au padding restent a definir; point
|
||||
d'entree documente:
|
||||
`modules/purchase_trade/docs/padding-invoice-accounting.md`.
|
||||
|
||||
Reference in New Issue
Block a user