from decimal import Decimal, ROUND_HALF_UP from datetime import date as dt_date 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.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 Invoice(metaclass=PoolMeta): __name__ = 'account.invoice' @staticmethod def _format_report_number(value, digits='0.0000', keep_trailing_decimal=False, strip_trailing_zeros=True): value = Decimal(str(value or 0)).quantize(Decimal(digits)) text = format(value, 'f') if strip_trailing_zeros: text = text.rstrip('0').rstrip('.') if keep_trailing_decimal and '.' not in text: text += '.0' return text or '0' 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 _get_report_related_lots(line): lots = [] seen = set() def add_lot(lot): if not lot: return lot_id = getattr(lot, 'id', None) key = ('id', lot_id) if lot_id is not None else ('obj', id(lot)) if key in seen: return seen.add(key) lots.append(lot) add_lot(getattr(line, 'lot', None)) origin = getattr(line, 'origin', None) for lot in getattr(origin, 'lots', []) or []: add_lot(lot) return lots @classmethod def _get_report_preferred_lots(cls, line): lots = cls._get_report_related_lots(line) physicals = [ lot for lot in lots if getattr(lot, 'lot_type', None) == 'physic' ] if physicals: return physicals virtuals = [ lot for lot in lots if getattr(lot, 'lot_type', None) == 'virtual' ] if len(virtuals) == 1: return virtuals return [] @staticmethod def _get_report_line_sign(line): quantity = Decimal(str(getattr(line, 'quantity', 0) or 0)) return Decimal(-1) if quantity < 0 else Decimal(1) @staticmethod def _get_report_lot_hist_weights(lot): if not lot: return None, None if hasattr(lot, 'get_hist_quantity'): net, gross = lot.get_hist_quantity() return ( Decimal(str(net or 0)), Decimal(str(gross if gross not in (None, '') else net or 0)), ) hist = list(getattr(lot, 'lot_hist', []) or []) state = getattr(lot, 'lot_state', None) state_id = getattr(state, 'id', None) if state_id is not None: for entry in hist: quantity_type = getattr(entry, 'quantity_type', None) if getattr(quantity_type, 'id', None) == state_id: net = Decimal(str(getattr(entry, 'quantity', 0) or 0)) gross = Decimal(str( getattr(entry, 'gross_quantity', None) if getattr(entry, 'gross_quantity', None) not in (None, '') else net)) return net, gross return None, None def _get_report_invoice_line_weights(self, line): lots = self._get_report_preferred_lots(line) if lots: sign = self._get_report_line_sign(line) net_total = Decimal(0) gross_total = Decimal(0) for lot in lots: net, gross = self._get_report_lot_hist_weights(lot) if net is None: continue net_total += net gross_total += gross if net_total or gross_total: return net_total * sign, gross_total * sign quantity = Decimal(str(getattr(line, 'quantity', 0) or 0)) return quantity, quantity @staticmethod def _get_report_invoice_line_unit(line): lots = Invoice._get_report_preferred_lots(line) if lots and getattr(lots[0], 'lot_unit_line', None): return lots[0].lot_unit_line return getattr(line, 'unit', None) @staticmethod 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(self.purchases 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(self.sales 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_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_preferred_lots(line): shipment = self._get_report_lot_shipment(lot) if not shipment: continue shipment_id = getattr(shipment, 'id', None) key = ( getattr(shipment, '__name__', None), shipment_id if shipment_id is not None else id(shipment), ) if key in seen: continue seen.add(key) shipments.append(shipment) return shipments def _get_report_invoice_lots(self): invoice_lines = self._get_report_invoice_lines() if not invoice_lines: return [] def _same_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 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( _same_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 _format_report_payment_amount(value): amount = Decimal(str(value or 0)).quantize(Decimal('0.01')) return format(amount, 'f') @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): 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_number( quantity, keep_trailing_decimal=True) unit = self._get_report_invoice_line_unit(line) unit_name = unit.rec_name.upper() if unit and unit.rec_name else '' lbs = self._convert_report_quantity_to_lbs(quantity, unit) parts = [quantity_text, unit_name] if lbs != '': parts.append( f"({self._format_report_number(lbs, digits='0.01')} 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): 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 '\n'.join(details) @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_gross(self): if self.lines: 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: 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_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_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 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': 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 _get_resolved_action(cls, action): report_path = cls._resolve_configured_report_path(action) if isinstance(action, dict): resolved = dict(action) resolved['report'] = report_path return resolved setattr(action, 'report', report_path) 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) if (report_path.endswith('/prepayment.fodt') or action_name == 'Prepayment'): field_name = 'invoice_prepayment_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' else: field_name = 'invoice_report_template' return cls._resolve_template_path(action, field_name, 'account_invoice') 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): return cls._resolve_template_path( action, 'purchase_report_template', 'purchase')