diff --git a/addons/sale/models/__init__.py b/addons/sale/models/__init__.py index 532abb1601e..abe35a5b922 100644 --- a/addons/sale/models/__init__.py +++ b/addons/sale/models/__init__.py @@ -1,6 +1,12 @@ # -*- 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 diff --git a/addons/sale/models/account_config_settings.py b/addons/sale/models/account_config_settings.py new file mode 100644 index 00000000000..2fe8b71fba5 --- /dev/null +++ b/addons/sale/models/account_config_settings.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from openerp.osv import fields, osv + +_logger = logging.getLogger(__name__) + +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/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/sale.py b/addons/sale/models/sale.py index dd7637db549..5dfd4ece4bc 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -12,11 +12,6 @@ from openerp.tools.misc import formatLang from openerp.addons.base.res.res_partner import WARNING_MESSAGE, WARNING_HELP -class res_company(models.Model): - _inherit = "res.company" - sale_note = fields.Text(string='Default Terms and Conditions', translate=True) - - class SaleOrder(models.Model): _name = "sale.order" _inherit = ['mail.thread', 'ir.needaction_mixin'] @@ -907,173 +902,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/models/sale_config_settings.py b/addons/sale/models/sale_config_settings.py index 3c2c77b2462..433d3d1f360 100644 --- a/addons/sale/models/sale_config_settings.py +++ b/addons/sale/models/sale_config_settings.py @@ -132,10 +132,3 @@ class sale_configuration(osv.TransientModel): 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/views/account_config_settings_views.xml b/addons/sale/views/account_config_settings_views.xml new file mode 100644 index 00000000000..3f2772e3ab6 --- /dev/null +++ b/addons/sale/views/account_config_settings_views.xml @@ -0,0 +1,25 @@ + + + + + account settings + account.config.settings + + + + + 0 + + + 0 + + +
+ +
+
+
+
+
+
diff --git a/addons/sale/views/sale_config_settings_views.xml b/addons/sale/views/sale_config_settings_views.xml index 11ed20163b0..d79f374f4a8 100644 --- a/addons/sale/views/sale_config_settings_views.xml +++ b/addons/sale/views/sale_config_settings_views.xml @@ -66,25 +66,5 @@ - - account settings - account.config.settings - - - - - 0 - - - 0 - - -
- -
-
-
-
diff --git a/addons/sale/wizard/__init__.py b/addons/sale/wizard/__init__.py index 92be178df5a..926a3a55b06 100644 --- a/addons/sale/wizard/__init__.py +++ b/addons/sale/wizard/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import sale_make_invoice_advance +import mail_compose_message \ No newline at end of file diff --git a/addons/sale/wizard/mail_compose_message.py b/addons/sale/wizard/mail_compose_message.py new file mode 100644 index 00000000000..784d07dd568 --- /dev/null +++ b/addons/sale/wizard/mail_compose_message.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models + + +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)