diff --git a/addons/sale/data/ir_sequence_data.xml b/addons/sale/data/ir_sequence_data.xml index 7ce4484d89d..8e4559a0cdf 100644 --- a/addons/sale/data/ir_sequence_data.xml +++ b/addons/sale/data/ir_sequence_data.xml @@ -1,5 +1,5 @@ - + @@ -12,4 +12,4 @@ - + diff --git a/addons/sale/data/product_product_demo.xml b/addons/sale/data/product_product_demo.xml index a4b8c6a5b47..e7ffee65b31 100644 --- a/addons/sale/data/product_product_demo.xml +++ b/addons/sale/data/product_product_demo.xml @@ -1,5 +1,5 @@ - + delivery @@ -154,4 +154,4 @@ - + diff --git a/addons/sale/data/sale_data.xml b/addons/sale/data/sale_data.xml index af87599d053..bfa78f5e596 100644 --- a/addons/sale/data/sale_data.xml +++ b/addons/sale/data/sale_data.xml @@ -1,5 +1,5 @@ - + @@ -75,4 +75,4 @@ 10 - + diff --git a/addons/sale/data/sale_demo.xml b/addons/sale/data/sale_demo.xml index 18338fa7476..be41326e3bc 100644 --- a/addons/sale/data/sale_demo.xml +++ b/addons/sale/data/sale_demo.xml @@ -1,5 +1,5 @@ - + @@ -320,4 +320,4 @@ Thanks! - + diff --git a/addons/sale/models/account_config_settings.py b/addons/sale/models/account_config_settings.py index 2fe8b71fba5..2d49b9bb411 100644 --- a/addons/sale/models/account_config_settings.py +++ b/addons/sale/models/account_config_settings.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import logging -from openerp.osv import fields, osv +from odoo import fields, models -_logger = logging.getLogger(__name__) - -class account_config_settings(osv.osv_memory): +class AccountConfigSettings(models.TransientModel): _inherit = 'account.config.settings' - _columns = { - 'group_analytic_account_for_sales': fields.boolean('Analytic accounting for sales', - implied_group='sale.group_analytic_accounting', - help="Allows you to specify an analytic account on sales orders."), - } + + group_analytic_account_for_sales = fields.Boolean( + 'Analytic accounting for sales', + implied_group='sale.group_analytic_accounting', + help="Allows you to specify an analytic account on sales orders.") diff --git a/addons/sale/models/res_partner.py b/addons/sale/models/res_partner.py index f5482ae6508..d8875db8234 100644 --- a/addons/sale/models/res_partner.py +++ b/addons/sale/models/res_partner.py @@ -1,29 +1,21 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.osv import fields,osv -from openerp.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP +from odoo import fields, models +from odoo.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP -class res_partner(osv.osv): + +class ResPartner(models.Model): _inherit = 'res.partner' - def _sale_order_count(self, cr, uid, ids, field_name, arg, context=None): - res = dict(map(lambda x: (x,0), ids)) - # The current user may not have access rights for sale orders - try: - for partner in self.browse(cr, uid, ids, context): - res[partner.id] = len(partner.sale_order_ids) + len(partner.mapped('child_ids.sale_order_ids')) - except: - pass - return res + sale_order_count = fields.Integer(compute='_compute_sale_order_count', string='# of Sales Order') + sale_order_ids = fields.One2many('sale.order', 'partner_id', 'Sales Order') + sale_warn = fields.Selection(WARNING_MESSAGE, 'Sales Order', default='no-message', help=WARNING_HELP, required=True) + sale_warn_msg = fields.Text('Message for Sales Order') - _columns = { - 'sale_order_count': fields.function(_sale_order_count, string='# of Sales Order', type='integer'), - 'sale_order_ids': fields.one2many('sale.order', 'partner_id', 'Sales Order'), - 'sale_warn': fields.selection(WARNING_MESSAGE, 'Sales Order', default='no-message', help=WARNING_HELP, required=True), - 'sale_warn_msg': fields.text('Message for Sales Order'), - } - - _defaults = { - 'sale_warn': 'no-message', - } + def _compute_sale_order_count(self): + SaleOrder = self.env['sale.order'] + for partner in self: + partner.sale_order_count = SaleOrder.search_count([ + ('partner_id', 'child_of', partner.ids) + ]) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 5dfd4ece4bc..2e034b33ab9 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -3,13 +3,13 @@ from itertools import groupby from datetime import datetime, timedelta -from openerp import SUPERUSER_ID -from openerp import api, fields, models, _ -import openerp.addons.decimal_precision as dp -from openerp.exceptions import UserError -from openerp.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT -from openerp.tools.misc import formatLang -from openerp.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError +from odoo.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT +from odoo.tools.misc import formatLang + +import odoo.addons.decimal_precision as dp class SaleOrder(models.Model): @@ -236,7 +236,7 @@ class SaleOrder(models.Model): # Block if partner only has warning but parent company is blocked if partner.sale_warn != 'block' and partner.parent_id and partner.parent_id.sale_warn == 'block': partner = partner.parent_id - title = _("Warning for %s") % partner.name + title = ("Warning for %s") % partner.name message = partner.sale_warn_msg warning = { 'title': title, @@ -264,7 +264,6 @@ class SaleOrder(models.Model): result = super(SaleOrder, self).create(vals) return result - @api.multi def _prepare_invoice(self): """ @@ -367,7 +366,6 @@ class SaleOrder(models.Model): if not invoices: raise UserError(_('There is no invoicable line.')) - for invoice in invoices.values(): if not invoice.invoice_line_ids: raise UserError(_('There is no invoicable line.')) @@ -482,7 +480,6 @@ class SaleOrder(models.Model): @api.multi def _notification_group_recipients(self, message, recipients, done_ids, group_data): - group_user = self.env.ref('base.group_user') for recipient in recipients: if recipient.id in done_ids: continue @@ -580,7 +577,7 @@ class SaleOrderLine(models.Model): @api.depends('product_id.invoice_policy', 'order_id.state') def _compute_qty_delivered_updateable(self): for line in self: - line.qty_delivered_updateable = (line.order_id.state == 'sale') and (line.product_id.track_service == 'manual') and (line.product_id.expense_policy=='no') + line.qty_delivered_updateable = (line.order_id.state == 'sale') and (line.product_id.track_service == 'manual') and (line.product_id.expense_policy == 'no') @api.depends('qty_invoiced', 'qty_delivered', 'product_uom_qty', 'order_id.state') def _get_to_invoice_qty(self): @@ -660,7 +657,7 @@ class SaleOrderLine(models.Model): procurements are created. If the quantity is decreased, no automated action is taken. """ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') - new_procs = self.env['procurement.order'] #Empty recordset + new_procs = self.env['procurement.order'] # Empty recordset for line in self: if line.state != 'sale' or not line.product_id._need_procurement(): continue @@ -782,8 +779,8 @@ class SaleOrderLine(models.Model): res = {} account = self.product_id.property_account_income_id or self.product_id.categ_id.property_account_income_categ_id if not account: - raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') % \ - (self.product_id.name, self.product_id.id, self.product_id.categ_id.name)) + raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') % + (self.product_id.name, self.product_id.id, self.product_id.categ_id.name)) fpos = self.order_id.fiscal_position_id or self.order_id.partner_id.property_account_position_id if fpos: diff --git a/addons/sale/models/sale_analytic.py b/addons/sale/models/sale_analytic.py index 64e62a6a87a..413f9967331 100644 --- a/addons/sale/models/sale_analytic.py +++ b/addons/sale/models/sale_analytic.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp import api, fields, models, _ -from openerp.exceptions import UserError +from odoo import api, fields, models class SaleOrderLine(models.Model): @@ -78,8 +77,8 @@ class AccountAnalyticLine(models.Model): def _get_sale_order_line(self, vals=None): result = dict(vals or {}) so_line = result.get('so_line', False) or self.so_line - if not so_line and self.account_id and self.product_id and (self.product_id.expense_policy!='no'): - order = self.env['sale.order'].search([('project_id', '=', self.account_id.id), ('state','=','sale')], limit=1) + if not so_line and self.account_id and self.product_id and (self.product_id.expense_policy != 'no'): + order = self.env['sale.order'].search([('project_id', '=', self.account_id.id), ('state', '=', 'sale')], limit=1) if not order: return result price = self._get_invoice_price(order) diff --git a/addons/sale/models/sale_config_settings.py b/addons/sale/models/sale_config_settings.py index 433d3d1f360..9fef051e635 100644 --- a/addons/sale/models/sale_config_settings.py +++ b/addons/sale/models/sale_config_settings.py @@ -3,132 +3,165 @@ import logging -from openerp import SUPERUSER_ID -from openerp.osv import fields, osv +from odoo import api, fields, models _logger = logging.getLogger(__name__) -class sale_configuration(osv.TransientModel): +class SaleConfiguration(models.TransientModel): _inherit = 'sale.config.settings' - _columns = { - 'group_product_variant': fields.selection([ - (0, "No variants on products"), - (1, 'Products can have several attributes, defining variants (Example: size, color,...)') - ], "Product Variants", - help='Work with product variant allows you to define some variant of the same products, an ease the product management in the ecommerce for example', - implied_group='product.group_product_variant'), - 'group_sale_pricelist':fields.boolean("Use pricelists to adapt your price per customers",implied_group='product.group_sale_pricelist', - help="""Allows to manage different prices based on rules per category of customers. - Example: 10% for retailers, promotion of 5 EUR on this product, etc."""), - 'group_pricelist_item':fields.boolean("Show pricelists to customers", implied_group='product.group_pricelist_item'), - 'group_product_pricelist':fields.boolean("Show pricelists On Products", implied_group='product.group_product_pricelist'), - 'group_uom':fields.selection([ - (0, 'Products have only one unit of measure (easier)'), - (1, 'Some products may be sold/purchased in different units of measure (advanced)') - ], "Units of Measure", - implied_group='product.group_uom', - help="""Allows you to select and maintain different units of measure for products."""), - 'group_discount_per_so_line': fields.selection([ - (0, 'No discount on sales order lines, global discount only'), - (1, 'Allow discounts on sales order lines') - ], "Discount", - implied_group='sale.group_discount_per_so_line'), - 'group_display_incoterm':fields.selection([ - (0, 'No incoterm on reports'), - (1, 'Show incoterms on sale orders and invoices') - ], "Incoterms", - implied_group='sale.group_display_incoterm', - help="The printed reports will display the incoterms for the sale orders and the related invoices"), - 'module_product_visible_discount': fields.selection([ - (0, 'No discount policy on sale order line'), - (1, 'Allow discount policy on sale order line') - ], "Discount Policy"), - 'module_sale_margin': fields.selection([ - (0, 'Salespeople do not need to view margins when quoting'), - (1, 'Display margins on quotations and sales orders') - ], "Margins"), - 'group_sale_layout': fields.selection([ - (0, 'Do not personalize sale orders and invoice reports'), - (1, 'Personalize the sale orders and invoice report with categories, subtotals and page-breaks') - ], "Sale Reports Layout", implied_group='sale.group_sale_layout'), - 'group_warning_sale': fields.selection([ - (0, 'All the products and the customers can be used in sale orders'), - (1, 'An informative or blocking warning can be set on a product or a customer') - ], "Warning", implied_group='sale.group_warning_sale'), - 'module_website_quote': fields.selection([ - (0, 'Print quotes or send by email'), - (1, 'Send online quotations based on templates (advanced)') - ], "Online Quotations"), - 'group_sale_delivery_address': fields.selection([ - (0, "Invoicing and shipping addresses are always the same (Example: services companies)"), - (1, 'Display 3 fields on sales orders: customer, invoice address, delivery address') - ], "Addresses", implied_group='sale.group_delivery_invoice_address'), - 'sale_pricelist_setting': fields.selection([('fixed', 'A single sale price per product'), ('percentage', 'Different prices per customer segment'), ('formula', 'Advanced pricing based on formula')], required=True, - help='Fix Price: all price manage from products sale price.\n' - 'Different prices per Customer: you can assign price on buying of minimum quantity in products sale tab.\n' - 'Advanced pricing based on formula: You can have all the rights on pricelist'), - 'group_show_price_subtotal': fields.boolean("Show subtotal", implied_group='sale.group_show_price_subtotal', group='base.group_portal,base.group_user,base.group_public'), - 'group_show_price_total': fields.boolean("Show total", implied_group='sale.group_show_price_total', group='base.group_portal,base.group_user,base.group_public'), - 'sale_show_tax': fields.selection([ - ('subtotal', 'Show line subtotals without taxes (B2B)'), - ('total', 'Show line subtotals with taxes included (B2C)')], "Tax Display", - required=True), - 'default_invoice_policy': fields.selection([ - ('order', 'Invoice ordered quantities'), - ('delivery', 'Invoice delivered quantities') - ], 'Default Invoicing', default_model='product.template'), - 'deposit_product_id_setting': fields.many2one('product.product', 'Deposit Product',\ - domain="[('type', '=', 'service')]",\ - help='Default product used for payment advances'), - 'auto_done_setting': fields.selection([ - (0, "Allow to edit sales order from the 'Sales Order' menu (not from the Quotation menu)"), - (1, "Never allow to modify a confirmed sale order") - ], "Sale Order Modification"), - 'module_sale_contract': fields.boolean("Manage subscriptions and recurring invoicing"), - 'module_website_sale_digital': fields.boolean("Sell digital products - provide downloadable content on your customer portal"), - 'module_website_portal': fields.boolean("Enable customer portal to track orders, delivery and invoices"), - 'module_sale_order_dates': fields.selection([ - (0, 'Procurements and deliveries dates are based on the sale order dates'), - (1, 'Allow to modify the sale order dates to postpone deliveries and procurements') - ], "Date"), - } + group_product_variant = fields.Selection([ + (0, "No variants on products"), + (1, 'Products can have several attributes, defining variants (Example: size, color,...)') + ], "Product Variants", + help="""Work with product variant allows you to define some variant of the same products + , an ease the product management in the ecommerce for example""", + implied_group='product.group_product_variant') + group_sale_pricelist = fields.Boolean("Use pricelists to adapt your price per customers", + implied_group='product.group_sale_pricelist', + help="""Allows to manage different prices based on rules per category of customers. + Example: 10% for retailers, promotion of 5 EUR on this product, etc.""") + group_pricelist_item = fields.Boolean("Show pricelists to customers", + implied_group='product.group_pricelist_item') + group_product_pricelist = fields.Boolean("Show pricelists On Products", + implied_group='product.group_product_pricelist') + group_uom = fields.Selection([ + (0, 'Products have only one unit of measure (easier)'), + (1, 'Some products may be sold/purchased in different units of measure (advanced)') + ], "Units of Measure", + implied_group='product.group_uom', + help="""Allows you to select and maintain different units of measure for products.""") + group_discount_per_so_line = fields.Selection([ + (0, 'No discount on sales order lines, global discount only'), + (1, 'Allow discounts on sales order lines') + ], "Discount", + implied_group='sale.group_discount_per_so_line') + group_display_incoterm = fields.Selection([ + (0, 'No incoterm on reports'), + (1, 'Show incoterms on sale orders and invoices') + ], "Incoterms", + implied_group='sale.group_display_incoterm', + help="The printed reports will display the incoterms for the sale orders and the related invoices") + module_product_visible_discount = fields.Selection([ + (0, 'No discount policy on sale order line'), + (1, 'Allow discount policy on sale order line') + ], "Discount Policy") + module_sale_margin = fields.Selection([ + (0, 'Salespeople do not need to view margins when quoting'), + (1, 'Display margins on quotations and sales orders') + ], "Margins") + group_sale_layout = fields.Selection([ + (0, 'Do not personalize sale orders and invoice reports'), + (1, 'Personalize the sale orders and invoice report with categories, subtotals and page-breaks') + ], "Sale Reports Layout", implied_group='sale.group_sale_layout') + group_warning_sale = fields.Selection([ + (0, 'All the products and the customers can be used in sale orders'), + (1, 'An informative or blocking warning can be set on a product or a customer') + ], "Warning", implied_group='sale.group_warning_sale') + module_website_quote = fields.Selection([ + (0, 'Print quotes or send by email'), + (1, 'Send online quotations based on templates (advanced)') + ], "Online Quotations") + group_sale_delivery_address = fields.Selection([ + (0, "Invoicing and shipping addresses are always the same (Example: services companies)"), + (1, 'Display 3 fields on sales orders: customer, invoice address, delivery address') + ], "Addresses", implied_group='sale.group_delivery_invoice_address') + sale_pricelist_setting = fields.Selection([ + ('fixed', 'A single sale price per product'), + ('percentage', 'Different prices per customer segment'), + ('formula', 'Advanced pricing based on formula') + ], required=True, + default='fixed', + help='Fix Price: all price manage from products sale price.\n' + 'Different prices per Customer: you can assign price on buying of minimum quantity in products sale tab.\n' + 'Advanced pricing based on formula: You can have all the rights on pricelist') + group_show_price_subtotal = fields.Boolean( + "Show subtotal", + implied_group='sale.group_show_price_subtotal', + group='base.group_portal,base.group_user,base.group_public') + group_show_price_total = fields.Boolean( + "Show total", + implied_group='sale.group_show_price_total', + group='base.group_portal,base.group_user,base.group_public') + sale_show_tax = fields.Selection([ + ('subtotal', 'Show line subtotals without taxes (B2B)'), + ('total', 'Show line subtotals with taxes included (B2C)')], "Tax Display", + default='subtotal', + required=True) + default_invoice_policy = fields.Selection([ + ('order', 'Invoice ordered quantities'), + ('delivery', 'Invoice delivered quantities') + ], 'Default Invoicing', + default='order', + default_model='product.template') + deposit_product_id_setting = fields.Many2one( + 'product.product', + 'Deposit Product', + domain="[('type', '=', 'service')]", + help='Default product used for payment advances') + auto_done_setting = fields.Selection([ + (0, "Allow to edit sales order from the 'Sales Order' menu (not from the Quotation menu)"), + (1, "Never allow to modify a confirmed sale order") + ], "Sale Order Modification") + module_sale_contract = fields.Boolean("Manage subscriptions and recurring invoicing") + module_website_sale_digital = fields.Boolean("Sell digital products - provide downloadable content on your customer portal") + module_website_portal = fields.Boolean("Enable customer portal to track orders, delivery and invoices") + module_sale_order_dates = fields.Selection([ + (0, 'Procurements and deliveries dates are based on the sale order dates'), + (1, 'Allow to modify the sale order dates to postpone deliveries and procurements') + ], "Date") - _defaults = { - 'sale_pricelist_setting': 'fixed', - 'sale_show_tax': 'subtotal', - 'default_invoice_policy': 'order', - } + @api.multi + def set_sale_defaults(self): + return self.env['ir.values'].sudo().set_default( + 'sale.config.settings', 'sale_pricelist_setting', self.sale_pricelist_setting) - def set_sale_defaults(self, cr, uid, ids, context=None): - sale_price = self.browse(cr, uid, ids, context=context).sale_pricelist_setting - res = self.pool.get('ir.values').set_default(cr, SUPERUSER_ID, 'sale.config.settings', 'sale_pricelist_setting', sale_price) - return res + @api.multi + def set_deposit_product_id_defaults(self): + return self.env['ir.values'].sudo().set_default( + 'sale.config.settings', 'deposit_product_id_setting', self.deposit_product_id_setting.id) - def set_deposit_product_id_defaults(self, cr, uid, ids, context=None): - deposit_product_id = self.browse(cr, uid, ids, context=context).deposit_product_id_setting - res = self.pool.get('ir.values').set_default(cr, SUPERUSER_ID, 'sale.config.settings', 'deposit_product_id_setting', deposit_product_id.id) - return res + @api.multi + def set_auto_done_defaults(self): + return self.env['ir.values'].sudo().set_default( + 'sale.config.settings', 'auto_done_setting', self.auto_done_setting) - def set_auto_done_defaults(self, cr, uid, ids, context=None): - auto_done = self.browse(cr, uid, ids, context=context).auto_done_setting - res = self.pool.get('ir.values').set_default(cr, SUPERUSER_ID, 'sale.config.settings', 'auto_done_setting', auto_done) - return res + @api.onchange('sale_pricelist_setting') + def _onchange_sale_price(self): + if self.sale_pricelist_setting == 'percentage': + self.update({ + 'group_product_pricelist': True, + 'group_sale_pricelist': True, + 'group_pricelist_item': False, + }) + elif self.sale_pricelist_setting == 'formula': + self.update({ + 'group_product_pricelist': False, + 'group_sale_pricelist': True, + 'group_pricelist_item': True, + }) + else: + self.update({ + 'group_product_pricelist': False, + 'group_sale_pricelist': False, + 'group_pricelist_item': False, + }) - def onchange_sale_price(self, cr, uid, ids, sale_pricelist_setting, context=None): - if sale_pricelist_setting == 'percentage': - return {'value': {'group_product_pricelist': True, 'group_sale_pricelist': True, 'group_pricelist_item': False}} - if sale_pricelist_setting == 'formula': - return {'value': {'group_pricelist_item': True, 'group_sale_pricelist': True, 'group_product_pricelist': False}} - return {'value': {'group_pricelist_item': False, 'group_sale_pricelist': False, 'group_product_pricelist': False}} + @api.multi + def set_sale_tax_defaults(self): + return self.env['ir.values'].sudo().set_default( + 'sale.config.settings', 'sale_show_tax', self.sale_show_tax) - def set_sale_tax_defaults(self, cr, uid, ids, context=None): - sale_tax = self.browse(cr, uid, ids, context=context).sale_show_tax - res = self.pool.get('ir.values').set_default(cr, SUPERUSER_ID, 'sale.config.settings', 'sale_show_tax', sale_tax) - return res - - def onchange_sale_tax(self, cr, uid, ids, sale_show_tax, context=None): - res = {'value': dict(group_show_price_subtotal=False, group_show_price_total=False)} - res['value']['group_show_price_%s' % sale_show_tax] = True - return res + @api.onchange('sale_show_tax') + def _onchange_sale_tax(self): + if self.sale_show_tax == "subtotal": + self.update({ + 'group_show_price_total': False, + 'group_show_price_subtotal': True, + }) + else: + self.update({ + 'group_show_price_total': True, + 'group_show_price_subtotal': False, + }) diff --git a/addons/sale/models/sale_layout.py b/addons/sale/models/sale_layout.py index adebb3e0275..7ee65029ca9 100644 --- a/addons/sale/models/sale_layout.py +++ b/addons/sale/models/sale_layout.py @@ -1,21 +1,14 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.osv import osv, fields +from odoo import fields, models -class SaleLayoutCategory(osv.Model): +class SaleLayoutCategory(models.Model): _name = 'sale.layout_category' _order = 'sequence, id' - _columns = { - 'name': fields.char('Name', required=True, translate=True), - 'sequence': fields.integer('Sequence', required=True), - 'subtotal': fields.boolean('Add subtotal'), - 'pagebreak': fields.boolean('Add pagebreak') - } - _defaults = { - 'subtotal': True, - 'pagebreak': False, - 'sequence': 10 - } + name = fields.Char('Name', required=True, translate=True) + sequence = fields.Integer('Sequence', required=True, default=10) + subtotal = fields.Boolean('Add subtotal', default=True) + pagebreak = fields.Boolean('Add pagebreak') diff --git a/addons/sale/models/sales_team.py b/addons/sale/models/sales_team.py index 309419eb97f..74bbe618751 100644 --- a/addons/sale/models/sales_team.py +++ b/addons/sale/models/sales_team.py @@ -1,65 +1,55 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. -import calendar from datetime import date -from dateutil import relativedelta -import json -from openerp import tools -from openerp.osv import fields, osv -from openerp.tools.float_utils import float_repr +from odoo import api, fields, models -class crm_team(osv.Model): + +class CrmTeam(models.Model): _inherit = 'crm.team' - def _get_sales_to_invoice_amount(self, cr, uid, ids, field_name, arg, context=None): - obj = self.pool['sale.order'] + use_quotations = fields.Boolean('Quotations', default=True, + help="Check this box to manage quotations in this sales team.") + use_invoices = fields.Boolean('Invoices', + help="Check this box to manage invoices in this sales team.") + invoiced = fields.Integer( + compute='_compute_invoiced', + string='Invoiced This Month', readonly=True, + help="Invoice revenue for the current month. This is the amount the sales " + "team has invoiced this month. It is used to compute the progression ratio " + "of the current and target revenue on the kanban view.") + invoiced_target = fields.Integer( + string='Invoice Target', + help="Target of invoice revenue for the current month. This is the amount the sales " + "team estimates to be able to invoice this month.") + sales_to_invoice_amount = fields.Integer( + compute='_compute_sales_to_invoice_amount', + string='Amount of sales to invoice', readonly=True, + ) + currency_id = fields.Many2one("res.currency", related='company_id.currency_id', + string="Currency", readonly=True, required=True) - res = dict.fromkeys(ids, 0) - domain = [ - ('team_id', 'in', ids), + @api.multi + def _compute_sales_to_invoice_amount(self): + amounts = self.env['sale.order'].read_group([ + ('team_id', 'in', self.ids), ('invoice_status', '=', 'to invoice'), - ] - amounts = obj.read_group(cr, uid, domain, ['amount_total', 'team_id'], ['team_id'], context=context) + ], ['amount_total', 'team_id'], ['team_id']) for rec in amounts: - res[rec['team_id'][0]] = rec['amount_total'] - return res + self.browse(rec['team_id'][0]).sales_to_invoice_amount = rec['amount_total'] - def _get_monthly_invoiced(self, cr, uid, ids, field_name, arg, context=None): - obj_inv = self.pool['account.invoice'] - res = dict.fromkeys(ids, 0) - - # Cannot use read_group because amount_untaxed_signed is an unstored computed field - for team in ids: - domain = [ + @api.multi + def _compute_invoiced(self): + for team in self: + invoices = self.env['account.invoice'].search([ ('state', 'in', ['open', 'paid']), - ('team_id', '=', team), + ('team_id', '=', team.id), ('date', '<=', date.today()), ('date', '>=', date.today().replace(day=1)) - ] - invoices = obj_inv.search_read(cr, uid, domain, ['amount_untaxed_signed'], context=context) - res[team] = sum([inv['amount_untaxed_signed'] for inv in invoices]) - return res + ]) + team.invoiced = sum(invoices.mapped('amount_untaxed_signed')) - _columns = { - 'use_quotations': fields.boolean('Quotations', help="Check this box to manage quotations in this sales team."), - 'use_invoices': fields.boolean('Invoices', help="Check this box to manage invoices in this sales team."), - 'invoiced': fields.function(_get_monthly_invoiced, type='integer', readonly=True, string='Invoiced This Month', - help="Invoice revenue for the current month. This is the amount the sales " - "team has invoiced this month. It is used to compute the progression ratio " - "of the current and target revenue on the kanban view."), - 'invoiced_target': fields.integer(string='Invoice Target', - help="Target of invoice revenue for the current month. This is the amount the sales " - "team estimates to be able to invoice this month."), - 'sales_to_invoice_amount': fields.function(_get_sales_to_invoice_amount, - type='integer', readonly=True, - string='Amount of sales to invoice'), - 'currency_id': fields.related('company_id', 'currency_id', type="many2one", relation="res.currency", string="Currency", readonly=True, required=True), - } - - _defaults = { - 'use_quotations': True, - } - - def update_invoiced_target(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_target': round(float(value or 0))}, context=context) + @api.multi + def update_invoiced_target(self, value): + return self.write({'invoiced_target': round(float(value or 0))}) diff --git a/addons/sale/report/invoice_report.py b/addons/sale/report/invoice_report.py index d3893178936..c70f82afe6c 100644 --- a/addons/sale/report/invoice_report.py +++ b/addons/sale/report/invoice_report.py @@ -1,17 +1,19 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp import api, fields, models, _ +from odoo import fields, models + class AccountInvoiceReport(models.Model): _inherit = 'account.invoice.report' + team_id = fields.Many2one('crm.team', string='Sales Team') def _select(self): - return super(AccountInvoiceReport, self)._select() + ", sub.team_id as team_id" + return super(AccountInvoiceReport, self)._select() + ", sub.team_id as team_id" def _sub_select(self): - return super(AccountInvoiceReport, self)._sub_select() + ", ai.team_id as team_id" + return super(AccountInvoiceReport, self)._sub_select() + ", ai.team_id as team_id" def _group_by(self): return super(AccountInvoiceReport, self)._group_by() + ", ai.team_id" diff --git a/addons/sale/report/invoice_report_templates.xml b/addons/sale/report/invoice_report_templates.xml index 48094594501..d22a4af8894 100644 --- a/addons/sale/report/invoice_report_templates.xml +++ b/addons/sale/report/invoice_report_templates.xml @@ -1,6 +1,5 @@ - - + - - + diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index 4d6bee95b12..05afd406778 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -1,49 +1,48 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp import tools -from openerp.osv import fields, osv +from odoo import tools +from odoo import api, fields, models -class sale_report(osv.osv): + +class SaleReport(models.Model): _name = "sale.report" _description = "Sales Orders Statistics" _auto = False _rec_name = 'date' - - _columns = { - 'name': fields.char('Order Reference', readonly=True), - 'date': fields.datetime('Date Order', readonly=True), - 'product_id': fields.many2one('product.product', 'Product', readonly=True), - 'product_uom': fields.many2one('product.uom', 'Unit of Measure', readonly=True), - 'product_uom_qty': fields.float('# of Qty', readonly=True), - 'qty_delivered': fields.float('Qty Delivered', readonly=True), - 'qty_to_invoice': fields.float('Qty To Invoice', readonly=True), - 'qty_invoiced': fields.float('Qty Invoiced', readonly=True), - 'partner_id': fields.many2one('res.partner', 'Partner', readonly=True), - 'company_id': fields.many2one('res.company', 'Company', readonly=True), - 'user_id': fields.many2one('res.users', 'Salesperson', readonly=True), - 'price_total': fields.float('Total', readonly=True), - 'price_subtotal': fields.float('Untaxed Total', readonly=True), - 'product_tmpl_id': fields.many2one('product.template', 'Product Template', readonly=True), - 'categ_id': fields.many2one('product.category','Product Category', readonly=True), - 'nbr': fields.integer('# of Lines', readonly=True), - 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', readonly=True), - 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), - 'team_id': fields.many2one('crm.team', 'Sales Team', readonly=True, oldname='section_id'), - 'country_id': fields.many2one('res.country', 'Partner Country', readonly=True), - 'commercial_partner_id': fields.many2one('res.partner', 'Commercial Entity', readonly=True), - 'state': fields.selection([ - ('draft', 'Draft Quotation'), - ('sent', 'Quotation Sent'), - ('sale', 'Sales Order'), - ('done', 'Sales Done'), - ('cancel', 'Cancelled'), - ], string='Status', readonly=True), - 'weight': fields.float('Gross Weight', readonly=True), - 'volume': fields.float('Volume', readonly=True), - } _order = 'date desc' + name = fields.Char('Order Reference', readonly=True) + date = fields.Datetime('Date Order', readonly=True) + product_id = fields.Many2one('product.product', 'Product', readonly=True) + product_uom = fields.Many2one('product.uom', 'Unit of Measure', readonly=True) + product_uom_qty = fields.Float('# of Qty', readonly=True) + qty_delivered = fields.Float('Qty Delivered', readonly=True) + qty_to_invoice = fields.Float('Qty To Invoice', readonly=True) + qty_invoiced = fields.Float('Qty Invoiced', readonly=True) + partner_id = fields.Many2one('res.partner', 'Partner', readonly=True) + company_id = fields.Many2one('res.company', 'Company', readonly=True) + user_id = fields.Many2one('res.users', 'Salesperson', readonly=True) + price_total = fields.Float('Total', readonly=True) + price_subtotal = fields.Float('Untaxed Total', readonly=True) + product_tmpl_id = fields.Many2one('product.template', 'Product Template', readonly=True) + categ_id = fields.Many2one('product.category', 'Product Category', readonly=True) + nbr = fields.Integer('# of Lines', readonly=True) + pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True) + analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True) + team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True, oldname='section_id') + country_id = fields.Many2one('res.country', 'Partner Country', readonly=True) + commercial_partner_id = fields.Many2one('res.partner', 'Commercial Entity', readonly=True) + state = fields.Selection([ + ('draft', 'Draft Quotation'), + ('sent', 'Quotation Sent'), + ('sale', 'Sales Order'), + ('done', 'Sales Done'), + ('cancel', 'Cancelled'), + ], string='Status', readonly=True) + weight = fields.Float('Gross Weight', readonly=True) + volume = fields.Float('Volume', readonly=True) + def _select(self): select_str = """ WITH currency_rate as (%s) @@ -73,7 +72,7 @@ class sale_report(osv.osv): partner.commercial_partner_id as commercial_partner_id, sum(p.weight * l.product_uom_qty / u.factor * u2.factor) as weight, sum(p.volume * l.product_uom_qty / u.factor * u2.factor) as volume - """ % self.pool['res.currency']._select_companies_rates() + """ % self.env['res.currency']._select_companies_rates() return select_str def _from(self): @@ -114,10 +113,11 @@ class sale_report(osv.osv): """ return group_by_str - def init(self, cr): + @api.model_cr + def init(self): # self._table = sale_report - tools.drop_view_if_exists(cr, self._table) - cr.execute("""CREATE or REPLACE VIEW %s as ( + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute("""CREATE or REPLACE VIEW %s as ( %s FROM ( %s ) %s diff --git a/addons/sale/report/sale_report.xml b/addons/sale/report/sale_report.xml index 7a8614b139b..04d9080928b 100644 --- a/addons/sale/report/sale_report.xml +++ b/addons/sale/report/sale_report.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml index 1e45bb32385..9e70cce41ef 100644 --- a/addons/sale/report/sale_report_templates.xml +++ b/addons/sale/report/sale_report_templates.xml @@ -1,6 +1,5 @@ - - + - - + diff --git a/addons/sale/report/sale_report_views.xml b/addons/sale/report/sale_report_views.xml index bee03b39d42..af00b04801a 100644 --- a/addons/sale/report/sale_report_views.xml +++ b/addons/sale/report/sale_report_views.xml @@ -1,6 +1,5 @@ - - + sale.report.pivot @@ -97,5 +96,4 @@ - - + diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml index e3126ce43f1..a5a069c8e37 100644 --- a/addons/sale/security/sale_security.xml +++ b/addons/sale/security/sale_security.xml @@ -1,6 +1,5 @@ - - + Personalize sale order and invoice report @@ -73,7 +72,6 @@ - @@ -140,6 +138,5 @@ [(1,'=',1)] - - + diff --git a/addons/sale/tests/test_product_id_change.py b/addons/sale/tests/test_product_id_change.py index ffac350cd5f..4a795baa4c8 100644 --- a/addons/sale/tests/test_product_id_change.py +++ b/addons/sale/tests/test_product_id_change.py @@ -1,4 +1,8 @@ -from openerp.tests.common import TransactionCase +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import TransactionCase + class TestOnchangeProductId(TransactionCase): """Test that when an included tax is mapped by a fiscal position, the included tax must be diff --git a/addons/sale/tests/test_sale_common.py b/addons/sale/tests/test_sale_common.py index 8622da2fcd4..a4101d99240 100644 --- a/addons/sale/tests/test_sale_common.py +++ b/addons/sale/tests/test_sale_common.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.addons.account.tests.account_test_classes import AccountingTestCase + +from odoo.addons.account.tests.account_test_classes import AccountingTestCase class TestSale(AccountingTestCase): diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py index f920aaa10ca..e8e9d6c4b98 100644 --- a/addons/sale/tests/test_sale_order.py +++ b/addons/sale/tests/test_sale_order.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.exceptions import UserError, AccessError + +from odoo.exceptions import UserError, AccessError from test_sale_common import TestSale diff --git a/addons/sale/tests/test_sale_to_invoice.py b/addons/sale/tests/test_sale_to_invoice.py index a7d8872bdbb..3c61608f0fd 100644 --- a/addons/sale/tests/test_sale_to_invoice.py +++ b/addons/sale/tests/test_sale_to_invoice.py @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.addons.mail.tests.common import TestMail -from openerp.tools import mute_logger from datetime import datetime +from odoo.addons.mail.tests.common import TestMail +from odoo.tools import mute_logger + class TestSale(TestMail): - @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm') + @mute_logger('odoo.addons.base.ir.ir_model', 'openerp.osv.orm') def setUp(self): super(TestSale, self).setUp() def test_sale_to_invoice(self): - """ Testing for invoice create,validate and pay with invoicing and payment user.""" + """ Testing for invoice create,validate and pay with invoicing and payment user.""" # Usefull models IrModelData = self.env['ir.model.data'] partner_obj = self.env['res.partner'] diff --git a/addons/sale/views/account_config_settings_views.xml b/addons/sale/views/account_config_settings_views.xml index 3f2772e3ab6..a70b509654e 100644 --- a/addons/sale/views/account_config_settings_views.xml +++ b/addons/sale/views/account_config_settings_views.xml @@ -1,6 +1,5 @@ - - + account settings account.config.settings @@ -21,5 +20,4 @@ - - + diff --git a/addons/sale/views/res_partner_views.xml b/addons/sale/views/res_partner_views.xml index 1e60b2dc8af..634ed0abd96 100644 --- a/addons/sale/views/res_partner_views.xml +++ b/addons/sale/views/res_partner_views.xml @@ -1,6 +1,5 @@ - - + Quotations and Sales sale.order @@ -60,5 +59,4 @@ - - + diff --git a/addons/sale/views/sale_config_settings_views.xml b/addons/sale/views/sale_config_settings_views.xml index d79f374f4a8..4e509e1f745 100644 --- a/addons/sale/views/sale_config_settings_views.xml +++ b/addons/sale/views/sale_config_settings_views.xml @@ -1,6 +1,5 @@ - - + sale settings sale.config.settings @@ -24,13 +23,13 @@