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)