2813 lines
103 KiB
Python
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')
|