# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import functools from trytond.i18n import gettext from trytond.model import ModelView, Workflow, fields from trytond.model.exceptions import AccessError from trytond.pool import PoolMeta from trytond.pyson import Bool, Eval, If from trytond.transaction import Transaction def no_payment(error): def decorator(func): @functools.wraps(func) def wrapper(cls, sales, *args, **kwargs): for sale in sales: if not all((p.state == 'failed' for p in sale.payments)): raise AccessError(gettext(error, sale=sale.rec_name)) return func(cls, sales, *args, **kwargs) return wrapper return decorator class Sale(metaclass=PoolMeta): __name__ = 'sale.sale' payments = fields.One2Many( 'account.payment', 'origin', "Payments", domain=[ ('company', '=', Eval('company', -1)), ['OR', ('party', '=', If(Bool(Eval('invoice_party')), Eval('invoice_party', -1), Eval('party', -1))), ('state', '!=', 'draft'), ], ('currency', '=', Eval('currency', -1)), ], states={ 'readonly': Eval('state') != 'quotation', }) @classmethod @ModelView.button @Workflow.transition('cancelled') @no_payment('sale_payment.msg_sale_cancel_payment') def cancel(cls, sales): super().cancel(sales) @classmethod @ModelView.button @Workflow.transition('draft') @no_payment('sale_payment.msg_sale_draft_payment') def draft(cls, sales): super().draft(sales) @classmethod def copy(cls, sales, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('payments', None) return super().copy(sales, default=default) @property def payment_amount_authorized(self): "Total amount of the authorized payments" return (sum( p.amount for p in self.payments if p.kind == 'receivable' and p.is_authorized) - sum(p.amount for p in self.payments if p.kind == 'payable')) @property def amount_to_pay(self): "Amount to pay to confirm the sale" return self.total_amount @classmethod def payment_confirm(cls, sales=None): "Confirm the sale based on payment authorization" if sales is None: context = Transaction().context sales = cls.search([ ('state', '=', 'quotation'), ('payments', '!=', None), ('company', '=', context.get('company')), ]) def cover(authorized, amount): return ( abs(authorized) >= abs(amount) and (authorized * amount >= 0)) to_confirm = [] for sale in sales: if cover(sale.payment_amount_authorized, sale.amount_to_pay): to_confirm.append(sale) if to_confirm: to_confirm = cls.browse(to_confirm) # optimize cache cls.confirm(to_confirm) @property def credit_limit_amount(self): amount = super().credit_limit_amount return max(0, amount - self.payment_amount_authorized)