diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py index 09b49c379e8..d84fb182e8f 100644 --- a/addons/sale/__init__.py +++ b/addons/sale/__init__.py @@ -1,11 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import sale -import sale_analytic -import sale_layout -import sales_team -import res_partner -import wizard +import models import report -import res_config +import wizard diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py index 588e6976c49..6804626c2df 100644 --- a/addons/sale/__openerp__.py +++ b/addons/sale/__openerp__.py @@ -43,29 +43,28 @@ The Dashboard for the Sales Manager will include 'website': 'https://www.odoo.com/page/crm', 'depends': ['sales_team', 'account', 'procurement', 'report', 'web_tour'], 'data': [ - 'wizard/sale_make_invoice_advance.xml', + 'data/ir_sequence_data.xml', + 'data/sale_data.xml', + 'data/sale_tour.xml', + 'report/sale_report.xml', + 'data/mail_template_data.xml', + 'report/sale_report_views.xml', + 'report/sale_report_templates.xml', + 'report/invoice_report_templates.xml', 'security/sale_security.xml', 'security/ir.model.access.csv', - 'sale_sequence.xml', - 'sale_report.xml', - 'sale_data.xml', - 'sale_layout_category_data.xml', - 'sale_view.xml', - 'sales_team_view.xml', - 'res_partner_view.xml', - 'tour_view.xml', - 'report/sale_report_view.xml', - 'data/mail_template_data.xml', - 'res_config_view.xml', - 'views/report_saleorder.xml', - 'views/sale.xml', - 'views/report_invoice_layouted.xml', + 'wizard/sale_make_invoice_advance_views.xml', + 'views/sale_views.xml', + 'views/sales_team_views.xml', + 'views/res_partner_views.xml', + 'views/sale_config_settings_views.xml', + 'views/sale_templates.xml', 'views/sale_layout_category_view.xml', - 'sales_team_dashboard.xml', ], - 'demo': ['sale_demo.xml', - 'sale_product_demo.xml', - ], + 'demo': [ + 'data/sale_demo.xml', + 'data/product_product_demo.xml', + ], 'css': ['static/src/css/sale.css'], 'installable': True, 'auto_install': False, diff --git a/addons/sale/sale_sequence.xml b/addons/sale/data/ir_sequence_data.xml similarity index 95% rename from addons/sale/sale_sequence.xml rename to addons/sale/data/ir_sequence_data.xml index 7ce4484d89d..8e4559a0cdf 100644 --- a/addons/sale/sale_sequence.xml +++ b/addons/sale/data/ir_sequence_data.xml @@ -1,5 +1,5 @@ - + @@ -12,4 +12,4 @@ - + diff --git a/addons/sale/sale_product_demo.xml b/addons/sale/data/product_product_demo.xml similarity index 99% rename from addons/sale/sale_product_demo.xml rename to addons/sale/data/product_product_demo.xml index a4b8c6a5b47..e7ffee65b31 100644 --- a/addons/sale/sale_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/sale_data.xml b/addons/sale/data/sale_data.xml similarity index 84% rename from addons/sale/sale_data.xml rename to addons/sale/data/sale_data.xml index 864d7c14e01..bfa78f5e596 100644 --- a/addons/sale/sale_data.xml +++ b/addons/sale/data/sale_data.xml @@ -1,5 +1,5 @@ - + @@ -61,6 +61,18 @@ team_id + + Services + + + 1 + + + Material + + + 10 + - + diff --git a/addons/sale/sale_demo.xml b/addons/sale/data/sale_demo.xml similarity index 99% rename from addons/sale/sale_demo.xml rename to addons/sale/data/sale_demo.xml index 18338fa7476..be41326e3bc 100644 --- a/addons/sale/sale_demo.xml +++ b/addons/sale/data/sale_demo.xml @@ -1,5 +1,5 @@ - + @@ -320,4 +320,4 @@ Thanks! - + diff --git a/addons/sale/tour_view.xml b/addons/sale/data/sale_tour.xml similarity index 100% rename from addons/sale/tour_view.xml rename to addons/sale/data/sale_tour.xml diff --git a/addons/sale/models/__init__.py b/addons/sale/models/__init__.py new file mode 100644 index 00000000000..abe35a5b922 --- /dev/null +++ b/addons/sale/models/__init__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import account_config_settings +import account_invoice +import procurement_order +import product_product +import product_template +import res_company +import res_partner +import sale +import sale_analytic +import sale_config_settings +import sale_layout +import sales_team \ No newline at end of file diff --git a/addons/sale/models/account_config_settings.py b/addons/sale/models/account_config_settings.py new file mode 100644 index 00000000000..2d49b9bb411 --- /dev/null +++ b/addons/sale/models/account_config_settings.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from odoo import fields, models + +class AccountConfigSettings(models.TransientModel): + _inherit = 'account.config.settings' + + 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/account_invoice.py b/addons/sale/models/account_invoice.py new file mode 100644 index 00000000000..d195e194714 --- /dev/null +++ b/addons/sale/models/account_invoice.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from itertools import groupby +from odoo import api, fields, models, _ + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + @api.model + def _get_default_team(self): + return self.env['crm.team']._get_default_team_id() + + def _default_comment(self): + invoice_type = self.env.context.get('type', 'out_invoice') + if invoice_type == 'out_invoice': + return self.env.user.company_id.sale_note + + team_id = fields.Many2one('crm.team', string='Sales Team', default=_get_default_team, oldname='section_id') + comment = fields.Text(default=_default_comment) + partner_shipping_id = fields.Many2one( + 'res.partner', + string='Delivery Address', + readonly=True, + states={'draft': [('readonly', False)]}, + help="Delivery address for current invoice.") + + @api.onchange('partner_shipping_id') + def _onchange_partner_shipping_id(self): + """ + Trigger the change of fiscal position when the shipping address is modified. + """ + fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, self.partner_shipping_id.id) + if fiscal_position: + self.fiscal_position_id = fiscal_position + + @api.onchange('partner_id', 'company_id') + def _onchange_delivery_address(self): + addr = self.partner_id.address_get(['delivery']) + self.partner_shipping_id = addr and addr.get('delivery') + + @api.multi + def confirm_paid(self): + res = super(AccountInvoice, self).confirm_paid() + todo = set() + for invoice in self: + for line in invoice.invoice_line_ids: + for sale_line in line.sale_line_ids: + todo.add((sale_line.order_id, invoice.number)) + for (order, name) in todo: + order.message_post(body=_("Invoice %s paid") % (name)) + return res + + @api.model + def _refund_cleanup_lines(self, lines): + result = super(AccountInvoice, self)._refund_cleanup_lines(lines) + if self.env.context.get('mode') == 'modify': + for i in xrange(0, len(lines)): + for name, field in lines[i]._fields.iteritems(): + if name == 'sale_line_ids': + result[i][2][name] = [(6, 0, lines[i][name].ids)] + lines[i][name] = False + return result + + @api.multi + def order_lines_layouted(self): + """ + Returns this sale order lines ordered by sale_layout_category sequence. Used to render the report. + """ + self.ensure_one() + report_pages = [[]] + for category, lines in groupby(self.invoice_line_ids, lambda l: l.layout_category_id): + # If last added category induced a pagebreak, this one will be on a new page + if report_pages[-1] and report_pages[-1][-1]['pagebreak']: + report_pages.append([]) + # Append category to current report page + report_pages[-1].append({ + 'name': category and category.name or 'Uncategorized', + 'subtotal': category and category.subtotal, + 'pagebreak': category and category.pagebreak, + 'lines': list(lines) + }) + + return report_pages + + +class AccountInvoiceLine(models.Model): + _inherit = 'account.invoice.line' + _order = 'invoice_id, layout_category_id, sequence, id' + + sale_line_ids = fields.Many2many( + 'sale.order.line', + 'sale_order_line_invoice_rel', + 'invoice_line_id', 'order_line_id', + string='Sale Order Lines', readonly=True, copy=False) + layout_category_id = fields.Many2one('sale.layout_category', string='Section') + layout_category_sequence = fields.Integer( + related='layout_category_id.sequence', + string='Layout Sequence', store=True, default=0) diff --git a/addons/sale/models/procurement_order.py b/addons/sale/models/procurement_order.py new file mode 100644 index 00000000000..a11840c76f9 --- /dev/null +++ b/addons/sale/models/procurement_order.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ProcurementOrder(models.Model): + _inherit = 'procurement.order' + sale_line_id = fields.Many2one('sale.order.line', string='Sale Order Line') diff --git a/addons/sale/models/product_product.py b/addons/sale/models/product_product.py new file mode 100644 index 00000000000..4c6223fd269 --- /dev/null +++ b/addons/sale/models/product_product.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class ProductProduct(models.Model): + _inherit = 'product.product' + + @api.multi + def _sales_count(self): + r = {} + domain = [ + ('state', 'in', ['sale', 'done']), + ('product_id', 'in', self.ids), + ] + for group in self.env['sale.report'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']): + r[group['product_id'][0]] = group['product_uom_qty'] + for product in self: + product.sales_count = r.get(product.id, 0) + return r + + sales_count = fields.Integer(compute='_sales_count', string='# Sales') diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py new file mode 100644 index 00000000000..6e04794c2f7 --- /dev/null +++ b/addons/sale/models/product_template.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models +from odoo.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + track_service = fields.Selection([('manual', 'Manually set quantities on order')], string='Track Service', + help="Manually set quantities on order: Invoice based on the manually entered quantity, without creating an analytic account.\n" + "Timesheets on contract: Invoice based on the tracked hours on the related timesheet.\n" + "Create a task and track hours: Create a task on the sale order validation and track the work hours.", + default='manual') + sale_line_warn = fields.Selection(WARNING_MESSAGE, 'Sales Order Line', help=WARNING_HELP, required=True, default="no-message") + sale_line_warn_msg = fields.Text('Message for Sales Order Line') + expense_policy = fields.Selection( + [('no', 'No'), ('cost', 'At cost'), ('sales_price', 'At sale price')], + string='Re-Invoice Expenses', + default='no') + + @api.multi + @api.depends('product_variant_ids.sales_count') + def _sales_count(self): + for product in self: + product.sales_count = sum([p.sales_count for p in product.product_variant_ids]) + + @api.multi + def action_view_sales(self): + self.ensure_one() + action = self.env.ref('sale.action_product_sale_list') + product_ids = self.product_variant_ids.ids + + return { + 'name': action.name, + 'help': action.help, + 'type': action.type, + 'view_type': action.view_type, + 'view_mode': action.view_mode, + 'target': action.target, + 'context': "{'default_product_id': " + str(product_ids[0]) + "}", + 'res_model': action.res_model, + 'domain': [('state', 'in', ['sale', 'done']), ('product_id.product_tmpl_id', '=', self.id)], + } + + sales_count = fields.Integer(compute='_sales_count', string='# Sales') + invoice_policy = fields.Selection( + [('order', 'Ordered quantities'), + ('delivery', 'Delivered quantities'), + ], string='Invoicing Policy', + help='Ordered Quantity: Invoice based on the quantity the customer ordered.\n' + 'Delivered Quantity: Invoiced based on the quantity the vendor delivered (time or deliveries).', + default='order') diff --git a/addons/sale/models/res_company.py b/addons/sale/models/res_company.py new file mode 100644 index 00000000000..91c226bf709 --- /dev/null +++ b/addons/sale/models/res_company.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + sale_note = fields.Text(string='Default Terms and Conditions', translate=True) diff --git a/addons/sale/models/res_partner.py b/addons/sale/models/res_partner.py new file mode 100644 index 00000000000..d8875db8234 --- /dev/null +++ b/addons/sale/models/res_partner.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models +from odoo.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + 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') + + 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/sale.py b/addons/sale/models/sale.py similarity index 84% rename from addons/sale/sale.py rename to addons/sale/models/sale.py index dd7637db549..2e034b33ab9 100644 --- a/addons/sale/sale.py +++ b/addons/sale/models/sale.py @@ -3,18 +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 -class res_company(models.Model): - _inherit = "res.company" - sale_note = fields.Text(string='Default Terms and Conditions', translate=True) +import odoo.addons.decimal_precision as dp class SaleOrder(models.Model): @@ -241,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, @@ -269,7 +264,6 @@ class SaleOrder(models.Model): result = super(SaleOrder, self).create(vals) return result - @api.multi def _prepare_invoice(self): """ @@ -372,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.')) @@ -487,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 @@ -585,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): @@ -665,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 @@ -787,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: @@ -907,173 +899,3 @@ class SaleOrderLine(models.Model): :rtype: float ''' return 0.0 - - -class MailComposeMessage(models.TransientModel): - _inherit = 'mail.compose.message' - - @api.multi - def send_mail(self, auto_commit=False): - if self._context.get('default_model') == 'sale.order' and self._context.get('default_res_id') and self._context.get('mark_so_as_sent'): - order = self.env['sale.order'].browse([self._context['default_res_id']]) - if order.state == 'draft': - order.state = 'sent' - return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit) - - -class AccountInvoice(models.Model): - _inherit = 'account.invoice' - - @api.model - def _get_default_team(self): - return self.env['crm.team']._get_default_team_id() - - def _default_comment(self): - invoice_type = self.env.context.get('type', 'out_invoice') - if invoice_type == 'out_invoice': - return self.env.user.company_id.sale_note - - team_id = fields.Many2one('crm.team', string='Sales Team', default=_get_default_team, oldname='section_id') - comment = fields.Text(default=_default_comment) - partner_shipping_id = fields.Many2one( - 'res.partner', - string='Delivery Address', - readonly=True, - states={'draft': [('readonly', False)]}, - help="Delivery address for current invoice.") - - @api.onchange('partner_shipping_id') - def _onchange_partner_shipping_id(self): - """ - Trigger the change of fiscal position when the shipping address is modified. - """ - fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, self.partner_shipping_id.id) - if fiscal_position: - self.fiscal_position_id = fiscal_position - - @api.onchange('partner_id', 'company_id') - def _onchange_delivery_address(self): - addr = self.partner_id.address_get(['delivery']) - self.partner_shipping_id = addr and addr.get('delivery') - - @api.multi - def confirm_paid(self): - res = super(AccountInvoice, self).confirm_paid() - todo = set() - for invoice in self: - for line in invoice.invoice_line_ids: - for sale_line in line.sale_line_ids: - todo.add((sale_line.order_id, invoice.number)) - for (order, name) in todo: - order.message_post(body=_("Invoice %s paid") % (name)) - return res - - @api.model - def _refund_cleanup_lines(self, lines): - result = super(AccountInvoice, self)._refund_cleanup_lines(lines) - if self.env.context.get('mode') == 'modify': - for i in xrange(0, len(lines)): - for name, field in lines[i]._fields.iteritems(): - if name == 'sale_line_ids': - result[i][2][name] = [(6, 0, lines[i][name].ids)] - lines[i][name] = False - return result - - @api.multi - def order_lines_layouted(self): - """ - Returns this sale order lines ordered by sale_layout_category sequence. Used to render the report. - """ - self.ensure_one() - report_pages = [[]] - for category, lines in groupby(self.invoice_line_ids, lambda l: l.layout_category_id): - # If last added category induced a pagebreak, this one will be on a new page - if report_pages[-1] and report_pages[-1][-1]['pagebreak']: - report_pages.append([]) - # Append category to current report page - report_pages[-1].append({ - 'name': category and category.name or 'Uncategorized', - 'subtotal': category and category.subtotal, - 'pagebreak': category and category.pagebreak, - 'lines': list(lines) - }) - - return report_pages - -class AccountInvoiceLine(models.Model): - _inherit = 'account.invoice.line' - _order = 'invoice_id, layout_category_id, sequence, id' - - sale_line_ids = fields.Many2many('sale.order.line', 'sale_order_line_invoice_rel', 'invoice_line_id', 'order_line_id', string='Sale Order Lines', readonly=True, copy=False) - layout_category_id = fields.Many2one('sale.layout_category', string='Section') - layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True, default=0) - - -class ProcurementOrder(models.Model): - _inherit = 'procurement.order' - sale_line_id = fields.Many2one('sale.order.line', string='Sale Order Line') - - -class ProductProduct(models.Model): - _inherit = 'product.product' - - @api.multi - def _sales_count(self): - r = {} - domain = [ - ('state', 'in', ['sale', 'done']), - ('product_id', 'in', self.ids), - ] - for group in self.env['sale.report'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']): - r[group['product_id'][0]] = group['product_uom_qty'] - for product in self: - product.sales_count = r.get(product.id, 0) - return r - - sales_count = fields.Integer(compute='_sales_count', string='# Sales') - - -class ProductTemplate(models.Model): - _inherit = 'product.template' - track_service = fields.Selection([('manual', 'Manually set quantities on order')], string='Track Service', - help="Manually set quantities on order: Invoice based on the manually entered quantity, without creating an analytic account.\n" - "Timesheets on contract: Invoice based on the tracked hours on the related timesheet.\n" - "Create a task and track hours: Create a task on the sale order validation and track the work hours.", - default='manual') - sale_line_warn = fields.Selection(WARNING_MESSAGE, 'Sales Order Line', help=WARNING_HELP, required=True, default="no-message") - sale_line_warn_msg = fields.Text('Message for Sales Order Line') - expense_policy = fields.Selection( - [('no', 'No'), ('cost', 'At cost'), ('sales_price', 'At sale price')], - string='Re-Invoice Expenses', - default='no') - @api.multi - @api.depends('product_variant_ids.sales_count') - def _sales_count(self): - for product in self: - product.sales_count = sum([p.sales_count for p in product.product_variant_ids]) - - @api.multi - def action_view_sales(self): - self.ensure_one() - action = self.env.ref('sale.action_product_sale_list') - product_ids = self.product_variant_ids.ids - - return { - 'name': action.name, - 'help': action.help, - 'type': action.type, - 'view_type': action.view_type, - 'view_mode': action.view_mode, - 'target': action.target, - 'context': "{'default_product_id': " + str(product_ids[0]) + "}", - 'res_model': action.res_model, - 'domain': [('state', 'in', ['sale', 'done']), ('product_id.product_tmpl_id', '=', self.id)], - } - - sales_count = fields.Integer(compute='_sales_count', string='# Sales') - invoice_policy = fields.Selection( - [('order', 'Ordered quantities'), - ('delivery', 'Delivered quantities'), - ], string='Invoicing Policy', help='Ordered Quantity: Invoice based on the quantity the customer ordered.\n' - 'Delivered Quantity: Invoiced based on the quantity the vendor delivered (time or deliveries).', - default='order') diff --git a/addons/sale/sale_analytic.py b/addons/sale/models/sale_analytic.py similarity index 96% rename from addons/sale/sale_analytic.py rename to addons/sale/models/sale_analytic.py index 64e62a6a87a..413f9967331 100644 --- a/addons/sale/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 new file mode 100644 index 00000000000..9fef051e635 --- /dev/null +++ b/addons/sale/models/sale_config_settings.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class SaleConfiguration(models.TransientModel): + _inherit = 'sale.config.settings' + + 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") + + @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) + + @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) + + @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) + + @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, + }) + + @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) + + @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 new file mode 100644 index 00000000000..7ee65029ca9 --- /dev/null +++ b/addons/sale/models/sale_layout.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class SaleLayoutCategory(models.Model): + _name = 'sale.layout_category' + _order = 'sequence, id' + + 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 new file mode 100644 index 00000000000..74bbe618751 --- /dev/null +++ b/addons/sale/models/sales_team.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import date + +from odoo import api, fields, models + + +class CrmTeam(models.Model): + _inherit = 'crm.team' + + 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) + + @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'), + ], ['amount_total', 'team_id'], ['team_id']) + for rec in amounts: + self.browse(rec['team_id'][0]).sales_to_invoice_amount = rec['amount_total'] + + @api.multi + def _compute_invoiced(self): + for team in self: + invoices = self.env['account.invoice'].search([ + ('state', 'in', ['open', 'paid']), + ('team_id', '=', team.id), + ('date', '<=', date.today()), + ('date', '>=', date.today().replace(day=1)) + ]) + team.invoiced = sum(invoices.mapped('amount_untaxed_signed')) + + @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/views/report_invoice_layouted.xml b/addons/sale/report/invoice_report_templates.xml similarity index 99% rename from addons/sale/views/report_invoice_layouted.xml rename to addons/sale/report/invoice_report_templates.xml index 48094594501..d22a4af8894 100644 --- a/addons/sale/views/report_invoice_layouted.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/sale_report.xml b/addons/sale/report/sale_report.xml similarity index 93% rename from addons/sale/sale_report.xml rename to addons/sale/report/sale_report.xml index 7a8614b139b..04d9080928b 100644 --- a/addons/sale/sale_report.xml +++ b/addons/sale/report/sale_report.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/sale/views/report_saleorder.xml b/addons/sale/report/sale_report_templates.xml similarity index 99% rename from addons/sale/views/report_saleorder.xml rename to addons/sale/report/sale_report_templates.xml index 1e45bb32385..9e70cce41ef 100644 --- a/addons/sale/views/report_saleorder.xml +++ b/addons/sale/report/sale_report_templates.xml @@ -1,6 +1,5 @@ - - + - - + diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_views.xml similarity index 99% rename from addons/sale/report/sale_report_view.xml rename to addons/sale/report/sale_report_views.xml index bee03b39d42..af00b04801a 100644 --- a/addons/sale/report/sale_report_view.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/res_config.py b/addons/sale/res_config.py deleted file mode 100644 index 3c2c77b2462..00000000000 --- a/addons/sale/res_config.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import logging - -from openerp import SUPERUSER_ID -from openerp.osv import fields, osv - -_logger = logging.getLogger(__name__) - - -class sale_configuration(osv.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"), - } - - _defaults = { - 'sale_pricelist_setting': 'fixed', - 'sale_show_tax': 'subtotal', - 'default_invoice_policy': 'order', - } - - 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 - - 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 - - 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 - - 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}} - - 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 -class account_config_settings(osv.osv_memory): - _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."), - } diff --git a/addons/sale/res_partner.py b/addons/sale/res_partner.py deleted file mode 100644 index f5482ae6508..00000000000 --- a/addons/sale/res_partner.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- 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 - -class res_partner(osv.osv): - _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 - - _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', - } diff --git a/addons/sale/sale_demo.yml b/addons/sale/sale_demo.yml deleted file mode 100644 index e405cebc04e..00000000000 --- a/addons/sale/sale_demo.yml +++ /dev/null @@ -1,77 +0,0 @@ -- - !python {model: ir.model.data, id: False} : | - journal_id = self.env['account.journal'].search([('type', '=', 'sale')], limit=1).id - account_recv_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_receivable'))], limit=1).id - account_sale_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_revenue')), ('tag_ids', 'in', [ref('account.account_tag_operating')])], limit=1).id - if not account_sale_id: - account_sale_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_revenue'))], limit=1).id - if journal_id and account_recv_id: - vals = { - 'currency': ref('base.EUR'), - 'company_id': ref('base.main_company'), - 'partner_id': ref('base.res_partner_1'), - 'journal_id': journal_id, - 'team_id': ref('sales_team.team_sales_department'), - 'state': 'draft', - 'type': 'out_invoice', - 'account_id': account_recv_id, - 'name': 'Test invoice 1', - } - self._update('account.invoice', 'sale', vals, 'test_crm_sale_invoice_1') - vals = { - 'currency': ref('base.EUR'), - 'company_id': ref('base.main_company'), - 'partner_id': ref('base.res_partner_1'), - 'journal_id': journal_id, - 'team_id': ref('sales_team.crm_team_1'), - 'state': 'draft', - 'type': 'out_invoice', - 'account_id': account_recv_id, - 'name': 'Test invoice 2', - } - self._update('account.invoice', 'sale', vals, 'test_crm_sale_invoice_2') - if account_sale_id: - vals = { - 'name': '', - 'product_id': ref('product.consu_delivery_03'), - 'invoice_id': ref('test_crm_sale_invoice_1'), - 'price_unit': 250, - 'quantity': 1, - 'account_id': account_sale_id, - } - self._update('account.invoice.line', 'sale', vals, 'test_crm_sale_invoice_1_line_1') - vals = { - 'name': '', - 'product_id': ref('product.consu_delivery_02'), - 'invoice_id': ref('test_crm_sale_invoice_1'), - 'price_unit': 800, - 'quantity': 2, - 'account_id': account_sale_id, - } - self._update('account.invoice.line', 'sale', vals, 'test_crm_sale_invoice_1_line_2') - vals = { - 'name': '', - 'product_id': ref('product.consu_delivery_01'), - 'invoice_id': ref('test_crm_sale_invoice_1'), - 'price_unit': 4800, - 'quantity': 4, - 'account_id': account_sale_id, - } - self._update('account.invoice.line', 'sale', vals, 'test_crm_sale_invoice_1_line_3') - vals = { - 'name': '', - 'product_id': ref('product.consu_delivery_01'), - 'invoice_id': ref('test_crm_sale_invoice_2'), - 'price_unit': 500, - 'quantity': 1, - 'account_id': account_sale_id, - } - self._update('account.invoice.line', 'sale', vals, 'test_crm_sale_invoice_2_line_1') - self.env['account.invoice'].browse(ref('test_crm_sale_invoice_1')).signal_workflow('invoice_open') - self.env['account.invoice'].browse(ref('test_crm_sale_invoice_2')).signal_workflow('invoice_open') - bank_journal_id = self.env['account.journal'].search([('type', '=', 'bank')], limit=1).id - if bank_journal_id: - bank_journal_id = bank_journal_id - self.env['account.invoice'].browse(ref('test_crm_sale_invoice_1')).pay_and_reconcile(bank_journal_id[0], 1850) - self.env['account.invoice'].browse(ref('test_crm_sale_invoice_2')).pay_and_reconcile(bank_journal_id[0], 500) -- diff --git a/addons/sale/sale_layout.py b/addons/sale/sale_layout.py deleted file mode 100644 index adebb3e0275..00000000000 --- a/addons/sale/sale_layout.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from openerp.osv import osv, fields - - -class SaleLayoutCategory(osv.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 - } diff --git a/addons/sale/sale_layout_category_data.xml b/addons/sale/sale_layout_category_data.xml deleted file mode 100644 index 6042278defa..00000000000 --- a/addons/sale/sale_layout_category_data.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Services - - - 1 - - - - Material - - - 10 - - - diff --git a/addons/sale/sale_unit_test.xml b/addons/sale/sale_unit_test.xml deleted file mode 100644 index 903bd3d9395..00000000000 --- a/addons/sale/sale_unit_test.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - [PCSC234] PC Assemble SC234 - - - 450 - 2 - - - - Switch, 24 ports - - - 90 - 3 - - - - - - - - - - manual - - - - - - progress - - - - - - - - - - - draft - - - - - - - - - - - - - paid - - - - - - - - - - - - - - - - - - - assigned - - - - - - - - - - - - - done - - - - - - - done - - - diff --git a/addons/sale/sales_team.py b/addons/sale/sales_team.py deleted file mode 100644 index 309419eb97f..00000000000 --- a/addons/sale/sales_team.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- - -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 - -class crm_team(osv.Model): - _inherit = 'crm.team' - - def _get_sales_to_invoice_amount(self, cr, uid, ids, field_name, arg, context=None): - obj = self.pool['sale.order'] - - res = dict.fromkeys(ids, 0) - domain = [ - ('team_id', 'in', ids), - ('invoice_status', '=', 'to invoice'), - ] - amounts = obj.read_group(cr, uid, domain, ['amount_total', 'team_id'], ['team_id'], context=context) - for rec in amounts: - res[rec['team_id'][0]] = rec['amount_total'] - return res - - 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 = [ - ('state', 'in', ['open', 'paid']), - ('team_id', '=', team), - ('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 - - _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) diff --git a/addons/sale/sales_team_view.xml b/addons/sale/sales_team_view.xml deleted file mode 100644 index 415887ff9f9..00000000000 --- a/addons/sale/sales_team_view.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - crm.team.form - crm.team - - - -
-
-
- - - - - - -
-
- - - account.invoice.report.search.inherit - account.invoice.report - - - - - - - -
-
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 new file mode 100644 index 00000000000..a70b509654e --- /dev/null +++ b/addons/sale/views/account_config_settings_views.xml @@ -0,0 +1,23 @@ + + + + account settings + account.config.settings + + + + + 0 + + + 0 + + +
+ +
+
+
+
+
diff --git a/addons/sale/res_partner_view.xml b/addons/sale/views/res_partner_views.xml similarity index 98% rename from addons/sale/res_partner_view.xml rename to addons/sale/views/res_partner_views.xml index 1e60b2dc8af..634ed0abd96 100644 --- a/addons/sale/res_partner_view.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/res_config_view.xml b/addons/sale/views/sale_config_settings_views.xml similarity index 76% rename from addons/sale/res_config_view.xml rename to addons/sale/views/sale_config_settings_views.xml index 11ed20163b0..4e509e1f745 100644 --- a/addons/sale/res_config_view.xml +++ b/addons/sale/views/sale_config_settings_views.xml @@ -1,6 +1,5 @@ - - + sale settings sale.config.settings @@ -24,13 +23,13 @@