ICT bulk
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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"><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:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
||||||
</table:table-cell>
|
</table:table-cell>
|
||||||
|
|||||||
@@ -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"><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: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"><format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != '' else ''></text:placeholder><text:s/></text:p>
|
<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>
|
<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"><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:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text"><invoice.report_controller_name></text:placeholder></text:p>
|
||||||
</table:table-cell>
|
</table:table-cell>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ''
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user