Add ir.message and use custom exceptions

issue3672
This commit is contained in:
Cédric Krier committed 2018-12-29 14:20:28 +01:00
1 parent 2f9187d287
commit bd7a6163f4
6 files changed
+203 -159

No files matched your search

+33
View File
@@ -0,0 +1,33 @@
# 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.exceptions import UserError
from trytond.model.exceptions import ValidationError
class PaymentTermValidationError(ValidationError):
pass
class PaymentTermComputeError(UserError):
pass
class InvoiceTaxValidationError(ValidationError):
pass
class InvoiceNumberError(ValidationError):
pass
class InvoiceValidationError(ValidationError):
pass
class InvoiceLineValidationError(ValidationError):
pass
class PayInvoiceError(UserError):
pass
+76 -112
View File
@@ -9,8 +9,10 @@ from sql.aggregate import Sum
from sql.conditionals import Coalesce, Case
from sql.functions import Round
from trytond.i18n import gettext
from trytond.model import Workflow, ModelView, ModelSQL, fields, \
sequence_ordered, Unique, DeactivableMixin
from trytond.model.exceptions import AccessError
from trytond.report import Report
from trytond.wizard import Wizard, StateView, StateTransition, StateAction, \
Button
@@ -25,6 +27,10 @@ from trytond.config import config
from trytond.modules.account.tax import TaxableMixin
from trytond.modules.product import price_digits
from .exceptions import (
InvoiceTaxValidationError, InvoiceNumberError, InvoiceValidationError,
InvoiceLineValidationError, PayInvoiceError)
__all__ = ['Invoice', 'InvoicePaymentLine', 'InvoiceLine',
'InvoiceLineTax', 'InvoiceTax', 'PaymentMethod',
'InvoiceReport',
@@ -224,38 +230,6 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
('type', 'in', cls._tax_identifier_types()),
]
cls.tax_identifier.depends += ['company_party']
cls._error_messages.update({
'missing_tax_line': ('Invoice "%s" has taxes defined but not '
'on invoice lines.\nRe-compute the invoice.'),
'diff_tax_line': ('Invoice "%s" tax bases are different from '
'invoice lines.\nRe-compute the invoice.'),
'missing_tax_line2': (
'Invoice "%s" has taxes on invoice lines '
'that are not in the invoice.\nRe-compute the invoice.'),
'no_invoice_sequence': ('Missing invoice sequence for '
'invoice "%(invoice)s" on fiscalyear "%(fiscalyear)s".'),
'modify_invoice': ('You can not modify invoice "%s" because '
'it is posted, paid or cancelled.'),
'same_account_on_line': ('Invoice "%(invoice)s" uses the same '
'account "%(account)s" for the invoice and in line '
'"%(line)s".'),
'delete_cancel': ('Invoice "%s" must be cancelled before '
'deletion.'),
'delete_numbered': ('The numbered invoice "%s" can not be '
'deleted.'),
'customer_invoice_cancel_move': (
'Customer invoice/credit note '
'"%s" can not be cancelled once posted.'),
'payment_lines_greater_amount': (
'The payment lines on invoice "%(invoice)s" can not be '
'greater than the invoice amount.'),
'modify_payment_lines_invoice_paid': (
'Payment lines can not be modified '
'on a paid invoice "%(invoice)s"'),
'credit_refund_non_posted': (
'Invoice "%(invoice)s" can not be refund '
'because it is not posted.'),
})
cls._transitions |= set((
('draft', 'validated'),
('validated', 'posted'),
@@ -861,24 +835,19 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
tax_id = tax.tax.id if tax.tax else None
key = (tax.account.id, tax_id)
if (key not in computed_taxes) or (key in tax_keys):
if exception:
cls.raise_user_error('missing_tax_line',
(invoice.rec_name,))
to_delete.append(tax)
continue
tax_keys.append(key)
if not invoice.currency.is_zero(
computed_taxes[key]['base'] - tax.base):
if exception:
cls.raise_user_error('diff_tax_line',
(invoice.rec_name,))
to_write.extend(([tax], computed_taxes[key]))
for key in computed_taxes:
if key not in tax_keys:
if exception:
cls.raise_user_error('missing_tax_line2',
(invoice.rec_name,))
to_create.append(computed_taxes[key])
if exception and (to_create or to_delete or to_write):
raise InvoiceTaxValidationError(
gettext('account_invoice.msg_invoice_tax_invalid',
invoice=invoice.rec_name))
if to_create:
Tax.create(to_create)
if to_delete:
@@ -1028,10 +997,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
invoice_sequence, '%s_sequence' % invoice_type)
break
else:
self.raise_user_error('no_invoice_sequence', {
'invoice': self.rec_name,
'fiscalyear': fiscalyear.rec_name,
})
raise InvoiceNumberError(
gettext('account_invoice.msg_invoice_no_sequence',
invoice=self.rec_name,
fiscalyear=fiscalyear.rec_name))
with Transaction().set_context(date=accounting_date):
return Sequence.get_id(sequence.id)
@@ -1059,7 +1028,9 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
'''
for invoice in invoices:
if not invoice.is_modifiable:
cls.raise_user_error('modify_invoice', (invoice.rec_name,))
raise AccessError(
gettext('account_invoice.msg_invoice_modify',
invoice=invoice.rec_name))
def get_rec_name(self, name):
items = []
@@ -1098,9 +1069,13 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
cls.cancel(invoices)
for invoice in invoices:
if invoice.state != 'cancel':
cls.raise_user_error('delete_cancel', (invoice.rec_name,))
raise AccessError(
gettext('account_invoice.msg_invoice_delete_cancel',
invoice=invoice.rec_name))
if invoice.number:
cls.raise_user_error('delete_numbered', (invoice.rec_name,))
raise AccessError(
gettext('account_invoice.msg_invoice_delete_numbered',
invoice=invoice.rec_name))
super(Invoice, cls).delete(invoices)
@classmethod
@@ -1144,19 +1119,20 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
for line in self.lines:
if (line.type == 'line'
and line.account == self.account):
self.raise_user_error('same_account_on_line', {
'invoice': self.rec_name,
'account': self.account.rec_name,
'line': line.rec_name,
})
raise InvoiceValidationError(
gettext('account_invoice.msg_invoice_same_account_line',
account=self.account.rec_name,
invoice=self.rec_name,
line=line.rec_name))
def check_payment_lines(self):
amount = sum(l.debit - l.credit for l in self.lines_to_pay)
payment_amount = sum(l.debit - l.credit for l in self.payment_lines)
if abs(amount) < abs(payment_amount):
self.raise_user_error('payment_lines_greater_amount', {
'invoice': self.rec_name,
})
raise InvoiceValidationError(
gettext('account_invoice'
'.msg_invoice_payment_lines_greater_amount',
invoice=self.rec_name))
def get_reconcile_lines_for_amount(self, amount):
'''
@@ -1242,9 +1218,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
to_write = []
for invoice, lines in payments.items():
if invoice.state == 'paid':
cls.raise_user_error('modify_payment_lines_invoice_paid', {
'invoice': invoice.rec_name,
})
raise AccessError(
gettext('account_invoice'
'.msg_invoice_payment_lines_add_remove_paid',
invoice=invoice.rec_name))
to_write.append([invoice])
to_write.append({'payment_lines': [('add', lines)]})
if to_write:
@@ -1268,9 +1245,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
to_write = []
for invoice, lines in payments.items():
if invoice.state == 'paid':
cls.raise_user_error('modify_payment_lines_invoice_paid', {
'invoice': invoice.rec_name,
})
raise AccessError(
gettext('account_invoice'
'.msg_invoice_payment_lines_add_remove_paid',
invoice=invoice.rec_name))
to_write.append([invoice])
to_write.append({'payment_lines': [('remove', lines)]})
if to_write:
@@ -1313,9 +1291,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
cls.post(new_invoices)
for invoice, new_invoice in zip(invoices, new_invoices):
if invoice.state != 'posted':
cls.raise_user_error('credit_refund_non_posted', {
'invoice': invoice.rec_name,
})
raise AccessError(
gettext('account_invoice'
'.msg_invoice_credit_refund_not_posted',
invoice=invoice.rec_name))
invoice.cancel_move = new_invoice.move
cls.save(invoices)
cls.cancel(invoices)
@@ -1418,8 +1397,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
delete_moves.append(invoice.move)
elif not invoice.cancel_move:
if invoice.type == 'out':
cls.raise_user_error(
'customer_invoice_cancel_move', invoice.rec_name)
raise AccessError(
gettext('account_invoice'
'.msg_invoice_customer_cancel_move',
invoice=invoice.rec_name))
invoice.cancel_move = invoice.move.cancel()
to_save.append(invoice)
cancel_moves.append(invoice.cancel_move)
@@ -1489,7 +1470,7 @@ class InvoicePaymentLine(ModelSQL):
t = cls.__table__()
cls._sql_constraints = [
('line_unique', Unique(t, t.line),
"A payment line can be linked to only one invoice."),
'account_invoice.msg_invoice_payment_line_unique'),
]
@classmethod
@@ -1660,16 +1641,6 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
@classmethod
def __setup__(cls):
super(InvoiceLine, cls).__setup__()
cls._error_messages.update({
'modify': ('You can not modify line "%(line)s" from invoice '
'"%(invoice)s" that is posted or paid.'),
'create': ('You can not add a line to invoice "%(invoice)s" '
'that is posted, paid or cancelled.'),
'same_account_on_invoice': ('You can not create invoice line '
'"%(line)s" on invoice "%(invoice)s" because the invoice '
'uses the same account (%(account)s).'),
})
cls._check_modify_exclude = {'note', 'origin'}
# Set account domain dynamically for kind
@@ -1983,10 +1954,10 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
if fields is None or fields - cls._check_modify_exclude:
for line in lines:
if line.invoice and not line.invoice.is_modifiable:
cls.raise_user_error('modify', {
'line': line.rec_name,
'invoice': line.invoice.rec_name
})
raise AccessError(
gettext('account_invoice.msg_invoice_line_modify',
line=line.rec_name,
invoice=line.invoice.rec_name))
@classmethod
def view_attributes(cls):
@@ -2017,7 +1988,9 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
invoice_ids.append(vals.get('invoice'))
for invoice in Invoice.browse(invoice_ids):
if invoice.state in ('posted', 'paid', 'cancel'):
cls.raise_user_error('create', (invoice.rec_name,))
raise AccessError(
gettext('account_invoice.msg_invoice_line_create',
invoice=invoice.rec_name))
return super(InvoiceLine, cls).create(vlist)
@classmethod
@@ -2039,11 +2012,11 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
if self.type == 'line':
if (self.invoice
and self.account == self.invoice.account):
self.raise_user_error('same_account_on_invoice', {
'line': self.rec_name,
'invoice': self.invoice.rec_name,
'account': self.account.rec_name,
})
raise InvoiceLineValidationError(
gettext('account_invoice.msg_invoice_same_account_line',
account=self.account.rec_name,
invoice=self.invoice.rec_name,
line=self.rec_name))
def _compute_taxes(self):
pool = Pool()
@@ -2185,16 +2158,6 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
del _states, _depends
@classmethod
def __setup__(cls):
super(InvoiceTax, cls).__setup__()
cls._error_messages.update({
'modify': ('You can not modify tax "%(tax)s" from invoice '
'"%(invoice)s" because it is posted or paid.'),
'create': ('You can not add tax to invoice '
'"%(invoice)s" because it is posted, paid or cancelled.'),
})
@classmethod
def __register__(cls, module_name):
super(InvoiceTax, cls).__register__(module_name)
@@ -2262,10 +2225,10 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
'''
for tax in taxes:
if not tax.invoice.is_modifiable:
cls.raise_user_error('modify', {
'tax': tax.rec_name,
'invoice': tax.invoice.rec_name,
})
raise AccessError(
gettext('account_invoice.msg_invoice_tax_modify',
tax=tax.rec_name,
invoice=tax.invoice.rec_name))
def get_sequence_number(self, name):
i = 1
@@ -2295,9 +2258,9 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
invoice_ids.append(vals['invoice'])
for invoice in Invoice.browse(invoice_ids):
if invoice.state in ('posted', 'paid', 'cancel'):
cls.raise_user_error('create', {
'invoice': invoice.rec_name,
})
raise AccessError(
gettext('account_invoice.msg_invoice_tax_create',
invoice=invoice.rec_name))
return super(InvoiceTax, cls).create(vlist)
def get_move_lines(self):
@@ -2581,11 +2544,6 @@ class PayInvoice(Wizard):
@classmethod
def __setup__(cls):
super(PayInvoice, cls).__setup__()
cls._error_messages.update({
'amount_greater_amount_to_pay': ('On invoice "%s" you can not '
'create a partial payment with an amount greater than the '
'amount to pay.'),
})
cls.__rpc__['create'].fresh_session = True
def get_reconcile_lines_for_amount(self, invoice, amount):
@@ -2672,6 +2630,7 @@ class PayInvoice(Wizard):
Invoice = pool.get('account.invoice')
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
Lang = pool.get('ir.lang')
invoice = Invoice(Transaction().context['active_id'])
@@ -2692,8 +2651,13 @@ class PayInvoice(Wizard):
if (amount_invoice > invoice.amount_to_pay
and self.ask.type != 'writeoff'):
self.raise_user_error('amount_greater_amount_to_pay',
(invoice.rec_name,))
lang = Lang.get()
raise PayInvoiceError(
gettext('account_invoice'
'.msg_invoice_pay_amount_greater_amount_to_pay',
invoice=invoice.rec_name,
amount_to_pay=lang.currency(
invoice.amount_to_pay, invoice.currency)))
line = None
if not invoice.company.currency.is_zero(amount):
+68
View File
@@ -0,0 +1,68 @@
<?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 group="1">
<record model="ir.message" id="msg_payment_term_missing_last_remainder">
<field name="text">To save payment term "%(payment_term)s", you must append a last remainder line.</field>
</record>
<record model="ir.message" id="msg_payment_term_missing_remainder">
<field name="text">To compute terms, you must append a remainder line on payment term "%(payment_term)s".</field>
</record>
<record model="ir.message" id="msg_payment_term_invalid_ratio_divisor">
<field name="text">The ratio and divisor are not consistent on line "%(line)s".</field>
</record>
<record model="ir.message" id="msg_erase_party_pending_invoice">
<field name="text">You cannot erase party "%(party)s" while they have pending invoices with company "%(company)s".</field>
</record>
<record model="ir.message" id="msg_invoice_tax_invalid">
<field name="text">The taxes on invoice "%(invoice)s" are not valid, you must save it again to force them to be recalculated.</field>
</record>
<record model="ir.message" id="msg_invoice_no_sequence">
<field name="text">To post invoice "%(invoice)s", you must define a sequence on fiscal year "%(fiscalyear)s".</field>
</record>
<record model="ir.message" id="msg_invoice_modify">
<field name="text">You cannot modify invoice "%(invoice)s" because it is posted, paid or cancelled.</field>
</record>
<record model="ir.message" id="msg_invoice_same_account_line">
<field name="text">You cannot use the same account "%(account)s" on invoice "%(invoice)s" and on line "%(lines)s".</field>
</record>
<record model="ir.message" id="msg_invoice_delete_cancel">
<field name="text">To delete invoice "%(invoice)s" you must cancel it.</field>
</record>
<record model="ir.message" id="msg_invoice_delete_numbered">
<field name="text">You cannot delete invoice "%(invoice)s" because it has a number.</field>
</record>
<record model="ir.message" id="msg_invoice_customer_cancel_move">
<field name="text">You cannot cancel customer invoice "%(invoice)s" because it is posted.</field>
</record>
<record model="ir.message" id="msg_invoice_payment_lines_greater_amount">
<field name="text">Payment lines amount on invoice "%(invoice)s" can not be greater than the invoice amount.</field>
</record>
<record model="ir.message" id="msg_invoice_payment_lines_add_remove_paid">
<field name="text">You cannot add/remove payment lines on paid invoice "%(invoice)s".</field>
</record>
<record model="ir.message" id="msg_invoice_credit_refund_not_posted">
<field name="text">You cannot refund invoice "%(invoice)s" because it is not posted.</field>
</record>
<record model="ir.message" id="msg_invoice_line_modify">
<field name="text">You cannot modify line "%(line)s" because its invoice "%(invoice)s" is posted, paid or cancelled.</field>
</record>
<record model="ir.message" id="msg_invoice_line_create">
<field name="text">You cannot add a line to invoice "%(invoice)s" because it is posted, paid or cancelled.</field>
</record>
<record model="ir.message" id="msg_invoice_tax_modify">
<field name="text">You cannot modify tax "%(tax)s" because its invoice "%(invoice)s" is posted, paid or cancelled.</field>
</record>
<record model="ir.message" id="msg_invoice_tax_create">
<field name="text">You cannot add a tax to invoice "%(invoice)s" because it is posted, paid or cancelled.</field>
</record>
<record model="ir.message" id="msg_invoice_pay_amount_greater_amount_to_pay">
<field name="text">You cannot add a partial payment on invoice "%(invoice)s" with an amount greater than the amount to pay "%(amount_to_pay)s".</field>
</record>
<record model="ir.message" id="msg_invoice_payment_line_unique">
<field name="text">A payment line can be linked to only one invoice.</field>
</record>
</data>
</tryton>
+7 -14
View File
@@ -1,10 +1,13 @@
# 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 import backend
from trytond.i18n import gettext
from trytond.model import ModelView, ModelSQL, ValueMixin, fields
from trytond.pool import Pool, PoolMeta
from trytond.tools.multivalue import migrate_property
from trytond.modules.party.exceptions import EraseError
__all__ = ['Address', 'Party', 'PartyPaymentTerm',
'PartyReplace', 'PartyErase']
customer_payment_term = fields.Many2One(
@@ -86,16 +89,6 @@ class PartyReplace(metaclass=PoolMeta):
class PartyErase(metaclass=PoolMeta):
__name__ = 'party.erase'
@classmethod
def __setup__(cls):
super(PartyErase, cls).__setup__()
cls._error_messages.update({
'pending_invoice': (
'The party "%(party)s" can not be erased '
'because he has pending invoices '
'for the company "%(company)s".'),
})
def check_erase_company(self, party, company):
pool = Pool()
Invoice = pool.get('account.invoice')
@@ -106,7 +99,7 @@ class PartyErase(metaclass=PoolMeta):
('state', 'not in', ['paid', 'cancel']),
])
if invoices:
self.raise_user_error('pending_invoice', {
'party': party.rec_name,
'company': company.rec_name,
})
raise EraseError(
gettext('account_invoice.msg_erase_party_pending_invoice',
party=party.rec_name,
company=company.rec_name))
+18 -33
View File
@@ -5,6 +5,7 @@ from dateutil.relativedelta import relativedelta
from sql import Column
from trytond.i18n import gettext
from trytond.model import (
ModelView, ModelSQL, DeactivableMixin, fields, sequence_ordered)
from trytond import backend
@@ -14,6 +15,8 @@ from trytond.pool import Pool
from trytond.wizard import Wizard, StateView, Button
from trytond.config import config
from .exceptions import PaymentTermValidationError, PaymentTermComputeError
__all__ = ['PaymentTerm', 'PaymentTermLine', 'PaymentTermLineRelativeDelta',
'TestPaymentTerm', 'TestPaymentTermView', 'TestPaymentTermViewResult']
@@ -30,14 +33,6 @@ class PaymentTerm(DeactivableMixin, ModelSQL, ModelView):
def __setup__(cls):
super(PaymentTerm, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._error_messages.update({
'invalid_line': ('Invalid line "%(line)s" in payment term '
'"%(term)s".'),
'missing_remainder': ('Missing remainder line in payment term '
'"%s".'),
'last_remainder': ('Last line of payment term "%s" must be of '
'type remainder.'),
})
@classmethod
def validate(cls, terms):
@@ -47,7 +42,10 @@ class PaymentTerm(DeactivableMixin, ModelSQL, ModelView):
def check_remainder(self):
if not self.lines or not self.lines[-1].type == 'remainder':
self.raise_user_error('last_remainder', self.rec_name)
raise PaymentTermValidationError(
gettext('account_invoive'
'.msg_payment_term_missing_last_remainder',
payment_term=self.rec_name))
def compute(self, amount, currency, date=None):
"""Calculate payment terms and return a list of tuples
@@ -70,12 +68,6 @@ class PaymentTerm(DeactivableMixin, ModelSQL, ModelView):
value = line.get_value(remainder, amount, currency)
value_date = line.get_date(date)
if value is None or not value_date:
if (not remainder) and line.amount:
self.raise_user_error('invalid_line', {
'line': line.rec_name,
'term': self.rec_name,
})
else:
continue
if ((remainder - value) * sign) < Decimal('0.0'):
res.append((value_date, remainder))
@@ -89,7 +81,9 @@ class PaymentTerm(DeactivableMixin, ModelSQL, ModelView):
res.append((date, Decimal(0)))
if not currency.is_zero(remainder):
self.raise_user_error('missing_remainder', (self.rec_name,))
raise PaymentTermComputeError(
gettext('account_invoice.msg_payment_term_missing_remainder',
payment_term=self.rec_name))
return res
@@ -129,15 +123,6 @@ class PaymentTermLine(sequence_ordered(), ModelSQL, ModelView):
relativedeltas = fields.One2Many(
'account.invoice.payment_term.line.delta', 'line', 'Deltas')
@classmethod
def __setup__(cls):
super(PaymentTermLine, cls).__setup__()
cls._error_messages.update({
'invalid_ratio_and_divisor': ('Ratio and '
'Divisor values are not consistent in line "%(line)s" '
'of payment term "%(term)s".'),
})
@classmethod
def __register__(cls, module_name):
sql_table = cls.__table__()
@@ -243,19 +228,19 @@ class PaymentTermLine(sequence_ordered(), ModelSQL, ModelView):
if line.type not in ('percent', 'percent_on_total'):
continue
if line.ratio is None or line.divisor is None:
cls.raise_user_error('invalid_ratio_and_divisor', {
'line': line.rec_name,
'term': line.payment.rec_name,
})
raise PaymentTermValidationError(
gettext('account_invoice'
'.msg_payment_term_invalid_ratio_divisor',
line=line.rec_name))
ratio = line.ratio
divisor = line.divisor
line.on_change_ratio()
line.on_change_divisor()
if (line.divisor != divisor) or (line.ratio != ratio):
cls.raise_user_error('invalid_ratio_and_divisor', {
'line': line.rec_name,
'term': line.payment.rec_name,
})
raise PaymentTermValidationError(
gettext('account_invoice'
'.msg_payment_term_invalid_ratio_divisor',
line=line.rec_name))
class PaymentTermLineRelativeDelta(sequence_ordered(), ModelSQL, ModelView):
+1
View File
@@ -14,3 +14,4 @@ xml:
payment_term.xml
party.xml
account.xml
message.xml