[MERGE] sale: Migrate to new API

This commit is contained in:
Yannick Tivisse
2016-08-10 11:01:10 +02:00
50 changed files with 699 additions and 843 deletions
+2 -7
View File
@@ -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
+18 -19
View File
@@ -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,
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<!-- Sequences for sale.order -->
@@ -12,4 +12,4 @@
</record>
</data>
</openerp>
</odoo>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<record id="product.consu_delivery_01" model="product.product">
<field name="invoice_policy">delivery</field>
@@ -154,4 +154,4 @@
</record>
</data>
</openerp>
</odoo>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<!-- Requests Links -->
<record id="req_link_sale_order" model="res.request.link">
@@ -61,6 +61,18 @@
<field name="parent_id" eval="ref('account.mt_invoice_validated')"/>
<field name="relation_field">team_id</field>
</record>
<record id="sale_layout_cat_1" model="sale.layout_category">
<field name="name">Services</field>
<field name="subtotal" eval="True"></field>
<field name="pagebreak" eval="True"></field>
<field name="sequence">1</field>
</record>
<record id="sale_layout_cat_2" model="sale.layout_category">
<field name="name">Material</field>
<field name="subtotal" eval="True"></field>
<field name="pagebreak" eval="False"></field>
<field name="sequence">10</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<record id="base.user_demo" model="res.users">
@@ -320,4 +320,4 @@ Thanks!</field>
</record>
</data>
</openerp>
</odoo>
+15
View File
@@ -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
@@ -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.")
+100
View File
@@ -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)
+9
View File
@@ -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')
+23
View File
@@ -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')
+54
View File
@@ -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')
+9
View File
@@ -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)
+21
View File
@@ -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)
])
@@ -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')
@@ -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)
+167
View File
@@ -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,
})
+14
View File
@@ -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')
+55
View File
@@ -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))})
+5 -3
View File
@@ -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"
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="report_invoice_layouted" inherit_id="account.report_invoice_document">
<xpath expr="//table" position="attributes">
<attribute name="groups">!sale.group_sale_layout</attribute>
@@ -98,5 +97,4 @@
</div>
</xpath>
</template>
</data>
</openerp>
</odoo>
+40 -40
View File
@@ -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
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data>
<report
id="report_sale_order"
@@ -10,4 +10,4 @@
name="sale.report_saleorder"
/>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="report_saleorder_document">
<t t-call="report.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
@@ -179,5 +178,4 @@
</t>
</t>
</template>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_order_product_pivot" model="ir.ui.view">
<field name="name">sale.report.pivot</field>
@@ -97,5 +96,4 @@
<menuitem name="Sales" action="action_order_report_all" id="menu_report_product_all" parent="base.menu_sale_report" sequence="10"/>
</data>
</openerp>
</odoo>
-141
View File
@@ -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."),
}
-29
View File
@@ -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',
}
-77
View File
@@ -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)
-
-21
View File
@@ -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
}
-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="sale_layout_cat_1" model="sale.layout_category">
<field name="name">Services</field>
<field name="subtotal" eval="True"></field>
<field name="pagebreak" eval="True"></field>
<field name="sequence">1</field>
</record>
<record id="sale_layout_cat_2" model="sale.layout_category">
<field name="name">Material</field>
<field name="subtotal" eval="True"></field>
<field name="pagebreak" eval="False"></field>
<field name="sequence">10</field>
</record>
</data>
</openerp>
-109
View File
@@ -1,109 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Resource: sale.order -->
<record id="test_order_1" model="sale.order">
<field model="product.pricelist" name="pricelist_id" search="[]"/>
<field name="user_id" ref="base.user_root"/>
<field model="res.partner" name="partner_id" search="[]"/>
<field model="res.partner" name="partner_invoice_id" search="[]"/>
<field model="res.partner" name="partner_shipping_id" search="[]"/>
</record>
<!-- Resource: sale.order.line -->
<record id="test_order_1_line_1" model="sale.order.line">
<field name="order_id" ref="test_order_1"/>
<field name="name">[PCSC234] PC Assemble SC234</field>
<field name="product_id" ref="product.product_product_3"/>
<field name="product_uom" ref="product.product_uom_unit"/>
<field name="price_unit">450</field>
<field name="product_uom_qty">2</field>
</record>
<record id="test_order_1_line_3" model="sale.order.line">
<field name="order_id" ref="test_order_1"/>
<field name="name">Switch, 24 ports</field>
<field name="product_id" ref="product.product_delivery_01"/>
<field name="product_uom" ref="product.product_uom_unit"/>
<field name="price_unit">90</field>
<field name="product_uom_qty">3</field>
</record>
<assert id="test_order_1" model="sale.order" severity="error" string="The amount of the sales order is correctly computed">
<test expr="sum([l.price_subtotal for l in order_line]) == amount_untaxed"/>
</assert>
<workflow action="order_confirm" model="sale.order" ref="test_order_1"/>
<assert id="test_order_1" model="sale.order" string="the sales order is now in 'Manual in progress' state">
<test expr="state">manual</test>
</assert>
<workflow action="manual_invoice" model="sale.order" ref="test_order_1" uid="base.user_root"/>
<assert id="test_order_1" model="sale.order" string="the sales order is now in progress">
<test expr="state">progress</test>
</assert>
<!-- Treat generated invoice -->
<assert id="test_order_1" model="sale.order" severity="fatal" string="the sales order has now a corresponding invoice">
<test expr="len(invoice_ids) == 1"/>
</assert>
<assert id="test_order_1" model="sale.order" severity="fatal" string="the sales order's has the same amount and is currently a draft">
<test expr="invoice_ids[0].amount_untaxed == amount_untaxed"/>
<test expr="invoice_ids[0].amount_total == amount_total"/>
<test expr="invoice_ids[0].state">draft</test>
</assert>
<workflow action="invoice_open" model="account.invoice">
<value eval="obj(ref('test_order_1')).invoice_ids[0].id" model="sale.order"/>
</workflow>
<function model="account.invoice" name="pay_and_reconcile">
<value eval="[obj(ref('test_order_1')).invoice_ids[0].id]" model="sale.order"/>
<value eval="ref('account.bank_journal')"/>
</function>
<assert id="test_order_1" model="sale.order" severity="fatal" string="the sales order's invoice is now paid">
<test expr="invoice_ids[0].state">paid</test>
</assert>
<!-- Treat generated picking -->
<assert id="test_order_1" model="sale.order" severity="fatal" string="the sales order has now a correspending picking">
<test expr="len(picking_ids) == 1"/>
</assert>
<assert id="test_order_1" model="sale.order" severity="error" string="the sales order's picking will be sent to the good address and is already confirmed">
<test expr="picking_ids[0].partner_id == partner_shipping_id"/>
<!-- test expr="picking_ids[0].state">confirmed</test # Desactivated because of MRP_JIT -->
</assert>
<!-- Here we need to use the function tag because action_assign is a method of stock_picking and not part of the workflow -->
<function model="stock.picking" name="action_assign">
<value eval="[obj(ref('test_order_1')).picking_ids[0].id]" model="sale.order"/>
</function>
<assert id="test_order_1" model="sale.order" severity="fatal" string="the sales order's picking is now assigned">
<test expr="picking_ids[0].state">assigned</test>
</assert>
<!-- Do the same as in wizard_partial_picking lines 143-144 -->
<function model="stock.picking" name="action_move">
<value eval="[obj(ref('test_order_1')).picking_ids[0].id]" model="sale.order"/>
</function>
<workflow action="button_done" model="stock.picking">
<value eval="obj(ref('test_order_1')).picking_ids[0].id" model="sale.order"/>
</workflow>
<assert id="test_order_1" model="sale.order" string="the sales order's picking is now done">
<test expr="picking_ids[0].state">done</test>
</assert>
<!-- Run all schedulers -->
<function model="procurement.order" name="run_scheduler"/>
<assert id="test_order_1" model="sale.order" string="the sales order is now done">
<test expr="state">done</test>
</assert>
</data>
</openerp>
-65
View File
@@ -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)
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="crm_team_salesteams_view_form" model="ir.ui.view">
<field name="name">crm.team.form</field>
<field name="model">crm.team</field>
<field name="inherit_id" ref="sales_team.crm_team_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='options_active']" position="inside">
<div class="o_row">
<field name="use_quotations"/><label for="use_quotations"/>
<field name="use_invoices" groups="account.group_account_invoice"/><label for="use_invoices" groups="account.group_account_invoice"/>
</div>
</xpath>
<xpath expr="//page[@name='more_info']" position="inside">
<group string="Invoicing/Progression Ratio">
<field name="invoiced_target"/>
<field name="invoiced"/>
</group>
</xpath>
</field>
</record>
<record id="view_account_invoice_report_search_inherit" model="ir.ui.view">
<field name="name">account.invoice.report.search.inherit</field>
<field name="model">account.invoice.report</field>
<field name="inherit_id" ref="account.view_account_invoice_report_search"/>
<field name="arch" type="xml">
<xpath expr="//group/filter[@name='user']" position="after">
<filter string="Sales Team" domain="[]" context="{'group_by':'team_id'}"/>
</xpath>
</field>
</record>
</data>
</openerp>
+2 -5
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<odoo>
<record id="sale.group_sale_layout" model="res.groups">
<field name="name">Personalize sale order and invoice report</field>
@@ -73,7 +72,6 @@
<field eval="[(4, ref('sales_team.group_sale_manager'))]" name="groups_id"/>
</record>
</data>
<data noupdate="1">
<!-- Multi - Company Rules -->
@@ -140,6 +138,5 @@
<field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('sales_team.group_sale_salesman_all_leads'))]"/>
</record>
</data>
</openerp>
</odoo>
+5 -1
View File
@@ -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
+2 -1
View File
@@ -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):
+2 -1
View File
@@ -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
+5 -4
View File
@@ -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']
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_config" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<separator name="analytic_account" position="attributes">
<attribute name="invisible">0</attribute>
</separator>
<group name="analytic_account_sale" position="attributes">
<attribute name="invisible">0</attribute>
</group>
<xpath expr="//group[@name='analytic_account_sale']" position="inside">
<div>
<field name="group_analytic_account_for_sales" class="oe_inline"/>
<label for="group_analytic_account_for_sales"/>
</div>
</xpath>
</field>
</record>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0"?>
<openerp>
<data>
<odoo>
<record id="act_res_partner_2_sale_order" model="ir.actions.act_window">
<field name="name">Quotations and Sales</field>
<field name="res_model">sale.order</field>
@@ -60,5 +59,4 @@
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_sales_config" model="ir.ui.view">
<field name="name">sale settings</field>
<field name="model">sale.config.settings</field>
@@ -24,13 +23,13 @@
<field name="group_warning_sale" widget="radio"/>
<label for="sale_show_tax"/>
<div>
<field name="sale_show_tax" class="oe_inline" widget="radio" on_change="onchange_sale_tax(sale_show_tax)"/>
<field name="sale_show_tax" class="oe_inline" widget="radio"/>
<field name="group_show_price_subtotal" class="oe_inline" invisible="1"/>
<field name="group_show_price_total" class="oe_inline" invisible="1"/>
</div>
<label for="sale_pricelist_setting" string="Sale Price"/>
<div>
<field name="sale_pricelist_setting" class="oe_inline" widget="radio" on_change="onchange_sale_price(sale_pricelist_setting)"/>
<field name="sale_pricelist_setting" class="oe_inline" widget="radio"/>
<field name="group_sale_pricelist" class="oe_inline" invisible="1"/>
<field name="group_pricelist_item" class="oe_inline" invisible="1"/>
<field name="group_product_pricelist" class="oe_inline" invisible="1"/>
@@ -66,25 +65,4 @@
<menuitem id="sales_team.menu_sale_general_settings" name="Settings" parent="sales_team.menu_sale_config"
sequence="0" action="sales_team.action_sale_config" groups="base.group_system"/>
<record id="view_account_config" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="priority" eval="16"/>
<field name="arch" type="xml">
<separator name="analytic_account" position="attributes">
<attribute name="invisible">0</attribute>
</separator>
<group name="analytic_account_sale" position="attributes">
<attribute name="invisible">0</attribute>
</group>
<xpath expr="//group[@name='analytic_account_sale']" position="inside">
<div>
<field name="group_analytic_account_for_sales" class="oe_inline"/>
<label for="group_analytic_account_for_sales"/>
</div>
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<!-- Report configuration -->
<record id="report_configuration_form_view" model="ir.ui.view">
<field name="name">report.configuration.form.view</field>
@@ -59,5 +58,4 @@
name="Report Layout Categories"
groups="sale.group_sale_layout"
/>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="assets_backend" name="project assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/sale/static/src/css/sale.css"/>
@@ -8,5 +7,4 @@
<script type="text/javascript" src="/sale/static/src/js/tour.js"></script>
</xpath>
</template>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<!-- Top menu item -->
<!--
@@ -822,5 +821,4 @@
<field name="context">{'search_default_month':1}</field>
<field name="help">From this report, you can have an overview of the amount invoiced to your customer. The search tool can also be used to personalise your Invoices reports and so, match this analysis to your needs.</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,4 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="crm_team_salesteams_view_form" model="ir.ui.view">
<field name="name">crm.team.form</field>
<field name="model">crm.team</field>
<field name="inherit_id" ref="sales_team.crm_team_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='options_active']" position="inside">
<div class="o_row">
<field name="use_quotations"/><label for="use_quotations"/>
<field name="use_invoices" groups="account.group_account_invoice"/><label for="use_invoices" groups="account.group_account_invoice"/>
</div>
</xpath>
<xpath expr="//page[@name='more_info']" position="inside">
<group string="Invoicing/Progression Ratio">
<field name="invoiced_target"/>
<field name="invoiced"/>
</group>
</xpath>
</field>
</record>
<record id="view_account_invoice_report_search_inherit" model="ir.ui.view">
<field name="name">account.invoice.report.search.inherit</field>
<field name="model">account.invoice.report</field>
<field name="inherit_id" ref="account.view_account_invoice_report_search"/>
<field name="arch" type="xml">
<xpath expr="//group/filter[@name='user']" position="after">
<filter string="Sales Team" domain="[]" context="{'group_by':'team_id'}"/>
</xpath>
</field>
</record>
<!-- Sales Team Dashboard Views -->
<record id="action_quotation_form" model="ir.actions.act_window">
<field name="name">New Quotation</field>
<field name="res_model">sale.order</field>
@@ -99,4 +132,5 @@
</data>
</field>
</record>
</odoo>
+1
View File
@@ -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
@@ -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)
@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp import api, fields, models, _
import openerp.addons.decimal_precision as dp
from openerp.exceptions import UserError
from odoo import api, fields, models, _
import odoo.addons.decimal_precision as dp
from odoo.exceptions import UserError
class SaleAdvancePaymentInv(models.TransientModel):
_name = "sale.advance.payment.inv"
@@ -42,11 +44,11 @@ class SaleAdvancePaymentInv(models.TransientModel):
('percentage', 'Down payment (percentage)'),
('fixed', 'Down payment (fixed amount)')
], string='What do you want to invoice?', default=_get_advance_payment_method, required=True)
product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')],\
product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')],
default=_default_product_id)
count = fields.Integer(default=_count, string='# of Orders')
amount = fields.Float('Down Payment Amount', digits=dp.get_precision('Account'), help="The amount to be invoiced in advance, taxes excluded.")
deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)],\
deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)],
help="Account used for deposits", default=_default_deposit_account_id)
deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id)
@@ -70,8 +72,8 @@ class SaleAdvancePaymentInv(models.TransientModel):
account_id = order.fiscal_position_id.map_account(prop_id)
if not account_id:
raise UserError(
_('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') % \
(self.product_id.name,))
_('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') %
(self.product_id.name,))
if self.amount <= 0.00:
raise UserError(_('The value of the down payment amount must be positive.'))
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_sale_advance_payment_inv" model="ir.ui.view">
<field name="name">Invoice Orders</field>
<field name="model">sale.advance.payment.inv</field>
@@ -61,5 +60,4 @@
<field name="model">sale.order</field>
</record>
</data>
</openerp>
</odoo>