Files
tradon/modules/purchase_trade/invoice.py

2813 lines
103 KiB
Python

from decimal import Decimal, ROUND_HALF_UP
from datetime import date as dt_date
import os
from pathlib import Path
from sql import Literal
from sql.conditionals import Case
from sql.functions import CurrentTimestamp
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.modules.purchase_trade.numbers_to_words import amount_to_currency_words
from trytond.exceptions import UserError
from trytond.transaction import Transaction
from trytond.tools import file_open
from trytond.modules.account_invoice.invoice import (
InvoiceReport as BaseInvoiceReport)
from trytond.modules.sale.sale import SaleReport as BaseSaleReport
from trytond.modules.purchase.purchase import (
PurchaseReport as BasePurchaseReport)
class InvoiceLineLotWeight(ModelSQL, ModelView):
"Invoice Line Lot Weight"
__name__ = 'account.invoice.line.lot.weight'
invoice_line = fields.Many2One(
'account.invoice.line', "Invoice Line", required=True,
ondelete='CASCADE')
lot = fields.Many2One('lot.lot', "Lot", required=True, ondelete='CASCADE')
lot_qt_hist = fields.Many2One('lot.qt.hist', "Quantity History")
quantity_type = fields.Many2One('lot.qt.type', "Quantity Type")
net_quantity = fields.Numeric("Net Weight", digits=(1, 5), required=True)
gross_quantity = fields.Numeric(
"Gross Weight", digits=(1, 5), required=True)
unit = fields.Many2One('product.uom', "Unit")
lot_qt = fields.Integer("Packing Quantity")
lot_unit = fields.Many2One('product.uom', "Packing Unit")
@classmethod
def _get_lot_hist_entry(cls, lot):
state = getattr(lot, 'lot_state', None)
state_id = getattr(state, 'id', None)
hist = list(getattr(lot, 'lot_hist', []) or [])
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:
return entry
if len(hist) == 1:
return hist[0]
return None
@classmethod
def _get_lot_weights(cls, lot):
hist = cls._get_lot_hist_entry(lot)
if hist:
net = Decimal(str(getattr(hist, 'quantity', 0) or 0))
gross = Decimal(str(
getattr(hist, 'gross_quantity', None)
if getattr(hist, 'gross_quantity', None) not in (None, '')
else net))
return hist, net, gross
if hasattr(lot, 'get_hist_quantity'):
net, gross = lot.get_hist_quantity()
net = Decimal(str(net or 0))
gross = Decimal(str(gross if gross not in (None, '') else net))
return None, net, gross
net = Decimal(str(getattr(lot, 'lot_quantity', 0) or 0))
gross = Decimal(str(
getattr(lot, 'lot_gross_quantity', None)
if getattr(lot, 'lot_gross_quantity', None) not in (None, '')
else net))
return None, net, gross
@classmethod
def create_for_invoice_line(cls, line, lot):
if not getattr(line, 'id', None) or not getattr(lot, 'id', None):
return
existing = cls.search([
('invoice_line', '=', line.id),
('lot', '=', lot.id),
])
if existing:
cls.delete(existing)
hist, net, gross = cls._get_lot_weights(lot)
snapshot = cls()
snapshot.invoice_line = line
snapshot.lot = lot
snapshot.lot_qt_hist = hist
snapshot.quantity_type = (
getattr(hist, 'quantity_type', None)
or getattr(lot, 'lot_state', None))
snapshot.net_quantity = net
snapshot.gross_quantity = gross
snapshot.unit = (
getattr(lot, 'lot_unit_line', None)
or getattr(lot, 'lot_unit', None)
or getattr(line, 'unit', None))
snapshot.lot_qt = getattr(lot, 'lot_qt', None)
snapshot.lot_unit = getattr(lot, 'lot_unit', None)
cls.save([snapshot])
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
@classmethod
def _sale_padding_lots_to_clear_on_delete(cls, invoices):
lots = {}
for invoice in invoices:
if (getattr(invoice, 'type', None) != 'out'
or getattr(invoice, 'reference', None) != 'Provisional'
or getattr(invoice, 'number', None)):
continue
invoice_id = getattr(invoice, 'id', None)
for line in getattr(invoice, 'lines', None) or []:
if (getattr(line, 'type', None) != 'line'
or getattr(line, 'description', None) != 'Pro forma'):
continue
lot = getattr(line, 'lot', None)
if not lot:
continue
provisional_line = getattr(lot, 'sale_invoice_line_prov', None)
provisional_invoice = getattr(provisional_line, 'invoice', None)
if (invoice_id and provisional_invoice
and getattr(provisional_invoice, 'id', None) != invoice_id):
continue
if Decimal(str(getattr(lot, 'sale_invoice_padding', 0) or 0)):
lots[getattr(lot, 'id', id(lot))] = lot
return list(lots.values())
@classmethod
def _fee_updates_on_delete(cls, invoices):
reset_fees = {}
clear_dn_cn_fees = {}
for invoice in invoices:
if getattr(invoice, 'number', None):
continue
invoice_id = getattr(invoice, 'id', None)
for line in getattr(invoice, 'lines', None) or []:
fee = getattr(line, 'fee', None)
if not fee:
continue
fee_key = getattr(fee, 'id', None) or id(fee)
dn_cn = getattr(fee, 'dn_cn', None)
dn_cn_id = getattr(dn_cn, 'id', dn_cn)
if invoice_id and dn_cn_id == invoice_id:
clear_dn_cn_fees[fee_key] = fee
else:
reset_fees[fee_key] = fee
return list(reset_fees.values()), list(clear_dn_cn_fees.values())
@classmethod
def delete(cls, invoices):
Lot = Pool().get('lot.lot')
Fee = Pool().get('fee.fee')
padding_lots = cls._sale_padding_lots_to_clear_on_delete(invoices)
reset_fees, clear_dn_cn_fees = cls._fee_updates_on_delete(invoices)
super().delete(invoices)
if padding_lots:
Lot.write(padding_lots, {'sale_invoice_padding': Decimal(0)})
if reset_fees:
Fee.write(reset_fees, {
'state': 'not invoiced',
'dn_cn': None,
})
if clear_dn_cn_fees:
Fee.write(clear_dn_cn_fees, {'dn_cn': None})
def do_lot_invoicing(self):
super().do_lot_invoicing()
self._create_lot_weight_snapshots()
self._create_sale_padding_moves()
def _create_lot_weight_snapshots(self):
Snapshot = Pool().get('account.invoice.line.lot.weight')
Lot = Pool().get('lot.lot')
for line in self._get_report_invoice_lines():
if (getattr(line, 'description', None) not in {'Pro forma', 'Final'}
or Decimal(str(getattr(line, 'quantity', 0) or 0)) <= 0
or not getattr(line, 'lot', None)):
continue
Snapshot.create_for_invoice_line(line, Lot(line.lot.id))
@classmethod
def _post(cls, invoices):
pool = Pool()
Move = pool.get('account.move')
super()._post(invoices)
padding_moves = []
for invoice in invoices:
padding_moves.extend(invoice._create_sale_padding_moves())
padding_moves.extend([
move for move in (invoice.additional_moves or [])
if move.description in {
invoice._get_sale_padding_move_description(False),
invoice._get_sale_padding_move_description(True),
}
and move.state != 'posted'
])
if padding_moves:
cls.save(invoices)
Move.post([m for m in padding_moves if m.state != 'posted'])
def _get_sale_padding_accounts(self):
Configuration = Pool().get('account.configuration')
config = Configuration(1)
sale_account = config.get_multivalue(
'default_sale_padding_account', company=self.company.id)
accrual_account = config.get_multivalue(
'default_accrual_padding_account', company=self.company.id)
if not sale_account or not accrual_account:
raise UserError(
'Default Sale Padding and Default Accrual Padding '
'accounts must be configured.')
return sale_account, accrual_account
def _has_sale_padding_move(self, reversal=False):
description = self._get_sale_padding_move_description(reversal)
return any(
move.description == description
for move in (self.additional_moves or []))
@staticmethod
def _get_sale_padding_move_description(reversal=False):
if reversal:
return 'Sale padding reversal'
return 'Sale padding accrual'
def _get_padding_company_amount(self, invoice_line, padding):
Currency = Pool().get('currency.currency')
invoice = invoice_line.invoice
amount = Decimal(str(padding or 0)) * Decimal(
str(invoice_line.unit_price or 0))
amount = invoice.currency.round(amount)
if invoice.currency == invoice.company.currency:
return amount, amount
if invoice.rate:
company_amount = invoice.company.currency.round(
amount / invoice.rate)
else:
with Transaction().set_context(date=invoice.currency_date):
company_amount = Currency.compute(
invoice.currency, amount, invoice.company.currency)
return amount, company_amount
def _get_sale_padding_entries(self):
if self.type != 'out':
return []
entries = []
for line in self.lines or []:
if getattr(line, 'type', None) != 'line':
continue
lot = getattr(line, 'lot', None)
if not lot:
continue
padding = Decimal(str(
getattr(lot, 'sale_invoice_padding', 0) or 0))
if padding <= 0:
continue
if self.reference == 'Provisional' and line.description == 'Pro forma':
entries.append((lot, line, padding, False))
elif self.reference == 'Final' and line.description == 'Final':
provisional_line = getattr(lot, 'sale_invoice_line_prov', None)
if provisional_line:
entries.append((lot, provisional_line, padding, True))
return entries
def _get_sale_padding_move_lines(self, entries):
MoveLine = Pool().get('account.move.line')
sale_account, accrual_account = self._get_sale_padding_accounts()
move_lines = []
for lot, invoice_line, padding, reversal in entries:
padding_amount, company_amount = self._get_padding_company_amount(
invoice_line, padding)
if not company_amount:
continue
sale_line = MoveLine()
accrual_line = MoveLine()
for move_line in (sale_line, accrual_line):
move_line.lot = lot
move_line.origin = invoice_line
move_line.description = 'Padding'
if not reversal:
sale_line.account = sale_account
sale_line.debit = company_amount
sale_line.credit = Decimal(0)
accrual_line.account = accrual_account
accrual_line.debit = Decimal(0)
accrual_line.credit = company_amount
else:
accrual_line.account = accrual_account
accrual_line.debit = company_amount
accrual_line.credit = Decimal(0)
sale_line.account = sale_account
sale_line.debit = Decimal(0)
sale_line.credit = company_amount
if self.currency != self.company.currency:
sale_line.second_currency = self.currency
accrual_line.second_currency = self.currency
sale_line.amount_second_currency = padding_amount.copy_sign(
sale_line.debit - sale_line.credit)
accrual_line.amount_second_currency = padding_amount.copy_sign(
accrual_line.debit - accrual_line.credit)
if sale_line.account.party_required:
sale_line.party = self.party
if accrual_line.account.party_required:
accrual_line.party = self.party
move_lines.extend([sale_line, accrual_line])
return move_lines
def _create_sale_padding_moves(self):
pool = Pool()
Move = pool.get('account.move')
Period = pool.get('account.period')
Date = pool.get('ir.date')
entries = self._get_sale_padding_entries()
if not entries:
return []
reversal = entries[0][3]
if self._has_sale_padding_move(reversal):
return []
move_lines = self._get_sale_padding_move_lines(entries)
if not move_lines:
return []
with Transaction().set_context(company=self.company.id):
today = Date.today()
accounting_date = self.accounting_date or self.invoice_date or today
period = Period.find(self.company, date=accounting_date)
move = Move()
move.journal = self.journal
move.period = period
move.date = accounting_date
move.origin = self
move.company = self.company
move.description = self._get_sale_padding_move_description(reversal)
move.lines = move_lines
Move.save([move])
self.additional_moves = tuple(self.additional_moves or ()) + (move,)
return [move]
@staticmethod
def _format_report_number(value, digits='0.0000', keep_trailing_decimal=False,
strip_trailing_zeros=True, use_grouping=False):
value = Decimal(str(value or 0)).quantize(Decimal(digits))
text = format(value, ',f' if use_grouping else 'f')
if strip_trailing_zeros:
text = text.rstrip('0').rstrip('.')
if keep_trailing_decimal and '.' not in text:
text += '.0'
return text or '0'
@classmethod
def _format_report_quantity_display(cls, value):
return cls._format_report_number(
value, digits='0.01', strip_trailing_zeros=False)
def _get_report_invoice_line(self):
for line in self.lines or []:
if getattr(line, 'type', None) == 'line':
return line
return self.lines[0] if self.lines else None
def _get_report_invoice_lines(self):
lines = [
line for line in (self.lines or [])
if getattr(line, 'type', None) == 'line'
]
return lines or list(self.lines or [])
@staticmethod
def _report_record_name(record):
if not record:
return ''
return (
getattr(record, 'rec_name', None)
or getattr(record, 'name', None)
or getattr(record, 'code', None)
or '')
def _get_report_commission_fee(self):
for line in self._get_report_invoice_lines():
fee = getattr(line, 'fee', None)
if fee:
return fee
origin = getattr(line, 'origin', None)
fee = getattr(origin, 'fee_', None)
if fee:
return fee
def _get_report_commission_lots(self):
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)
for line in self._get_report_invoice_lines():
add_lot(getattr(line, 'lot', None))
fee = self._get_report_commission_fee()
if fee and hasattr(fee, '_get_effective_fee_lots'):
for lot in fee._get_effective_fee_lots():
add_lot(lot)
for lot in getattr(fee, 'lots', []) or []:
add_lot(lot)
return lots
def _get_report_commission_trade_for(self, kind):
fee = self._get_report_commission_fee()
if fee:
if kind == 'sale' and getattr(fee, 'sale_line', None):
return fee.sale_line.sale
if kind == 'purchase' and getattr(fee, 'line', None):
return fee.line.purchase
for lot in self._get_report_commission_lots():
if kind == 'sale' and getattr(lot, 'sale_line', None):
return lot.sale_line.sale
if kind == 'purchase' and getattr(lot, 'line', None):
return lot.line.purchase
records = getattr(self, kind + 's', None)
if records:
return records[0]
def _get_report_commission_trade(self):
for kind in ('sale', 'purchase'):
trade = self._get_report_commission_trade_for(kind)
if trade:
return trade
for line in self._get_report_invoice_lines():
origin = getattr(line, 'origin', None)
if not origin:
continue
trade = getattr(origin, 'sale', None) or getattr(origin, 'purchase', None)
if trade:
return trade
def _get_report_commission_kind(self):
fee = self._get_report_commission_fee()
if fee:
if getattr(fee, 'sale_line', None):
return 'sale'
if getattr(fee, 'line', None):
return 'purchase'
for lot in self._get_report_commission_lots():
if getattr(lot, 'sale_line', None):
return 'sale'
if getattr(lot, 'line', None):
return 'purchase'
def _get_report_commission_adjustment_unit(self):
fee = self._get_report_commission_fee()
if fee:
unit = (
getattr(fee, 'linked_unit', None)
if getattr(fee, 'enable_linked_currency', False) else None)
if unit:
return unit
if getattr(fee, 'unit', None):
return fee.unit
lots = self._get_report_commission_lots()
if lots:
lot = lots[0]
return (
getattr(lot, 'lot_unit_line', None)
or getattr(lot, 'lot_unit', None))
line = self._get_report_invoice_line()
return getattr(line, 'unit', None) if line else None
def _get_report_commission_provisional_line(self, lot, kind):
if kind == 'sale':
return getattr(lot, 'sale_invoice_line_prov', None)
return getattr(lot, 'invoice_line_prov', None)
def _get_report_commission_lot_current_quantity(self, lot, unit):
if hasattr(lot, 'get_current_quantity_converted'):
try:
return Decimal(str(
lot.get_current_quantity_converted(0, unit) or 0))
except TypeError:
return Decimal(str(
lot.get_current_quantity_converted() or 0))
net, _ = self._get_report_lot_hist_weights(lot)
if net is None:
return Decimal(0)
return self._convert_report_quantity(
net, getattr(lot, 'lot_unit_line', None), unit)
def _get_report_commission_lot_provisional_quantity(self, lot, kind, unit):
line = self._get_report_commission_provisional_line(lot, kind)
if not line:
return None
quantity = Decimal(str(getattr(line, 'quantity', 0) or 0))
if kind == 'sale':
quantity -= Decimal(str(
getattr(lot, 'sale_invoice_padding', 0) or 0))
return self._convert_report_quantity(
quantity, getattr(line, 'unit', None), unit)
def _get_report_commission_adjustment_totals(self):
kind = self._get_report_commission_kind()
unit = self._get_report_commission_adjustment_unit()
if not kind or not unit:
return None
invoice_total = Decimal(0)
landed_total = Decimal(0)
matched = False
for lot in self._get_report_commission_lots():
provisional_quantity = (
self._get_report_commission_lot_provisional_quantity(
lot, kind, unit))
if provisional_quantity is None:
continue
invoice_total += abs(provisional_quantity)
landed_total += abs(
self._get_report_commission_lot_current_quantity(lot, unit))
matched = True
if not matched:
return None
return {
'invoice': invoice_total,
'landed': landed_total,
'difference': abs(landed_total - invoice_total),
'unit': unit,
}
@property
def report_commission_adjustment_unit_upper(self):
totals = self._get_report_commission_adjustment_totals()
unit = totals['unit'] if totals else self._get_report_commission_adjustment_unit()
if unit:
return (
getattr(unit, 'symbol', None)
or getattr(unit, 'rec_name', None)
or getattr(unit, 'name', None)
or '').upper()
return ''
@property
def report_commission_invoice_weight_display(self):
totals = self._get_report_commission_adjustment_totals()
if not totals:
return self.report_net_display
return self._format_report_quantity_display(totals['invoice'])
@property
def report_commission_landed_weight_display(self):
totals = self._get_report_commission_adjustment_totals()
if not totals:
return self.report_gross_display
return self._format_report_quantity_display(totals['landed'])
@property
def report_commission_landed_lbs_display(self):
totals = self._get_report_commission_adjustment_totals()
if not totals:
return self.report_gross_lbs_display
lbs = self._convert_report_quantity_to_lbs(
totals['landed'], totals['unit'])
return self._format_report_quantity_display(lbs)
@property
def report_commission_weight_difference_display(self):
totals = self._get_report_commission_adjustment_totals()
if not totals:
return self.report_weight_difference_display
return self._format_report_quantity_display(totals['difference'])
@property
def report_commission_weight_difference_lbs_display(self):
totals = self._get_report_commission_adjustment_totals()
if not totals:
return self.report_weight_difference_lbs_display
lbs = self._convert_report_quantity_to_lbs(
totals['difference'], totals['unit'])
return self._format_report_quantity_display(lbs)
@property
def report_commission_adjustment_rate_line(self):
fee = self._get_report_commission_fee()
if not fee:
return self.report_commission_rate_line
mode = getattr(fee, 'mode', None)
price = getattr(fee, 'price', None) or 0
if mode in {'pprice', 'rate', 'pcost'}:
kind = self._get_report_commission_kind() or 'sale'
return ' '.join(part for part in [
self._format_report_number(price, digits='0.0000'),
'% on',
kind,
'amount',
self._get_report_commission_base_amount_display_for(kind),
self._get_report_commission_currency_name_for(kind),
] if part)
if (getattr(fee, 'enable_linked_currency', False)
and getattr(fee, 'linked_price', None) is not None):
price = fee.linked_price
currency = getattr(fee, 'linked_currency', None)
unit = getattr(fee, 'linked_unit', None)
else:
currency = getattr(fee, 'currency', None)
unit = getattr(fee, 'unit', None)
price_text = self._format_report_number(price, digits='0.0000')
currency_text = self._report_record_name(currency)
unit_text = self._report_record_name(unit)
if currency_text and unit_text:
return '%s %s / %s' % (price_text, currency_text, unit_text)
return ' '.join(part for part in [
price_text, currency_text or unit_text,
] if part)
@property
def report_commission_tax_rate_line(self):
for tax in getattr(self, 'taxes', []) or []:
description = getattr(tax, 'description', None) or '0%'
return 'VAT %s RATE' % description
return 'VAT 0% RATE'
@property
def report_commission_address(self):
trade = self._get_report_commission_trade()
return getattr(trade, 'report_address', '') or ''
@property
def report_commission_date(self):
trade = self._get_report_commission_trade()
return (
getattr(trade, 'sale_date', None)
or getattr(trade, 'purchase_date', None)
or self.invoice_date)
def _get_report_commission_value(self, name):
trade = self._get_report_commission_trade()
return getattr(trade, name, '') if trade else ''
def _get_report_commission_value_for(self, kind, name):
trade = self._get_report_commission_trade_for(kind)
return getattr(trade, name, '') if trade else ''
@property
def report_commission_invoice_number(self):
return self._get_report_commission_value(
'report_commission_invoice_number')
@property
def report_commission_contract_number(self):
return self._get_report_commission_value(
'report_commission_contract_number')
@property
def report_commission_bl_number(self):
return self._get_report_commission_value(
'report_commission_bl_number')
@property
def report_commission_bl_date(self):
return self._get_report_commission_value(
'report_commission_bl_date')
@property
def report_commission_vessel(self):
return self._get_report_commission_value(
'report_commission_vessel')
@property
def report_commission_invoice_line(self):
return self._get_report_commission_value(
'report_commission_invoice_line')
@property
def report_commission_quantity_unit_upper(self):
return self._get_report_commission_value(
'report_commission_quantity_unit_upper')
@property
def report_commission_quantity_display(self):
return self._get_report_commission_value(
'report_commission_quantity_display')
@property
def report_commission_lbs_display(self):
return self._get_report_commission_value(
'report_commission_lbs_display')
@property
def report_commission_rate_line(self):
return self._get_report_commission_value(
'report_commission_rate_line')
@property
def report_commission_secondary_rate_line(self):
return self._get_report_commission_value(
'report_commission_secondary_rate_line')
def _get_report_commission_base_amount_display_for(self, kind):
trade = self._get_report_commission_trade_for(kind)
if trade:
base = getattr(trade, 'report_commission_base_amount_display', None)
if base:
return base
amount = (
getattr(trade, 'untaxed_amount', None)
or getattr(trade, 'total_amount', None)
or 0)
return self._format_report_number(
amount, digits='0.01', strip_trailing_zeros=False)
return self.report_commission_total_display
def _get_report_commission_currency_name_for(self, kind):
trade = self._get_report_commission_trade_for(kind)
currency = getattr(trade, 'currency', None) if trade else self.currency
return self._report_record_name(currency)
def _get_report_commission_rate_line_for(self, kind):
fee = self._get_report_commission_fee()
if not fee:
return self._get_report_commission_value(
'report_commission_rate_line')
mode = getattr(fee, 'mode', None)
price = getattr(fee, 'price', None) or 0
if mode in {'pprice', 'rate', 'pcost'}:
return ' '.join(part for part in [
self._format_report_number(price, digits='0.0000'),
'% on',
kind,
'amount',
self._get_report_commission_base_amount_display_for(kind),
self._get_report_commission_currency_name_for(kind),
] if part)
if (getattr(fee, 'enable_linked_currency', False)
and getattr(fee, 'linked_price', None) is not None):
price = fee.linked_price
currency = getattr(fee, 'linked_currency', None)
unit = getattr(fee, 'linked_unit', None)
else:
currency = getattr(fee, 'currency', None)
unit = getattr(fee, 'unit', None)
price_text = self._format_report_number(price, digits='0.0000')
currency_text = self._report_record_name(currency)
unit_text = self._report_record_name(unit)
if currency_text and unit_text:
return '%s %s/%s' % (price_text, currency_text, unit_text)
return ' '.join(part for part in [
price_text, currency_text or unit_text,
] if part)
@property
def report_sale_commission_rate_line(self):
return self._get_report_commission_rate_line_for('sale')
@property
def report_purchase_commission_rate_line(self):
return self._get_report_commission_rate_line_for('purchase')
@property
def report_sale_commission_secondary_rate_line(self):
return ''
@property
def report_purchase_commission_secondary_rate_line(self):
return ''
@property
def report_commission_currency_name(self):
return self._get_report_commission_value(
'report_commission_currency_name')
@property
def report_commission_total_display(self):
return self._get_report_commission_value(
'report_commission_total_display')
def _get_report_commission_amount(self):
return self.total_amount or 0
@property
def report_commission_note_title(self):
amount = Decimal(str(self._get_report_commission_amount() or 0))
if amount < 0:
return 'Credit Note'
return 'Debit Note'
@property
def report_commission_due_label(self):
amount = Decimal(str(self._get_report_commission_amount() or 0))
if amount < 0:
return 'TOTAL DUE TO US'
return 'TOTAL DUE TO YOU'
@property
def report_commission_note_total_display(self):
amount = self._get_report_commission_amount()
return self._format_report_number(
abs(Decimal(str(amount or 0))), digits='0.01',
strip_trailing_zeros=False)
def _get_report_commission_address_for(self, kind):
trade = self._get_report_commission_trade_for(kind)
return getattr(trade, 'report_address', '') or ''
def _get_report_commission_date_for(self, kind):
trade = self._get_report_commission_trade_for(kind)
return (
getattr(trade, 'sale_date', None)
or getattr(trade, 'purchase_date', None)
or self.invoice_date)
def _get_report_commission_total_display_for(self, kind):
fee = self._get_report_commission_fee()
if fee and hasattr(fee, 'get_amount'):
amount = fee.get_amount()
else:
amount = self.total_amount or 0
return self._format_report_number(
abs(Decimal(str(amount or 0))), digits='0.01',
strip_trailing_zeros=False)
@property
def report_sale_commission_address(self):
return self._get_report_commission_address_for('sale')
@property
def report_purchase_commission_address(self):
return self._get_report_commission_address_for('purchase')
@property
def report_sale_commission_date(self):
return self._get_report_commission_date_for('sale')
@property
def report_purchase_commission_date(self):
return self._get_report_commission_date_for('purchase')
@property
def report_sale_commission_invoice_number(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_invoice_number')
@property
def report_purchase_commission_invoice_number(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_invoice_number')
@property
def report_sale_commission_contract_number(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_contract_number')
@property
def report_purchase_commission_contract_number(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_contract_number')
@property
def report_sale_commission_bl_number(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_bl_number')
@property
def report_purchase_commission_bl_number(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_bl_number')
@property
def report_sale_commission_bl_date(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_bl_date')
@property
def report_purchase_commission_bl_date(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_bl_date')
@property
def report_sale_commission_vessel(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_vessel')
@property
def report_purchase_commission_vessel(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_vessel')
@property
def report_sale_commission_invoice_line(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_invoice_line')
@property
def report_purchase_commission_invoice_line(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_invoice_line')
@property
def report_sale_commission_quantity_unit_upper(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_quantity_unit_upper')
@property
def report_purchase_commission_quantity_unit_upper(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_quantity_unit_upper')
@property
def report_sale_commission_quantity_display(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_quantity_display')
@property
def report_purchase_commission_quantity_display(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_quantity_display')
@property
def report_sale_commission_lbs_display(self):
return self._get_report_commission_value_for(
'sale', 'report_commission_lbs_display')
@property
def report_purchase_commission_lbs_display(self):
return self._get_report_commission_value_for(
'purchase', 'report_commission_lbs_display')
@property
def report_sale_commission_currency_name(self):
return self._report_record_name(self.currency)
@property
def report_purchase_commission_currency_name(self):
return self._report_record_name(self.currency)
@property
def report_sale_commission_total_display(self):
return self._get_report_commission_total_display_for('sale')
@property
def report_purchase_commission_total_display(self):
return self._get_report_commission_total_display_for('purchase')
@staticmethod
def _same_report_invoice_line(left, right):
if not left or not right:
return False
left_id = getattr(left, 'id', None)
right_id = getattr(right, 'id', None)
if left_id is not None and right_id is not None:
return left_id == right_id
return left is right
def _get_report_invoice_line_lots(self, invoice_line):
lots = []
seen = set()
def add_lot(lot):
if not lot or getattr(lot, 'lot_type', None) == 'virtual':
return
lot_id = getattr(lot, 'id', None)
key = lot_id if lot_id is not None else id(lot)
if key in seen:
return
seen.add(key)
lots.append(lot)
add_lot(getattr(invoice_line, 'lot', None))
trade = self._get_report_trade()
trade_lines = getattr(trade, 'lines', []) if trade else []
for line in trade_lines or []:
for lot in getattr(line, 'lots', []) or []:
if getattr(lot, 'lot_type', None) != 'physic':
continue
refs = [
getattr(lot, 'sale_invoice_line', None),
getattr(lot, 'sale_invoice_line_prov', None),
getattr(lot, 'invoice_line', None),
getattr(lot, 'invoice_line_prov', None),
]
if any(
self._same_report_invoice_line(ref, invoice_line)
for ref in refs):
add_lot(lot)
return lots
@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):
snapshots = self._get_report_invoice_line_weight_snapshots(line)
if snapshots:
sign = self._get_report_line_sign(line)
net_total = sum(
Decimal(str(snapshot.net_quantity or 0))
for snapshot in snapshots)
gross_total = sum(
Decimal(str(snapshot.gross_quantity or 0))
for snapshot in snapshots)
unit = self._get_report_invoice_line_unit(line)
padding = self._get_report_invoice_line_padding(line, unit)
net_total += padding
gross_total += padding
return net_total * sign, gross_total * sign
lots = self._get_report_invoice_line_lots(line)
if lots and self._report_invoice_line_reuses_lot(line):
quantity = self._get_report_invoice_line_quantity_from_line(line)
return quantity, quantity
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_weight_snapshots(line):
snapshots = getattr(line, 'lot_weight_snapshots', None)
if snapshots and isinstance(snapshots, (list, tuple)):
return list(snapshots)
line_id = getattr(line, 'id', None)
if not isinstance(line_id, int):
return []
Snapshot = Pool().get('account.invoice.line.lot.weight')
return Snapshot.search([('invoice_line', '=', line_id)])
@classmethod
def _get_report_invoice_line_padding(cls, line, to_unit):
if getattr(line, 'description', None) != 'Pro forma':
return Decimal(0)
invoice_type = getattr(line, 'invoice_type', None)
invoice = getattr(line, 'invoice', None)
if invoice_type != 'out' and getattr(invoice, 'type', None) != 'out':
return Decimal(0)
lot = getattr(line, 'lot', None)
padding = Decimal(str(
getattr(lot, 'sale_invoice_padding', 0) or 0))
if not padding:
return Decimal(0)
return cls._convert_report_quantity(
padding, getattr(line, 'unit', None), to_unit)
@staticmethod
def _get_report_line_lot_keys(line):
keys = []
invoice = getattr(line, 'invoice', None)
lots = (
invoice._get_report_invoice_line_lots(line)
if invoice else Invoice._get_report_preferred_lots(line))
for lot in lots:
lot_id = getattr(lot, 'id', None)
keys.append(lot_id if lot_id is not None else id(lot))
return tuple(sorted(keys))
def _report_invoice_line_reuses_lot(self, line):
line_keys = self._get_report_line_lot_keys(line)
if not line_keys:
return False
for other in self._get_report_invoice_lines():
if other is line:
continue
if self._get_report_line_lot_keys(other) == line_keys:
return True
return False
def _get_report_reused_lot_lines(self):
groups = {}
for line in self._get_report_invoice_lines():
lots = self._get_report_invoice_line_lots(line)
if not lots:
continue
for lot in lots:
lot_id = getattr(lot, 'id', None)
key = lot_id if lot_id is not None else id(lot)
groups.setdefault(key, {'lot': lot, 'lines': []})
groups[key]['lines'].append(line)
return {
key: value for key, value in groups.items()
if len(value['lines']) > 1
}
@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
converted = Pool().get('product.uom').compute_qty(
from_unit, float(value), to_unit) or 0
return Decimal(str(converted))
@classmethod
def _get_report_invoice_line_quantity_from_line(cls, line):
quantity = Decimal(str(getattr(line, 'quantity', 0) or 0))
return cls._convert_report_quantity(
quantity,
getattr(line, 'unit', None),
cls._get_report_invoice_line_unit(line),
)
@classmethod
def _find_report_hist_entry_for_quantity(cls, lot, quantity, exclude_state_id=None):
target = Decimal(str(quantity or 0)).copy_abs()
for entry in list(getattr(lot, 'lot_hist', []) or []):
quantity_type = getattr(entry, 'quantity_type', None)
if getattr(quantity_type, 'id', None) == exclude_state_id:
continue
entry_quantity = Decimal(str(getattr(entry, 'quantity', 0) or 0))
if entry_quantity == target:
return entry
return None
def _get_report_reused_lot_gross_total(self):
reused_lots = self._get_report_reused_lot_lines()
if not reused_lots:
return None
total = Decimal(0)
for data in reused_lots.values():
lot = data['lot']
_, current_gross = self._get_report_lot_hist_weights(lot)
if current_gross is None:
continue
current_state = getattr(getattr(lot, 'lot_state', None), 'id', None)
negative_lines = [
line for line in data['lines']
if Decimal(str(getattr(line, 'quantity', 0) or 0)) < 0
]
previous_gross = Decimal(0)
matched = False
for line in negative_lines:
previous_entry = self._find_report_hist_entry_for_quantity(
lot,
self._get_report_invoice_line_quantity_from_line(line),
exclude_state_id=current_state,
)
if not previous_entry:
continue
previous_gross += Decimal(str(
getattr(previous_entry, 'gross_quantity', 0) or 0))
matched = True
if matched:
total += current_gross - previous_gross
return total
def _get_report_weight_adjustment_totals(self):
reused_lots = self._get_report_reused_lot_lines()
if not reused_lots:
return None
invoice_total = Decimal(0)
landed_total = Decimal(0)
matched = False
for data in reused_lots.values():
lines = data['lines']
negative_lines = [
line for line in lines
if Decimal(str(getattr(line, 'quantity', 0) or 0)) < 0
]
positive_lines = [
line for line in lines
if Decimal(str(getattr(line, 'quantity', 0) or 0)) > 0
]
if not negative_lines or not positive_lines:
continue
invoice_values = [
abs(self._get_report_invoice_line_weights(line)[0])
for line in negative_lines]
landed_values = [
abs(self._get_report_invoice_line_quantity_from_line(line))
for line in positive_lines]
invoice_total += sum(invoice_values)
landed_total += sum(landed_values)
matched = True
if not matched:
return None
return {
'invoice': invoice_total,
'landed': landed_total,
'difference': abs(landed_total - invoice_total),
}
@staticmethod
def _get_report_invoice_line_unit(line):
snapshots = Invoice._get_report_invoice_line_weight_snapshots(line)
if snapshots and getattr(snapshots[0], 'unit', None):
return snapshots[0].unit
invoice = getattr(line, 'invoice', None)
lots = (
invoice._get_report_invoice_line_lots(line)
if invoice else 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 _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()
normalized = text.replace(' ', '').upper()
if normalized == 'PROFORMA':
return ''
return text.upper() if text else ''
def _get_report_purchase(self):
purchases = list(getattr(self, 'purchases', None) or [])
return purchases[0] if purchases else None
def _get_report_sale(self):
# Bridge invoice templates to the originating sale so FODT files can
# reuse stable sale.report_* properties instead of complex expressions.
sales = list(getattr(self, 'sales', None) or [])
return sales[0] if sales else None
def _get_report_trade(self):
return self._get_report_sale() or self._get_report_purchase()
def _get_report_deal_sale(self):
sale = self._get_report_sale()
if sale:
return sale
for lot in self._get_report_invoice_lots() or []:
line = getattr(lot, 'sale_line', None)
sale = getattr(line, 'sale', None) if line else None
if sale:
return sale
def _get_report_purchase_line(self):
purchase = self._get_report_purchase()
if purchase and purchase.lines:
return purchase.lines[0]
def _get_report_sale_line(self):
sale = self._get_report_sale()
if sale and sale.lines:
return sale.lines[0]
def _get_report_trade_line(self):
return self._get_report_sale_line() or self._get_report_purchase_line()
def _get_report_lot(self):
line = self._get_report_trade_line()
if line and line.lots:
for lot in line.lots:
if lot.lot_type == 'physic':
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_invoice_line_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:
return []
trade = self._get_report_trade()
trade_lines = getattr(trade, 'lines', []) if trade else []
lots = []
for line in trade_lines or []:
for lot in getattr(line, 'lots', []) or []:
if getattr(lot, 'lot_type', None) != 'physic':
continue
refs = [
getattr(lot, 'sale_invoice_line', None),
getattr(lot, 'sale_invoice_line_prov', None),
getattr(lot, 'invoice_line', None),
getattr(lot, 'invoice_line_prov', None),
]
if any(
self._same_report_invoice_line(ref, invoice_line)
for ref in refs for invoice_line in invoice_lines):
lots.append(lot)
return lots
@staticmethod
def _format_report_package_label(unit):
label = (
getattr(unit, 'symbol', None)
or getattr(unit, 'rec_name', None)
or getattr(unit, 'name', None)
or 'BALE'
)
label = label.upper()
if not label.endswith('S'):
label += 'S'
return label
def _get_report_freight_fee(self):
pool = Pool()
Fee = pool.get('fee.fee')
shipment = self._get_report_shipment()
if not shipment:
return None
fees = Fee.search([
('shipment_in', '=', shipment.id),
('product.name', '=', 'Maritime freight'),
], limit=1)
return fees[0] if fees else None
def _get_report_shipment(self):
shipments = self._get_report_invoice_shipments()
if len(shipments) == 1:
return shipments[0]
if len(shipments) > 1:
return None
lot = self._get_report_lot()
return self._get_report_lot_shipment(lot)
@staticmethod
def _get_report_bank_account(party):
accounts = list(getattr(party, 'bank_accounts', []) or [])
return accounts[0] if accounts else None
@staticmethod
def _get_report_bank_account_number(account):
if not account:
return ''
numbers = list(getattr(account, 'numbers', []) or [])
for number in numbers:
if getattr(number, 'type', None) == 'iban' and getattr(number, 'number', None):
return number.number or ''
for number in numbers:
if getattr(number, 'number', None):
return number.number or ''
return ''
@staticmethod
def _get_report_bank_name(account):
bank = getattr(account, 'bank', None) if account else None
party = getattr(bank, 'party', None) if bank else None
return getattr(party, 'rec_name', None) or getattr(bank, 'rec_name', None) or ''
@staticmethod
def _get_report_bank_city(account):
bank = getattr(account, 'bank', None) if account else None
party = getattr(bank, 'party', None) if bank else None
address = party.address_get() if party and hasattr(party, 'address_get') else None
return getattr(address, 'city', None) or ''
@staticmethod
def _get_report_bank_swift(account):
bank = getattr(account, 'bank', None) if account else None
return getattr(bank, 'bic', None) or ''
@staticmethod
def _get_report_bank_address(account):
bank = getattr(account, 'bank', None) if account else None
party = getattr(bank, 'party', None) if bank else None
address_get = getattr(party, 'address_get', None) if party else None
if callable(address_get):
return address_get()
return None
@staticmethod
def _get_report_bank_party_name(account):
bank = getattr(account, 'bank', None) if account else None
party = getattr(bank, 'party', None) if bank else None
address = Invoice._get_report_bank_address(account)
return (
getattr(address, 'party_full_name', None)
or getattr(address, 'party_name', None)
or getattr(party, 'rec_name', None)
or getattr(bank, 'rec_name', None)
or '')
@staticmethod
def _get_report_bank_address_lines(account):
address = Invoice._get_report_bank_address(account)
if not address:
return ''
city_line = ' '.join(filter(None, [
getattr(address, 'postal_code', None),
getattr(address, 'city', None),
]))
full_address = '\n'.join(filter(None, [
getattr(address, 'name', None),
getattr(address, 'street', None),
city_line,
]))
return '\n'.join(
line for line in (full_address or '').splitlines()
if line.strip())
@staticmethod
def _format_report_payment_amount(value):
amount = Decimal(str(value or 0)).quantize(Decimal('0.01'))
return format(amount, 'f')
def _get_report_melya_bank_account(self):
sale = self._get_report_deal_sale()
return getattr(sale, 'our_bank_account', None) if sale else None
@property
def report_melya_bank_name(self):
return self._get_report_bank_party_name(
self._get_report_melya_bank_account())
@property
def report_melya_bank_address(self):
return self._get_report_bank_address_lines(
self._get_report_melya_bank_account())
@property
def report_melya_bank_iban(self):
return self._get_report_bank_account_number(
self._get_report_melya_bank_account())
@property
def report_melya_bank_swift(self):
return self._get_report_bank_swift(
self._get_report_melya_bank_account())
@property
def report_melya_bank_details_line(self):
name = self.report_melya_bank_name
return 'BANK DETAILS:\t\t%s' % name if name else ''
@property
def report_melya_bank_account_line(self):
parts = []
iban = self.report_melya_bank_iban
swift = self.report_melya_bank_swift
if iban:
parts.append('IBAN : %s' % iban)
if swift:
parts.append('Swift Code: %s' % swift)
return ', '.join(parts)
@property
def report_melya_maturity_date(self):
maturity_dates = [
line.maturity_date for line in (self.lines_to_pay or [])
if getattr(line, 'maturity_date', None)]
if maturity_dates:
return min(maturity_dates)
@property
def _report_payment_order_company_account(self):
return self._get_report_bank_account(getattr(self.company, 'party', None))
@property
def _report_payment_order_beneficiary_account(self):
return self._get_report_bank_account(self.party)
@property
def report_payment_order_short_name(self):
company_party = getattr(self.company, 'party', None)
return getattr(company_party, 'rec_name', '') or ''
@property
def report_payment_order_document_reference(self):
return self.number or self.reference or ''
@property
def report_payment_order_from_account_nb(self):
return self._get_report_bank_account_number(
self._report_payment_order_company_account)
@property
def report_payment_order_to_bank_name(self):
return self._get_report_bank_name(self._report_payment_order_beneficiary_account)
@property
def report_payment_order_to_bank_city(self):
return self._get_report_bank_city(self._report_payment_order_beneficiary_account)
@property
def report_payment_order_amount(self):
return self._format_report_payment_amount(self.total_amount)
@property
def report_payment_order_currency_code(self):
currency = self.currency
code = getattr(currency, 'code', None) or ''
rec_name = getattr(currency, 'rec_name', None) or ''
symbol = getattr(currency, 'symbol', None) or ''
if code and any(ch.isalpha() for ch in code):
return code
if rec_name and any(ch.isalpha() for ch in rec_name):
return rec_name
if symbol and any(ch.isalpha() for ch in symbol):
return symbol
return code or rec_name or symbol or ''
@property
def report_payment_order_amount_text(self):
return amount_to_currency_words(self.total_amount)
@property
def report_payment_order_value_date(self):
value_date = self.payment_term_date or self.invoice_date
if isinstance(value_date, dt_date):
return value_date.strftime('%d-%m-%Y')
return ''
@property
def report_payment_order_company_address(self):
if self.invoice_address and getattr(self.invoice_address, 'full_address', None):
return self.invoice_address.full_address
return self.report_address
@property
def report_payment_order_beneficiary_account_nb(self):
return self._get_report_bank_account_number(
self._report_payment_order_beneficiary_account)
@property
def report_payment_order_beneficiary_bank_name(self):
return self._get_report_bank_name(self._report_payment_order_beneficiary_account)
@property
def report_payment_order_beneficiary_bank_city(self):
return self._get_report_bank_city(self._report_payment_order_beneficiary_account)
@property
def report_payment_order_swift_code(self):
return self._get_report_bank_swift(self._report_payment_order_beneficiary_account)
@property
def report_payment_order_other_instructions(self):
return self.description or ''
@property
def report_payment_order_reference(self):
return self.reference or self.number or ''
@staticmethod
def _get_report_current_user():
user_id = Transaction().user
if not user_id:
return None
User = Pool().get('res.user')
return User(user_id)
@property
def report_payment_order_current_user(self):
user = self._get_report_current_user()
return getattr(user, 'rec_name', None) or ''
@property
def report_payment_order_current_user_email(self):
user = self._get_report_current_user()
party = getattr(user, 'party', None) if user else None
if party and hasattr(party, 'contact_mechanism_get'):
return party.contact_mechanism_get('email') or ''
return getattr(user, 'email', None) or ''
@property
def report_address(self):
trade = self._get_report_trade()
if trade and trade.report_address:
return trade.report_address
if self.invoice_address and self.invoice_address.full_address:
return self.invoice_address.full_address
return ''
@property
def report_contract_number(self):
sale = self._get_report_deal_sale()
deal = getattr(sale, 'report_deal', None) if sale else None
if deal:
return deal.replace(' ', '/')
trade = self._get_report_trade()
if trade and trade.full_number:
return trade.full_number
return self.origins or ''
@property
def report_shipment(self):
trade = self._get_report_trade()
if trade and trade.report_shipment:
return trade.report_shipment
return self.description or ''
@property
def report_trader_initial(self):
trade = self._get_report_trade()
if trade and getattr(trade, 'trader', None):
return trade.trader.initial or ''
return ''
@property
def report_origin(self):
trade = self._get_report_trade()
if trade and getattr(trade, 'product_origin', None):
return trade.product_origin or ''
return ''
@property
def report_operator_initial(self):
trade = self._get_report_trade()
if trade and getattr(trade, 'operator', None):
return trade.operator.initial or ''
return ''
@property
def report_product_description(self):
line = self._get_report_trade_line()
if line and line.product:
return line.product.description or ''
return ''
@property
def report_product_name(self):
line = self._get_report_trade_line()
if line and line.product:
return line.product.name or ''
return ''
@property
def report_description_upper(self):
if self.lines:
return self._clean_report_description(self.lines[0].description)
return ''
@property
def report_crop_name(self):
trade = self._get_report_trade()
if trade and getattr(trade, 'crop', None):
return trade.crop.name or ''
return ''
@property
def report_attributes_name(self):
line = self._get_report_trade_line()
if line:
return getattr(line, 'attributes_name', '') or ''
return ''
@property
def report_price(self):
trade = self._get_report_trade()
if trade and trade.report_price:
return trade.report_price
return ''
@property
def report_quantity_lines(self):
details = []
for line in self._get_report_invoice_lines():
quantity, _ = self._get_report_invoice_line_weights(line)
if quantity == '':
continue
quantity_text = self._format_report_quantity_display(quantity)
unit = self._get_report_invoice_line_unit(line)
unit_name = unit.rec_name.upper() if unit and unit.rec_name else ''
lbs = self._convert_report_quantity_to_lbs(quantity, unit)
parts = [quantity_text, unit_name]
if lbs != '':
parts.append(
f"({self._format_report_quantity_display(lbs)} LBS)")
detail = ' '.join(part for part in parts if part)
if detail:
details.append(detail)
return '\n'.join(details)
@property
def report_trade_blocks(self):
blocks = []
quantity_lines = self.report_quantity_lines.splitlines()
rate_lines = self.report_rate_lines.splitlines()
for index, quantity_line in enumerate(quantity_lines):
price_line = rate_lines[index] if index < len(rate_lines) else ''
blocks.append((quantity_line, price_line))
return blocks
@property
def report_rate_currency_upper(self):
line = self._get_report_invoice_line()
if line:
return line.report_rate_currency_upper
return ''
@property
def report_rate_value(self):
line = self._get_report_invoice_line()
if line:
return line.report_rate_value
return ''
@property
def report_rate_unit_upper(self):
line = self._get_report_invoice_line()
if line:
return line.report_rate_unit_upper
return ''
@property
def report_rate_price_words(self):
line = self._get_report_invoice_line()
if line:
return line.report_rate_price_words
return self.report_price or ''
@property
def report_rate_pricing_text(self):
line = self._get_report_invoice_line()
if line:
return line.report_rate_pricing_text
return ''
@property
def report_rate_lines(self):
rows = self.report_rate_rows
if rows:
return '\n'.join(row['label'] for row in rows)
return ''
@property
def report_rate_rows(self):
price_composition_rows = self._get_report_price_composition_rows()
if price_composition_rows:
return price_composition_rows
return [{
'label': line,
'amount': None,
} for line in self._get_report_rate_line_details()]
@property
def report_melya_rate_rows(self):
return self._get_report_price_composition_rows()
def _get_report_rate_line_details(self):
details = []
for line in self._get_report_invoice_lines():
currency = getattr(line, 'report_rate_currency_upper', '') or ''
value = getattr(line, 'report_rate_value', '')
value_text = ''
if value != '':
value_text = self._format_report_number(
value, strip_trailing_zeros=False)
unit = getattr(line, 'report_rate_unit_upper', '') or ''
words = getattr(line, 'report_rate_price_words', '') or ''
pricing_text = getattr(line, 'report_rate_pricing_text', '') or ''
detail = ' '.join(
part for part in [
currency,
value_text,
'PER' if unit else '',
unit,
f"({words})" if words else '',
pricing_text,
] if part)
if detail:
details.append(detail)
return details
def _get_report_price_composition_rows(self):
rows = self._get_report_invoice_price_composition_rows()
if rows:
return rows
sale = self._get_report_sale()
if sale and getattr(sale, 'report_price_composition_rows', None):
return sale.report_price_composition_rows
for line in self._get_report_invoice_lines():
origin = getattr(line, 'origin', None)
sale = getattr(origin, 'sale', None)
if sale and getattr(sale, 'report_price_composition_rows', None):
return sale.report_price_composition_rows
return []
def _get_report_invoice_price_composition_rows(self):
rows = []
for invoice_line in self._get_report_invoice_lines():
sale_line = getattr(invoice_line, 'origin', None)
compositions = list(
getattr(sale_line, 'price_composition', []) or [])
if not compositions:
continue
quantity = self._get_report_price_composition_quantity(
invoice_line, sale_line)
currency = getattr(invoice_line, 'currency', None)
sale = getattr(sale_line, 'sale', None)
currency = currency or getattr(sale, 'currency', None)
currency_name = (
currency.rec_name.upper()
if currency and getattr(currency, 'rec_name', None) else '')
missing = [
composition for composition in compositions
if getattr(composition, 'price', None) in (None, '')]
known_total = sum(
Decimal(str(getattr(composition, 'price', 0) or 0)) * quantity
for composition in compositions
if getattr(composition, 'price', None) not in (None, ''))
residual = None
if len(missing) == 1:
residual = (
self._get_report_price_composition_invoice_line_amount(
invoice_line, sale_line, quantity)
- known_total)
for composition in compositions:
component = getattr(composition, 'component', '') or ''
price = getattr(composition, 'price', None)
if price in (None, ''):
if composition is missing[0] and residual is not None:
amount = residual
else:
continue
else:
amount = Decimal(str(price or 0)) * quantity
label = ' '.join(
part for part in [
component,
'value in',
currency_name,
] if part)
if label:
rows.append({
'label': label,
'amount': amount,
'amount_text': self._format_report_number(
amount, digits='0.01',
strip_trailing_zeros=False,
use_grouping=True),
})
return rows
def _get_report_price_composition_quantity(self, invoice_line, sale_line):
quantity = self._get_report_invoice_line_weights(invoice_line)[0]
return self._convert_report_quantity(
quantity,
self._get_report_invoice_line_unit(invoice_line),
getattr(sale_line, 'unit', None),
)
def _get_report_price_composition_invoice_line_amount(
self, invoice_line, sale_line, quantity):
amount = getattr(invoice_line, 'amount', None)
if amount not in (None, ''):
return Decimal(str(amount or 0))
unit_price = getattr(invoice_line, 'unit_price', None)
if unit_price not in (None, ''):
return Decimal(str(unit_price or 0)) * quantity
unit_price = Decimal(str(getattr(sale_line, 'unit_price', 0) or 0))
premium_getter = getattr(sale_line, '_get_premium_price', None)
premium = premium_getter() if premium_getter else getattr(
sale_line, 'premium', 0)
return quantity * (unit_price + Decimal(str(premium or 0)))
@property
def report_positive_rate_lines(self):
sale = self._get_report_sale()
if sale and getattr(sale, 'report_price_lines', None):
return sale.report_price_lines
details = []
for line in self._get_report_invoice_lines():
quantity = getattr(line, 'report_net', '')
if quantity == '':
quantity = getattr(line, 'quantity', '')
if Decimal(str(quantity or 0)) <= 0:
continue
currency = getattr(line, 'report_rate_currency_upper', '') or ''
value = getattr(line, 'report_rate_value', '')
value_text = ''
if value != '':
value_text = self._format_report_number(
value, strip_trailing_zeros=False)
unit = getattr(line, 'report_rate_unit_upper', '') or ''
words = getattr(line, 'report_rate_price_words', '') or ''
pricing_text = getattr(line, 'report_rate_pricing_text', '') or ''
detail = ' '.join(
part for part in [
currency,
value_text,
'PER' if unit else '',
unit,
f"({words})" if words else '',
pricing_text,
] if part)
if detail:
details.append(detail)
return '\n'.join(details)
@property
def report_payment_date(self):
trade = self._get_report_trade()
if trade and trade.report_payment_date:
return trade.report_payment_date
return ''
@property
def report_delivery_period_description(self):
trade = self._get_report_trade()
if trade and getattr(trade, 'report_delivery_period_description', None):
return trade.report_delivery_period_description
line = self._get_report_trade_line()
if line and getattr(line, 'del_period', None):
return line.del_period.description or ''
return ''
@property
def report_payment_description(self):
trade = self._get_report_trade()
if trade and trade.payment_term:
return trade.payment_term.description or ''
if self.payment_term:
return self.payment_term.description or ''
return ''
@property
def report_nb_bale(self):
total_packages = Decimal(0)
package_unit = None
has_invoice_line_packages = False
for line in self._get_report_invoice_lines():
lot = getattr(line, 'lot', None)
if not lot or getattr(lot, 'lot_qt', None) in (None, ''):
continue
has_invoice_line_packages = True
if not package_unit and getattr(lot, 'lot_unit', None):
package_unit = lot.lot_unit
sign = Decimal(1)
if Decimal(str(getattr(line, 'quantity', 0) or 0)) < 0:
sign = Decimal(-1)
total_packages += (
Decimal(str(lot.lot_qt or 0)).quantize(
Decimal('1'), rounding=ROUND_HALF_UP) * sign)
if has_invoice_line_packages:
label = self._format_report_package_label(package_unit)
return f"NB {label}: {int(total_packages)}"
lots = self._get_report_invoice_lots()
if lots:
total_packages = Decimal(0)
package_unit = None
for lot in lots:
if getattr(lot, 'lot_qt', None):
total_packages += Decimal(str(lot.lot_qt or 0))
if not package_unit and getattr(lot, 'lot_unit', None):
package_unit = lot.lot_unit
package_qty = total_packages.quantize(
Decimal('1'), rounding=ROUND_HALF_UP)
label = self._format_report_package_label(package_unit)
return f"NB {label}: {int(package_qty)}"
sale = self._get_report_sale()
if sale and sale.report_nb_bale:
return sale.report_nb_bale
line = self._get_report_trade_line()
if line and line.lots:
nb_bale = sum(
lot.lot_qt for lot in line.lots if lot.lot_type == 'physic'
)
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_packaging(self):
nb_bale = self.report_nb_bale
if nb_bale.startswith('NB ') and ': ' in nb_bale:
label, quantity = nb_bale[3:].split(': ', 1)
return f"{quantity} {label}"
return nb_bale
@property
def report_net_display(self):
net = self.report_net
if net == '':
return ''
return self._format_report_quantity_display(net)
@property
def report_gross_display(self):
gross = self.report_gross
if gross == '':
return ''
return self._format_report_quantity_display(gross)
@property
def report_lbs_display(self):
lbs = self.report_lbs
if lbs == '':
return ''
return self._format_report_quantity_display(lbs)
@property
def report_gross_lbs_display(self):
lbs = self.report_gross_lbs
if lbs == '':
return ''
return self._format_report_quantity_display(lbs)
@property
def report_weight_difference_display(self):
difference = self.report_weight_difference
if difference == '':
return ''
return self._format_report_quantity_display(difference)
@property
def report_weight_difference_lbs_display(self):
lbs = self.report_weight_difference_lbs
if lbs == '':
return ''
return self._format_report_quantity_display(lbs)
@property
def report_gross(self):
if self.lines:
adjustment = self._get_report_weight_adjustment_totals()
if adjustment:
return adjustment['landed']
reused_gross = self._get_report_reused_lot_gross_total()
if reused_gross is not None:
non_reused_total = sum(
self._get_report_invoice_line_weights(line)[1]
for line in self._get_report_invoice_lines()
if not self._report_invoice_line_reuses_lot(line))
return non_reused_total + reused_gross
return sum(
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:
return sum(
lot.get_current_gross_quantity()
for lot in line.lots if lot.lot_type == 'physic'
)
return ''
@property
def report_net(self):
if self.lines:
adjustment = self._get_report_weight_adjustment_totals()
if adjustment:
return adjustment['invoice']
return sum(
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:
return sum(
lot.get_current_quantity()
for lot in line.lots if lot.lot_type == 'physic'
)
if self.lines:
return self.lines[0].quantity
return ''
@property
def report_lbs(self):
net = self.report_net
if net == '':
return ''
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_gross_lbs(self):
gross = self.report_gross
if gross == '':
return ''
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(gross, unit)
@property
def report_weight_difference(self):
adjustment = self._get_report_weight_adjustment_totals()
if adjustment:
return adjustment['difference']
gross = self.report_gross
net = self.report_net
if gross == '' or net == '':
return ''
return gross - net
@property
def report_weight_difference_lbs(self):
difference = self.report_weight_difference
if difference == '':
return ''
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(difference, unit)
@property
def report_weight_unit_upper(self):
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'
@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'
@property
def report_transportation(self):
shipment = self._get_report_shipment()
if not shipment:
return ''
if getattr(shipment, 'transport_type', None) == 'truck':
return 'By Truck'
vessel = getattr(shipment, 'vessel', None)
vessel_name = getattr(vessel, 'vessel_name', None) or ''
return vessel_name
@property
def report_bl_date(self):
shipment = self._get_report_shipment()
if shipment:
return shipment.bl_date
@property
def report_bl_nb(self):
shipment = self._get_report_shipment()
if shipment:
return shipment.bl_number
@property
def report_vessel(self):
shipment = self._get_report_shipment()
if shipment and shipment.vessel:
return shipment.vessel.vessel_name
@property
def report_loading_port(self):
shipment = self._get_report_shipment()
if shipment and shipment.from_location:
return shipment.from_location.rec_name
return ''
@property
def report_discharge_port(self):
shipment = self._get_report_shipment()
if shipment and shipment.to_location:
return shipment.to_location.rec_name
return ''
@property
def report_incoterm(self):
trade = self._get_report_trade()
if not trade:
return ''
incoterm = trade.incoterm.code if getattr(trade, 'incoterm', None) else ''
location = (
trade.incoterm_location.party_name
if getattr(trade, 'incoterm_location', None) else ''
)
if incoterm and location:
return f"{incoterm} {location}"
return incoterm or location
@property
def report_proforma_invoice_number(self):
lot = self._get_report_lot()
if lot:
line = (
getattr(lot, 'sale_invoice_line_prov', None)
or getattr(lot, 'invoice_line_prov', None)
)
if line and line.invoice:
return line.invoice.number or ''
return ''
@property
def report_proforma_invoice_date(self):
lot = self._get_report_lot()
if lot:
line = (
getattr(lot, 'sale_invoice_line_prov', None)
or getattr(lot, 'invoice_line_prov', None)
)
if line and line.invoice:
return line.invoice.invoice_date
@property
def report_controller_name(self):
shipment = self._get_report_shipment()
if shipment and shipment.controller:
return shipment.controller.rec_name
return ''
@property
def report_si_number(self):
shipment = self._get_report_shipment()
if shipment:
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()
if fee:
return fee.get_amount()
return ''
@property
def report_freight_currency_symbol(self):
fee = self._get_report_freight_fee()
if fee and fee.currency:
return fee.currency.symbol or ''
if self.currency:
return self.currency.symbol or ''
return 'USD'
class InvoicePaddingReport(ModelSQL, ModelView):
"Invoice with padding"
__name__ = 'invoice.padding.report'
party = fields.Many2One('party.party', "Party")
sale = fields.Many2One('sale.sale', "Sale")
sale_line = fields.Many2One('sale.line', "Sale Line")
lot = fields.Many2One('lot.lot', "Lot")
product = fields.Many2One('product.product', "Product")
currency = fields.Many2One('currency.currency', "Currency")
unit = fields.Many2One('product.uom', "Unit")
padding_quantity = fields.Numeric("Padding Quantity", digits='unit')
unit_price = fields.Numeric("Unit Price", digits=(16, 6))
padding_amount = fields.Numeric("Padding Amount", digits=(16, 2))
provisional_invoice = fields.Many2One(
'account.invoice', "Provisional Invoice")
provisional_date = fields.Date("Provisional Date")
provisional_state = fields.Selection([
('draft', "Draft"),
('validated', "Validated"),
('posted', "Posted"),
('paid', "Paid"),
('cancelled', "Cancelled"),
], "Provisional State")
final_invoice = fields.Many2One('account.invoice', "Final Invoice")
final_date = fields.Date("Final Date")
final_state = fields.Selection([
(None, ''),
('draft', "Draft"),
('validated', "Validated"),
('posted', "Posted"),
('paid', "Paid"),
('cancelled', "Cancelled"),
], "Final State")
padding_status = fields.Selection([
('active', "Active"),
('reversed', "Reversed"),
], "Padding Status")
@classmethod
def table_query(cls):
pool = Pool()
Lot = pool.get('lot.lot')
SaleLine = pool.get('sale.line')
Sale = pool.get('sale.sale')
InvoiceLine = pool.get('account.invoice.line')
Invoice = pool.get('account.invoice')
lot = Lot.__table__()
sale_line = SaleLine.__table__()
sale = Sale.__table__()
prov_line = InvoiceLine.__table__()
prov_invoice = Invoice.__table__()
final_line = InvoiceLine.__table__()
final_invoice = Invoice.__table__()
context = Transaction().context
party = context.get('party')
currency = context.get('currency')
status_filter = context.get('status')
from_date = context.get('from_date')
to_date = context.get('to_date')
sale_filter = context.get('sale')
lot_filter = context.get('lot')
reversed_condition = (
(final_invoice.id > 0)
& ~final_invoice.state.in_(['draft', 'cancelled']))
padding_status = Case(
(reversed_condition, 'reversed'),
else_='active')
where = Literal(True)
where &= lot.sale_invoice_padding > 0
where &= prov_invoice.id > 0
if party:
where &= prov_invoice.party == party
if currency:
where &= prov_invoice.currency == currency
if status_filter and status_filter != 'all':
where &= padding_status == status_filter
if from_date:
where &= prov_invoice.invoice_date >= from_date
if to_date:
where &= prov_invoice.invoice_date <= to_date
if sale_filter:
where &= sale.id == sale_filter
if lot_filter:
where &= lot.id == lot_filter
return (
lot
.join(sale_line, 'LEFT', condition=lot.sale_line == sale_line.id)
.join(sale, 'LEFT', condition=sale_line.sale == sale.id)
.join(prov_line, 'LEFT',
condition=lot.sale_invoice_line_prov == prov_line.id)
.join(prov_invoice, 'LEFT',
condition=prov_line.invoice == prov_invoice.id)
.join(final_line, 'LEFT',
condition=lot.sale_invoice_line == final_line.id)
.join(final_invoice, 'LEFT',
condition=final_line.invoice == final_invoice.id)
.select(
Literal(0).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Literal(0).as_('write_uid'),
Literal(None).as_('write_date'),
lot.id.as_('id'),
prov_invoice.party.as_('party'),
sale.id.as_('sale'),
sale_line.id.as_('sale_line'),
lot.id.as_('lot'),
prov_line.product.as_('product'),
prov_invoice.currency.as_('currency'),
prov_line.unit.as_('unit'),
lot.sale_invoice_padding.as_('padding_quantity'),
prov_line.unit_price.as_('unit_price'),
(lot.sale_invoice_padding * prov_line.unit_price).as_(
'padding_amount'),
prov_invoice.id.as_('provisional_invoice'),
prov_invoice.invoice_date.as_('provisional_date'),
prov_invoice.state.as_('provisional_state'),
final_invoice.id.as_('final_invoice'),
final_invoice.invoice_date.as_('final_date'),
final_invoice.state.as_('final_state'),
padding_status.as_('padding_status'),
where=where,
))
class InvoicePaddingContext(ModelView):
"Invoice with padding context"
__name__ = 'invoice.padding.context'
from_date = fields.Date("From")
to_date = fields.Date("To")
party = fields.Many2One('party.party', "Party")
currency = fields.Many2One('currency.currency', "Currency")
status = fields.Selection([
('all', "All"),
('active', "Active"),
('reversed', "Reversed"),
], "Status")
sale = fields.Many2One('sale.sale', "Sale")
lot = fields.Many2One('lot.lot', "Lot")
@classmethod
def default_from_date(cls):
Date = Pool().get('ir.date')
return Date.today().replace(day=1, month=1, year=1999)
@classmethod
def default_to_date(cls):
Date = Pool().get('ir.date')
return Date.today()
@classmethod
def default_status(cls):
return 'active'
class InvoiceLine(metaclass=PoolMeta):
__name__ = 'account.invoice.line'
def _get_report_trade(self):
origin = getattr(self, 'origin', None)
if not origin:
return None
return getattr(origin, 'sale', None) or getattr(origin, 'purchase', None)
def _get_report_trade_line(self):
return getattr(self, 'origin', None)
@property
def report_product_description(self):
if self.product:
return self.product.description or ''
origin = getattr(self, 'origin', None)
if origin and getattr(origin, 'product', None):
return origin.product.description or ''
return ''
@property
def report_product_name(self):
if self.product:
return self.product.name or ''
origin = getattr(self, 'origin', None)
if origin and getattr(origin, 'product', None):
return origin.product.name or ''
return ''
@property
def report_description_upper(self):
return Invoice._clean_report_description(self.description)
@property
def report_rate_currency_upper(self):
origin = self._get_report_trade_line()
currency = getattr(origin, 'linked_currency', None) or self.currency
if currency and currency.rec_name:
return currency.rec_name.upper()
return ''
@property
def report_rate_value(self):
origin = self._get_report_trade_line()
if origin and getattr(origin, 'price_type', None) == 'basis':
if getattr(origin, 'enable_linked_currency', False) and getattr(origin, 'linked_currency', None):
return Decimal(str(origin.premium or 0))
return Decimal(str(origin._get_premium_price() or 0))
return self.unit_price if self.unit_price is not None else ''
@property
def report_rate_unit_upper(self):
origin = self._get_report_trade_line()
unit = getattr(origin, 'linked_unit', None) or self.unit
if unit and unit.rec_name:
return unit.rec_name.upper()
return ''
@property
def report_rate_price_words(self):
origin = self._get_report_trade_line()
if origin and getattr(origin, 'price_type', None) == 'basis':
value = self.report_rate_value
if self.report_rate_currency_upper == 'USC':
return amount_to_currency_words(value, 'USC', 'USC')
return amount_to_currency_words(value)
trade = self._get_report_trade()
if trade and getattr(trade, 'report_price', None):
return trade.report_price
return ''
@property
def report_rate_pricing_text(self):
origin = self._get_report_trade_line()
return getattr(origin, 'get_pricing_text', '') or ''
@property
def report_crop_name(self):
trade = self._get_report_trade()
if trade and getattr(trade, 'crop', None):
return trade.crop.name or ''
return ''
@property
def report_attributes_name(self):
origin = getattr(self, 'origin', None)
if origin:
return getattr(origin, 'attributes_name', '') or ''
return ''
@property
def report_net(self):
if self.type == 'line':
invoice = getattr(self, 'invoice', None)
snapshots = Invoice._get_report_invoice_line_weight_snapshots(self)
if snapshots:
sign = Invoice._get_report_line_sign(self)
net = sum(
Decimal(str(snapshot.net_quantity or 0))
for snapshot in snapshots)
unit = Invoice._get_report_invoice_line_unit(self)
net += Invoice._get_report_invoice_line_padding(self, unit)
return net * sign
if invoice and invoice._report_invoice_line_reuses_lot(self):
return Invoice._get_report_invoice_line_quantity_from_line(self)
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 ''
@property
def report_lbs(self):
net = self.report_net
if net == '':
return ''
unit = Invoice._get_report_invoice_line_unit(self)
return Invoice._convert_report_quantity_to_lbs(net, unit)
class ReportTemplateMixin:
@classmethod
def _get_purchase_trade_configuration(cls):
Configuration = Pool().get('purchase_trade.configuration')
configurations = Configuration.search([], limit=1)
return configurations[0] if configurations else None
@classmethod
def _get_action_name(cls, action):
if isinstance(action, dict):
return action.get('name') or ''
return getattr(action, 'name', '') or ''
@classmethod
def _get_action_report_path(cls, action):
if isinstance(action, dict):
return action.get('report') or ''
return getattr(action, 'report', '') or ''
@classmethod
def _resolve_template_path(cls, action, field_name, default_prefix):
config = cls._get_purchase_trade_configuration()
template = getattr(config, field_name, '') if config else ''
template = (template or '').strip()
if not template:
raise UserError('No template found')
if '/' not in template:
return f'{default_prefix}/{template}'
return template
@classmethod
def _load_report_content(cls, report_path):
file_name = report_path.replace('/', os.sep)
try:
with file_open(file_name, mode='rb') as fp:
return fp.read()
except FileNotFoundError:
modules_dir = Path(__file__).resolve().parent.parent
path = modules_dir.joinpath(*report_path.split('/'))
try:
return path.read_bytes()
except FileNotFoundError:
raise UserError('No template found')
@classmethod
def _get_resolved_action(cls, action):
report_path = cls._resolve_configured_report_path(action)
report_content = cls._load_report_content(report_path)
if isinstance(action, dict):
resolved = dict(action)
resolved['report'] = report_path
resolved['report_content'] = report_content
return resolved
setattr(action, 'report', report_path)
setattr(action, 'report_content', report_content)
action._template_cache.clear()
return action
@classmethod
def _execute(cls, records, header, data, action):
resolved_action = cls._get_resolved_action(action)
return super()._execute(records, header, data, resolved_action)
class InvoiceReport(ReportTemplateMixin, BaseInvoiceReport):
__name__ = 'account.invoice'
@classmethod
def _resolve_configured_report_path(cls, action):
report_path = cls._get_action_report_path(action) or ''
action_name = cls._get_action_name(action)
legacy_prefix = None
if (report_path.endswith('/prepayment.fodt')
or action_name == 'Prepayment'):
field_name = 'invoice_prepayment_report_template'
elif report_path.endswith('/packing_list.fodt'):
field_name = 'invoice_packing_list_report_template'
elif (report_path.endswith('/payment_order.fodt')
or action_name == 'Payment Order'):
field_name = 'invoice_payment_order_report_template'
elif (report_path.endswith('/invoice_ict_final.fodt')
or action_name == 'CN/DN'):
field_name = 'invoice_cndn_report_template'
elif (report_path.endswith('/commission__ict_final.fodt')
or action_name == 'CN/DN Commission'):
field_name = 'invoice_commission_cndn_report_template'
elif (report_path.endswith('/sale_commission_ict.fodt')
or action_name == 'Commission invoice Sale'):
field_name = 'sale_commission_report_template'
legacy_prefix = 'sale/'
elif (report_path.endswith('/purchase_commission_ict.fodt')
or action_name == 'Commission invoice Purchase'):
field_name = 'purchase_commission_report_template'
legacy_prefix = 'purchase/'
else:
field_name = 'invoice_report_template'
resolved_path = cls._resolve_template_path(
action, field_name, 'account_invoice')
if legacy_prefix and resolved_path.startswith(legacy_prefix):
return 'account_invoice/%s' % resolved_path.rsplit('/', 1)[-1]
return resolved_path
class SaleReport(ReportTemplateMixin, BaseSaleReport):
__name__ = 'sale.sale'
@classmethod
def _resolve_configured_report_path(cls, action):
report_path = cls._get_action_report_path(action)
action_name = cls._get_action_name(action)
if report_path.endswith('/bill.fodt') or action_name == 'Bill':
field_name = 'sale_bill_report_template'
elif report_path.endswith('/sale_final.fodt') or action_name == 'Sale (final)':
field_name = 'sale_final_report_template'
else:
field_name = 'sale_report_template'
return cls._resolve_template_path(action, field_name, 'sale')
class PurchaseReport(ReportTemplateMixin, BasePurchaseReport):
__name__ = 'purchase.purchase'
@classmethod
def _resolve_configured_report_path(cls, action):
report_path = cls._get_action_report_path(action)
action_name = cls._get_action_name(action)
return cls._resolve_template_path(
action, 'purchase_report_template', 'purchase')