This commit is contained in:
2026-04-09 19:46:08 +02:00
parent 5ae8af84fb
commit a1ab7dec82
11 changed files with 669 additions and 118 deletions

View File

@@ -485,7 +485,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
}) })
cls.__rpc__.update({ cls.__rpc__.update({
'post': RPC( 'post': RPC(
readonly=False, instantiate=0, fresh_session=True), readonly=False, instantiate=0, fresh_session=False),
}) })
@classmethod @classmethod
@@ -1896,11 +1896,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
moves = [] moves = []
for invoice in invoices: for invoice in invoices:
if invoice.type == 'in': move = invoice.get_move()
move = invoice.get_move() if move != invoice.move:
if move != invoice.move: invoice.move = move
invoice.move = move moves.append(move)
moves.append(move)
invoice.do_lot_invoicing() invoice.do_lot_invoicing()
if moves: if moves:
Move.save(moves) Move.save(moves)

View File

@@ -4059,7 +4059,7 @@
<text:p text:style-name="P13">Controller Name</text:p> <text:p text:style-name="P13">Controller Name</text:p>
</table:table-cell> </table:table-cell>
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string"> <table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_number&gt;</text:placeholder></text:p> <text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_reference&gt;</text:placeholder></text:p>
<text:p text:style-name="P25"/> <text:p text:style-name="P25"/>
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p> <text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p>
</table:table-cell> </table:table-cell>

View File

@@ -3956,7 +3956,7 @@
</table:table-row> </table:table-row>
<table:table-row table:style-name="Tableau6.1"> <table:table-row table:style-name="Tableau6.1">
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string"> <table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;invoice.report_nb_bale&gt;</text:placeholder><text:s/></text:p> <text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;invoice.report_cndn_nb_bale&gt;</text:placeholder><text:s/></text:p>
</table:table-cell> </table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string"> <table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p> <text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
@@ -4044,7 +4044,7 @@
<text:p text:style-name="P13">Controller Name</text:p> <text:p text:style-name="P13">Controller Name</text:p>
</table:table-cell> </table:table-cell>
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string"> <table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_number&gt;</text:placeholder></text:p> <text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_reference&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"/> <text:p text:style-name="P26"/>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p> <text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p>
</table:table-cell> </table:table-cell>

View File

@@ -3,6 +3,7 @@
import datetime import datetime
from decimal import Decimal from decimal import Decimal
from unittest.mock import Mock, patch
from trytond.modules.account_invoice.exceptions import ( from trytond.modules.account_invoice.exceptions import (
PaymentTermValidationError) PaymentTermValidationError)
@@ -251,5 +252,43 @@ class AccountInvoiceTestCase(
(datetime.date(2012, 1, 14), Decimal('-1.0')), (datetime.date(2012, 1, 14), Decimal('-1.0')),
]) ])
def test_post_rpc_does_not_require_fresh_session(self):
'posting invoices does not force a fresh session'
Invoice = Pool().get('account.invoice')
self.assertFalse(Invoice.__rpc__['post'].fresh_session)
@with_transaction()
def test_validate_invoice_creates_move_for_customer_invoice(self):
'validating customer invoices now creates the account move'
Invoice = Pool().get('account.invoice')
move = Mock()
invoice = Invoice()
invoice.type = 'out'
invoice.move = None
invoice.get_move = Mock(return_value=move)
invoice.do_lot_invoicing = Mock()
move_model = Mock()
with patch.object(Invoice, '_check_taxes'), patch.object(
Invoice, '_store_cache'), patch.object(
Invoice, 'browse', return_value=[]), patch.object(
Invoice, 'cleanMoves') as clean_moves, patch.object(
Invoice, 'save') as save_invoices, patch(
'trytond.modules.account_invoice.invoice.Pool'
) as PoolMock:
PoolMock.return_value.get.return_value = move_model
Invoice.validate_invoice([invoice])
self.assertIs(invoice.move, move)
invoice.get_move.assert_called_once_with()
invoice.do_lot_invoicing.assert_called_once_with()
move_model.save.assert_called_once_with([move])
clean_moves.assert_called_once_with([move])
save_invoices.assert_called()
del ModuleTestCase del ModuleTestCase

View File

@@ -39,6 +39,98 @@ class Invoice(metaclass=PoolMeta):
] ]
return lines or list(self.lines or []) return lines or list(self.lines or [])
@staticmethod
def _get_report_related_lots(line):
lots = []
seen = set()
def add_lot(lot):
if not lot:
return
lot_id = getattr(lot, 'id', None)
key = ('id', lot_id) if lot_id is not None else ('obj', id(lot))
if key in seen:
return
seen.add(key)
lots.append(lot)
add_lot(getattr(line, 'lot', None))
origin = getattr(line, 'origin', None)
for lot in getattr(origin, 'lots', []) or []:
add_lot(lot)
return lots
@classmethod
def _get_report_preferred_lots(cls, line):
lots = cls._get_report_related_lots(line)
physicals = [
lot for lot in lots
if getattr(lot, 'lot_type', None) == 'physic'
]
if physicals:
return physicals
virtuals = [
lot for lot in lots
if getattr(lot, 'lot_type', None) == 'virtual'
]
if len(virtuals) == 1:
return virtuals
return []
@staticmethod
def _get_report_line_sign(line):
quantity = Decimal(str(getattr(line, 'quantity', 0) or 0))
return Decimal(-1) if quantity < 0 else Decimal(1)
@staticmethod
def _get_report_lot_hist_weights(lot):
if not lot:
return None, None
if hasattr(lot, 'get_hist_quantity'):
net, gross = lot.get_hist_quantity()
return (
Decimal(str(net or 0)),
Decimal(str(gross if gross not in (None, '') else net or 0)),
)
hist = list(getattr(lot, 'lot_hist', []) or [])
state = getattr(lot, 'lot_state', None)
state_id = getattr(state, 'id', None)
if state_id is not None:
for entry in hist:
quantity_type = getattr(entry, 'quantity_type', None)
if getattr(quantity_type, 'id', None) == state_id:
net = Decimal(str(getattr(entry, 'quantity', 0) or 0))
gross = Decimal(str(
getattr(entry, 'gross_quantity', None)
if getattr(entry, 'gross_quantity', None) not in (None, '')
else net))
return net, gross
return None, None
def _get_report_invoice_line_weights(self, line):
lots = self._get_report_preferred_lots(line)
if lots:
sign = self._get_report_line_sign(line)
net_total = Decimal(0)
gross_total = Decimal(0)
for lot in lots:
net, gross = self._get_report_lot_hist_weights(lot)
if net is None:
continue
net_total += net
gross_total += gross
if net_total or gross_total:
return net_total * sign, gross_total * sign
quantity = Decimal(str(getattr(line, 'quantity', 0) or 0))
return quantity, quantity
@staticmethod
def _get_report_invoice_line_unit(line):
lots = Invoice._get_report_preferred_lots(line)
if lots and getattr(lots[0], 'lot_unit_line', None):
return lots[0].lot_unit_line
return getattr(line, 'unit', None)
@staticmethod @staticmethod
def _clean_report_description(value): def _clean_report_description(value):
text = (value or '').strip() text = (value or '').strip()
@@ -81,6 +173,35 @@ class Invoice(metaclass=PoolMeta):
return lot return lot
return line.lots[0] return line.lots[0]
@staticmethod
def _get_report_lot_shipment(lot):
if not lot:
return None
return (
getattr(lot, 'lot_shipment_in', None)
or getattr(lot, 'lot_shipment_out', None)
or getattr(lot, 'lot_shipment_internal', None)
)
def _get_report_invoice_shipments(self):
shipments = []
seen = set()
for line in self._get_report_invoice_lines():
for lot in self._get_report_preferred_lots(line):
shipment = self._get_report_lot_shipment(lot)
if not shipment:
continue
shipment_id = getattr(shipment, 'id', None)
key = (
getattr(shipment, '__name__', None),
shipment_id if shipment_id is not None else id(shipment),
)
if key in seen:
continue
seen.add(key)
shipments.append(shipment)
return shipments
def _get_report_invoice_lots(self): def _get_report_invoice_lots(self):
invoice_lines = self._get_report_invoice_lines() invoice_lines = self._get_report_invoice_lines()
if not invoice_lines: if not invoice_lines:
@@ -140,14 +261,13 @@ class Invoice(metaclass=PoolMeta):
return fees[0] if fees else None return fees[0] if fees else None
def _get_report_shipment(self): def _get_report_shipment(self):
lot = self._get_report_lot() shipments = self._get_report_invoice_shipments()
if not lot: if len(shipments) == 1:
return shipments[0]
if len(shipments) > 1:
return None return None
return ( lot = self._get_report_lot()
getattr(lot, 'lot_shipment_in', None) return self._get_report_lot_shipment(lot)
or getattr(lot, 'lot_shipment_out', None)
or getattr(lot, 'lot_shipment_internal', None)
)
@staticmethod @staticmethod
def _get_report_bank_account(party): def _get_report_bank_account(party):
@@ -390,16 +510,14 @@ class Invoice(metaclass=PoolMeta):
def report_quantity_lines(self): def report_quantity_lines(self):
details = [] details = []
for line in self._get_report_invoice_lines(): for line in self._get_report_invoice_lines():
quantity = getattr(line, 'report_net', '') quantity, _ = self._get_report_invoice_line_weights(line)
if quantity == '':
quantity = getattr(line, 'quantity', '')
if quantity == '': if quantity == '':
continue continue
quantity_text = self._format_report_number( quantity_text = self._format_report_number(
quantity, keep_trailing_decimal=True) quantity, keep_trailing_decimal=True)
unit = getattr(line, 'unit', None) unit = self._get_report_invoice_line_unit(line)
unit_name = unit.rec_name.upper() if unit and unit.rec_name else '' unit_name = unit.rec_name.upper() if unit and unit.rec_name else ''
lbs = getattr(line, 'report_lbs', '') lbs = round(Decimal(quantity) * Decimal('2204.62'), 2)
parts = [quantity_text, unit_name] parts = [quantity_text, unit_name]
if lbs != '': if lbs != '':
parts.append( parts.append(
@@ -586,11 +704,18 @@ class Invoice(metaclass=PoolMeta):
return 'NB BALES: ' + str(int(nb_bale)) return 'NB BALES: ' + str(int(nb_bale))
return '' return ''
@property
def report_cndn_nb_bale(self):
nb_bale = self.report_nb_bale
if nb_bale == 'NB BALES: 0':
return 'Unchanged'
return nb_bale
@property @property
def report_gross(self): def report_gross(self):
if self.lines: if self.lines:
return sum( return sum(
Decimal(str(getattr(line, 'quantity', 0) or 0)) self._get_report_invoice_line_weights(line)[1]
for line in self._get_report_invoice_lines()) for line in self._get_report_invoice_lines())
line = self._get_report_trade_line() line = self._get_report_trade_line()
if line and line.lots: if line and line.lots:
@@ -604,7 +729,7 @@ class Invoice(metaclass=PoolMeta):
def report_net(self): def report_net(self):
if self.lines: if self.lines:
return sum( return sum(
Decimal(str(getattr(line, 'quantity', 0) or 0)) self._get_report_invoice_line_weights(line)[0]
for line in self._get_report_invoice_lines()) for line in self._get_report_invoice_lines())
line = self._get_report_trade_line() line = self._get_report_trade_line()
if line and line.lots: if line and line.lots:
@@ -625,8 +750,15 @@ class Invoice(metaclass=PoolMeta):
@property @property
def report_weight_unit_upper(self): def report_weight_unit_upper(self):
line = self._get_report_trade_line() or self._get_report_invoice_line() invoice_line = self._get_report_invoice_line()
unit = getattr(line, 'unit', None) if line else None unit = self._get_report_invoice_line_unit(invoice_line) if invoice_line else None
if not unit:
line = self._get_report_trade_line()
lot = self._get_report_lot()
unit = (
getattr(lot, 'lot_unit_line', None)
or getattr(line, 'unit', None) if line else None
)
if unit and unit.rec_name: if unit and unit.rec_name:
return unit.rec_name.upper() return unit.rec_name.upper()
return 'KGS' return 'KGS'
@@ -634,6 +766,16 @@ class Invoice(metaclass=PoolMeta):
@property @property
def report_note_title(self): def report_note_title(self):
total = Decimal(str(self.total_amount or 0)) total = Decimal(str(self.total_amount or 0))
invoice_type = getattr(self, 'type', None)
if not invoice_type:
if self.sales:
invoice_type = 'out'
elif self.purchases:
invoice_type = 'in'
if invoice_type == 'out':
if total < 0:
return 'Credit Note'
return 'Debit Note'
if total < 0: if total < 0:
return 'Debit Note' return 'Debit Note'
return 'Credit Note' return 'Credit Note'
@@ -721,6 +863,13 @@ class Invoice(metaclass=PoolMeta):
return shipment.number or '' return shipment.number or ''
return '' return ''
@property
def report_si_reference(self):
shipment = self._get_report_shipment()
if shipment:
return getattr(shipment, 'reference', None) or ''
return ''
@property @property
def report_freight_amount(self): def report_freight_amount(self):
fee = self._get_report_freight_fee() fee = self._get_report_freight_fee()
@@ -832,6 +981,13 @@ class InvoiceLine(metaclass=PoolMeta):
@property @property
def report_net(self): def report_net(self):
if self.type == 'line': if self.type == 'line':
lot = getattr(self, 'lot', None)
if lot:
net, _ = Invoice._get_report_lot_hist_weights(lot)
if net is None:
net = 0
sign = Invoice._get_report_line_sign(self)
return Decimal(str(net or 0)) * sign
return self.quantity return self.quantity
return '' return ''

View File

@@ -1334,11 +1334,12 @@ class LotQt(
Case((lp.id>0, lp.lot_premium_sale),else_=ls.lot_premium_sale).as_('r_lot_premium_sale'), Case((lp.id>0, lp.lot_premium_sale),else_=ls.lot_premium_sale).as_('r_lot_premium_sale'),
Case((lp.id>0, lp.lot_parent),else_=ls.lot_parent).as_('r_lot_parent'), Case((lp.id>0, lp.lot_parent),else_=ls.lot_parent).as_('r_lot_parent'),
Case((lp.id>0, lp.lot_himself),else_=ls.lot_himself).as_('r_lot_himself'), Case((lp.id>0, lp.lot_himself),else_=ls.lot_himself).as_('r_lot_himself'),
Case((lp.id>0, lp.lot_container),else_=ls.lot_container).as_('r_lot_container'), Case((lp.id>0, lp.lot_container),else_=ls.lot_container).as_('r_lot_container'),
Case((lp.id>0, lp.line),else_=None).as_('r_line'), Case((lp.id>0, lp.line),else_=None).as_('r_line'),
Case((pu.id>0, pu.id),else_=None).as_('r_purchase'), Case((pl.id>0, pl.del_period),else_=None).as_('r_del_period'),
Case((sa.id>0, sa.id),else_=None).as_('r_sale'), Case((pu.id>0, pu.id),else_=None).as_('r_purchase'),
Case((ls.id>0, ls.sale_line),else_=None).as_('r_sale_line'), Case((sa.id>0, sa.id),else_=None).as_('r_sale'),
Case((ls.id>0, ls.sale_line),else_=None).as_('r_sale_line'),
(MaQt + AvQt).as_('r_tot'), (MaQt + AvQt).as_('r_tot'),
pu.party.as_('r_supplier'), pu.party.as_('r_supplier'),
sa.party.as_('r_client'), sa.party.as_('r_client'),
@@ -1439,13 +1440,14 @@ class LotQt(
lp.lot_av.as_("r_lot_av"), lp.lot_av.as_("r_lot_av"),
lp.lot_premium.as_("r_lot_premium"), lp.lot_premium.as_("r_lot_premium"),
lp.lot_premium_sale.as_("r_lot_premium_sale"), lp.lot_premium_sale.as_("r_lot_premium_sale"),
lp.lot_parent.as_("r_lot_parent"), lp.lot_parent.as_("r_lot_parent"),
lp.lot_himself.as_("r_lot_himself"), lp.lot_himself.as_("r_lot_himself"),
lp.lot_container.as_("r_lot_container"), lp.lot_container.as_("r_lot_container"),
lp.line.as_("r_line"), lp.line.as_("r_line"),
Case((pu.id > 0, pu.id), else_=None).as_("r_purchase"), pl.del_period.as_("r_del_period"),
Case((sa.id > 0, sa.id), else_=None).as_("r_sale"), Case((pu.id > 0, pu.id), else_=None).as_("r_purchase"),
lp.sale_line.as_("r_sale_line"), Case((sa.id > 0, sa.id), else_=None).as_("r_sale"),
lp.sale_line.as_("r_sale_line"),
(MaQt2 + Abs(AvQt2)).as_("r_tot"), (MaQt2 + Abs(AvQt2)).as_("r_tot"),
pu.party.as_("r_supplier"), pu.party.as_("r_supplier"),
sa.party.as_("r_client"), sa.party.as_("r_client"),
@@ -1504,13 +1506,14 @@ class LotQt(
Max(lp.lot_av).as_("r_lot_av"), Max(lp.lot_av).as_("r_lot_av"),
Avg(lp.lot_premium).as_("r_lot_premium"), Avg(lp.lot_premium).as_("r_lot_premium"),
Literal(None).as_("r_lot_premium_sale"), Literal(None).as_("r_lot_premium_sale"),
Literal(None).as_("r_lot_parent"), Literal(None).as_("r_lot_parent"),
Literal(None).as_("r_lot_himself"), Literal(None).as_("r_lot_himself"),
Max(lp.lot_container).as_("r_lot_container"), Max(lp.lot_container).as_("r_lot_container"),
lp.line.as_("r_line"), lp.line.as_("r_line"),
Max(Case((pu.id > 0, pu.id), else_=None)).as_("r_purchase"), Max(pl.del_period).as_("r_del_period"),
Max(Case((sa.id > 0, sa.id), else_=None)).as_("r_sale"), Max(Case((pu.id > 0, pu.id), else_=None)).as_("r_purchase"),
lp.sale_line.as_("r_sale_line"), Max(Case((sa.id > 0, sa.id), else_=None)).as_("r_sale"),
lp.sale_line.as_("r_sale_line"),
Sum(MaQt2 + Abs(AvQt2)).as_("r_tot"), Sum(MaQt2 + Abs(AvQt2)).as_("r_tot"),
Max(pu.party).as_("r_supplier"), Max(pu.party).as_("r_supplier"),
Max(sa.party).as_("r_client"), Max(sa.party).as_("r_client"),
@@ -1557,13 +1560,14 @@ class LotQt(
union.r_lot_av.as_("r_lot_av"), union.r_lot_av.as_("r_lot_av"),
union.r_lot_premium.as_("r_lot_premium"), union.r_lot_premium.as_("r_lot_premium"),
union.r_lot_premium_sale.as_("r_lot_premium_sale"), union.r_lot_premium_sale.as_("r_lot_premium_sale"),
union.r_lot_parent.as_("r_lot_parent"), union.r_lot_parent.as_("r_lot_parent"),
union.r_lot_himself.as_("r_lot_himself"), union.r_lot_himself.as_("r_lot_himself"),
union.r_lot_container.as_("r_lot_container"), union.r_lot_container.as_("r_lot_container"),
union.r_line.as_("r_line"), union.r_line.as_("r_line"),
union.r_purchase.as_("r_purchase"), union.r_del_period.as_("r_del_period"),
union.r_sale.as_("r_sale"), union.r_purchase.as_("r_purchase"),
union.r_sale_line.as_("r_sale_line"), union.r_sale.as_("r_sale"),
union.r_sale_line.as_("r_sale_line"),
union.r_tot.as_("r_tot"), union.r_tot.as_("r_tot"),
union.r_supplier.as_("r_supplier"), union.r_supplier.as_("r_supplier"),
union.r_client.as_("r_client"), union.r_client.as_("r_client"),
@@ -1630,14 +1634,15 @@ class LotReport(
r_lot_shipment_out = fields.Many2One('stock.shipment.out', "Shipment Out") r_lot_shipment_out = fields.Many2One('stock.shipment.out', "Shipment Out")
r_lot_shipment_internal = fields.Many2One('stock.shipment.internal', "Shipment Internal") r_lot_shipment_internal = fields.Many2One('stock.shipment.internal', "Shipment Internal")
r_lot_move = fields.Many2One('stock.move', "Move") r_lot_move = fields.Many2One('stock.move', "Move")
r_lot_parent = fields.Many2One('lot.lot',"Parent") r_lot_parent = fields.Many2One('lot.lot',"Parent")
r_lot_himself = fields.Many2One('lot.lot',"Lot") r_lot_himself = fields.Many2One('lot.lot',"Lot")
r_lot_container = fields.Char("Container") r_lot_container = fields.Char("Container")
r_lot_unit_line = fields.Function(fields.Many2One('product.uom', "Unit"),'get_unit') r_lot_unit_line = fields.Function(fields.Many2One('product.uom', "Unit"),'get_unit')
r_lot_price = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_price') r_lot_price = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_price')
r_lot_price_sale = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_sale_price') r_lot_price_sale = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_sale_price')
r_sale_line = fields.Many2One('sale.line',"S. line") r_del_period = fields.Many2One('product.month', "Delivery Period")
r_sale = fields.Many2One('sale.sale',"Sale") r_sale_line = fields.Many2One('sale.line',"S. line")
r_sale = fields.Many2One('sale.sale',"Sale")
r_tot = fields.Numeric("Qt tot", digits='r_lot_unit_line') r_tot = fields.Numeric("Qt tot", digits='r_lot_unit_line')
r_supplier = fields.Many2One('party.party',"Supplier") r_supplier = fields.Many2One('party.party',"Supplier")
r_client = fields.Many2One('party.party',"Client") r_client = fields.Many2One('party.party',"Client")
@@ -3041,25 +3046,26 @@ class LotWeighing(Wizard):
Lot = Pool().get('lot.lot') Lot = Pool().get('lot.lot')
context = Transaction().context context = Transaction().context
ids = context.get('active_ids') ids = context.get('active_ids')
for i in ids: for i in ids:
if i > 10000000: if i > 10000000:
raise UserError("Trying to do weighing on open quantity!") raise UserError("Trying to do weighing on open quantity!")
val = {} val = {}
lot = Lot(i) lot = Lot(i)
val['lot'] = lot.id val['lot'] = lot.id
val['lot_name'] = lot.lot_name val['lot_name'] = lot.lot_name
if lot.lot_shipment_in: if lot.lot_shipment_in:
val['lot_shipment_in'] = lot.lot_shipment_in.id val['lot_shipment_in'] = lot.lot_shipment_in.id
if lot.lot_shipment_internal: if lot.lot_shipment_internal:
val['lot_shipment_internal'] = lot.lot_shipment_internal.id val['lot_shipment_internal'] = lot.lot_shipment_internal.id
if lot.lot_shipment_out: if lot.lot_shipment_out:
val['lot_shipment_out'] = lot.lot_shipment_out.id val['lot_shipment_out'] = lot.lot_shipment_out.id
val['lot_product'] = lot.lot_product.id val['lot_product'] = lot.lot_product.id
val['lot_quantity'] = lot.lot_quantity val['lot_qt'] = lot.lot_qt
val['lot_gross_quantity'] = lot.lot_gross_quantity val['lot_quantity'] = lot.lot_quantity
val['lot_unit'] = lot.lot_unit.id val['lot_gross_quantity'] = lot.lot_gross_quantity
val['lot_unit_line'] = lot.lot_unit_line.id val['lot_unit'] = lot.lot_unit.id
lot_p.append(val) val['lot_unit_line'] = lot.lot_unit_line.id
lot_p.append(val)
return { return {
'lot_p': lot_p, 'lot_p': lot_p,
} }
@@ -3074,17 +3080,18 @@ class LotWeighing(Wizard):
lhs = LotHist.search([('lot',"=",l.lot.id),('quantity_type','=',self.w.lot_state.id)]) lhs = LotHist.search([('lot',"=",l.lot.id),('quantity_type','=',self.w.lot_state.id)])
if lhs: if lhs:
lh = lhs[0] lh = lhs[0]
else: else:
lh = LotHist() lh = LotHist()
lh.lot = l.lot lh.lot = l.lot
lh.quantity_type = self.w.lot_state lh.quantity_type = self.w.lot_state
lh.quantity = round(l.lot_quantity_new,5) lh.quantity = round(l.lot_quantity_new,5)
lh.gross_quantity = round(l.lot_gross_quantity_new,5) lh.gross_quantity = round(l.lot_gross_quantity_new,5)
LotHist.save([lh]) LotHist.save([lh])
l.lot.lot_qt = l.lot_qt
if self.w.lot_update_state :
l.lot.lot_state = self.w.lot_state if self.w.lot_update_state :
Lot.save([l.lot]) l.lot.lot_state = self.w.lot_state
Lot.save([l.lot])
diff = round(Decimal(l.lot.get_current_quantity_converted() - quantity),5) diff = round(Decimal(l.lot.get_current_quantity_converted() - quantity),5)
if diff != 0 : if diff != 0 :
#need to update virtual part #need to update virtual part
@@ -3119,12 +3126,13 @@ class LotWeighingLot(ModelView):
lot_name = fields.Char("Name",readonly=True) lot_name = fields.Char("Name",readonly=True)
lot_shipment_in = fields.Many2One('stock.shipment.in',"Shipment In") lot_shipment_in = fields.Many2One('stock.shipment.in',"Shipment In")
lot_shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal") lot_shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal")
lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out") lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out")
lot_product = fields.Many2One('product.product',"Product",readonly=True) lot_product = fields.Many2One('product.product',"Product",readonly=True)
lot_quantity = fields.Numeric("Net weight",digits=(1,5),readonly=True) lot_qt = fields.Integer("Qt")
lot_gross_quantity = fields.Numeric("Gross weight",digits=(1,5),readonly=True) lot_quantity = fields.Numeric("Net weight",digits=(1,5),readonly=True)
lot_unit = fields.Many2One('product.uom',"Unit",readonly=True) lot_gross_quantity = fields.Numeric("Gross weight",digits=(1,5),readonly=True)
lot_unit_line = fields.Many2One('product.uom',"Unit",readonly=True) lot_unit = fields.Many2One('product.uom',"Unit",readonly=True)
lot_unit_line = fields.Many2One('product.uom',"Unit",readonly=True)
lot_quantity_new = fields.Numeric("New net weight",digits=(1,5)) lot_quantity_new = fields.Numeric("New net weight",digits=(1,5))
lot_gross_quantity_new = fields.Numeric("New gross weight",digits=(1,5)) lot_gross_quantity_new = fields.Numeric("New gross weight",digits=(1,5))
lot_shipment_origin = fields.Function( lot_shipment_origin = fields.Function(

View File

@@ -289,8 +289,12 @@ class Purchase(metaclass=PoolMeta):
'purchase', 'purchase',
'Analytic Dimensions' 'Analytic Dimensions'
) )
trader = fields.Many2One('party.party',"Trader") trader = fields.Many2One(
operator = fields.Many2One('party.party',"Operator") 'party.party', "Trader",
domain=[('categories.name', '=', 'TRADER')])
operator = fields.Many2One(
'party.party', "Operator",
domain=[('categories.name', '=', 'OPERATOR')])
our_reference = fields.Char("Our Reference") our_reference = fields.Char("Our Reference")
company_visible = fields.Function( company_visible = fields.Function(
fields.Boolean("Visible"), 'on_change_with_company_visible') fields.Boolean("Visible"), 'on_change_with_company_visible')

View File

@@ -253,8 +253,12 @@ class Sale(metaclass=PoolMeta):
'sale', 'sale',
'Analytic Dimensions' 'Analytic Dimensions'
) )
trader = fields.Many2One('party.party',"Trader") trader = fields.Many2One(
operator = fields.Many2One('party.party',"Operator") 'party.party', "Trader",
domain=[('categories.name', '=', 'TRADER')])
operator = fields.Many2One(
'party.party', "Operator",
domain=[('categories.name', '=', 'OPERATOR')])
our_reference = fields.Char("Our Reference") our_reference = fields.Char("Our Reference")
company_visible = fields.Function( company_visible = fields.Function(
fields.Boolean("Visible"), 'on_change_with_company_visible') fields.Boolean("Visible"), 'on_change_with_company_visible')
@@ -395,6 +399,76 @@ class Sale(metaclass=PoolMeta):
if line: if line:
return line.note return line.note
return '' return ''
@staticmethod
def _get_report_line_lots(line):
return list(getattr(line, 'lots', []) or [])
@classmethod
def _get_report_preferred_lots(cls, line):
lots = cls._get_report_line_lots(line)
physicals = [
lot for lot in lots
if getattr(lot, 'lot_type', None) == 'physic'
]
if physicals:
return physicals
virtuals = [
lot for lot in lots
if getattr(lot, 'lot_type', None) == 'virtual'
]
if len(virtuals) == 1:
return virtuals
return []
@staticmethod
def _get_report_lot_hist_weights(lot):
if not lot:
return None, None
if hasattr(lot, 'get_hist_quantity'):
net, gross = lot.get_hist_quantity()
return (
Decimal(str(net or 0)),
Decimal(str(gross if gross not in (None, '') else net or 0)),
)
hist = list(getattr(lot, 'lot_hist', []) or [])
state = getattr(lot, 'lot_state', None)
state_id = getattr(state, 'id', None)
if state_id is not None:
for entry in hist:
quantity_type = getattr(entry, 'quantity_type', None)
if getattr(quantity_type, 'id', None) == state_id:
net = Decimal(str(getattr(entry, 'quantity', 0) or 0))
gross = Decimal(str(
getattr(entry, 'gross_quantity', None)
if getattr(entry, 'gross_quantity', None) not in (None, '')
else net))
return net, gross
return None, None
@classmethod
def _get_report_line_weights(cls, line):
lots = cls._get_report_preferred_lots(line)
if lots:
net_total = Decimal(0)
gross_total = Decimal(0)
for lot in lots:
net, gross = cls._get_report_lot_hist_weights(lot)
if net is None:
continue
net_total += net
gross_total += gross
if net_total or gross_total:
return net_total, gross_total
quantity = Decimal(str(getattr(line, 'quantity', 0) or 0))
return quantity, quantity
@classmethod
def _get_report_line_unit(cls, line):
lots = cls._get_report_preferred_lots(line)
if lots and getattr(lots[0], 'lot_unit_line', None):
return lots[0].lot_unit_line
return getattr(line, 'unit', None)
@property @property
def report_gross(self): def report_gross(self):
@@ -402,12 +476,7 @@ class Sale(metaclass=PoolMeta):
if lines: if lines:
total = Decimal(0) total = Decimal(0)
for line in lines: for line in lines:
phys_lots = [l for l in line.lots if l.lot_type == 'physic'] total += self._get_report_line_weights(line)[1]
if phys_lots:
total += sum(Decimal(str(l.get_current_gross_quantity() or 0))
for l in phys_lots)
else:
total += Decimal(str(line.quantity or 0))
return total return total
return '' return ''
@@ -417,12 +486,7 @@ class Sale(metaclass=PoolMeta):
if lines: if lines:
total = Decimal(0) total = Decimal(0)
for line in lines: for line in lines:
phys_lots = [l for l in line.lots if l.lot_type == 'physic'] total += self._get_report_line_weights(line)[0]
if phys_lots:
total += sum(Decimal(str(l.get_current_quantity() or 0))
for l in phys_lots)
else:
total += Decimal(str(line.quantity or 0))
return total return total
return '' return ''
@@ -430,23 +494,20 @@ class Sale(metaclass=PoolMeta):
def report_total_quantity(self): def report_total_quantity(self):
lines = self._get_report_lines() lines = self._get_report_lines()
if lines: if lines:
total = sum(Decimal(str(line.quantity or 0)) for line in lines) total = sum(self._get_report_line_weights(line)[0] for line in lines)
return self._format_report_number(total, keep_trailing_decimal=True) return self._format_report_number(total, keep_trailing_decimal=True)
return '0.0' return '0.0'
@property @property
def report_quantity_unit_upper(self): def report_quantity_unit_upper(self):
line = self._get_report_first_line() line = self._get_report_first_line()
if line and line.unit: unit = self._get_report_line_unit(line) if line else None
return line.unit.rec_name.upper() if unit and unit.rec_name:
return unit.rec_name.upper()
return '' return ''
def _get_report_line_quantity(self, line): def _get_report_line_quantity(self, line):
phys_lots = [l for l in line.lots if l.lot_type == 'physic'] return self._get_report_line_weights(line)[0]
if phys_lots:
return sum(Decimal(str(l.get_current_quantity() or 0))
for l in phys_lots)
return Decimal(str(line.quantity or 0))
@property @property
def report_qt(self): def report_qt(self):
@@ -466,7 +527,11 @@ class Sale(metaclass=PoolMeta):
current_quantity = self._get_report_line_quantity(line) current_quantity = self._get_report_line_quantity(line)
quantity = self._format_report_number( quantity = self._format_report_number(
current_quantity, keep_trailing_decimal=True) current_quantity, keep_trailing_decimal=True)
unit = line.unit.rec_name.upper() if line.unit and line.unit.rec_name else '' line_unit = self._get_report_line_unit(line)
unit = (
line_unit.rec_name.upper()
if line_unit and line_unit.rec_name else ''
)
words = quantity_to_words(current_quantity) words = quantity_to_words(current_quantity)
period = line.del_period.description if getattr(line, 'del_period', None) else '' period = line.del_period.description if getattr(line, 'del_period', None) else ''
detail = ' '.join( detail = ' '.join(

View File

@@ -184,6 +184,20 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual( self.assertEqual(
PurchaseLine.default_pricing_rule(), 'Default pricing rule') PurchaseLine.default_pricing_rule(), 'Default pricing rule')
def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self):
'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories'
Sale = Pool().get('sale.sale')
Purchase = Pool().get('purchase.purchase')
self.assertEqual(
Sale.trader.domain, [('categories.name', '=', 'TRADER')])
self.assertEqual(
Sale.operator.domain, [('categories.name', '=', 'OPERATOR')])
self.assertEqual(
Purchase.trader.domain, [('categories.name', '=', 'TRADER')])
self.assertEqual(
Purchase.operator.domain, [('categories.name', '=', 'OPERATOR')])
def test_sale_line_write_updates_virtual_lot_when_theorical_qty_increases(self): def test_sale_line_write_updates_virtual_lot_when_theorical_qty_increases(self):
'sale line write increases virtual lot and open lot.qt when contractual qty grows' 'sale line write increases virtual lot and open lot.qt when contractual qty grows'
SaleLine = Pool().get('sale.line') SaleLine = Pool().get('sale.line')
@@ -841,15 +855,86 @@ class PurchaseTradeTestCase(ModuleTestCase):
'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MARCH 2026', 'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MARCH 2026',
)]) )])
def test_invoice_report_note_title_uses_total_amount_sign(self): def test_sale_report_uses_single_virtual_lot_hist_when_no_physical(self):
'final invoice title switches between credit and debit note' 'sale report uses the unique virtual lot hist when no physical lot exists'
Sale = Pool().get('sale.sale')
virtual = Mock(lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('0'),
)
line = Mock(type='line', quantity=Decimal('1000'))
line.lots = [virtual]
line.unit = Mock(rec_name='MT')
line.del_period = Mock(description='MARCH 2026')
sale = Sale()
sale.lines = [line]
self.assertEqual(sale.report_net, Decimal('930'))
self.assertEqual(sale.report_gross, Decimal('930'))
self.assertEqual(sale.report_total_quantity, '930.0')
self.assertEqual(sale.report_quantity_unit_upper, 'LBS')
self.assertEqual(
sale.report_quantity_lines,
'930.0 LBS (NINE HUNDRED AND THIRTY POUNDS) - MARCH 2026')
def test_sale_report_prefers_physical_lot_hist_over_virtual(self):
'sale report prioritizes physical lot hist values over virtual ones'
Sale = Pool().get('sale.sale')
virtual = Mock(lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('940'),
)
physical = Mock(lot_type='physic', lot_unit_line=Mock(rec_name='LBS'))
physical.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
line = Mock(type='line', quantity=Decimal('1000'))
line.lots = [virtual, physical]
line.unit = Mock(rec_name='MT')
line.del_period = Mock(description='MARCH 2026')
sale = Sale()
sale.lines = [line]
self.assertEqual(sale.report_net, Decimal('950'))
self.assertEqual(sale.report_gross, Decimal('980'))
self.assertEqual(sale.report_total_quantity, '950.0')
self.assertEqual(sale.report_quantity_unit_upper, 'LBS')
self.assertEqual(
sale.report_quantity_lines,
'950.0 LBS (NINE HUNDRED AND FIFTY POUNDS) - MARCH 2026')
def test_invoice_report_note_title_uses_sale_direction(self):
'sale final note title is inverted from the raw amount sign'
Invoice = Pool().get('account.invoice')
debit = Invoice()
debit.type = 'out'
debit.total_amount = Decimal('10')
self.assertEqual(debit.report_note_title, 'Debit Note')
credit = Invoice()
credit.type = 'out'
credit.total_amount = Decimal('-10')
self.assertEqual(credit.report_note_title, 'Credit Note')
def test_invoice_report_note_title_keeps_inverse_for_purchase(self):
'purchase final note title keeps the opposite mapping'
Invoice = Pool().get('account.invoice') Invoice = Pool().get('account.invoice')
credit = Invoice() credit = Invoice()
credit.type = 'in'
credit.total_amount = Decimal('10') credit.total_amount = Decimal('10')
self.assertEqual(credit.report_note_title, 'Credit Note') self.assertEqual(credit.report_note_title, 'Credit Note')
debit = Invoice() debit = Invoice()
debit.type = 'in'
debit.total_amount = Decimal('-10') debit.total_amount = Decimal('-10')
self.assertEqual(debit.report_note_title, 'Debit Note') self.assertEqual(debit.report_note_title, 'Debit Note')
@@ -864,6 +949,187 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(invoice.report_net, Decimal('800')) self.assertEqual(invoice.report_net, Decimal('800'))
def test_invoice_report_weights_use_current_lot_hist_values(self):
'invoice net and gross weights come from the current lot hist entry'
Invoice = Pool().get('account.invoice')
unit = Mock(rec_name='LBS')
lot = Mock(lot_unit_line=unit)
lot.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
line = Mock(type='line', quantity=Decimal('1000'), lot=lot, unit=Mock(rec_name='MT'))
invoice = Invoice()
invoice.lines = [line]
self.assertEqual(invoice.report_net, Decimal('950'))
self.assertEqual(invoice.report_gross, Decimal('980'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
self.assertEqual(
invoice.report_quantity_lines,
'950.0 LBS (2094389.00 LBS)')
def test_invoice_report_weights_keep_line_sign_with_lot_hist_values(self):
'invoice lot hist values keep the invoice line sign for final notes'
Invoice = Pool().get('account.invoice')
positive_lot = Mock(lot_unit_line=Mock(rec_name='LBS'))
positive_lot.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
negative_lot = Mock(lot_unit_line=Mock(rec_name='LBS'))
negative_lot.get_hist_quantity.return_value = (
Decimal('150'),
Decimal('160'),
)
positive = Mock(type='line', quantity=Decimal('1000'), lot=positive_lot)
negative = Mock(type='line', quantity=Decimal('-200'), lot=negative_lot)
invoice = Invoice()
invoice.lines = [positive, negative]
self.assertEqual(invoice.report_net, Decimal('800'))
self.assertEqual(invoice.report_gross, Decimal('820'))
def test_invoice_report_weights_use_single_virtual_lot_when_no_physical(self):
'invoice uses the unique virtual lot hist when no physical lot exists'
Invoice = Pool().get('account.invoice')
virtual = Mock(id=1, lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('0'),
)
origin = Mock(lots=[virtual])
line = Mock(
type='line',
quantity=Decimal('1000'),
lot=None,
origin=origin,
unit=Mock(rec_name='MT'),
)
invoice = Invoice()
invoice.lines = [line]
self.assertEqual(invoice.report_net, Decimal('930'))
self.assertEqual(invoice.report_gross, Decimal('930'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
def test_invoice_report_weights_prefer_physical_lots_over_virtual(self):
'invoice uses physical lot hist values whenever physical lots exist'
Invoice = Pool().get('account.invoice')
virtual = Mock(id=1, lot_type='virtual', lot_unit_line=Mock(rec_name='LBS'))
virtual.get_hist_quantity.return_value = (
Decimal('930'),
Decimal('940'),
)
physical = Mock(id=2, lot_type='physic', lot_unit_line=Mock(rec_name='LBS'))
physical.get_hist_quantity.return_value = (
Decimal('950'),
Decimal('980'),
)
origin = Mock(lots=[virtual, physical])
line = Mock(
type='line',
quantity=Decimal('1000'),
lot=virtual,
origin=origin,
unit=Mock(rec_name='MT'),
)
invoice = Invoice()
invoice.lines = [line]
self.assertEqual(invoice.report_net, Decimal('950'))
self.assertEqual(invoice.report_gross, Decimal('980'))
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
def test_invoice_report_shipment_uses_invoice_line_lot_not_first_trade_line(self):
'invoice shipment info comes from the lots linked to the invoiced line'
Invoice = Pool().get('account.invoice')
shipment_a = Mock(
id=1,
bl_date='2026-04-01',
bl_number='BL-A',
vessel=Mock(vessel_name='VESSEL A'),
from_location=Mock(rec_name='LOADING A'),
to_location=Mock(rec_name='DISCHARGE A'),
controller=Mock(rec_name='CTRL A'),
number='SI-A',
)
shipment_b = Mock(
id=2,
bl_date='2026-04-05',
bl_number='BL-B',
vessel=Mock(vessel_name='VESSEL B'),
from_location=Mock(rec_name='LOADING B'),
to_location=Mock(rec_name='DISCHARGE B'),
controller=Mock(rec_name='CTRL B'),
number='SI-B',
)
lot_a = Mock(id=10, lot_type='physic', lot_shipment_in=shipment_a)
lot_b = Mock(id=20, lot_type='physic', lot_shipment_in=shipment_b)
line_a = Mock(lots=[lot_a])
line_b = Mock(lots=[lot_b])
purchase = Mock(lines=[line_a, line_b])
invoice_line = Mock(type='line', lot=lot_b, origin=line_b)
invoice = Invoice()
invoice.purchases = [purchase]
invoice.lines = [invoice_line]
self.assertEqual(invoice.report_bl_nb, 'BL-B')
self.assertEqual(invoice.report_bl_date, '2026-04-05')
self.assertEqual(invoice.report_vessel, 'VESSEL B')
self.assertEqual(invoice.report_loading_port, 'LOADING B')
self.assertEqual(invoice.report_discharge_port, 'DISCHARGE B')
self.assertEqual(invoice.report_controller_name, 'CTRL B')
self.assertEqual(invoice.report_si_number, 'SI-B')
self.assertEqual(invoice.report_si_reference, 'REF-B')
def test_invoice_report_shipment_is_blank_if_invoice_mixes_shipments(self):
'invoice shipment fields stay empty when multiple shipments are invoiced together'
Invoice = Pool().get('account.invoice')
shipment_a = Mock(
id=1,
bl_date='2026-04-01',
bl_number='BL-A',
vessel=Mock(vessel_name='VESSEL A'),
from_location=Mock(rec_name='LOADING A'),
to_location=Mock(rec_name='DISCHARGE A'),
controller=Mock(rec_name='CTRL A'),
number='SI-A',
)
shipment_b = Mock(
id=2,
bl_date='2026-04-05',
bl_number='BL-B',
reference='REF-B',
vessel=Mock(vessel_name='VESSEL B'),
from_location=Mock(rec_name='LOADING B'),
to_location=Mock(rec_name='DISCHARGE B'),
controller=Mock(rec_name='CTRL B'),
number='SI-B',
)
lot_a = Mock(id=10, lot_type='physic', lot_shipment_in=shipment_a)
lot_b = Mock(id=20, lot_type='physic', lot_shipment_in=shipment_b)
line_a = Mock(type='line', lot=lot_a, origin=Mock(lots=[lot_a]))
line_b = Mock(type='line', lot=lot_b, origin=Mock(lots=[lot_b]))
invoice = Invoice()
invoice.lines = [line_a, line_b]
self.assertIsNone(invoice.report_bl_nb)
self.assertIsNone(invoice.report_bl_date)
self.assertEqual(invoice.report_vessel, None)
self.assertEqual(invoice.report_loading_port, '')
self.assertEqual(invoice.report_discharge_port, '')
self.assertEqual(invoice.report_controller_name, '')
self.assertEqual(invoice.report_si_number, '')
self.assertEqual(invoice.report_si_reference, '')
def test_invoice_report_nb_bale_sums_signed_line_lot_quantities(self): def test_invoice_report_nb_bale_sums_signed_line_lot_quantities(self):
'invoice reports packaging from the signed sum of line lot_qt values' 'invoice reports packaging from the signed sum of line lot_qt values'
Invoice = Pool().get('account.invoice') Invoice = Pool().get('account.invoice')
@@ -876,6 +1142,18 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(invoice.report_nb_bale, 'NB BALES: 0') self.assertEqual(invoice.report_nb_bale, 'NB BALES: 0')
def test_invoice_report_cndn_nb_bale_displays_unchanged_for_zero(self):
'CN/DN bale label displays Unchanged when the signed balance is zero'
Invoice = Pool().get('account.invoice')
lot = Mock(lot_qt=Decimal('350'), lot_unit=Mock(symbol='bale'))
negative = Mock(type='line', quantity=Decimal('-1000'), lot=lot)
positive = Mock(type='line', quantity=Decimal('1000'), lot=lot)
invoice = Invoice()
invoice.lines = [negative, positive]
self.assertEqual(invoice.report_cndn_nb_bale, 'Unchanged')
def test_invoice_report_positive_rate_lines_keep_positive_components(self): def test_invoice_report_positive_rate_lines_keep_positive_components(self):
'invoice final note pricing section keeps only positive component lines' 'invoice final note pricing section keeps only positive component lines'
Invoice = Pool().get('account.invoice') Invoice = Pool().get('account.invoice')

View File

@@ -3,6 +3,7 @@
<prefix name="qt_icon"/> <prefix name="qt_icon"/>
</field> </field>
<field name="r_lot_p" width="60"/> <field name="r_lot_p" width="60"/>
<field name="r_del_period" width="110"/>
<field name="r_supplier" width="90"/> <field name="r_supplier" width="90"/>
<field name="r_purchase" width="120"/> <field name="r_purchase" width="120"/>
<field name="r_lot_pur_inv" width="120"/> <field name="r_lot_pur_inv" width="120"/>

View File

@@ -4,6 +4,7 @@
<field name="lot_shipment_origin"/> <field name="lot_shipment_origin"/>
<field name="lot_product"/> <field name="lot_product"/>
<field name="lot_unit_line"/> <field name="lot_unit_line"/>
<field name="lot_qt"/>
<field name="lot_quantity"/> <field name="lot_quantity"/>
<field name="lot_gross_quantity"/> <field name="lot_gross_quantity"/>
<field name="lot_quantity_new"/> <field name="lot_quantity_new"/>