changeset c720259bb5b2 in modules/sale:default details: https://hg.tryton.org/modules/sale?cmd=changeset;node=c720259bb5b2 description: Add ir.message and use custom exceptions
issue3672 diffstat: exceptions.py | 21 +++++++++++ invoice.py | 20 +++------ message.xml | 46 ++++++++++++++++++++++++ party.py | 21 +++------- sale.py | 108 ++++++++++++++++++++++++++------------------------------- stock.py | 26 ++++--------- tryton.cfg | 1 + 7 files changed, 140 insertions(+), 103 deletions(-) diffs (461 lines): diff -r a04f626934c1 -r c720259bb5b2 exceptions.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exceptions.py Sat Dec 29 14:20:29 2018 +0100 @@ -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.exceptions import UserError +from trytond.model.exceptions import ValidationError + + +class SaleValidationError(ValidationError): + pass + + +class SaleQuotationError(ValidationError): + pass + + +class SaleConfirmError(UserError): + pass + + +class PartyLocationError(UserError): + pass diff -r a04f626934c1 -r c720259bb5b2 invoice.py --- a/invoice.py Sat Dec 22 00:16:15 2018 +0100 +++ b/invoice.py Sat Dec 29 14:20:29 2018 +0100 @@ -1,9 +1,10 @@ # 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 itertools import chain from functools import wraps +from trytond.i18n import gettext from trytond.model import Workflow, fields +from trytond.model.exceptions import AccessError from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction @@ -33,14 +34,6 @@ sales = fields.Function(fields.One2Many('sale.sale', None, 'Sales'), 'get_sales', searcher='search_sales') - @classmethod - def __setup__(cls): - super(Invoice, cls).__setup__() - cls._error_messages.update({ - 'reset_invoice_sale': ('You cannot reset to draft ' - 'an invoice generated by a sale.'), - }) - def get_sale_exception_state(self, name): sales = self.sales @@ -99,10 +92,11 @@ @classmethod @Workflow.transition('draft') def draft(cls, invoices): - sales = list(chain(*(i.sales for i in invoices))) - - if sales and any(i.state == 'cancel' for i in invoices): - cls.raise_user_error('reset_invoice_sale') + for invoice in invoices: + if invoice.sales and invoice.state == 'cancel': + raise AccessError( + gettext('sale.msg_sale_invoice_reset_draft', + invoice=invoice.rec_name)) return super(Invoice, cls).draft(invoices) diff -r a04f626934c1 -r c720259bb5b2 message.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/message.xml Sat Dec 29 14:20:29 2018 +0100 @@ -0,0 +1,46 @@ +<?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_erase_party_pending_sale"> + <field name="text">You cannot erase party "%(party)s" while they have pending sales with company "%(company)s".</field> + </record> + <record model="ir.message" id="msg_sale_invoice_reset_draft"> + <field name="text">You cannot reset invoice "%(invoice)s" to draft because it was generated by a sale.</field> + </record> + <record model="ir.message" id="msg_sale_move_reset_draft"> + <field name="text">You cannot reset move "%(move)s" to draft because it was generated by a sale.</field> + </record> + <record model="ir.message" id="msg_sale_invalid_method"> + <field name="text">You cannot use together invoice "%(invoice_method)s" and shipment "%(shipment_method)s" on sale "%(sale)s".</field> + </record> + <record model="ir.message" id="msg_sale_invoice_address_required_for_quotation"> + <field name="text">To get a quote for sale "%(sale)s" you must enter an invoice address.</field> + </record> + <record model="ir.message" id="msg_sale_shipment_address_required_for_quotation"> + <field name="text">To get a quote for sale "%(sale)s" you must enter a shipment address.</field> + </record> + <record model="ir.message" id="msg_sale_warehouse_required_for_quotation"> + <field name="text">To get a quote for sale "%(sale)s" you must enter a warehouse.</field> + </record> + <record model="ir.message" id="msg_sale_delete_cancel"> + <field name="text">To delete sale "%(sale)s" you must cancel it.</field> + </record> + <record model="ir.message" id="msg_sale_customer_location_required"> + <field name="text">To process sale "%(sale)s" you must set a customer location on party "%(party)s".</field> + </record> + <record model="ir.message" id="msg_sale_product_missing_account_expense"> + <field name="text">To invoice sale "%(sale)s" you must define an account revenue for product "%(product)s".</field> + </record> + <record model="ir.message" id="msg_sale_missing_account_expense"> + <field name="text">To invoice sale "%(sale)s" you must configure a default account revenue.</field> + </record> + <record model="ir.message" id="msg_sale_line_delete_cancel_draft"> + <field name="text">To delete line "%(line)s" you must cancel or reset to draft sale "%(sale)s".</field> + </record> + <record model="ir.message" id="msg_sale_modify_header_draft"> + <field name="text">To modify the header of sale "%(sale)s", it must be in "draft" state.</field> + </record> + </data> +</tryton> diff -r a04f626934c1 -r c720259bb5b2 party.py --- a/party.py Sat Dec 22 00:16:15 2018 +0100 +++ b/party.py Sat Dec 29 14:20:29 2018 +0100 @@ -1,7 +1,10 @@ # 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.i18n import gettext from trytond.pool import PoolMeta, Pool +from trytond.modules.party.exceptions import EraseError + __all__ = ['PartyReplace', 'PartyErase'] @@ -19,16 +22,6 @@ class PartyErase(metaclass=PoolMeta): __name__ = 'party.erase' - @classmethod - def __setup__(cls): - super(PartyErase, cls).__setup__() - cls._error_messages.update({ - 'pending_sale': ( - 'The party "%(party)s" can not be erased ' - 'because he has pending sales ' - 'for the company "%(company)s".'), - }) - def check_erase_company(self, party, company): pool = Pool() Sale = pool.get('sale.sale') @@ -42,7 +35,7 @@ ('state', 'not in', ['done', 'cancel']), ]) if sales: - self.raise_user_error('pending_sale', { - 'party': party.rec_name, - 'company': company.rec_name, - }) + raise EraseError( + gettext('sale.msg_erase_party_pending_sale', + party=party.rec_name, + company=company.rec_name)) diff -r a04f626934c1 -r c720259bb5b2 sale.py --- a/sale.py Sat Dec 22 00:16:15 2018 +0100 +++ b/sale.py Sat Dec 29 14:20:29 2018 +0100 @@ -5,8 +5,10 @@ from itertools import groupby, chain from functools import partial +from trytond.i18n import gettext from trytond.model import Workflow, Model, ModelView, ModelSQL, fields, \ sequence_ordered +from trytond.model.exceptions import AccessError from trytond.modules.company import CompanyReport from trytond.wizard import Wizard, StateAction, StateView, StateTransition, \ Button @@ -15,7 +17,10 @@ from trytond.pool import Pool from trytond.modules.account.tax import TaxableMixin +from trytond.modules.account_product.exceptions import AccountError from trytond.modules.product import price_digits +from .exceptions import ( + SaleValidationError, SaleQuotationError, PartyLocationError) __all__ = ['Sale', 'SaleIgnoredInvoice', 'SaleRecreatedInvoice', 'SaleLine', 'SaleLineTax', 'SaleLineIgnoredMove', @@ -175,6 +180,7 @@ 'readonly': Eval('state') != 'draft', }, depends=['state']) + invoice_method_string = invoice_method.translated('invoice_method') invoice_state = fields.Selection([ ('none', 'None'), ('waiting', 'Waiting'), @@ -197,6 +203,7 @@ 'readonly': Eval('state') != 'draft', }, depends=['state']) + shipment_method_string = shipment_method.translated('shipment_method') shipment_state = fields.Selection([ ('none', 'None'), ('waiting', 'Waiting'), @@ -222,17 +229,6 @@ ('sale_date', 'DESC'), ('id', 'DESC'), ] - cls._error_messages.update({ - 'invalid_method': ('Invalid combination of shipment and ' - 'invoicing methods on sale "%s".'), - 'addresses_required': ( - 'Invoice and Shipment addresses must be ' - 'defined for the quotation of sale "%s".'), - 'warehouse_required': ('Warehouse must be defined for the ' - 'quotation of sale "%s".'), - 'delete_cancel': ('Sale "%s" must be cancelled before ' - 'deletion.'), - }) cls._transitions |= set(( ('draft', 'quotation'), ('quotation', 'confirmed'), @@ -591,10 +587,18 @@ ''' if (self.invoice_method == 'shipment' and self.shipment_method in ('invoice', 'manual')): - self.raise_user_error('invalid_method', (self.rec_name,)) + raise SaleValidationError( + gettext('sale.msg_sale_invalid_method', + invoice_method=self.invoice_method_string, + shipment_method=self.shipment_method_string, + sale=self.rec_name)) if (self.shipment_method == 'invoice' and self.invoice_method in ('shipment', 'manual')): - self.raise_user_error('invalid_method', (self.rec_name,)) + raise SaleValidationError( + gettext('sale.msg_sale_invalid_method', + invoice_method=self.invoice_method_string, + shipment_method=self.shipment_method_string, + sale=self.rec_name)) def get_rec_name(self, name): items = [] @@ -650,8 +654,15 @@ return super(Sale, cls).copy(sales, default=default) def check_for_quotation(self): - if not self.invoice_address or not self.shipment_address: - self.raise_user_error('addresses_required', (self.rec_name,)) + if not self.invoice_address: + raise SaleQuotationError( + gettext('sale.msg_sale_invoice_address_required_for_quotation', + sale=self.rec_name)) + if not self.shipment_address: + raise SaleQuotationError( + gettext('sale' + '.msg_sale_shipment_address_required_for_quotation', + sale=self.rec_name)) for line in self.lines: if (line.quantity or 0) >= 0: location = line.from_location @@ -660,8 +671,9 @@ if ((not location or not line.warehouse) and line.product and line.product.type in ('goods', 'assets')): - self.raise_user_error('warehouse_required', - (self.rec_name,)) + raise SaleQuotationError( + gettext('sale.msg_sale_warehouse_required_for_quotation', + sale=self.rec_name)) @classmethod def set_number(cls, sales): @@ -816,7 +828,9 @@ cls.cancel(sales) for sale in sales: if sale.state != 'cancel': - cls.raise_user_error('delete_cancel', (sale.rec_name,)) + raise AccessError( + gettext('sale.msg_sale_delete_cancel', + sale=sale.rec_name)) super(Sale, cls).delete(sales) @classmethod @@ -1044,21 +1058,6 @@ 'on_change_with_sale_state') @classmethod - def __setup__(cls): - super(SaleLine, cls).__setup__() - cls._error_messages.update({ - 'customer_location_required': ( - 'Sale "%(sale)s" is missing the ' - 'customer location in line "%(line)s".'), - 'missing_account_revenue': ('Product "%(product)s" of sale ' - '%(sale)s misses a revenue account.'), - 'missing_default_account_revenue': ('Sale "%(sale)s" ' - 'misses a default "account revenue".'), - 'delete_cancel_draft': ('The line "%(line)s" must be on ' - 'canceled or draft sale to be deleted.'), - }) - - @classmethod def __register__(cls, module_name): super(SaleLine, cls).__register__(module_name) table = cls.__table_handler__(module_name) @@ -1316,17 +1315,17 @@ if self.product: invoice_line.account = self.product.account_revenue_used if not invoice_line.account: - self.raise_user_error('missing_account_revenue', { - 'sale': self.sale.rec_name, - 'product': self.product.rec_name, - }) + raise AccountError( + gettext('sale.msg_sale_product_missing_account_expense', + sale=self.sale.rec_name, + product=self.product.rec_name)) else: invoice_line.account = account_config.get_multivalue( 'default_category_account_revenue') if not invoice_line.account: - self.raise_user_error('missing_default_account_revenue', { - 'sale': self.sale.rec_name, - }) + raise AccountError( + gettext('sale.msg_sale_missing_account_expense', + sale=self.sale.rec_name)) invoice_line.stock_moves = self._get_invoice_line_moves() return [invoice_line] @@ -1407,10 +1406,10 @@ return if not self.sale.party.customer_location: - self.raise_user_error('customer_location_required', { - 'sale': self.sale.rec_name, - 'line': self.rec_name, - }) + raise PartyLocationError( + gettext('sale.msg_sale_customer_location_required', + sale=self.sale.rec_name, + party=self.sale.party.rec_name)) move = Move() move.quantity = quantity move.uom = self.unit @@ -1496,9 +1495,10 @@ def delete(cls, lines): for line in lines: if line.sale_state not in {'cancel', 'draft'}: - cls.raise_user_error('delete_cancel_draft', { - 'line': line.rec_name, - }) + raise AccessError( + gettext('sale.msg_sale_line_delete_cancel_draft', + line=line.rec_name, + sale=line.sale.rec_name)) super(SaleLine, cls).delete(lines) @classmethod @@ -1763,23 +1763,15 @@ ]) modify = StateTransition() - @classmethod - def __setup__(cls): - super(ModifyHeader, cls).__setup__() - cls._error_messages.update({ - 'not_in_draft': ( - 'The sale "%(sale)s" must be in draft to modify header.'), - }) - def get_sale(self): pool = Pool() Sale = pool.get('sale.sale') sale = Sale(Transaction().context['active_id']) if sale.state != 'draft': - self.raise_user_error('not_in_draft', { - 'sale': sale.rec_name, - }) + raise AccessError( + gettext('sale.msg_sale_modify_header_draft', + sale=sale.rec_name)) return sale def default_start(self, fields): diff -r a04f626934c1 -r c720259bb5b2 stock.py --- a/stock.py Sat Dec 22 00:16:15 2018 +0100 +++ b/stock.py Sat Dec 29 14:20:29 2018 +0100 @@ -2,7 +2,9 @@ # this repository contains the full copyright notices and license terms. from functools import wraps +from trytond.i18n import gettext from trytond.model import Workflow, ModelView, fields +from trytond.model.exceptions import AccessError from trytond.transaction import Transaction from trytond.pool import Pool, PoolMeta @@ -29,14 +31,6 @@ __name__ = 'stock.shipment.out' @classmethod - def __setup__(cls): - super(ShipmentOut, cls).__setup__() - cls._error_messages.update({ - 'reset_move': ('You cannot reset to draft a move generated ' - 'by a sale.'), - }) - - @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, shipments): @@ -45,7 +39,9 @@ for move in shipment.outgoing_moves: if (move.state == 'cancel' and isinstance(move.origin, SaleLine)): - cls.raise_user_error('reset_move') + raise AccessError( + gettext('sale.msg_sale_move_reset_draft', + move=move.rec_name)) return super(ShipmentOut, cls).draft(shipments) @@ -68,14 +64,6 @@ __name__ = 'stock.shipment.out.return' @classmethod - def __setup__(cls): - super(ShipmentOutReturn, cls).__setup__() - cls._error_messages.update({ - 'reset_move': ('You cannot reset to draft a move generated ' - 'by a sale.'), - }) - - @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, shipments): @@ -84,7 +72,9 @@ for move in shipment.incoming_moves: if (move.state == 'cancel' and isinstance(move.origin, SaleLine)): - cls.raise_user_error('reset_move') + raise AccessError( + gettext('sale.msg_sale_move_reset_draft', + move=move.rec_name)) return super(ShipmentOutReturn, cls).draft(shipments) diff -r a04f626934c1 -r c720259bb5b2 tryton.cfg --- a/tryton.cfg Sat Dec 22 00:16:15 2018 +0100 +++ b/tryton.cfg Sat Dec 29 14:20:29 2018 +0100 @@ -19,3 +19,4 @@ party.xml stock.xml product.xml + message.xml