[MERGE] sale: Migrate to new API
This commit is contained in:
@@ -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
@@ -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>
|
||||
@@ -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.")
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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')
|
||||
@@ -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))})
|
||||
@@ -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"
|
||||
|
||||
+2
-4
@@ -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>
|
||||
@@ -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>
|
||||
+2
-4
@@ -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>
|
||||
@@ -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."),
|
||||
}
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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)
|
||||
-
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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.'))
|
||||
|
||||
+2
-4
@@ -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>
|
||||
Reference in New Issue
Block a user