kg lbs
This commit is contained in:
@@ -131,6 +131,45 @@ class Invoice(metaclass=PoolMeta):
|
|||||||
return lots[0].lot_unit_line
|
return lots[0].lot_unit_line
|
||||||
return getattr(line, 'unit', None)
|
return getattr(line, 'unit', None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_report_lbs_unit():
|
||||||
|
Uom = Pool().get('product.uom')
|
||||||
|
for domain in (
|
||||||
|
[('symbol', '=', 'LBS')],
|
||||||
|
[('rec_name', '=', 'LBS')],
|
||||||
|
[('name', '=', 'LBS')],
|
||||||
|
[('symbol', '=', 'LB')],
|
||||||
|
[('rec_name', '=', 'LB')],
|
||||||
|
[('name', '=', 'LB')]):
|
||||||
|
units = Uom.search(domain, limit=1)
|
||||||
|
if units:
|
||||||
|
return units[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _convert_report_quantity_to_lbs(cls, quantity, unit):
|
||||||
|
value = Decimal(str(quantity or 0))
|
||||||
|
if value == 0:
|
||||||
|
return Decimal('0.00')
|
||||||
|
if not unit:
|
||||||
|
return (value * Decimal('2204.62')).quantize(Decimal('0.01'))
|
||||||
|
label = (
|
||||||
|
getattr(unit, 'symbol', None)
|
||||||
|
or getattr(unit, 'rec_name', None)
|
||||||
|
or getattr(unit, 'name', None)
|
||||||
|
or ''
|
||||||
|
).strip().upper()
|
||||||
|
if label in {'LBS', 'LB', 'POUND', 'POUNDS'}:
|
||||||
|
return value.quantize(Decimal('0.01'))
|
||||||
|
lbs_unit = cls._get_report_lbs_unit()
|
||||||
|
if lbs_unit:
|
||||||
|
converted = Pool().get('product.uom').compute_qty(
|
||||||
|
unit, float(value), lbs_unit) or 0
|
||||||
|
return Decimal(str(converted)).quantize(Decimal('0.01'))
|
||||||
|
if label in {'KG', 'KGS', 'KILOGRAM', 'KILOGRAMS'}:
|
||||||
|
return (value * Decimal('2.20462')).quantize(Decimal('0.01'))
|
||||||
|
return (value * Decimal('2204.62')).quantize(Decimal('0.01'))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _clean_report_description(value):
|
def _clean_report_description(value):
|
||||||
text = (value or '').strip()
|
text = (value or '').strip()
|
||||||
@@ -517,7 +556,7 @@ class Invoice(metaclass=PoolMeta):
|
|||||||
quantity, keep_trailing_decimal=True)
|
quantity, keep_trailing_decimal=True)
|
||||||
unit = self._get_report_invoice_line_unit(line)
|
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 = round(Decimal(quantity) * Decimal('2204.62'), 2)
|
lbs = self._convert_report_quantity_to_lbs(quantity, unit)
|
||||||
parts = [quantity_text, unit_name]
|
parts = [quantity_text, unit_name]
|
||||||
if lbs != '':
|
if lbs != '':
|
||||||
parts.append(
|
parts.append(
|
||||||
@@ -746,7 +785,9 @@ class Invoice(metaclass=PoolMeta):
|
|||||||
net = self.report_net
|
net = self.report_net
|
||||||
if net == '':
|
if net == '':
|
||||||
return ''
|
return ''
|
||||||
return round(Decimal(net) * Decimal('2204.62'),2)
|
invoice_line = self._get_report_invoice_line()
|
||||||
|
unit = self._get_report_invoice_line_unit(invoice_line) if invoice_line else None
|
||||||
|
return self._convert_report_quantity_to_lbs(net, unit)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def report_weight_unit_upper(self):
|
def report_weight_unit_upper(self):
|
||||||
@@ -996,7 +1037,8 @@ class InvoiceLine(metaclass=PoolMeta):
|
|||||||
net = self.report_net
|
net = self.report_net
|
||||||
if net == '':
|
if net == '':
|
||||||
return ''
|
return ''
|
||||||
return round(Decimal(net) * Decimal('2204.62'),2)
|
unit = Invoice._get_report_invoice_line_unit(self)
|
||||||
|
return Invoice._convert_report_quantity_to_lbs(net, unit)
|
||||||
|
|
||||||
|
|
||||||
class ReportTemplateMixin:
|
class ReportTemplateMixin:
|
||||||
|
|||||||
@@ -473,32 +473,108 @@ class Sale(metaclass=PoolMeta):
|
|||||||
if lots and getattr(lots[0], 'lot_unit_line', None):
|
if lots and getattr(lots[0], 'lot_unit_line', None):
|
||||||
return lots[0].lot_unit_line
|
return lots[0].lot_unit_line
|
||||||
return getattr(line, 'unit', None)
|
return getattr(line, 'unit', None)
|
||||||
|
|
||||||
|
def _get_report_total_unit(self):
|
||||||
|
virtual_units = []
|
||||||
|
for line in self._get_report_lines():
|
||||||
|
for lot in self._get_report_line_lots(line):
|
||||||
|
if (
|
||||||
|
getattr(lot, 'lot_type', None) == 'virtual'
|
||||||
|
and getattr(lot, 'lot_unit_line', None)):
|
||||||
|
virtual_units.append(lot.lot_unit_line)
|
||||||
|
if len(virtual_units) == 1:
|
||||||
|
return virtual_units[0]
|
||||||
|
line = self._get_report_first_line()
|
||||||
|
if line:
|
||||||
|
return self._get_report_line_unit(line)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_report_unit_wording(unit):
|
||||||
|
label = ''
|
||||||
|
for attr in ('symbol', 'rec_name', 'name'):
|
||||||
|
value = getattr(unit, attr, None)
|
||||||
|
if value:
|
||||||
|
label = str(value).strip().upper()
|
||||||
|
break
|
||||||
|
mapping = {
|
||||||
|
'MT': ('METRIC TON', 'METRIC TONS'),
|
||||||
|
'METRIC TON': ('METRIC TON', 'METRIC TONS'),
|
||||||
|
'METRIC TONS': ('METRIC TON', 'METRIC TONS'),
|
||||||
|
'KG': ('KILOGRAM', 'KILOGRAMS'),
|
||||||
|
'KGS': ('KILOGRAM', 'KILOGRAMS'),
|
||||||
|
'KILOGRAM': ('KILOGRAM', 'KILOGRAMS'),
|
||||||
|
'KILOGRAMS': ('KILOGRAM', 'KILOGRAMS'),
|
||||||
|
'LB': ('POUND', 'POUNDS'),
|
||||||
|
'LBS': ('POUND', 'POUNDS'),
|
||||||
|
'POUND': ('POUND', 'POUNDS'),
|
||||||
|
'POUNDS': ('POUND', 'POUNDS'),
|
||||||
|
'BALE': ('BALE', 'BALES'),
|
||||||
|
'BALES': ('BALE', 'BALES'),
|
||||||
|
}
|
||||||
|
if label in mapping:
|
||||||
|
return mapping[label]
|
||||||
|
if label.endswith('S') and len(label) > 1:
|
||||||
|
return label[:-1], label
|
||||||
|
return label, label
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _report_quantity_to_words(cls, quantity, unit):
|
||||||
|
singular, plural = cls._get_report_unit_wording(unit)
|
||||||
|
return quantity_to_words(
|
||||||
|
quantity,
|
||||||
|
unit_singular=singular,
|
||||||
|
unit_plural=plural,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_report_quantity(quantity, from_unit, to_unit):
|
||||||
|
value = Decimal(str(quantity or 0))
|
||||||
|
if not from_unit or not to_unit:
|
||||||
|
return value
|
||||||
|
if getattr(from_unit, 'id', None) == getattr(to_unit, 'id', None):
|
||||||
|
return value
|
||||||
|
from_name = getattr(from_unit, 'rec_name', None)
|
||||||
|
to_name = getattr(to_unit, 'rec_name', None)
|
||||||
|
if from_name and to_name and from_name == to_name:
|
||||||
|
return value
|
||||||
|
Uom = Pool().get('product.uom')
|
||||||
|
converted = Uom.compute_qty(from_unit, float(value), to_unit) or 0
|
||||||
|
return Decimal(str(converted))
|
||||||
|
|
||||||
|
def _get_report_total_weight(self, index):
|
||||||
|
lines = self._get_report_lines()
|
||||||
|
if not lines:
|
||||||
|
return None
|
||||||
|
total_unit = self._get_report_total_unit()
|
||||||
|
total = Decimal(0)
|
||||||
|
for line in lines:
|
||||||
|
quantity = self._get_report_line_weights(line)[index]
|
||||||
|
total += self._convert_report_quantity(
|
||||||
|
quantity,
|
||||||
|
self._get_report_line_unit(line),
|
||||||
|
total_unit,
|
||||||
|
)
|
||||||
|
return total
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def report_gross(self):
|
def report_gross(self):
|
||||||
lines = self._get_report_lines()
|
total = self._get_report_total_weight(1)
|
||||||
if lines:
|
if total is not None:
|
||||||
total = Decimal(0)
|
|
||||||
for line in lines:
|
|
||||||
total += self._get_report_line_weights(line)[1]
|
|
||||||
return total
|
return total
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def report_net(self):
|
def report_net(self):
|
||||||
lines = self._get_report_lines()
|
total = self._get_report_total_weight(0)
|
||||||
if lines:
|
if total is not None:
|
||||||
total = Decimal(0)
|
|
||||||
for line in lines:
|
|
||||||
total += self._get_report_line_weights(line)[0]
|
|
||||||
return total
|
return total
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def report_total_quantity(self):
|
def report_total_quantity(self):
|
||||||
lines = self._get_report_lines()
|
total = self._get_report_total_weight(0)
|
||||||
if lines:
|
if total is not None:
|
||||||
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'
|
||||||
|
|
||||||
@@ -515,10 +591,10 @@ class Sale(metaclass=PoolMeta):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def report_qt(self):
|
def report_qt(self):
|
||||||
lines = self._get_report_lines()
|
total = self._get_report_total_weight(0)
|
||||||
if lines:
|
if total is not None:
|
||||||
total = sum(self._get_report_line_quantity(line) for line in lines)
|
return self._report_quantity_to_words(
|
||||||
return quantity_to_words(total)
|
total, self._get_report_total_unit())
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -536,7 +612,7 @@ class Sale(metaclass=PoolMeta):
|
|||||||
line_unit.rec_name.upper()
|
line_unit.rec_name.upper()
|
||||||
if line_unit and line_unit.rec_name else ''
|
if line_unit and line_unit.rec_name else ''
|
||||||
)
|
)
|
||||||
words = quantity_to_words(current_quantity)
|
words = self._report_quantity_to_words(current_quantity, line_unit)
|
||||||
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(
|
||||||
part for part in [
|
part for part in [
|
||||||
@@ -623,8 +699,12 @@ 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)
|
||||||
words = quantity_to_words(current_quantity)
|
unit = (
|
||||||
|
line_unit.rec_name.upper()
|
||||||
|
if line_unit and line_unit.rec_name else ''
|
||||||
|
)
|
||||||
|
words = self._report_quantity_to_words(current_quantity, line_unit)
|
||||||
period = line.del_period.description if getattr(line, 'del_period', None) else ''
|
period = line.del_period.description if getattr(line, 'del_period', None) else ''
|
||||||
quantity_line = ' '.join(
|
quantity_line = ' '.join(
|
||||||
part for part in [
|
part for part in [
|
||||||
|
|||||||
@@ -789,6 +789,106 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
'USC 70.2500 PER POUND (SEVENTY USC AND TWENTY FIVE CENTS) ON ICE Cotton #2 MAY 2026',
|
'USC 70.2500 PER POUND (SEVENTY USC AND TWENTY FIVE CENTS) ON ICE Cotton #2 MAY 2026',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_sale_report_converts_mixed_units_for_total_and_words(self):
|
||||||
|
'sale report totals prefer the virtual lot unit as common unit'
|
||||||
|
Sale = Pool().get('sale.sale')
|
||||||
|
|
||||||
|
mt = Mock(id=1, rec_name='MT')
|
||||||
|
kg = Mock(id=2, rec_name='KILOGRAM')
|
||||||
|
|
||||||
|
line_mt = Mock()
|
||||||
|
line_mt.type = 'line'
|
||||||
|
line_mt.quantity = Decimal('1000')
|
||||||
|
line_mt.unit = mt
|
||||||
|
line_mt.del_period = Mock(description='MARCH 2026')
|
||||||
|
line_mt.lots = []
|
||||||
|
|
||||||
|
virtual = Mock(lot_type='virtual', lot_unit_line=kg)
|
||||||
|
virtual.get_hist_quantity.return_value = (
|
||||||
|
Decimal('1000000'),
|
||||||
|
Decimal('1000000'),
|
||||||
|
)
|
||||||
|
line_kg = Mock()
|
||||||
|
line_kg.type = 'line'
|
||||||
|
line_kg.quantity = Decimal('1000')
|
||||||
|
line_kg.unit = mt
|
||||||
|
line_kg.del_period = Mock(description='MAY 2026')
|
||||||
|
line_kg.lots = [virtual]
|
||||||
|
|
||||||
|
sale = Sale()
|
||||||
|
sale.lines = [line_mt, line_kg]
|
||||||
|
|
||||||
|
uom_model = Mock()
|
||||||
|
uom_model.compute_qty.side_effect = (
|
||||||
|
lambda from_unit, qty, to_unit: (
|
||||||
|
qty * 1000
|
||||||
|
if getattr(from_unit, 'rec_name', None) == 'MT'
|
||||||
|
and getattr(to_unit, 'rec_name', None) == 'KILOGRAM'
|
||||||
|
else (
|
||||||
|
qty / 1000
|
||||||
|
if getattr(from_unit, 'rec_name', None) == 'KILOGRAM'
|
||||||
|
and getattr(to_unit, 'rec_name', None) == 'MT'
|
||||||
|
else qty
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.sale.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.return_value = uom_model
|
||||||
|
|
||||||
|
self.assertEqual(sale.report_total_quantity, '2000000.0')
|
||||||
|
self.assertEqual(sale.report_quantity_unit_upper, 'KILOGRAM')
|
||||||
|
self.assertEqual(sale.report_qt, 'TWO MILLION KILOGRAMS')
|
||||||
|
self.assertEqual(
|
||||||
|
sale.report_quantity_lines.splitlines(),
|
||||||
|
[
|
||||||
|
'1000.0 MT (ONE THOUSAND METRIC TONS) - MARCH 2026',
|
||||||
|
'1000000.0 KILOGRAM (ONE MILLION KILOGRAMS) - MAY 2026',
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_sale_report_total_unit_falls_back_when_multiple_virtual_lots(self):
|
||||||
|
'sale report common unit uses virtual only when there is a single one'
|
||||||
|
Sale = Pool().get('sale.sale')
|
||||||
|
|
||||||
|
mt = Mock(id=1, rec_name='MT')
|
||||||
|
kg = Mock(id=2, rec_name='KILOGRAM')
|
||||||
|
|
||||||
|
line_mt = Mock(type='line', quantity=Decimal('1000'), unit=mt)
|
||||||
|
line_mt.del_period = Mock(description='MARCH 2026')
|
||||||
|
line_mt.lots = []
|
||||||
|
|
||||||
|
virtual_a = Mock(lot_type='virtual', lot_unit_line=kg)
|
||||||
|
virtual_a.get_hist_quantity.return_value = (
|
||||||
|
Decimal('1000000'),
|
||||||
|
Decimal('1000000'),
|
||||||
|
)
|
||||||
|
virtual_b = Mock(lot_type='virtual', lot_unit_line=kg)
|
||||||
|
virtual_b.get_hist_quantity.return_value = (
|
||||||
|
Decimal('1000000'),
|
||||||
|
Decimal('1000000'),
|
||||||
|
)
|
||||||
|
line_kg = Mock(type='line', quantity=Decimal('1000'), unit=mt)
|
||||||
|
line_kg.del_period = Mock(description='MAY 2026')
|
||||||
|
line_kg.lots = [virtual_a, virtual_b]
|
||||||
|
|
||||||
|
sale = Sale()
|
||||||
|
sale.lines = [line_mt, line_kg]
|
||||||
|
|
||||||
|
uom_model = Mock()
|
||||||
|
uom_model.compute_qty.side_effect = (
|
||||||
|
lambda from_unit, qty, to_unit: (
|
||||||
|
qty / 1000
|
||||||
|
if getattr(from_unit, 'rec_name', None) == 'KILOGRAM'
|
||||||
|
and getattr(to_unit, 'rec_name', None) == 'MT'
|
||||||
|
else qty
|
||||||
|
))
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.sale.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.return_value = uom_model
|
||||||
|
|
||||||
|
self.assertEqual(sale.report_quantity_unit_upper, 'MT')
|
||||||
|
self.assertEqual(sale.report_total_quantity, '2000.0')
|
||||||
|
self.assertEqual(sale.report_qt, 'TWO THOUSAND METRIC TONS')
|
||||||
|
|
||||||
def test_report_product_fields_expose_name_and_description(self):
|
def test_report_product_fields_expose_name_and_description(self):
|
||||||
'sale and invoice templates use stable product name/description helpers'
|
'sale and invoice templates use stable product name/description helpers'
|
||||||
Sale = Pool().get('sale.sale')
|
Sale = Pool().get('sale.sale')
|
||||||
@@ -1015,7 +1115,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
'invoice net and gross weights come from the current lot hist entry'
|
'invoice net and gross weights come from the current lot hist entry'
|
||||||
Invoice = Pool().get('account.invoice')
|
Invoice = Pool().get('account.invoice')
|
||||||
|
|
||||||
unit = Mock(rec_name='LBS')
|
unit = Mock(rec_name='LBS', symbol='LBS')
|
||||||
lot = Mock(lot_unit_line=unit)
|
lot = Mock(lot_unit_line=unit)
|
||||||
lot.get_hist_quantity.return_value = (
|
lot.get_hist_quantity.return_value = (
|
||||||
Decimal('950'),
|
Decimal('950'),
|
||||||
@@ -1030,7 +1130,36 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
|
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
invoice.report_quantity_lines,
|
invoice.report_quantity_lines,
|
||||||
'950.0 LBS (2094389.00 LBS)')
|
'950.0 LBS (950.00 LBS)')
|
||||||
|
|
||||||
|
def test_invoice_report_lbs_converts_kilogram_to_lbs(self):
|
||||||
|
'invoice lbs helper converts kilogram quantities with the proper uom ratio'
|
||||||
|
Invoice = Pool().get('account.invoice')
|
||||||
|
|
||||||
|
kg = Mock(id=1, rec_name='KILOGRAM', symbol='KG')
|
||||||
|
lbs = Mock(id=2, rec_name='LBS', symbol='LBS')
|
||||||
|
lot = Mock(lot_unit_line=kg)
|
||||||
|
lot.get_hist_quantity.return_value = (
|
||||||
|
Decimal('999995'),
|
||||||
|
Decimal('999995'),
|
||||||
|
)
|
||||||
|
line = Mock(type='line', quantity=Decimal('999995'), lot=lot, unit=kg)
|
||||||
|
invoice = Invoice()
|
||||||
|
invoice.lines = [line]
|
||||||
|
|
||||||
|
uom_model = Mock()
|
||||||
|
uom_model.search.return_value = [lbs]
|
||||||
|
uom_model.compute_qty.side_effect = (
|
||||||
|
lambda from_unit, qty, to_unit: qty * 2.20462
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.invoice.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.return_value = uom_model
|
||||||
|
|
||||||
|
self.assertEqual(invoice.report_lbs, Decimal('2204608.98'))
|
||||||
|
self.assertEqual(
|
||||||
|
invoice.report_quantity_lines,
|
||||||
|
'999995.0 KILOGRAM (2204608.98 LBS)')
|
||||||
|
|
||||||
def test_invoice_report_weights_keep_line_sign_with_lot_hist_values(self):
|
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 lot hist values keep the invoice line sign for final notes'
|
||||||
|
|||||||
Reference in New Issue
Block a user