trytond-sale_payment/sale.py

220 lines
7.5 KiB
Python

# This file is part of the sale_payment module for Tryton.
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from decimal import Decimal
from trytond.model import ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, StateTransition, Button
__all__ = ['Sale', 'SalePaymentForm', 'WizardSalePayment', 'WizardSaleReconcile']
__metaclass__ = PoolMeta
class Sale:
__name__ = 'sale.sale'
payments = fields.One2Many('account.statement.line', 'sale', 'Payments')
paid_amount = fields.Function(fields.Numeric('Paid Amount', readonly=True),
'get_paid_amount')
residual_amount = fields.Function(fields.Numeric('Residual Amount',
readonly=True), 'get_residual_amount')
@classmethod
def __setup__(cls):
super(Sale, cls).__setup__()
cls._buttons.update({
'wizard_sale_payment': {
'invisible': Eval('state') == 'done',
},
})
@classmethod
def get_paid_amount(cls, sales, names):
result = {n: {s.id: Decimal(0) for s in sales} for n in names}
for name in names:
for sale in sales:
for payment in sale.payments:
result[name][sale.id] += payment.amount
return result
@classmethod
def get_residual_amount(cls, sales, names):
return {
n: {s.id: s.total_amount - s.paid_amount for s in sales}
for n in names
}
@classmethod
@ModelView.button_action('sale_payment.wizard_sale_payment')
def wizard_sale_payment(cls, sales):
pass
@classmethod
def copy(cls, sales, default=None):
if default is None:
default = {}
default['payments'] = None
return super(Sale, cls).copy(sales, default)
def create_moves_without_shipment(self):
pass
class SalePaymentForm(ModelView):
'Sale Payment Form'
__name__ = 'sale.payment.form'
journal = fields.Many2One('account.statement.journal', 'Statement Journal',
domain=[
('id', 'in', Eval('journals', [])),
],
depends=['journals'], required=True)
journals = fields.One2Many('account.statement.journal', None,
'Allowed Statement Journals')
payment_amount = fields.Numeric('Payment amount', required=True,
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
currency_digits = fields.Integer('Currency Digits')
party = fields.Many2One('party.party', 'Party', readonly=True)
class WizardSalePayment(Wizard):
'Wizard Sale Payment'
__name__ = 'sale.payment'
start = StateView('sale.payment.form',
'sale_payment.sale_payment_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Pay', 'pay_', 'tryton-ok', default=True),
])
pay_ = StateTransition()
@classmethod
def __setup__(cls):
super(WizardSalePayment, cls).__setup__()
cls._error_messages.update({
'not_sale_device': ('You have not defined a sale device for '
'your user.'),
'not_draft_statement': ('A draft statement for "%s" payments '
'has not been created.'),
'not_customer_invoice': ('A customer invoice/refund '
'from sale device has not been created.'),
})
def default_start(self, fields):
pool = Pool()
Sale = pool.get('sale.sale')
User = pool.get('res.user')
sale = Sale(Transaction().context['active_id'])
user = User(Transaction().user)
if user.id != 0 and not user.sale_device:
self.raise_user_error('not_sale_device')
return {
'journal': user.sale_device.journal.id
if user.sale_device.journal else None,
'journals': [j.id for j in user.sale_device.journals],
'payment_amount': sale.total_amount - sale.paid_amount
if sale.paid_amount else sale.total_amount,
'currency_digits': sale.currency_digits,
'party': sale.party.id,
}
def transition_pay_(self):
pool = Pool()
Date = pool.get('ir.date')
Invoice = pool.get('account.invoice')
Sale = pool.get('sale.sale')
Statement = pool.get('account.statement')
StatementLine = pool.get('account.statement.line')
form = self.start
statements = Statement.search([
('journal', '=', form.journal),
('state', '=', 'draft'),
], order=[('date', 'DESC')])
if not statements:
self.raise_user_error('not_draft_statement', (form.journal.name,))
active_id = Transaction().context.get('active_id', False)
sale = Sale(active_id)
if not sale.reference:
Sale.set_reference([sale])
payment = StatementLine(
statement=statements[0].id,
date=Date.today(),
amount=form.payment_amount,
party=sale.party.id,
account=sale.party.account_receivable.id,
description=sale.reference,
sale=active_id
)
payment.save()
if sale.total_amount != sale.paid_amount:
return 'start'
if sale.state != 'draft':
return 'end'
sale.description = sale.reference
sale.save()
Sale.quote([sale])
Sale.confirm([sale])
Sale.process([sale])
if not sale.invoices and sale.invoice_method == 'order':
self.raise_user_error('not_customer_invoice')
sale.create_moves_without_shipment()
grouping = getattr(sale.party, 'sale_invoice_grouping_method', False)
if sale.invoices and not grouping:
for invoice in sale.invoices:
if invoice.state == 'draft':
invoice.description = sale.reference
invoice.save()
Invoice.post(sale.invoices)
for payment in sale.payments:
payment.invoice = sale.invoices[0].id
payment.save()
if sale.is_done():
sale.state = 'done'
sale.save()
return 'end'
class WizardSaleReconcile(Wizard):
'Reconcile Sales'
__name__ = 'sale.reconcile'
start = StateTransition()
reconcile = StateTransition()
def transition_start(self):
pool = Pool()
Sale = pool.get('sale.sale')
Line = pool.get('account.move.line')
for sale in Sale.browse(Transaction().context['active_ids']):
account = sale.party.account_receivable
lines = []
amount = Decimal('0.0')
for invoice in sale.invoices:
for line in Line.browse(invoice.get_lines_to_pay(None)):
if not line.reconciliation:
lines.append(line)
amount += line.debit - line.credit
for payment in sale.payments:
if not payment.move:
continue
for line in payment.move.lines:
if (not line.reconciliation and
line.account.id == account.id):
lines.append(line)
amount += line.debit - line.credit
if lines and amount == Decimal('0.0'):
Line.reconcile(lines)
return 'end'