first commit
This commit is contained in:
2
modules/sale_payment/__init__.py
Normal file
2
modules/sale_payment/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
BIN
modules/sale_payment/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
modules/sale_payment/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
modules/sale_payment/__pycache__/account.cpython-311.pyc
Normal file
BIN
modules/sale_payment/__pycache__/account.cpython-311.pyc
Normal file
Binary file not shown.
BIN
modules/sale_payment/__pycache__/ir.cpython-311.pyc
Normal file
BIN
modules/sale_payment/__pycache__/ir.cpython-311.pyc
Normal file
Binary file not shown.
BIN
modules/sale_payment/__pycache__/sale.cpython-311.pyc
Normal file
BIN
modules/sale_payment/__pycache__/sale.cpython-311.pyc
Normal file
Binary file not shown.
195
modules/sale_payment/account.py
Normal file
195
modules/sale_payment/account.py
Normal file
@@ -0,0 +1,195 @@
|
||||
# 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 decimal import Decimal
|
||||
|
||||
from trytond.model import fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval, If
|
||||
|
||||
|
||||
def sale_payment_confirm(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(cls, payments, *args, **kwargs):
|
||||
pool = Pool()
|
||||
Sale = pool.get('sale.sale')
|
||||
|
||||
result = func(cls, payments, *args, **kwargs)
|
||||
|
||||
sales = {p.origin for p in payments if isinstance(p.origin, Sale)}
|
||||
Sale.__queue__.payment_confirm(sales)
|
||||
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
|
||||
class Payment(metaclass=PoolMeta):
|
||||
__name__ = 'account.payment'
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.origin.domain['sale.sale'] = [
|
||||
If(~Eval('state').in_(['failed', 'succeeded']),
|
||||
('state', '!=', 'draft'),
|
||||
()),
|
||||
If(Eval('state') == 'draft',
|
||||
('state', '!=', 'cancelled'),
|
||||
()),
|
||||
('company', '=', Eval('company', -1)),
|
||||
If(Eval('state') == 'draft',
|
||||
['OR',
|
||||
('invoice_party', '=', Eval('party', -1)),
|
||||
[
|
||||
('invoice_party', '=', None),
|
||||
('party', '=', Eval('party', -1)),
|
||||
],
|
||||
],
|
||||
[]),
|
||||
('currency', '=', Eval('currency', -1)),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _get_origin(cls):
|
||||
return super()._get_origin() + ['sale.sale']
|
||||
|
||||
@fields.depends('origin')
|
||||
def on_change_origin(self):
|
||||
pool = Pool()
|
||||
Sale = pool.get('sale.sale')
|
||||
try:
|
||||
super().on_change_origin()
|
||||
except AttributeError:
|
||||
pass
|
||||
if self.origin and isinstance(self.origin, Sale):
|
||||
sale = self.origin
|
||||
party = (
|
||||
getattr(sale, 'invoice_party', None)
|
||||
or getattr(sale, 'party', None))
|
||||
if party:
|
||||
self.party = party
|
||||
sale_amount = getattr(sale, 'total_amount', None)
|
||||
payment_amount = sum(
|
||||
(p.amount for p in getattr(sale, 'payments', [])
|
||||
if p.state != 'failed' and p != self),
|
||||
Decimal(0))
|
||||
if sale_amount is not None:
|
||||
self.kind = 'receivable' if sale_amount > 0 else 'payable'
|
||||
self.amount = abs(sale_amount) - payment_amount
|
||||
currency = getattr(sale, 'currency', None)
|
||||
if currency is not None:
|
||||
self.currency = currency
|
||||
|
||||
@classmethod
|
||||
def on_modification(cls, mode, payments, field_names=None):
|
||||
super().on_modification(mode, payments, field_names=field_names)
|
||||
if mode == 'create':
|
||||
cls.trigger_authorized([p for p in payments if p.is_authorized])
|
||||
|
||||
@classmethod
|
||||
def on_write(cls, payments, values):
|
||||
callback = super().on_write(payments, values)
|
||||
if unauthorized := {p for p in payments if not p.is_authorized}:
|
||||
def trigger():
|
||||
authorized = {p for p in payments if p.is_authorized}
|
||||
cls.trigger_authorized(cls.browse(unauthorized & authorized))
|
||||
callback.append(trigger)
|
||||
return callback
|
||||
|
||||
@property
|
||||
def is_authorized(self): # TODO: move to account_payment
|
||||
return self.state == 'succeeded'
|
||||
|
||||
@classmethod
|
||||
@sale_payment_confirm
|
||||
def trigger_authorized(cls, payments):
|
||||
pass
|
||||
|
||||
|
||||
class Invoice(metaclass=PoolMeta):
|
||||
__name__ = 'account.invoice'
|
||||
|
||||
def add_payments(self, payments=None):
|
||||
"Add payments from sales lines to pay"
|
||||
if payments is None:
|
||||
payments = []
|
||||
payments = set(payments)
|
||||
for sale in self.sales:
|
||||
payments.update(sale.payments)
|
||||
payments = list(payments)
|
||||
# Knapsack problem:
|
||||
# simple heuristic by trying to fill biggest amount first.
|
||||
payments.sort(key=lambda p: p.amount)
|
||||
lines_to_pay = sorted(
|
||||
self.lines_to_pay, key=lambda l: l.payment_amount)
|
||||
for line in lines_to_pay:
|
||||
if line.reconciliation:
|
||||
continue
|
||||
payment_amount = line.payment_amount
|
||||
for payment in payments:
|
||||
if payment.line or payment.state == 'failed':
|
||||
continue
|
||||
if ((payment.kind == 'receivable' and line.credit > 0)
|
||||
or (payment.kind == 'payable' and line.debit > 0)):
|
||||
continue
|
||||
if payment.party != line.party:
|
||||
continue
|
||||
if (getattr(payment, 'account', None)
|
||||
and payment.account != line.account):
|
||||
continue
|
||||
if payment.amount <= payment_amount:
|
||||
payment.line = line
|
||||
if hasattr(payment, 'account'):
|
||||
payment.account = None
|
||||
payment_amount -= payment.amount
|
||||
return payments
|
||||
|
||||
def reconcile_payments(self):
|
||||
pool = Pool()
|
||||
Payment = pool.get('account.payment')
|
||||
Line = pool.get('account.move.line')
|
||||
if not hasattr(Payment, 'clearing_move'):
|
||||
return
|
||||
|
||||
def balance(line):
|
||||
if self.currency == line.second_currency:
|
||||
return line.amount_second_currency
|
||||
elif self.currency == self.company.currency:
|
||||
return line.debit - line.credit
|
||||
else:
|
||||
return 0
|
||||
to_reconcile = []
|
||||
for line in self.lines_to_pay:
|
||||
if line.reconciliation:
|
||||
continue
|
||||
lines = [line]
|
||||
for payment in line.payments:
|
||||
if payment.state == 'succeeded' and payment.clearing_move:
|
||||
for pline in payment.clearing_move.lines:
|
||||
if (pline.account == line.account
|
||||
and not pline.reconciliation):
|
||||
lines.append(pline)
|
||||
if not sum(map(balance, lines)):
|
||||
to_reconcile.append(lines)
|
||||
for lines in to_reconcile:
|
||||
Line.reconcile(lines)
|
||||
|
||||
@classmethod
|
||||
def _post(cls, invoices):
|
||||
pool = Pool()
|
||||
Payment = pool.get('account.payment')
|
||||
|
||||
super()._post(invoices)
|
||||
|
||||
payments = set()
|
||||
for invoice in invoices:
|
||||
payments.update(invoice.add_payments())
|
||||
if payments:
|
||||
Payment.save(payments)
|
||||
if hasattr(Payment, 'clearing_move'):
|
||||
# Ensure clearing move is created as succeed may happen
|
||||
# before the payment has a line.
|
||||
Payment.set_clearing_move(
|
||||
[p for p in payments if p.state == 'succeeded'])
|
||||
for invoice in invoices:
|
||||
invoice.reconcile_payments()
|
||||
14
modules/sale_payment/ir.py
Normal file
14
modules/sale_payment/ir.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
|
||||
class Cron(metaclass=PoolMeta):
|
||||
__name__ = 'ir.cron'
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.method.selection.append(
|
||||
('sale.sale|payment_confirm', "Confirm sales based on payment"))
|
||||
25
modules/sale_payment/locale/bg.po
Normal file
25
modules/sale_payment/locale/bg.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/ca.po
Normal file
25
modules/sale_payment/locale/ca.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Cobraments"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Cobraments"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr "No podeu cancel·lar la venda \"%(sale)s\" perquè té pagaments."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr "No podeu restablir a esborrany la venda \"%(sale)s\" perquè té pagaments."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr "Confirma vendes pagades"
|
||||
25
modules/sale_payment/locale/cs.po
Normal file
25
modules/sale_payment/locale/cs.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
29
modules/sale_payment/locale/de.po
Normal file
29
modules/sale_payment/locale/de.po
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Zahlungen"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Zahlungen"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Der Verkauf \"%(sale)s\" kann nicht annulliert werden, weil es zugehörige "
|
||||
"Zahlungen gibt."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Der Verkauf \"%(sale)s\" kann nicht auf den Entwurfsstatus zurückgesetzt "
|
||||
"werden, weil es zu ihm gehörende Zahlungen gibt."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr "Verkäufe aufgrund von Zahlungen bestätigen"
|
||||
25
modules/sale_payment/locale/es.po
Normal file
25
modules/sale_payment/locale/es.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Cobros"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Cobros"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr "No puede cancelar la venta \"%(sale)s\" porque tiene pagos."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr "No puede restablecer a borrador la venta \"%(sale)s\" porque tiene pagos."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr "Confirmar ventas pagadas"
|
||||
25
modules/sale_payment/locale/es_419.po
Normal file
25
modules/sale_payment/locale/es_419.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
28
modules/sale_payment/locale/et.po
Normal file
28
modules/sale_payment/locale/et.po
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Laekumised"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Laekumised"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr "Ei saa tühistada müüki \"%(sale)s\", kuna sellega on seotud laekumised."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Ei saa muuta müüki \"%(sale)s\" mustandiks, kuna sellega on seotud "
|
||||
"laekumised."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
28
modules/sale_payment/locale/fa.po
Normal file
28
modules/sale_payment/locale/fa.po
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "پرداخت ها"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "پرداخت ها"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr "شما نمیتوانید فروش :\"%(sale)s\"راحذف کنید، چرا که پرداخت شده است."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"شما نمیتوانید حالت فروش :\"%(sale)s\" را به پیش نویس بازنشانی کنید، چرا که "
|
||||
"پرداخت شده است."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/fi.po
Normal file
25
modules/sale_payment/locale/fi.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
28
modules/sale_payment/locale/fr.po
Normal file
28
modules/sale_payment/locale/fr.po
Normal file
@@ -0,0 +1,28 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Paiements"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Paiements"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas annuler la vente « %(sale)s » car elle a des paiements."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas réinitialiser à l'état brouillon la vente « %(sale)s » "
|
||||
"car elle a des paiements."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr "Confirmer les ventes en fonction des paiements"
|
||||
25
modules/sale_payment/locale/hu.po
Normal file
25
modules/sale_payment/locale/hu.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
29
modules/sale_payment/locale/id.po
Normal file
29
modules/sale_payment/locale/id.po
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Pembayaran"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Pembayaran"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Anda tidak dapat membatalkan penjualan \"%(sale)s\" karena memiliki "
|
||||
"pembayaran."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Anda tidak dapat mengatur ulang ke konsep penjualan \"%(sale)s\" karena "
|
||||
"memiliki pembayaran."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
27
modules/sale_payment/locale/it.po
Normal file
27
modules/sale_payment/locale/it.po
Normal file
@@ -0,0 +1,27 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Pagamenti"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Pagamenti"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr "Non è possibile annullare la vendita \"%(sale)s\" perché ha pagamenti."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"Non è possibile annullare la bozza di vendita \"%(sale)s\" perché ha "
|
||||
"pagamenti."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/lo.po
Normal file
25
modules/sale_payment/locale/lo.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/lt.po
Normal file
25
modules/sale_payment/locale/lt.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
29
modules/sale_payment/locale/nl.po
Normal file
29
modules/sale_payment/locale/nl.po
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "betalingen"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Betalingen"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"U kunt de verkoop \"%(sale)s\" niet annuleren omdat deze een betalingen "
|
||||
"heeft."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
"U kunt de verkoop \"%(sale)s\" niet terug zetten naar concept status omdat "
|
||||
"deze betalingen heeft."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr "Bevestig verkopen gebaseerd op betaling"
|
||||
26
modules/sale_payment/locale/pl.po
Normal file
26
modules/sale_payment/locale/pl.po
Normal file
@@ -0,0 +1,26 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Płatności"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Płatności"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
26
modules/sale_payment/locale/pt.po
Normal file
26
modules/sale_payment/locale/pt.po
Normal file
@@ -0,0 +1,26 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Pagamentos"
|
||||
|
||||
#, fuzzy
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Pagamentos"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/ro.po
Normal file
25
modules/sale_payment/locale/ro.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr "Plați"
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr "Plați"
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr "Vânzarea \"%(sale)s\" nu se poate anula pentru că are plați."
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr "Nu se poate reseta vânzarea \"%(sale)s\" la draft pentru ca are plați."
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/ru.po
Normal file
25
modules/sale_payment/locale/ru.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/sl.po
Normal file
25
modules/sale_payment/locale/sl.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/tr.po
Normal file
25
modules/sale_payment/locale/tr.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/uk.po
Normal file
25
modules/sale_payment/locale/uk.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
25
modules/sale_payment/locale/zh_CN.po
Normal file
25
modules/sale_payment/locale/zh_CN.po
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
||||
|
||||
msgctxt "field:sale.sale,payments:"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "model:ir.action,name:act_payments_relate"
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_cancel_payment"
|
||||
msgid "You cannot cancel sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
msgctxt "model:ir.message,text:msg_sale_draft_payment"
|
||||
msgid "You cannot reset to draft sale \"%(sale)s\" because it has payments."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "selection:ir.cron,method:"
|
||||
msgid "Confirm sales based on payment"
|
||||
msgstr ""
|
||||
13
modules/sale_payment/message.xml
Normal file
13
modules/sale_payment/message.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data grouped="1">
|
||||
<record model="ir.message" id="msg_sale_cancel_payment">
|
||||
<field name="text">You cannot cancel sale "%(sale)s" because it has payments.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_sale_draft_payment">
|
||||
<field name="text">You cannot reset to draft sale "%(sale)s" because it has payments.</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
105
modules/sale_payment/sale.py
Normal file
105
modules/sale_payment/sale.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# 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)
|
||||
29
modules/sale_payment/sale.xml
Normal file
29
modules/sale_payment/sale.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="sale_view_form">
|
||||
<field name="model">sale.sale</field>
|
||||
<field name="inherit" ref="sale.sale_view_form"/>
|
||||
<field name="name">sale_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_payments_relate">
|
||||
<field name="name">Payments</field>
|
||||
<field name="res_model">account.payment</field>
|
||||
<field
|
||||
name="domain"
|
||||
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('origin.id', '=', Eval('active_id'), 'sale.sale'), ('origin.id', 'in', Eval('active_ids'), 'sale.sale'))]"
|
||||
pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="act_payments_relate_keyword1">
|
||||
<field name="keyword">form_relate</field>
|
||||
<field name="model">sale.sale,-1</field>
|
||||
<field name="action" ref="act_payments_relate"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.cron" id="cron_sale_payment_confirm">
|
||||
<field name="method">sale.sale|payment_confirm</field>
|
||||
<field name="interval_number" eval="15"/>
|
||||
<field name="interval_type">minutes</field>
|
||||
</record>
|
||||
</data>
|
||||
</tryton>
|
||||
2
modules/sale_payment/tests/__init__.py
Normal file
2
modules/sale_payment/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
BIN
modules/sale_payment/tests/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
modules/sale_payment/tests/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
204
modules/sale_payment/tests/scenario_sale_payment.rst
Normal file
204
modules/sale_payment/tests/scenario_sale_payment.rst
Normal file
@@ -0,0 +1,204 @@
|
||||
=====================
|
||||
Sale Payment Scenario
|
||||
=====================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules(
|
||||
... ['sale_payment', 'account_payment_clearing'],
|
||||
... create_company, create_chart)
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
>>> revenue = accounts['revenue']
|
||||
>>> payable = accounts['payable']
|
||||
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> bank_clearing = Account(parent=payable.parent)
|
||||
>>> bank_clearing.name = 'Bank Clearing'
|
||||
>>> bank_clearing.type = payable.type
|
||||
>>> bank_clearing.reconcile = True
|
||||
>>> bank_clearing.deferral = True
|
||||
>>> bank_clearing.save()
|
||||
|
||||
>>> Journal = Model.get('account.journal')
|
||||
>>> expense, = Journal.find([('code', '=', 'EXP')])
|
||||
|
||||
Create payment journal::
|
||||
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> payment_journal = PaymentJournal(name='Manual',
|
||||
... process_method='manual', clearing_journal=expense,
|
||||
... clearing_account=bank_clearing)
|
||||
>>> payment_journal.save()
|
||||
|
||||
Create parties::
|
||||
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> customer = Party(name='Customer')
|
||||
>>> customer.save()
|
||||
|
||||
Default account product::
|
||||
|
||||
>>> AccountConfiguration = Model.get('account.configuration')
|
||||
>>> account_configuration = AccountConfiguration(1)
|
||||
>>> account_configuration.default_category_account_revenue = revenue
|
||||
>>> account_configuration.save()
|
||||
|
||||
Create a sale quotation::
|
||||
|
||||
>>> Sale = Model.get('sale.sale')
|
||||
>>> sale = Sale()
|
||||
>>> sale.party = customer
|
||||
>>> sale.invoice_method = 'order'
|
||||
>>> sale_line = sale.lines.new()
|
||||
>>> sale_line.description = "Test"
|
||||
>>> sale_line.quantity = 1.0
|
||||
>>> sale_line.unit_price = Decimal(100)
|
||||
>>> sale.click('quote')
|
||||
>>> sale.total_amount
|
||||
Decimal('100.00')
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
|
||||
Create a partial payment::
|
||||
|
||||
>>> Payment = Model.get('account.payment')
|
||||
>>> payment = Payment()
|
||||
>>> payment.journal = payment_journal
|
||||
>>> payment.kind = 'receivable'
|
||||
>>> payment.party = sale.party
|
||||
>>> payment.origin = sale
|
||||
>>> payment.amount = Decimal('40.00')
|
||||
>>> payment.click('submit')
|
||||
>>> payment.state
|
||||
'submitted'
|
||||
|
||||
Attempt to put sale back to draft::
|
||||
|
||||
>>> sale.click('draft')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AccessError: ...
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
|
||||
Attempt to cancel sale::
|
||||
|
||||
>>> sale.click('cancel')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AccessError: ...
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
|
||||
Revert sale to draft after failed payment::
|
||||
|
||||
>>> process_payment = payment.click('process_wizard')
|
||||
>>> payment.click('fail')
|
||||
>>> payment.state
|
||||
'failed'
|
||||
>>> sale.click('draft')
|
||||
>>> sale.state
|
||||
'draft'
|
||||
|
||||
Attempt to add a second payment to draft sale::
|
||||
|
||||
>>> payment = Payment()
|
||||
>>> payment.journal = payment_journal
|
||||
>>> payment.kind = 'receivable'
|
||||
>>> payment.party = sale.party
|
||||
>>> payment.origin = sale
|
||||
>>> payment.amount = Decimal('30.00')
|
||||
>>> payment.save()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DomainValidationError: ...
|
||||
|
||||
Cancel the sale::
|
||||
|
||||
>>> sale.click('cancel')
|
||||
>>> sale.state
|
||||
'cancelled'
|
||||
|
||||
Attempt to add a second payment to the cancelled sale::
|
||||
|
||||
>>> payment = Payment()
|
||||
>>> payment.journal = payment_journal
|
||||
>>> payment.kind = 'receivable'
|
||||
>>> payment.party = sale.party
|
||||
>>> payment.origin = sale
|
||||
>>> payment.amount = Decimal('30.00')
|
||||
>>> payment.save()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DomainValidationError: ...
|
||||
|
||||
Revive the sale::
|
||||
|
||||
>>> sale.click('draft')
|
||||
>>> sale.click('quote')
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
|
||||
Change the first payment to succeed::
|
||||
|
||||
>>> payment, = sale.payments
|
||||
>>> payment.click('succeed')
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
|
||||
Create and process a final payment::
|
||||
|
||||
>>> payment = Payment()
|
||||
>>> payment.journal = payment_journal
|
||||
>>> payment.kind = 'receivable'
|
||||
>>> payment.party = sale.party
|
||||
>>> payment.origin = sale
|
||||
>>> payment.amount = Decimal('60.00')
|
||||
>>> payment.click('submit')
|
||||
>>> process_payment = payment.click('process_wizard')
|
||||
>>> payment.click('succeed')
|
||||
|
||||
The sale should be processing::
|
||||
|
||||
>>> sale.reload()
|
||||
>>> sale.state
|
||||
'processing'
|
||||
|
||||
Post the invoice and check amount to pay::
|
||||
|
||||
>>> sale.click('process')
|
||||
>>> invoice, = sale.invoices
|
||||
>>> invoice.total_amount
|
||||
Decimal('100.00')
|
||||
>>> invoice.click('post')
|
||||
>>> invoice.amount_to_pay
|
||||
Decimal('0')
|
||||
>>> invoice.state
|
||||
'paid'
|
||||
|
||||
Fail one payment and check invoice is no more paid::
|
||||
|
||||
>>> payment.click('fail')
|
||||
>>> invoice.reload()
|
||||
>>> invoice.state
|
||||
'posted'
|
||||
100
modules/sale_payment/tests/scenario_sale_payment_no_clearing.rst
Normal file
100
modules/sale_payment/tests/scenario_sale_payment_no_clearing.rst
Normal file
@@ -0,0 +1,100 @@
|
||||
=================================
|
||||
Sale Payment Scenario No Clearing
|
||||
=================================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules('sale_payment', create_company, create_chart)
|
||||
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> AccountConfiguration = Model.get('account.configuration')
|
||||
>>> Sale = Model.get('sale.sale')
|
||||
>>> Payment = Model.get('account.payment')
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
>>> revenue = accounts['revenue']
|
||||
>>> payable = accounts['payable']
|
||||
|
||||
Create payment journal::
|
||||
|
||||
>>> payment_journal = PaymentJournal(
|
||||
... name="Manual", process_method='manual')
|
||||
>>> payment_journal.save()
|
||||
|
||||
Create parties::
|
||||
|
||||
>>> customer = Party(name="Customer")
|
||||
>>> customer.save()
|
||||
|
||||
Default account product::
|
||||
|
||||
>>> account_configuration = AccountConfiguration(1)
|
||||
>>> account_configuration.default_category_account_revenue = revenue
|
||||
>>> account_configuration.save()
|
||||
|
||||
Create a sale quotation::
|
||||
|
||||
>>> sale = Sale()
|
||||
>>> sale.party = customer
|
||||
>>> sale.invoice_method = 'order'
|
||||
>>> sale_line = sale.lines.new()
|
||||
>>> sale_line.description = "Test"
|
||||
>>> sale_line.quantity = 1.0
|
||||
>>> sale_line.unit_price = Decimal(100)
|
||||
>>> sale.click('quote')
|
||||
>>> sale.total_amount
|
||||
Decimal('100.00')
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
|
||||
Pay the sale using payment::
|
||||
|
||||
>>> payment = Payment()
|
||||
>>> payment.journal = payment_journal
|
||||
>>> payment.kind = 'receivable'
|
||||
>>> payment.party = sale.party
|
||||
>>> payment.origin = sale
|
||||
>>> payment.amount = Decimal('100.00')
|
||||
>>> payment.click('submit')
|
||||
>>> payment.state
|
||||
'submitted'
|
||||
>>> process_payment = payment.click('process_wizard')
|
||||
>>> payment.click('succeed')
|
||||
|
||||
The sale should be processing::
|
||||
|
||||
>>> sale.reload()
|
||||
>>> sale.state
|
||||
'processing'
|
||||
|
||||
Post the invoice and check amount to pay::
|
||||
|
||||
>>> sale.click('process')
|
||||
>>> invoice, = sale.invoices
|
||||
>>> invoice.total_amount
|
||||
Decimal('100.00')
|
||||
>>> invoice.click('post')
|
||||
>>> invoice.amount_to_pay
|
||||
Decimal('0.00')
|
||||
>>> invoice.state
|
||||
'posted'
|
||||
21
modules/sale_payment/tests/test_module.py
Normal file
21
modules/sale_payment/tests/test_module.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import Pool
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
|
||||
|
||||
class AccountPaymentTestCase(ModuleTestCase):
|
||||
'Test Sale Payment module'
|
||||
module = 'sale_payment'
|
||||
|
||||
@with_transaction()
|
||||
def test_sale_payment_confirm_cron(self):
|
||||
"Test running sale payment_confirm without sales"
|
||||
pool = Pool()
|
||||
Sale = pool.get('sale.sale')
|
||||
|
||||
Sale.payment_confirm()
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
8
modules/sale_payment/tests/test_scenario.py
Normal file
8
modules/sale_payment/tests/test_scenario.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.tests.test_tryton import load_doc_tests
|
||||
|
||||
|
||||
def load_tests(*args, **kwargs):
|
||||
return load_doc_tests(__name__, __file__, *args, **kwargs)
|
||||
21
modules/sale_payment/tryton.cfg
Normal file
21
modules/sale_payment/tryton.cfg
Normal file
@@ -0,0 +1,21 @@
|
||||
[tryton]
|
||||
version=7.8.0
|
||||
depends:
|
||||
account_invoice
|
||||
account_payment
|
||||
ir
|
||||
res
|
||||
sale
|
||||
extras_depend:
|
||||
account_payment_clearing
|
||||
sale_credit_limit
|
||||
xml:
|
||||
sale.xml
|
||||
message.xml
|
||||
|
||||
[register]
|
||||
model:
|
||||
ir.Cron
|
||||
sale.Sale
|
||||
account.Payment
|
||||
account.Invoice
|
||||
8
modules/sale_payment/view/sale_form.xml
Normal file
8
modules/sale_payment/view/sale_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="//link[@name='sale.act_invoice_form']" position="before">
|
||||
<link icon="tryton-payment" name="sale_payment.act_payments_relate"/>
|
||||
</xpath>
|
||||
</data>
|
||||
Reference in New Issue
Block a user