ICT bulk
This commit is contained in:
@@ -485,7 +485,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
})
|
||||
cls.__rpc__.update({
|
||||
'post': RPC(
|
||||
readonly=False, instantiate=0, fresh_session=True),
|
||||
readonly=False, instantiate=0, fresh_session=False),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -1896,11 +1896,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
|
||||
moves = []
|
||||
for invoice in invoices:
|
||||
if invoice.type == 'in':
|
||||
move = invoice.get_move()
|
||||
if move != invoice.move:
|
||||
invoice.move = move
|
||||
moves.append(move)
|
||||
move = invoice.get_move()
|
||||
if move != invoice.move:
|
||||
invoice.move = move
|
||||
moves.append(move)
|
||||
invoice.do_lot_invoicing()
|
||||
if moves:
|
||||
Move.save(moves)
|
||||
|
||||
@@ -4059,7 +4059,7 @@
|
||||
<text:p text:style-name="P13">Controller Name</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
|
||||
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_si_number></text:placeholder></text:p>
|
||||
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_si_reference></text:placeholder></text:p>
|
||||
<text:p text:style-name="P25"/>
|
||||
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
|
||||
@@ -3956,7 +3956,7 @@
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Tableau6.1">
|
||||
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
|
||||
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text"><invoice.report_nb_bale></text:placeholder><text:s/></text:p>
|
||||
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text"><invoice.report_cndn_nb_bale></text:placeholder><text:s/></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
|
||||
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text"><format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != '' else ''></text:placeholder><text:s/></text:p>
|
||||
@@ -4044,7 +4044,7 @@
|
||||
<text:p text:style-name="P13">Controller Name</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
|
||||
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><invoice.report_si_number></text:placeholder></text:p>
|
||||
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><invoice.report_si_reference></text:placeholder></text:p>
|
||||
<text:p text:style-name="P26"/>
|
||||
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
||||
</table:table-cell>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from trytond.modules.account_invoice.exceptions import (
|
||||
PaymentTermValidationError)
|
||||
@@ -251,5 +252,43 @@ class AccountInvoiceTestCase(
|
||||
(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
|
||||
|
||||
@@ -39,6 +39,98 @@ class Invoice(metaclass=PoolMeta):
|
||||
]
|
||||
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
|
||||
def _clean_report_description(value):
|
||||
text = (value or '').strip()
|
||||
@@ -81,6 +173,35 @@ class Invoice(metaclass=PoolMeta):
|
||||
return lot
|
||||
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):
|
||||
invoice_lines = self._get_report_invoice_lines()
|
||||
if not invoice_lines:
|
||||
@@ -140,14 +261,13 @@ class Invoice(metaclass=PoolMeta):
|
||||
return fees[0] if fees else None
|
||||
|
||||
def _get_report_shipment(self):
|
||||
lot = self._get_report_lot()
|
||||
if not lot:
|
||||
shipments = self._get_report_invoice_shipments()
|
||||
if len(shipments) == 1:
|
||||
return shipments[0]
|
||||
if len(shipments) > 1:
|
||||
return None
|
||||
return (
|
||||
getattr(lot, 'lot_shipment_in', None)
|
||||
or getattr(lot, 'lot_shipment_out', None)
|
||||
or getattr(lot, 'lot_shipment_internal', None)
|
||||
)
|
||||
lot = self._get_report_lot()
|
||||
return self._get_report_lot_shipment(lot)
|
||||
|
||||
@staticmethod
|
||||
def _get_report_bank_account(party):
|
||||
@@ -390,16 +510,14 @@ class Invoice(metaclass=PoolMeta):
|
||||
def report_quantity_lines(self):
|
||||
details = []
|
||||
for line in self._get_report_invoice_lines():
|
||||
quantity = getattr(line, 'report_net', '')
|
||||
if quantity == '':
|
||||
quantity = getattr(line, 'quantity', '')
|
||||
quantity, _ = self._get_report_invoice_line_weights(line)
|
||||
if quantity == '':
|
||||
continue
|
||||
quantity_text = self._format_report_number(
|
||||
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 ''
|
||||
lbs = getattr(line, 'report_lbs', '')
|
||||
lbs = round(Decimal(quantity) * Decimal('2204.62'), 2)
|
||||
parts = [quantity_text, unit_name]
|
||||
if lbs != '':
|
||||
parts.append(
|
||||
@@ -586,11 +704,18 @@ class Invoice(metaclass=PoolMeta):
|
||||
return 'NB BALES: ' + str(int(nb_bale))
|
||||
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
|
||||
def report_gross(self):
|
||||
if self.lines:
|
||||
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())
|
||||
line = self._get_report_trade_line()
|
||||
if line and line.lots:
|
||||
@@ -604,7 +729,7 @@ class Invoice(metaclass=PoolMeta):
|
||||
def report_net(self):
|
||||
if self.lines:
|
||||
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())
|
||||
line = self._get_report_trade_line()
|
||||
if line and line.lots:
|
||||
@@ -625,8 +750,15 @@ class Invoice(metaclass=PoolMeta):
|
||||
|
||||
@property
|
||||
def report_weight_unit_upper(self):
|
||||
line = self._get_report_trade_line() or self._get_report_invoice_line()
|
||||
unit = getattr(line, 'unit', None) if line else None
|
||||
invoice_line = self._get_report_invoice_line()
|
||||
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:
|
||||
return unit.rec_name.upper()
|
||||
return 'KGS'
|
||||
@@ -634,6 +766,16 @@ class Invoice(metaclass=PoolMeta):
|
||||
@property
|
||||
def report_note_title(self):
|
||||
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:
|
||||
return 'Debit Note'
|
||||
return 'Credit Note'
|
||||
@@ -721,6 +863,13 @@ class Invoice(metaclass=PoolMeta):
|
||||
return shipment.number or ''
|
||||
return ''
|
||||
|
||||
@property
|
||||
def report_si_reference(self):
|
||||
shipment = self._get_report_shipment()
|
||||
if shipment:
|
||||
return getattr(shipment, 'reference', None) or ''
|
||||
return ''
|
||||
|
||||
@property
|
||||
def report_freight_amount(self):
|
||||
fee = self._get_report_freight_fee()
|
||||
@@ -832,6 +981,13 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||
@property
|
||||
def report_net(self):
|
||||
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 ''
|
||||
|
||||
|
||||
@@ -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_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_container),else_=ls.lot_container).as_('r_lot_container'),
|
||||
Case((lp.id>0, lp.line),else_=None).as_('r_line'),
|
||||
Case((pu.id>0, pu.id),else_=None).as_('r_purchase'),
|
||||
Case((sa.id>0, sa.id),else_=None).as_('r_sale'),
|
||||
Case((ls.id>0, ls.sale_line),else_=None).as_('r_sale_line'),
|
||||
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((pl.id>0, pl.del_period),else_=None).as_('r_del_period'),
|
||||
Case((pu.id>0, pu.id),else_=None).as_('r_purchase'),
|
||||
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'),
|
||||
pu.party.as_('r_supplier'),
|
||||
sa.party.as_('r_client'),
|
||||
@@ -1439,13 +1440,14 @@ class LotQt(
|
||||
lp.lot_av.as_("r_lot_av"),
|
||||
lp.lot_premium.as_("r_lot_premium"),
|
||||
lp.lot_premium_sale.as_("r_lot_premium_sale"),
|
||||
lp.lot_parent.as_("r_lot_parent"),
|
||||
lp.lot_himself.as_("r_lot_himself"),
|
||||
lp.lot_container.as_("r_lot_container"),
|
||||
lp.line.as_("r_line"),
|
||||
Case((pu.id > 0, pu.id), else_=None).as_("r_purchase"),
|
||||
Case((sa.id > 0, sa.id), else_=None).as_("r_sale"),
|
||||
lp.sale_line.as_("r_sale_line"),
|
||||
lp.lot_parent.as_("r_lot_parent"),
|
||||
lp.lot_himself.as_("r_lot_himself"),
|
||||
lp.lot_container.as_("r_lot_container"),
|
||||
lp.line.as_("r_line"),
|
||||
pl.del_period.as_("r_del_period"),
|
||||
Case((pu.id > 0, pu.id), else_=None).as_("r_purchase"),
|
||||
Case((sa.id > 0, sa.id), else_=None).as_("r_sale"),
|
||||
lp.sale_line.as_("r_sale_line"),
|
||||
(MaQt2 + Abs(AvQt2)).as_("r_tot"),
|
||||
pu.party.as_("r_supplier"),
|
||||
sa.party.as_("r_client"),
|
||||
@@ -1504,13 +1506,14 @@ class LotQt(
|
||||
Max(lp.lot_av).as_("r_lot_av"),
|
||||
Avg(lp.lot_premium).as_("r_lot_premium"),
|
||||
Literal(None).as_("r_lot_premium_sale"),
|
||||
Literal(None).as_("r_lot_parent"),
|
||||
Literal(None).as_("r_lot_himself"),
|
||||
Max(lp.lot_container).as_("r_lot_container"),
|
||||
lp.line.as_("r_line"),
|
||||
Max(Case((pu.id > 0, pu.id), else_=None)).as_("r_purchase"),
|
||||
Max(Case((sa.id > 0, sa.id), else_=None)).as_("r_sale"),
|
||||
lp.sale_line.as_("r_sale_line"),
|
||||
Literal(None).as_("r_lot_parent"),
|
||||
Literal(None).as_("r_lot_himself"),
|
||||
Max(lp.lot_container).as_("r_lot_container"),
|
||||
lp.line.as_("r_line"),
|
||||
Max(pl.del_period).as_("r_del_period"),
|
||||
Max(Case((pu.id > 0, pu.id), else_=None)).as_("r_purchase"),
|
||||
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"),
|
||||
Max(pu.party).as_("r_supplier"),
|
||||
Max(sa.party).as_("r_client"),
|
||||
@@ -1557,13 +1560,14 @@ class LotQt(
|
||||
union.r_lot_av.as_("r_lot_av"),
|
||||
union.r_lot_premium.as_("r_lot_premium"),
|
||||
union.r_lot_premium_sale.as_("r_lot_premium_sale"),
|
||||
union.r_lot_parent.as_("r_lot_parent"),
|
||||
union.r_lot_himself.as_("r_lot_himself"),
|
||||
union.r_lot_container.as_("r_lot_container"),
|
||||
union.r_line.as_("r_line"),
|
||||
union.r_purchase.as_("r_purchase"),
|
||||
union.r_sale.as_("r_sale"),
|
||||
union.r_sale_line.as_("r_sale_line"),
|
||||
union.r_lot_parent.as_("r_lot_parent"),
|
||||
union.r_lot_himself.as_("r_lot_himself"),
|
||||
union.r_lot_container.as_("r_lot_container"),
|
||||
union.r_line.as_("r_line"),
|
||||
union.r_del_period.as_("r_del_period"),
|
||||
union.r_purchase.as_("r_purchase"),
|
||||
union.r_sale.as_("r_sale"),
|
||||
union.r_sale_line.as_("r_sale_line"),
|
||||
union.r_tot.as_("r_tot"),
|
||||
union.r_supplier.as_("r_supplier"),
|
||||
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_internal = fields.Many2One('stock.shipment.internal', "Shipment Internal")
|
||||
r_lot_move = fields.Many2One('stock.move', "Move")
|
||||
r_lot_parent = fields.Many2One('lot.lot',"Parent")
|
||||
r_lot_himself = fields.Many2One('lot.lot',"Lot")
|
||||
r_lot_container = fields.Char("Container")
|
||||
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_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_sale = fields.Many2One('sale.sale',"Sale")
|
||||
r_lot_parent = fields.Many2One('lot.lot',"Parent")
|
||||
r_lot_himself = fields.Many2One('lot.lot',"Lot")
|
||||
r_lot_container = fields.Char("Container")
|
||||
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_sale = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_sale_price')
|
||||
r_del_period = fields.Many2One('product.month', "Delivery Period")
|
||||
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_supplier = fields.Many2One('party.party',"Supplier")
|
||||
r_client = fields.Many2One('party.party',"Client")
|
||||
@@ -3041,25 +3046,26 @@ class LotWeighing(Wizard):
|
||||
Lot = Pool().get('lot.lot')
|
||||
context = Transaction().context
|
||||
ids = context.get('active_ids')
|
||||
for i in ids:
|
||||
if i > 10000000:
|
||||
raise UserError("Trying to do weighing on open quantity!")
|
||||
val = {}
|
||||
lot = Lot(i)
|
||||
val['lot'] = lot.id
|
||||
val['lot_name'] = lot.lot_name
|
||||
for i in ids:
|
||||
if i > 10000000:
|
||||
raise UserError("Trying to do weighing on open quantity!")
|
||||
val = {}
|
||||
lot = Lot(i)
|
||||
val['lot'] = lot.id
|
||||
val['lot_name'] = lot.lot_name
|
||||
if lot.lot_shipment_in:
|
||||
val['lot_shipment_in'] = lot.lot_shipment_in.id
|
||||
if lot.lot_shipment_internal:
|
||||
val['lot_shipment_internal'] = lot.lot_shipment_internal.id
|
||||
if lot.lot_shipment_out:
|
||||
val['lot_shipment_out'] = lot.lot_shipment_out.id
|
||||
val['lot_product'] = lot.lot_product.id
|
||||
val['lot_quantity'] = lot.lot_quantity
|
||||
val['lot_gross_quantity'] = lot.lot_gross_quantity
|
||||
val['lot_unit'] = lot.lot_unit.id
|
||||
val['lot_unit_line'] = lot.lot_unit_line.id
|
||||
lot_p.append(val)
|
||||
if lot.lot_shipment_out:
|
||||
val['lot_shipment_out'] = lot.lot_shipment_out.id
|
||||
val['lot_product'] = lot.lot_product.id
|
||||
val['lot_qt'] = lot.lot_qt
|
||||
val['lot_quantity'] = lot.lot_quantity
|
||||
val['lot_gross_quantity'] = lot.lot_gross_quantity
|
||||
val['lot_unit'] = lot.lot_unit.id
|
||||
val['lot_unit_line'] = lot.lot_unit_line.id
|
||||
lot_p.append(val)
|
||||
return {
|
||||
'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)])
|
||||
if lhs:
|
||||
lh = lhs[0]
|
||||
else:
|
||||
lh = LotHist()
|
||||
lh.lot = l.lot
|
||||
lh.quantity_type = self.w.lot_state
|
||||
lh.quantity = round(l.lot_quantity_new,5)
|
||||
lh.gross_quantity = round(l.lot_gross_quantity_new,5)
|
||||
LotHist.save([lh])
|
||||
|
||||
if self.w.lot_update_state :
|
||||
l.lot.lot_state = self.w.lot_state
|
||||
Lot.save([l.lot])
|
||||
else:
|
||||
lh = LotHist()
|
||||
lh.lot = l.lot
|
||||
lh.quantity_type = self.w.lot_state
|
||||
lh.quantity = round(l.lot_quantity_new,5)
|
||||
lh.gross_quantity = round(l.lot_gross_quantity_new,5)
|
||||
LotHist.save([lh])
|
||||
l.lot.lot_qt = l.lot_qt
|
||||
|
||||
if self.w.lot_update_state :
|
||||
l.lot.lot_state = self.w.lot_state
|
||||
Lot.save([l.lot])
|
||||
diff = round(Decimal(l.lot.get_current_quantity_converted() - quantity),5)
|
||||
if diff != 0 :
|
||||
#need to update virtual part
|
||||
@@ -3119,12 +3126,13 @@ class LotWeighingLot(ModelView):
|
||||
lot_name = fields.Char("Name",readonly=True)
|
||||
lot_shipment_in = fields.Many2One('stock.shipment.in',"Shipment In")
|
||||
lot_shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal")
|
||||
lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out")
|
||||
lot_product = fields.Many2One('product.product',"Product",readonly=True)
|
||||
lot_quantity = fields.Numeric("Net weight",digits=(1,5),readonly=True)
|
||||
lot_gross_quantity = fields.Numeric("Gross weight",digits=(1,5),readonly=True)
|
||||
lot_unit = fields.Many2One('product.uom',"Unit",readonly=True)
|
||||
lot_unit_line = fields.Many2One('product.uom',"Unit",readonly=True)
|
||||
lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out")
|
||||
lot_product = fields.Many2One('product.product',"Product",readonly=True)
|
||||
lot_qt = fields.Integer("Qt")
|
||||
lot_quantity = fields.Numeric("Net weight",digits=(1,5),readonly=True)
|
||||
lot_gross_quantity = fields.Numeric("Gross weight",digits=(1,5),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_gross_quantity_new = fields.Numeric("New gross weight",digits=(1,5))
|
||||
lot_shipment_origin = fields.Function(
|
||||
|
||||
@@ -289,8 +289,12 @@ class Purchase(metaclass=PoolMeta):
|
||||
'purchase',
|
||||
'Analytic Dimensions'
|
||||
)
|
||||
trader = fields.Many2One('party.party',"Trader")
|
||||
operator = fields.Many2One('party.party',"Operator")
|
||||
trader = fields.Many2One(
|
||||
'party.party', "Trader",
|
||||
domain=[('categories.name', '=', 'TRADER')])
|
||||
operator = fields.Many2One(
|
||||
'party.party', "Operator",
|
||||
domain=[('categories.name', '=', 'OPERATOR')])
|
||||
our_reference = fields.Char("Our Reference")
|
||||
company_visible = fields.Function(
|
||||
fields.Boolean("Visible"), 'on_change_with_company_visible')
|
||||
|
||||
@@ -253,8 +253,12 @@ class Sale(metaclass=PoolMeta):
|
||||
'sale',
|
||||
'Analytic Dimensions'
|
||||
)
|
||||
trader = fields.Many2One('party.party',"Trader")
|
||||
operator = fields.Many2One('party.party',"Operator")
|
||||
trader = fields.Many2One(
|
||||
'party.party', "Trader",
|
||||
domain=[('categories.name', '=', 'TRADER')])
|
||||
operator = fields.Many2One(
|
||||
'party.party', "Operator",
|
||||
domain=[('categories.name', '=', 'OPERATOR')])
|
||||
our_reference = fields.Char("Our Reference")
|
||||
company_visible = fields.Function(
|
||||
fields.Boolean("Visible"), 'on_change_with_company_visible')
|
||||
@@ -395,6 +399,76 @@ class Sale(metaclass=PoolMeta):
|
||||
if line:
|
||||
return line.note
|
||||
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
|
||||
def report_gross(self):
|
||||
@@ -402,12 +476,7 @@ class Sale(metaclass=PoolMeta):
|
||||
if lines:
|
||||
total = Decimal(0)
|
||||
for line in lines:
|
||||
phys_lots = [l for l in line.lots if l.lot_type == 'physic']
|
||||
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))
|
||||
total += self._get_report_line_weights(line)[1]
|
||||
return total
|
||||
return ''
|
||||
|
||||
@@ -417,12 +486,7 @@ class Sale(metaclass=PoolMeta):
|
||||
if lines:
|
||||
total = Decimal(0)
|
||||
for line in lines:
|
||||
phys_lots = [l for l in line.lots if l.lot_type == 'physic']
|
||||
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))
|
||||
total += self._get_report_line_weights(line)[0]
|
||||
return total
|
||||
return ''
|
||||
|
||||
@@ -430,23 +494,20 @@ class Sale(metaclass=PoolMeta):
|
||||
def report_total_quantity(self):
|
||||
lines = self._get_report_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 '0.0'
|
||||
|
||||
@property
|
||||
def report_quantity_unit_upper(self):
|
||||
line = self._get_report_first_line()
|
||||
if line and line.unit:
|
||||
return line.unit.rec_name.upper()
|
||||
unit = self._get_report_line_unit(line) if line else None
|
||||
if unit and unit.rec_name:
|
||||
return unit.rec_name.upper()
|
||||
return ''
|
||||
|
||||
def _get_report_line_quantity(self, line):
|
||||
phys_lots = [l for l in line.lots if l.lot_type == 'physic']
|
||||
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))
|
||||
return self._get_report_line_weights(line)[0]
|
||||
|
||||
@property
|
||||
def report_qt(self):
|
||||
@@ -466,7 +527,11 @@ class Sale(metaclass=PoolMeta):
|
||||
current_quantity = self._get_report_line_quantity(line)
|
||||
quantity = self._format_report_number(
|
||||
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)
|
||||
period = line.del_period.description if getattr(line, 'del_period', None) else ''
|
||||
detail = ' '.join(
|
||||
|
||||
@@ -184,6 +184,20 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(
|
||||
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):
|
||||
'sale line write increases virtual lot and open lot.qt when contractual qty grows'
|
||||
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',
|
||||
)])
|
||||
|
||||
def test_invoice_report_note_title_uses_total_amount_sign(self):
|
||||
'final invoice title switches between credit and debit note'
|
||||
def test_sale_report_uses_single_virtual_lot_hist_when_no_physical(self):
|
||||
'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')
|
||||
|
||||
credit = Invoice()
|
||||
credit.type = 'in'
|
||||
credit.total_amount = Decimal('10')
|
||||
self.assertEqual(credit.report_note_title, 'Credit Note')
|
||||
|
||||
debit = Invoice()
|
||||
debit.type = 'in'
|
||||
debit.total_amount = Decimal('-10')
|
||||
self.assertEqual(debit.report_note_title, 'Debit Note')
|
||||
|
||||
@@ -864,6 +949,187 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
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):
|
||||
'invoice reports packaging from the signed sum of line lot_qt values'
|
||||
Invoice = Pool().get('account.invoice')
|
||||
@@ -876,6 +1142,18 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
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):
|
||||
'invoice final note pricing section keeps only positive component lines'
|
||||
Invoice = Pool().get('account.invoice')
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<prefix name="qt_icon"/>
|
||||
</field>
|
||||
<field name="r_lot_p" width="60"/>
|
||||
<field name="r_del_period" width="110"/>
|
||||
<field name="r_supplier" width="90"/>
|
||||
<field name="r_purchase" width="120"/>
|
||||
<field name="r_lot_pur_inv" width="120"/>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<field name="lot_shipment_origin"/>
|
||||
<field name="lot_product"/>
|
||||
<field name="lot_unit_line"/>
|
||||
<field name="lot_qt"/>
|
||||
<field name="lot_quantity"/>
|
||||
<field name="lot_gross_quantity"/>
|
||||
<field name="lot_quantity_new"/>
|
||||
|
||||
Reference in New Issue
Block a user