kg lbs
This commit is contained in:
@@ -131,6 +131,45 @@ class Invoice(metaclass=PoolMeta):
|
||||
return lots[0].lot_unit_line
|
||||
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
|
||||
def _clean_report_description(value):
|
||||
text = (value or '').strip()
|
||||
@@ -517,7 +556,7 @@ class Invoice(metaclass=PoolMeta):
|
||||
quantity, keep_trailing_decimal=True)
|
||||
unit = self._get_report_invoice_line_unit(line)
|
||||
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]
|
||||
if lbs != '':
|
||||
parts.append(
|
||||
@@ -746,7 +785,9 @@ class Invoice(metaclass=PoolMeta):
|
||||
net = self.report_net
|
||||
if net == '':
|
||||
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
|
||||
def report_weight_unit_upper(self):
|
||||
@@ -996,7 +1037,8 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||
net = self.report_net
|
||||
if net == '':
|
||||
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:
|
||||
|
||||
@@ -473,32 +473,108 @@ class Sale(metaclass=PoolMeta):
|
||||
if lots and getattr(lots[0], 'lot_unit_line', None):
|
||||
return lots[0].lot_unit_line
|
||||
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
|
||||
def report_gross(self):
|
||||
lines = self._get_report_lines()
|
||||
if lines:
|
||||
total = Decimal(0)
|
||||
for line in lines:
|
||||
total += self._get_report_line_weights(line)[1]
|
||||
total = self._get_report_total_weight(1)
|
||||
if total is not None:
|
||||
return total
|
||||
return ''
|
||||
|
||||
@property
|
||||
def report_net(self):
|
||||
lines = self._get_report_lines()
|
||||
if lines:
|
||||
total = Decimal(0)
|
||||
for line in lines:
|
||||
total += self._get_report_line_weights(line)[0]
|
||||
total = self._get_report_total_weight(0)
|
||||
if total is not None:
|
||||
return total
|
||||
return ''
|
||||
|
||||
@property
|
||||
def report_total_quantity(self):
|
||||
lines = self._get_report_lines()
|
||||
if lines:
|
||||
total = sum(self._get_report_line_weights(line)[0] for line in lines)
|
||||
total = self._get_report_total_weight(0)
|
||||
if total is not None:
|
||||
return self._format_report_number(total, keep_trailing_decimal=True)
|
||||
return '0.0'
|
||||
|
||||
@@ -515,10 +591,10 @@ class Sale(metaclass=PoolMeta):
|
||||
|
||||
@property
|
||||
def report_qt(self):
|
||||
lines = self._get_report_lines()
|
||||
if lines:
|
||||
total = sum(self._get_report_line_quantity(line) for line in lines)
|
||||
return quantity_to_words(total)
|
||||
total = self._get_report_total_weight(0)
|
||||
if total is not None:
|
||||
return self._report_quantity_to_words(
|
||||
total, self._get_report_total_unit())
|
||||
return ''
|
||||
|
||||
@property
|
||||
@@ -536,7 +612,7 @@ class Sale(metaclass=PoolMeta):
|
||||
line_unit.rec_name.upper()
|
||||
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 ''
|
||||
detail = ' '.join(
|
||||
part for part in [
|
||||
@@ -623,8 +699,12 @@ 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 ''
|
||||
words = quantity_to_words(current_quantity)
|
||||
line_unit = self._get_report_line_unit(line)
|
||||
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 ''
|
||||
quantity_line = ' '.join(
|
||||
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',
|
||||
])
|
||||
|
||||
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):
|
||||
'sale and invoice templates use stable product name/description helpers'
|
||||
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 = Pool().get('account.invoice')
|
||||
|
||||
unit = Mock(rec_name='LBS')
|
||||
unit = Mock(rec_name='LBS', symbol='LBS')
|
||||
lot = Mock(lot_unit_line=unit)
|
||||
lot.get_hist_quantity.return_value = (
|
||||
Decimal('950'),
|
||||
@@ -1030,7 +1130,36 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(invoice.report_weight_unit_upper, 'LBS')
|
||||
self.assertEqual(
|
||||
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):
|
||||
'invoice lot hist values keep the invoice line sign for final notes'
|
||||
|
||||
Reference in New Issue
Block a user