Check invoice payment means when submitting payment
This commit is contained in:
1 parent
cc1f6e424c
commit
8cb0fb2bbd
9 files changed
+264
-3
No files matched your search
@@ -866,6 +866,16 @@ class InvoicePaymentMean(metaclass=PoolMeta):
|
||||
'account_payment.msg_invoice_payment_mean_direct_debit')
|
||||
return name
|
||||
|
||||
def is_valid_with_payment(self, payment):
|
||||
pool = Pool()
|
||||
ReceptionDirectDebit = pool.get('party.party.reception_direct_debit')
|
||||
valid = None
|
||||
if isinstance(self.instrument, ReceptionDirectDebit):
|
||||
valid = self.instrument.journal == payment.journal
|
||||
elif payment.journal.process_method == 'manual':
|
||||
valid = True
|
||||
return valid
|
||||
|
||||
|
||||
class Statement(metaclass=PoolMeta):
|
||||
__name__ = 'account.statement'
|
||||
|
||||
@@ -17,6 +17,10 @@ class ReconciledWarning(UserWarning):
|
||||
pass
|
||||
|
||||
|
||||
class PaymentMeanWarning(UserWarning):
|
||||
pass
|
||||
|
||||
|
||||
class PaymentValidationError(ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<record model="ir.message" id="msg_payment_reconciled">
|
||||
<field name="text">The line "%(line)s" of payment "%(payment)s" is already reconciled.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_payment_means">
|
||||
<field name="text">The journal "%(journal)s" of payment "%(payment)s" is not valid with the payment means of invoice "%(invoice)s".</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.message" id="msg_payment_reference_invalid">
|
||||
<field name="text">The %(type)s "%(reference)s" on payment "%(payment)s" is not valid.</field>
|
||||
</record>
|
||||
|
||||
@@ -28,7 +28,8 @@ from trytond.transaction import Transaction
|
||||
from trytond.wizard import StateAction, Wizard
|
||||
|
||||
from .exceptions import (
|
||||
OverpayWarning, PaymentValidationError, ReconciledWarning)
|
||||
OverpayWarning, PaymentMeanWarning, PaymentValidationError,
|
||||
ReconciledWarning)
|
||||
|
||||
KINDS = [
|
||||
('payable', 'Payable'),
|
||||
@@ -826,6 +827,35 @@ class Payment_Invoice(metaclass=PoolMeta):
|
||||
reference = invoice.customer_payment_reference
|
||||
return reference
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('submitted')
|
||||
def submit(cls, payments):
|
||||
super().submit(payments)
|
||||
cls._check_payment_means(payments)
|
||||
|
||||
@classmethod
|
||||
def _check_payment_means(cls, payments):
|
||||
pool = Pool()
|
||||
Warning = pool.get('res.user.warning')
|
||||
Invoice = pool.get('account.invoice')
|
||||
for payment in payments:
|
||||
if (payment.line
|
||||
and isinstance(payment.line.move.origin, Invoice)):
|
||||
invoice = payment.line.move.origin
|
||||
if invoice.payment_means:
|
||||
if not any(
|
||||
mean.is_valid_with_payment(payment)
|
||||
for mean in invoice.payment_means):
|
||||
key = Warning.format('payment_means', [payment])
|
||||
if Warning.check(key):
|
||||
raise PaymentMeanWarning(
|
||||
key, gettext(
|
||||
'account_payment.msg_payment_means',
|
||||
journal=payment.journal.rec_name,
|
||||
payment=payment.rec_name,
|
||||
invoice=invoice.rec_name))
|
||||
|
||||
|
||||
class ProcessPayment(Wizard):
|
||||
__name__ = 'account.payment.process'
|
||||
|
||||
@@ -4,7 +4,9 @@ Invoice Payment Means Scenario
|
||||
|
||||
Imports::
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model, Wizard
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
@@ -56,8 +58,34 @@ Create invoice with payment means::
|
||||
|
||||
>>> invoice = Invoice(type='out')
|
||||
>>> invoice.party = customer
|
||||
>>> line = invoice.lines.new()
|
||||
>>> line.account = accounts['revenue']
|
||||
>>> line.quantity = 1
|
||||
>>> line.unit_price = Decimal('10.0000')
|
||||
>>> payment_mean = invoice.payment_means.new()
|
||||
>>> payment_mean.instrument = customer.reception_direct_debits[0]
|
||||
>>> invoice.click('post')
|
||||
>>> payment_mean, = invoice.payment_means
|
||||
>>> assertEqual(payment_mean.instrument, customer.reception_direct_debits[0])
|
||||
|
||||
Try to pay with a different journal::
|
||||
|
||||
>>> other_payment_journal, = payment_journal.duplicate()
|
||||
|
||||
>>> line_to_pay, = invoice.lines_to_pay
|
||||
>>> pay_line = Wizard('account.move.line.pay', [line_to_pay])
|
||||
>>> pay_line.execute('next_')
|
||||
>>> pay_line.form.journal = other_payment_journal
|
||||
>>> pay_line.execute('next_')
|
||||
>>> payment, = pay_line.actions[0]
|
||||
>>> payment.click('submit')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
PaymentMeanWarning: ...
|
||||
|
||||
Pay with the same journal::
|
||||
|
||||
>>> payment.journal = payment_journal
|
||||
>>> payment.click('submit')
|
||||
>>> payment.state
|
||||
'submitted'
|
||||
@@ -4,7 +4,7 @@
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import ModelSQL, fields
|
||||
from trytond.modules.company.model import CompanyValueMixin
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval, Id
|
||||
|
||||
|
||||
@@ -45,3 +45,20 @@ class InvoicePaymentMean(metaclass=PoolMeta):
|
||||
mandate=mandate.identification,
|
||||
account_number=mandate.account_number.number)
|
||||
return name
|
||||
|
||||
def is_valid_with_payment(self, payment):
|
||||
pool = Pool()
|
||||
BankAccount = pool.get('bank.account')
|
||||
ReceptionDirectDebit = pool.get('party.party.reception_direct_debit')
|
||||
valid = super().is_valid_with_payment(payment)
|
||||
if payment.journal.process_method == 'sepa':
|
||||
if isinstance(self.instrument, BankAccount):
|
||||
bank_account = self.instrument
|
||||
valid = (
|
||||
payment.sepa_bank_account_number in bank_account.numbers)
|
||||
elif isinstance(self.instrument, ReceptionDirectDebit):
|
||||
reception = self.instrument
|
||||
if reception.journal == payment.journal:
|
||||
if payment.sepa_mandate and reception.sepa_mandate:
|
||||
valid &= payment.sepa_mandate == reception.sepa_mandate
|
||||
return valid
|
||||
@@ -490,6 +490,50 @@ class Payment(metaclass=PoolMeta):
|
||||
]
|
||||
|
||||
|
||||
class Payment_Invoice(metaclass=PoolMeta):
|
||||
__name__ = 'account.payment'
|
||||
|
||||
@property
|
||||
def sepa_bank_account_number(self):
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
BankAccount = pool.get('bank.account')
|
||||
sepa_number = super().sepa_bank_account_number
|
||||
if (self.kind == 'payable'
|
||||
and not self.sepa_payable_bank_account_number
|
||||
and self.line
|
||||
and isinstance(self.line.move.origin, Invoice)):
|
||||
invoice = self.line.move.origin
|
||||
for mean in invoice.payment_means:
|
||||
if isinstance(mean.instrument, BankAccount):
|
||||
bank_account = mean.instrument
|
||||
for number in bank_account.numbers:
|
||||
if number.type == 'iban':
|
||||
sepa_number = number
|
||||
break
|
||||
return sepa_number
|
||||
|
||||
@classmethod
|
||||
def get_sepa_mandates(cls, payments):
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
ReceptionDirectDebit = pool.get('party.party.reception_direct_debit')
|
||||
mandates = []
|
||||
for payment, mandate in zip(
|
||||
payments,
|
||||
super().get_sepa_mandates(payments)):
|
||||
if (not payment.sepa_mandate
|
||||
and payment.line
|
||||
and isinstance(payment.line.move.origin, Invoice)):
|
||||
invoice = payment.line.move.origin
|
||||
for mean in invoice.payment_means:
|
||||
if (isinstance(mean.instrument, ReceptionDirectDebit)
|
||||
and mean.instrument.sepa_mandate):
|
||||
mandate = mean.instrument.sepa_mandate
|
||||
mandates.append(mandate)
|
||||
return mandates
|
||||
|
||||
|
||||
class Mandate(Workflow, ModelSQL, ModelView):
|
||||
__name__ = 'account.payment.sepa.mandate'
|
||||
party = fields.Many2One(
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
==============================
|
||||
Invoice Payment Means Scenario
|
||||
==============================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> import datetime as dt
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> from proteus import Model, Wizard
|
||||
>>> 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, get_company
|
||||
>>> from trytond.tests.tools import activate_modules, assertEqual
|
||||
|
||||
>>> today = dt.date.today()
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules(
|
||||
... ['account_payment_sepa', 'account_invoice'], create_company, create_chart)
|
||||
|
||||
>>> Bank = Model.get('bank')
|
||||
>>> BankAccount = Model.get('bank.account')
|
||||
>>> Invoice = Model.get('account.invoice')
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
|
||||
>>> company = get_company()
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
|
||||
Create party::
|
||||
|
||||
>>> supplier = Party(name="Supplier")
|
||||
>>> supplier.save()
|
||||
|
||||
Create bank accounts::
|
||||
|
||||
>>> bank_party = Party(name="EU Bank")
|
||||
>>> bank_party.save()
|
||||
|
||||
>>> company_bank = Bank(party=bank_party, bic='CTBKBEBX')
|
||||
>>> company_bank.save()
|
||||
>>> company_account = BankAccount(bank=company_bank)
|
||||
>>> company_account.owners.append(company.party)
|
||||
>>> company_account_number = company_account.numbers.new()
|
||||
>>> company_account_number.type = 'iban'
|
||||
>>> company_account_number.number = 'BE70953368654125'
|
||||
>>> company_account.save()
|
||||
|
||||
>>> supplier_bank1 = Bank(party=bank_party, bic='KREDBEBB')
|
||||
>>> supplier_bank1.save()
|
||||
>>> supplier_account1 = BankAccount(bank=supplier_bank1)
|
||||
>>> supplier_account1.owners.append(Party(supplier.id))
|
||||
>>> supplier_account1_number = supplier_account1.numbers.new()
|
||||
>>> supplier_account1_number.type = 'iban'
|
||||
>>> supplier_account1_number.number = 'BE85735556927306'
|
||||
>>> supplier_account1.save()
|
||||
>>> supplier_bank2 = Bank(party=bank_party, bic='CITIBEBX')
|
||||
>>> supplier_bank2.save()
|
||||
>>> supplier_account2 = BankAccount(bank=supplier_bank2)
|
||||
>>> supplier_account2.owners.append(Party(supplier.id))
|
||||
>>> supplier_account2_number = supplier_account2.numbers.new()
|
||||
>>> supplier_account2_number.type = 'iban'
|
||||
>>> supplier_account2_number.number = 'BE96570728435605'
|
||||
>>> supplier_account2.save()
|
||||
|
||||
Create payment journal::
|
||||
|
||||
>>> payment_journal = PaymentJournal(name="SEPA", process_method='sepa')
|
||||
>>> payment_journal.sepa_bank_account_number = company_account.numbers[0]
|
||||
>>> payment_journal.sepa_payable_flavor = 'pain.001.001.03'
|
||||
>>> payment_journal.sepa_receivable_flavor = 'pain.008.001.02'
|
||||
>>> payment_journal.save()
|
||||
|
||||
Create a supplier invoice with a payment means::
|
||||
|
||||
>>> invoice = Invoice(type='in')
|
||||
>>> invoice.invoice_date = today
|
||||
>>> invoice.party = supplier
|
||||
>>> line = invoice.lines.new()
|
||||
>>> line.account = accounts['expense']
|
||||
>>> line.quantity = 1
|
||||
>>> line.unit_price = Decimal('10.0000')
|
||||
>>> _ = invoice.payment_means.new(instrument=supplier_account2)
|
||||
>>> invoice.click('post')
|
||||
>>> invoice.state
|
||||
'posted'
|
||||
|
||||
Try to pay with a different bank account::
|
||||
|
||||
>>> line_to_pay, = invoice.lines_to_pay
|
||||
>>> pay_line = Wizard('account.move.line.pay', [line_to_pay])
|
||||
>>> pay_line.execute('next_')
|
||||
>>> pay_line.execute('next_')
|
||||
>>> payment, = pay_line.actions[0]
|
||||
>>> payment.sepa_payable_bank_account_number, = supplier_account1.numbers
|
||||
>>> payment.click('submit')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
PaymentMeanWarning: ...
|
||||
|
||||
Pay with no bank account::
|
||||
|
||||
>>> payment.sepa_payable_bank_account_number = None
|
||||
>>> payment.click('submit')
|
||||
>>> payment.state
|
||||
'submitted'
|
||||
>>> payment.click('approve')
|
||||
>>> _ = payment.click('process_wizard')
|
||||
>>> assertEqual(
|
||||
... payment.sepa_payable_bank_account_number,
|
||||
... supplier_account2.numbers[0])
|
||||
@@ -37,3 +37,4 @@ report:
|
||||
[register account_invoice]
|
||||
model:
|
||||
account.InvoicePaymentMean
|
||||
payment.Payment_Invoice
|
||||
Reference in new issue
Block a user