[IMP] delivery, website_sale_delivery: refactoring and usability

* migration to new API
* removed delivery.grid object, has been merged with delivery.carrier
* change view of delivery.carrier (price rules, etc.)
* tests from yml to py
* website : restrict the list of countries for delivery address in the shop
This commit is contained in:
Mahendra barad
2015-09-01 08:14:16 +02:00
committed by David Monjoie
parent bb1200adc7
commit a223111b92
33 changed files with 775 additions and 1008 deletions
-4
View File
@@ -1,8 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import delivery
import partner
import sale
import stock
import models
+5 -8
View File
@@ -10,25 +10,22 @@
Allows you to add delivery methods in sale orders and picking.
==============================================================
You can define your own carrier and delivery grids for prices. When creating
You can define your own carrier for prices. When creating
invoices from picking, the system is able to add and compute the shipping line.
""",
'depends': ['sale_stock'],
'data': [
'security/ir.model.access.csv',
'delivery_view.xml',
'partner_view.xml',
'delivery_data.xml',
'views/delivery_view.xml',
'views/partner_view.xml',
'data/delivery_data.xml',
'views/report_shipping.xml',
'views/report_deliveryslip.xml'
],
'demo': ['delivery_demo.xml'],
'demo': ['data/delivery_demo.xml'],
'test': [
'../account/test/account_minimal_test.xml',
'test/delivery_cost.yml',
'test/stock_move_values_with_invoice_before_delivery.yml',
],
'installable': True,
'auto_install': False,
}
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<!-- Create a transporter -->
<record id="res_partner_delivery" model="res.partner">
<field name="name">Transporter (Delivery provider)</field>
@@ -10,31 +9,21 @@
<field name="customer" eval="False"/>
</record>
<!-- Create a service product -->
<record id="product_product_delivery" model="product.product">
<field name="name">Standard delivery</field>
<field name="default_code">Delivery</field>
<field name="type">service</field>
<field name="categ_id" ref="product.product_category_all"/>
<field name="standard_price">10.0</field>
<field name="list_price">10.0</field>
<field name="image" type="base64" file="delivery/static/img/product_product_delivery-image.jpg"/>
</record>
<record id="free_delivery_carrier" model="delivery.carrier">
<field name="name">Free delivery charges</field>
<field name="normal_price">0</field>
<field name="fixed_price">0</field>
<field name="free_if_more_than" eval="True"/>
<field name="amount">1000</field>
<field name="sequence">1</field>
<field name="delivery_type">fixed</field>
<field name="product_type">service</field>
<field name="product_sale_ok" eval="False"/>
<field name="partner_id" ref="res_partner_delivery"/>
<field name="product_id" ref="product_product_delivery"/>
</record>
<record forcecreate="True" id="property_delivery_carrier" model="ir.property">
<field name="name">property_delivery_carrier</field>
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_delivery_carrier')]"/>
<field name="name">property_delivery_carrier_id</field>
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_delivery_carrier_id')]"/>
</record>
</data>
</openerp>
</odoo>
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<!-- Update a transporter -->
<record id="res_partner_delivery" model="res.partner">
<field name="city">Manchester</field>
@@ -14,39 +13,33 @@
<record id="delivery_carrier" model="delivery.carrier">
<field name="name">The Poste</field>
<field name="normal_price">20</field>
<field name="fixed_price">20</field>
<field name="sequence">2</field>
<field name="partner_id" ref="res_partner_delivery"/>
<field name="product_id" ref="product_product_delivery"/>
<field name="use_detailed_pricelist" eval="True"/>
<field name="delivery_type">base_on_rule</field>
<field name="product_type">service</field>
<field name="product_sale_ok" eval="False"/>
</record>
<record id="normal_delivery_carrier" model="delivery.carrier">
<field name="name">Normal Delivery Charges</field>
<field name="normal_price">10</field>
<field name="fixed_price">10</field>
<field name="sequence">3</field>
<field name="partner_id" ref="res_partner_delivery"/>
<field name="product_id" ref="product_product_delivery"/>
<field name="delivery_type">fixed</field>
<field name="product_type">service</field>
<field name="product_sale_ok" eval="False"/>
</record>
<!-- Carrier Grids -->
<record id="delivery_grid" model="delivery.grid">
<field name="name">The Poste - Pricelist</field>
<record id="delivery_price_rule1" model="delivery.price.rule">
<field name="carrier_id" ref="delivery_carrier"/>
</record>
<!-- delivery charge of product if weight less than and equal 5kg-->
<record id="delivery_grid_line1" model="delivery.grid.line">
<field name="name">Weight &lt;= 5kg</field>
<field name="grid_id" ref="delivery_grid"/>
<field eval="5" name="max_value"/>
<field eval="20" name="list_base_price"/>
<field eval="10" name="standard_price"/>
</record>
<!-- delivery charge of product if weight more than 5kg-->
<record id="delivery_grid_line2" model="delivery.grid.line">
<field name="name">Weight &gt; 5kg</field>
<field name="grid_id" ref="delivery_grid"/>
<record id="delivery_price_rule2" model="delivery.price.rule">
<field name="carrier_id" ref="delivery_carrier"/>
<field name="operator">&gt;=</field>
<field eval="5" name="max_value"/>
<field eval="50" name="list_base_price"/>
@@ -54,20 +47,19 @@
</record>
<!-- free delivery charge if price more than 300-->
<record id="delivery_grid_line3" model="delivery.grid.line">
<field name="name">Free if price &gt;= 300</field>
<field name="grid_id" ref="delivery_grid"/>
<record id="delivery_price_rule3" model="delivery.price.rule">
<field name="carrier_id" ref="delivery_carrier"/>
<field eval="300" name="max_value"/>
<field name="operator">&gt;=</field>
<field name="type">price</field>
<field name="variable">price</field>
<field eval="0" name="list_base_price"/>
<field eval="0" name="standard_price"/>
</record>
<record forcecreate="True" id="property_delivery_carrier" model="ir.property">
<field name="name">property_delivery_carrier</field>
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_delivery_carrier')]"/>
<field name="name">property_delivery_carrier_id</field>
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_delivery_carrier_id')]"/>
<field name="value" eval="'delivery.carrier,'+str(ref('normal_delivery_carrier'))"/>
</record>
</data>
</openerp>
</odoo>
-244
View File
@@ -1,244 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import time
from openerp.osv import fields,osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
from openerp.tools.safe_eval import safe_eval as eval
from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
class delivery_carrier(osv.osv):
_name = "delivery.carrier"
_description = "Carrier"
_order = 'sequence, id'
def get_price(self, cr, uid, ids, field_name, arg=None, context=None):
res={}
if context is None:
context = {}
sale_obj=self.pool.get('sale.order')
grid_obj=self.pool.get('delivery.grid')
for carrier in self.browse(cr, uid, ids, context=context):
order_id=context.get('order_id',False)
price=False
available = False
if order_id:
order = sale_obj.browse(cr, uid, order_id, context=context)
carrier_grid=self.grid_get(cr,uid,[carrier.id],order.partner_shipping_id.id,context)
if carrier_grid:
try:
price=grid_obj.get_price(cr, uid, carrier_grid, order, time.strftime('%Y-%m-%d'), context)
available = True
except UserError, e:
# no suitable delivery method found, probably configuration error
_logger.info("Carrier %s: %s", carrier.name, e.name)
price = 0.0
else:
price = 0.0
res[carrier.id] = {
'price': price,
'available': available
}
return res
_columns = {
'name': fields.char('Delivery Method', required=True, translate=True),
'sequence': fields.integer('Sequence', help="Determine the display order"),
'partner_id': fields.many2one('res.partner', 'Transport Company', required=True, help="The partner that is doing the delivery service."),
'product_id': fields.many2one('product.product', 'Delivery Product', required=True),
'grids_id': fields.one2many('delivery.grid', 'carrier_id', 'Delivery Grids'),
'available' : fields.function(get_price, string='Available',type='boolean', multi='price',
help="Is the carrier method possible with the current order."),
'price' : fields.function(get_price, string='Price', multi='price'),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the delivery carrier without removing it."),
'normal_price': fields.float('Normal Price', help="Keep empty if the pricing depends on the advanced pricing per destination"),
'free_if_more_than': fields.boolean('Free If Order Total Amount Is More Than', help="If the order is more expensive than a certain amount, the customer can benefit from a free shipping"),
'amount': fields.float('Amount', help="Amount of the order to benefit from a free shipping, expressed in the company currency"),
'use_detailed_pricelist': fields.boolean('Advanced Pricing per Destination', help="Check this box if you want to manage delivery prices that depends on the destination, the weight, the total of the order, etc."),
'pricelist_ids': fields.one2many('delivery.grid', 'carrier_id', 'Advanced Pricing'),
}
_defaults = {
'active': 1,
'free_if_more_than': False,
'sequence': 10,
}
def grid_get(self, cr, uid, ids, contact_id, context=None):
contact = self.pool.get('res.partner').browse(cr, uid, contact_id, context=context)
for carrier in self.browse(cr, uid, ids, context=context):
for grid in carrier.grids_id:
get_id = lambda x: x.id
country_ids = map(get_id, grid.country_ids)
state_ids = map(get_id, grid.state_ids)
if country_ids and not contact.country_id.id in country_ids:
continue
if state_ids and not contact.state_id.id in state_ids:
continue
if grid.zip_from and (contact.zip or '')< grid.zip_from:
continue
if grid.zip_to and (contact.zip or '')> grid.zip_to:
continue
return grid.id
return False
def create_grid_lines(self, cr, uid, ids, vals, context=None):
if context is None:
context = {}
grid_line_pool = self.pool.get('delivery.grid.line')
grid_pool = self.pool.get('delivery.grid')
for record in self.browse(cr, uid, ids, context=context):
# if using advanced pricing per destination: do not change
if record.use_detailed_pricelist:
continue
# not using advanced pricing per destination: override grid
grid_id = grid_pool.search(cr, uid, [('carrier_id', '=', record.id)], context=context)
if grid_id and not (record.normal_price is not False or record.free_if_more_than):
grid_pool.unlink(cr, uid, grid_id, context=context)
grid_id = None
if not (record.normal_price is not False or record.free_if_more_than):
continue
if not grid_id:
grid_data = {
'name': record.name,
'carrier_id': record.id,
'sequence': 10,
}
grid_id = [grid_pool.create(cr, uid, grid_data, context=context)]
lines = grid_line_pool.search(cr, uid, [('grid_id','in',grid_id)], context=context)
if lines:
grid_line_pool.unlink(cr, uid, lines, context=context)
#create the grid lines
if record.free_if_more_than:
line_data = {
'grid_id': grid_id and grid_id[0],
'name': _('Free if more than %.2f') % record.amount,
'type': 'price',
'operator': '>=',
'max_value': record.amount,
'standard_price': 0.0,
'list_base_price': 0.0,
}
grid_line_pool.create(cr, uid, line_data, context=context)
if record.normal_price is not False:
line_data = {
'grid_id': grid_id and grid_id[0],
'name': _('Default price'),
'type': 'price',
'operator': '>=',
'max_value': 0.0,
'standard_price': record.normal_price,
'list_base_price': record.normal_price,
}
grid_line_pool.create(cr, uid, line_data, context=context)
return True
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int,long)):
ids = [ids]
res = super(delivery_carrier, self).write(cr, uid, ids, vals, context=context)
self.create_grid_lines(cr, uid, ids, vals, context=context)
return res
def create(self, cr, uid, vals, context=None):
res_id = super(delivery_carrier, self).create(cr, uid, vals, context=context)
self.create_grid_lines(cr, uid, [res_id], vals, context=context)
return res_id
class delivery_grid(osv.osv):
_name = "delivery.grid"
_description = "Delivery Grid"
_columns = {
'name': fields.char('Grid Name', required=True),
'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of delivery grid."),
'carrier_id': fields.many2one('delivery.carrier', 'Carrier', required=True, ondelete='cascade'),
'country_ids': fields.many2many('res.country', 'delivery_grid_country_rel', 'grid_id', 'country_id', 'Countries'),
'state_ids': fields.many2many('res.country.state', 'delivery_grid_state_rel', 'grid_id', 'state_id', 'States'),
'zip_from': fields.char('Start Zip', size=12),
'zip_to': fields.char('To Zip', size=12),
'line_ids': fields.one2many('delivery.grid.line', 'grid_id', 'Grid Line', copy=True),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the delivery grid without removing it."),
}
_defaults = {
'active': lambda *a: 1,
'sequence': lambda *a: 1,
}
_order = 'sequence'
def get_price(self, cr, uid, id, order, dt, context=None):
total = 0
weight = 0
volume = 0
quantity = 0
total_delivery = 0.0
product_uom_obj = self.pool.get('product.uom')
for line in order.order_line:
if line.state == 'cancel':
continue
if line.is_delivery:
total_delivery += line.price_total
if not line.product_id or line.is_delivery:
continue
q = product_uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_uom_qty, line.product_id.uom_id.id)
weight += (line.product_id.weight or 0.0) * q
volume += (line.product_id.volume or 0.0) * q
quantity += q
total = (order.amount_total or 0.0) - total_delivery
return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context)
def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None):
grid = self.browse(cr, uid, id, context=context)
price = 0.0
ok = False
price_dict = {'price': total, 'volume':volume, 'weight': weight, 'wv':volume*weight, 'quantity': quantity}
for line in grid.line_ids:
test = eval(line.type+line.operator+str(line.max_value), price_dict)
if test:
price = line.list_base_price + line.list_price * price_dict[line.variable_factor]
ok = True
break
if not ok:
raise UserError(_("Selected product in the delivery method doesn't fulfill any of the delivery grid(s) criteria."))
return price
class delivery_grid_line(osv.osv):
_name = "delivery.grid.line"
_description = "Delivery Grid Line"
_columns = {
'name': fields.char('Name', required=True),
'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when calculating delivery grid."),
'grid_id': fields.many2one('delivery.grid', 'Grid',required=True, ondelete='cascade'),
'type': fields.selection([('weight','Weight'),('volume','Volume'),\
('wv','Weight * Volume'), ('price','Price'), ('quantity','Quantity')],\
'Variable', required=True),
'operator': fields.selection([('==','='),('<=','<='),('<','<'),('>=','>='),('>','>')], 'Operator', required=True),
'max_value': fields.float('Maximum Value', required=True),
'variable_factor': fields.selection([('weight','Weight'),('volume','Volume'),('wv','Weight * Volume'), ('price','Price'), ('quantity','Quantity')], 'Variable Factor', required=True),
'list_base_price': fields.float('Sale Base Price', digits_compute=dp.get_precision('Product Price'), required=True),
'list_price': fields.float('Sale Price', digits_compute= dp.get_precision('Product Price'), required=True),
'standard_price': fields.float('Shipper Price', digits_compute= dp.get_precision('Product Price'), required=True),
}
_defaults = {
'sequence': lambda *args: 10,
'type': lambda *args: 'weight',
'operator': lambda *args: '<=',
'variable_factor': lambda *args: 'weight',
'list_price': 0.0,
'list_base_price': 0.0,
'standard_price': 0.0,
}
_order = 'sequence, list_price'
+5 -19
View File
@@ -1,24 +1,10 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (c) 2015 Odoo S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import delivery_carrier
import delivery_price_rule
import product_template
import sale_order
import partner
import stock_picking
import stock_move
+165 -31
View File
@@ -1,33 +1,19 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (c) 2015 Odoo S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from openerp import models, fields, api
from openerp import api, fields, models, _
from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
class DeliveryCarrier(models.Model):
_inherit = 'delivery.carrier'
_name = 'delivery.carrier'
_inherits = {'product.product': 'product_id'}
_description = "Carrier"
_order = 'sequence, id'
''' A Shipping Provider
In order to add your own external provider, follow these steps:
@@ -47,10 +33,40 @@ class DeliveryCarrier(models.Model):
# Internals for shipping providers #
# -------------------------------- #
delivery_type = fields.Selection([('grid', 'Default Delivery')], string="Delivery Type", default='grid', required='True')
sequence = fields.Integer(help="Determine the display order", default=10)
# This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex')
delivery_type = fields.Selection([('fixed', 'Fixed Price'), ('base_on_rule', 'Based on Rules')], string='Price Computation', default='fixed', required=True)
product_type = fields.Selection(related='product_id.type', default='service')
product_sale_ok = fields.Boolean(related='product_id.sale_ok', default=False)
partner_id = fields.Many2one('res.partner', string='Transporter Company', required=True, help="The partner that is doing the delivery service.")
product_id = fields.Many2one('product.product', string='Delivery Product', required=True, ondelete="cascade")
price = fields.Float(compute='get_price')
available = fields.Boolean(compute='get_price')
free_if_more_than = fields.Boolean('Free if Order total is more than', help="If the order is more expensive than a certain amount, the customer can benefit from a free shipping", default=False)
amount = fields.Float(string='Amount', help="Amount of the order to benefit from a free shipping, expressed in the company currency")
country_ids = fields.Many2many('res.country', 'delivery_carrier_country_rel', 'carrier_id', 'country_id', 'Countries')
state_ids = fields.Many2many('res.country.state', 'delivery_carrier_state_rel', 'carrier_id', 'state_id', 'States')
zip_from = fields.Char('Zip From')
zip_to = fields.Char('Zip To')
price_rule_ids = fields.One2many('delivery.price.rule', 'carrier_id', 'Pricing Rules', copy=True)
state = fields.Selection([('test', 'Testing'), ('production', 'Production')], 'State')
fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price',help="Keep empty if the pricing depends on the advanced pricing per destination")
@api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price')
def _compute_fixed_price(self):
for carrier in self:
carrier.fixed_price = carrier.product_id.list_price
def _set_product_fixed_price(self):
for carrier in self:
carrier.product_id.list_price = carrier.fixed_price
@api.onchange('delivery_type')
def onchange_delivery_type(self):
if self.delivery_type in ["fixed", "base_on_rule"]:
self.state = 'production'
else:
self.state = 'test'
@api.one
def get_price(self):
@@ -63,19 +79,27 @@ class DeliveryCarrier(models.Model):
if order_id:
# FIXME: temporary hack until we refactor the delivery API in master
if self.delivery_type != 'grid':
order = SaleOrder.browse(order_id)
if self.delivery_type not in ['fixed', 'base_on_rule']:
try:
order = SaleOrder.browse(order_id)
self.price = self.get_shipping_price_from_so(order)[0]
self.available = True
except UserError as e:
# no suitable delivery method found, probably configuration error
_logger.info("Carrier %s: %s, not found", self.name, e.name)
self.price = 0.0
# No suitable delivery method found, probably configuration error
_logger.info("Carrier %s: %s, not found", self.name, e.name)
self.price = 0.0
else:
res = super(DeliveryCarrier, self).get_price('price', [])
self.available = res[self.id]['available']
self.price = res[self.id]['price']
carrier = self.verify_carrier(order.partner_shipping_id)
if carrier:
try:
self.price = carrier.get_price_available(order)
self.available = True
except UserError, e:
# No suitable delivery method found, probably configuration error
_logger.info("Carrier %s: %s", carrier.name, e.name)
self.price = 0.0
else:
self.price = 0.0
# -------------------------- #
# API for external providers #
@@ -123,3 +147,113 @@ class DeliveryCarrier(models.Model):
self.ensure_one()
if hasattr(self, '%s_cancel_shipment' % self.delivery_type):
return getattr(self, '%s_cancel_shipment' % self.delivery_type)(pickings)
@api.onchange('state_ids')
def onchange_states(self):
self.country_ids = [(6, 0, self.country_ids.ids + self.state_ids.mapped('country_id.id'))]
@api.onchange('country_ids')
def onchange_countries(self):
self.state_ids = [(6, 0, self.state_ids.filtered(lambda state: state.id in self.country_ids.mapped('state_ids').ids).ids)]
@api.multi
def verify_carrier(self, contact):
self.ensure_one()
if self.country_ids and contact.country_id not in self.country_ids:
return False
if self.state_ids and contact.state_id not in self.state_ids:
return False
if self.zip_from and (contact.zip or '') < self.zip_from:
return False
if self.zip_to and (contact.zip or '') > self.zip_to:
return False
return self
@api.multi
def create_price_rules(self):
PriceRule = self.env['delivery.price.rule']
for record in self:
# If using advanced pricing per destination: do not change
if record.delivery_type == 'base_on_rule':
continue
# Not using advanced pricing per destination: override lines
if record.delivery_type == 'base_on_rule' and not (record.fixed_price is not False or record.free_if_more_than):
record.price_rule_ids.unlink()
# Check that float, else 0.0 is False
if not (record.fixed_price is not False or record.free_if_more_than):
continue
if record.delivery_type == 'fixed':
PriceRule.search([('carrier_id', '=', record.id)]).unlink()
line_data = {
'carrier_id': record.id,
'variable': 'price',
'operator': '>=',
}
# Create the delivery price rules
if record.free_if_more_than:
line_data.update({
'max_value': record.amount,
'standard_price': 0.0,
'list_base_price': 0.0,
})
PriceRule.create(line_data)
if record.fixed_price is not False:
line_data.update({
'max_value': 0.0,
'standard_price': record.fixed_price,
'list_base_price': record.fixed_price,
})
PriceRule.create(line_data)
return True
@api.model
def create(self, vals):
res = super(DeliveryCarrier, self).create(vals)
res.create_price_rules()
return res
@api.multi
def write(self, vals):
res = super(DeliveryCarrier, self).write(vals)
self.create_price_rules()
return res
@api.multi
def get_price_available(self, order):
self.ensure_one()
total = weight = volume = quantity = 0
total_delivery = 0.0
ProductUom = self.env['product.uom']
for line in order.order_line:
if line.state == 'cancel':
continue
if line.is_delivery:
total_delivery += line.price_total
if not line.product_id or line.is_delivery:
continue
qty = ProductUom._compute_qty(line.product_uom.id, line.product_uom_qty, line.product_id.uom_id.id)
weight += (line.product_id.weight or 0.0) * qty
volume += (line.product_id.volume or 0.0) * qty
quantity += qty
total = (order.amount_total or 0.0) - total_delivery
return self.get_price_from_picking(total, weight, volume, quantity)
def get_price_from_picking(self, total, weight, volume, quantity):
price = 0.0
criteria_found = False
price_dict = {'price': total, 'volume': volume, 'weight': weight, 'wv': volume * weight, 'quantity': quantity}
for line in self.price_rule_ids:
test = eval(line.variable + line.operator + str(line.max_value), price_dict)
if test:
price = line.list_base_price + line.list_price * price_dict[line.variable_factor]
criteria_found = True
break
if not criteria_found:
raise UserError(_("Selected product in the delivery method doesn't fulfill any of the delivery carrier(s) criteria."))
return price
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, fields, api
import openerp.addons.decimal_precision as dp
class PriceRule(models.Model):
_name = "delivery.price.rule"
_description = "Delivery Price Rules"
_order = 'sequence, list_price'
@api.depends('variable', 'operator', 'max_value', 'list_base_price', 'list_price', 'variable_factor')
def _get_name(self):
for rule in self:
name = 'if %s %s %s then' % (rule.variable, rule.operator, rule.max_value)
if rule.list_base_price and not rule.list_price:
name = '%s fixed price %s' % (name, rule.list_base_price)
elif rule.list_price and not rule.list_base_price:
name = '%s %s times %s' % (name, rule.list_price, rule.variable_factor)
else:
name = '%s fixed price %s and %s times %s Extra' % (name, rule.list_base_price, rule.list_price, rule.variable_factor)
rule.name = name
name = fields.Char(compute='_get_name')
sequence = fields.Integer(required=True, help="Gives the sequence order when calculating delivery carrier.", default=10)
carrier_id = fields.Many2one('delivery.carrier', 'Carrier', required=True, ondelete='cascade')
variable = fields.Selection([('weight', 'Weight'), ('volume', 'Volume'), ('wv', 'Weight * Volume'), ('price', 'Price'), ('quantity', 'Quantity')], 'Variable', required=True, default='weight')
operator = fields.Selection([('==', '='), ('<=', '<='), ('<', '<'), ('>=', '>='), ('>', '>')], 'Operator', required=True, default='<=')
max_value = fields.Float('Maximum Value', required=True)
variable_factor = fields.Selection([('weight', 'Weight'), ('volume', 'Volume'), ('wv', 'Weight * Volume'), ('price', 'Price'), ('quantity', 'Quantity')], 'Variable Factor', required=True, default='weight')
list_base_price = fields.Float(string='Sale Base Price', digits_compute=dp.get_precision('Product Price'), required=True, default=0.0)
list_price = fields.Float('Sale Price', digits_compute=dp.get_precision('Product Price'), required=True, default=0.0)
standard_price = fields.Float('Cost Price', digits_compute=dp.get_precision('Product Price'), required=True, default=0.0)
+10
View File
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
property_delivery_carrier_id = fields.Many2one('delivery.carrier', company_dependent=True, string="Delivery Method", help="This delivery method will be used when invoicing from picking.")
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import api, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
@api.multi
def write(self, vals):
res = super(ProductTemplate, self).write(vals)
if vals.get('list_price'):
DeliveryCarrier = self.env['delivery.carrier']
for template in self:
carrier = DeliveryCarrier.search([('product_id', 'in', template.product_variant_ids.ids)])
if carrier:
carrier.create_price_rules()
return res
+41 -33
View File
@@ -1,23 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (c) 2015 Odoo S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, fields, api, _
from openerp.exceptions import UserError
@@ -27,19 +9,29 @@ class SaleOrder(models.Model):
_inherit = 'sale.order'
delivery_price = fields.Float(string='Estimated Delivery Price', compute='_compute_delivery_price', store=True)
carrier_id = fields.Many2one("delivery.carrier", string="Delivery Method", help="Fill this field if you plan to invoice the shipping based on picking.")
@api.depends('carrier_id', 'partner_id', 'order_line')
def _compute_delivery_price(self):
for order in self:
if order.state != 'draft':
# we do not want to recompute the shipping price of an already validated/done SO
# We do not want to recompute the shipping price of an already validated/done SO
continue
elif order.carrier_id.delivery_type != 'grid' and not order.order_line:
# prevent SOAP call to external shipping provider when SO has no lines yet
# Prevent SOAP call to external shipping provider when SO has no lines yet
continue
else:
order.delivery_price = order.carrier_id.with_context(order_id=order.id).price
@api.onchange('partner_id')
def onchange_partner_id_dtype(self):
if self.partner_id:
self.carrier_id = self.partner_id.property_delivery_carrier_id
@api.multi
def _delivery_unset(self):
self.env['sale.order.line'].search([('order_id', 'in', self.ids), ('is_delivery', '=', True)]).unlink()
@api.multi
def delivery_set(self):
@@ -49,16 +41,26 @@ class SaleOrder(models.Model):
self._delivery_unset()
for order in self:
if order.carrier_id and order.carrier_id.delivery_type != 'grid':
# Shipping providers are used when delivery_type is other than 'grid'
carrier = order.carrier_id
if carrier:
if order.state not in ('draft', 'sent'):
raise UserError(_('The order state have to be draft to add delivery lines.'))
carrier = order.carrier_id
account_id = carrier.product_id.property_account_income.id
if carrier.delivery_type not in ['fixed', 'base_on_rule']:
# Shipping providers are used when delivery_type is other than 'fixed' or 'base_on_rule'
price_unit = order.carrier_id.get_shipping_price_from_so(order)[0]
else:
# Classic grid-based carriers
carrier = order.carrier_id.verify_carrier(order.partner_shipping_id)
if not carrier:
raise UserError(_('No carrier matching.'))
price_unit = carrier.get_price_available(order)
if order.company_id.currency_id.id != order.pricelist_id.currency_id.id:
price_unit = order.company_id.currency_id.with_context(date=order.date_order).compute(order.pricelist_id.currency_id.id, price_unit)
account_id = carrier.product_id.property_account_income_id.id
if not account_id:
account_id = carrier.product_id.categ_id.property_account_income_categ.id
account_id = carrier.product_id.categ_id.property_account_income_categ_id.id
# Apply fiscal position
taxes = carrier.product_id.taxes_id
@@ -67,17 +69,23 @@ class SaleOrder(models.Model):
account_id = order.fiscal_position_id.map_account(account_id)
taxes_ids = order.fiscal_position_id.map_tax(taxes).ids
# Create the sale order line
SaleOrderLine.create({
'order_id': order.id,
'name': order.carrier_id.name,
'name': carrier.name,
'product_uom_qty': 1,
'product_uom': order.carrier_id.product_id.uom_id.id,
'product_id': order.carrier_id.product_id.id,
'price_unit': order.carrier_id.get_shipping_price_from_so(order)[0],
'product_uom': carrier.product_id.uom_id.id,
'product_id': carrier.product_id.id,
'price_unit': price_unit,
'tax_id': [(6, 0, taxes_ids)],
'is_delivery': True
})
else:
# Classic grid-based carriers
super(SaleOrder, self).delivery_set()
raise UserError(_('No carrier set for this order.'))
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
is_delivery = fields.Boolean(string="Is a Delivery", default=False)
+39
View File
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import api, fields, models
import openerp.addons.decimal_precision as dp
class StockMove(models.Model):
_inherit = 'stock.move'
def _default_uom(self):
uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id
return self.env['product.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1)
weight = fields.Float(compute='_cal_move_weight', digits_compute=dp.get_precision('Stock Weight'), store=True)
weight_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for Weight", default=_default_uom)
@api.depends('product_id', 'product_uom_qty', 'product_uom')
def _cal_move_weight(self):
for move in self.filtered(lambda moves: moves.product_id.weight > 0.00):
move.weight = (move.product_qty * move.product_id.weight)
@api.multi
def action_confirm(self):
"""
Pass the carrier to the picking from the sales order
(Should also work in case of Phantom BoMs when on explosion the original move is deleted)
"""
procs_to_check = []
for move in self:
if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.carrier_id:
procs_to_check += [move.procurement_id]
res = super(StockMove, self).action_confirm()
for proc in procs_to_check:
pickings = (proc.move_ids.mapped('picking_id')).filtered(lambda record: not record.carrier_id)
if pickings:
pickings.write({'carrier_id': proc.sale_line_id.order_id.carrier_id.id})
return res
+54 -35
View File
@@ -1,47 +1,48 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (c) 2015 Odoo S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, fields, api, _
from openerp.exceptions import UserError
import openerp.addons.decimal_precision as dp
from openerp import models, fields, api
class StockPicking(models.Model):
_inherit = 'stock.picking'
def _default_uom(self):
uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id
return self.env['product.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1)
carrier_price = fields.Float(string="Shipping Cost", readonly=True)
delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True)
carrier_id = fields.Many2one("delivery.carrier", string="Carrier")
volume = fields.Float(copy=False)
weight = fields.Float(compute='_cal_weight', digits_compute=dp.get_precision('Stock Weight'), store=True)
carrier_tracking_ref = fields.Char(string='Carrier Tracking Ref', copy=False)
number_of_packages = fields.Integer(string='Number of Packages', copy=False)
weight_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly="1", help="Unit of measurement for Weight", default=_default_uom)
@api.depends('product_id', 'move_lines')
def _cal_weight(self):
for picking in self:
picking.weight = sum(move.weight for move in picking.move_lines if move.state != 'cancel')
@api.multi
def do_transfer(self):
self.ensure_one()
res = super(StockPicking, self).do_transfer()
if self.carrier_id and self.carrier_id.delivery_type != 'grid':
if self.carrier_id and self.carrier_id.delivery_type not in ['fixed', 'base_on_rule']:
self.send_to_shipper()
return res
# Signature due to strange old api methods
@api.model
def _prepare_shipping_invoice_line(self, picking, invoice):
picking.ensure_one()
@api.multi
def _prepare_shipping_invoice_line(self, invoice):
self.ensure_one()
invoice.ensure_one()
carrier = picking.carrier_id
carrier = self.carrier_id
# No carrier
if not carrier:
@@ -51,12 +52,18 @@ class StockPicking(models.Model):
return None
# Classic carrier
if carrier.delivery_type == 'grid':
return super(StockPicking, self)._prepare_shipping_invoice_line(picking, invoice)
# Shipping provider
price = picking.carrier_price
if carrier.delivery_type in ['fixed', 'base_on_rule']:
carrier = carrier.verify_carrier(self.partner_id)
if not carrier:
raise UserError(_('The carrier %s (id: %d) has no delivery method!') % (self.carrier_id.name, self.carrier_id.id))
quantity = sum([line.product_uom_qty for line in self.move_lines])
price = carrier.get_price_from_picking(invoice.amount_untaxed, self.weight, self.volume, quantity)
else:
# Shipping provider
price = self.carrier_price
if invoice.company_id.currency_id.id != invoice.currency_id.id:
price = invoice.company_id.currency_id.with_context(date=invoice.date_invoice).compute(invoice.currency_id.id, price)
account_id = carrier.product_id.property_account_income.id
if not account_id:
account_id = carrier.product_id.categ_id.property_account_income_categ.id
@@ -65,8 +72,8 @@ class StockPicking(models.Model):
taxes_ids = taxes.ids
# Apply original SO fiscal position
if picking.sale_id.fiscal_position_id:
fpos = picking.sale_id.fiscal_position_id
if self.sale_id.fiscal_position_id:
fpos = self.sale_id.fiscal_position_id
account_id = fpos.map_account(account_id)
taxes_ids = fpos.map_tax(taxes).ids
@@ -83,12 +90,24 @@ class StockPicking(models.Model):
return res
@api.one
@api.model
def _invoice_create_line(self, moves, journal_id, inv_type='out_invoice'):
InvoiceLine = self.env['account.invoice.line']
invoice_ids = super(StockPicking, self)._invoice_create_line(moves, journal_id, inv_type=inv_type)
for move in moves:
for invoice in move.picking_id.sale_id.invoice_ids.filtered(lambda invoice: invoice.id in invoice_ids):
invoice_line = move.picking_id._prepare_shipping_invoice_line(invoice)
if invoice_line:
InvoiceLine.create(invoice_line)
return invoice_ids
@api.multi
def send_to_shipper(self):
self.ensure_one()
res = self.carrier_id.send_shipping(self)[0]
self.carrier_price = res['exact_price']
self.carrier_tracking_ref = res['tracking_number']
msg = "Shipment sent to carrier %s for expedition with tracking number %s" % (self.carrier_id.name, self.carrier_tracking_ref)
msg = _("Shipment sent to carrier %s for expedition with tracking number %s") % (self.carrier_id.name, self.carrier_tracking_ref)
self.message_post(body=msg)
@api.multi
-14
View File
@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
class res_partner(osv.osv):
_inherit = 'res.partner'
_columns = {
'property_delivery_carrier': fields.property(
type='many2one',
relation='delivery.carrier',
string="Delivery Method",
help="This delivery method will be used when invoicing from picking."),
}
-79
View File
@@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp import api
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
class sale_order_line(osv.Model):
_inherit = 'sale.order.line'
_columns = {
'is_delivery': fields.boolean("Is a Delivery"),
}
_defaults = {
'is_delivery': False
}
class sale_order(osv.Model):
_inherit = 'sale.order'
_columns = {
'carrier_id': fields.many2one(
"delivery.carrier", string="Delivery Method",
help="Complete this field if you plan to invoice the shipping based on picking."),
}
@api.onchange('partner_id')
def onchange_partner_id_dtype(self):
if self.partner_id:
self.carrier_id = self.partner_id.property_delivery_carrier
def _delivery_unset(self, cr, uid, ids, context=None):
sale_obj = self.pool['sale.order.line']
line_ids = sale_obj.search(cr, uid, [('order_id', 'in', ids), ('is_delivery', '=', True)],context=context)
sale_obj.unlink(cr, uid, line_ids, context=context)
def delivery_set(self, cr, uid, ids, context=None):
line_obj = self.pool.get('sale.order.line')
grid_obj = self.pool.get('delivery.grid')
carrier_obj = self.pool.get('delivery.carrier')
acc_fp_obj = self.pool.get('account.fiscal.position')
self._delivery_unset(cr, uid, ids, context=context)
currency_obj = self.pool.get('res.currency')
line_ids = []
for order in self.browse(cr, uid, ids, context=context):
grid_id = carrier_obj.grid_get(cr, uid, [order.carrier_id.id], order.partner_shipping_id.id)
if not grid_id:
raise UserError(_('No grid matching for this carrier!'))
if order.state not in ('draft', 'sent'):
raise UserError(_('The order state have to be draft to add delivery lines.'))
grid = grid_obj.browse(cr, uid, grid_id, context=context)
taxes = grid.carrier_id.product_id.taxes_id
fpos = order.fiscal_position_id or False
taxes_ids = acc_fp_obj.map_tax(cr, uid, fpos, taxes)
price_unit = grid_obj.get_price(cr, uid, grid.id, order, time.strftime('%Y-%m-%d'), context)
if order.company_id.currency_id.id != order.pricelist_id.currency_id.id:
price_unit = currency_obj.compute(cr, uid, order.company_id.currency_id.id, order.pricelist_id.currency_id.id,
price_unit, context=dict(context or {}, date=order.date_order))
#create the sale order line
line_id = line_obj.create(cr, uid, {
'order_id': order.id,
'name': grid.carrier_id.name,
'product_uom_qty': 1,
'product_uom': grid.carrier_id.product_id.uom_id.id,
'product_id': grid.carrier_id.product_id.id,
'price_unit': price_unit,
'tax_id': [(6, 0, taxes_ids)],
'is_delivery': True
}, context=context)
line_ids.append(line_id)
return line_ids
+3 -6
View File
@@ -1,12 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_delivery_carrier,delivery.carrier,model_delivery_carrier,base.group_sale_salesman,1,0,0,0
access_delivery_grid,delivery.grid,model_delivery_grid,base.group_sale_salesman,1,0,0,0
access_delivery_grid_line,delivery.grid.line,model_delivery_grid_line,base.group_sale_salesman,1,0,0,0
access_delivery_price_rule,delivery.price.rule,model_delivery_price_rule,base.group_sale_salesman,1,0,0,0
access_delivery_carrier_manager,delivery.carrier,model_delivery_carrier,base.group_sale_manager,1,1,1,1
access_delivery_grid_manager,delivery.grid,model_delivery_grid,base.group_sale_manager,1,1,1,1
access_delivery_grid_line_manager,delivery.grid.line,model_delivery_grid_line,base.group_sale_manager,1,1,1,1
access_delivery_price_rule_manager,delivery.price.rule,model_delivery_price_rule,base.group_sale_manager,1,1,1,1
access_delivery_carrier_partner_manager,delivery.carrier partner_manager,model_delivery_carrier,base.group_partner_manager,1,0,0,0
access_delivery_carrier_stock_user,delivery.carrier stock_user,model_delivery_carrier,stock.group_stock_user,1,0,0,0
access_delivery_carrier_stock_manager,delivery.carrier,model_delivery_carrier,stock.group_stock_manager,1,1,1,1
access_delivery_grid_stock_manager,delivery.grid,model_delivery_grid,stock.group_stock_manager,1,1,1,1
access_delivery_grid_line_stock_manager,delivery.grid.line,model_delivery_grid_line,stock.group_stock_manager,1,1,1,1
access_delivery_price_rule_stock_manager,delivery.price.rule,model_delivery_price_rule,stock.group_stock_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_delivery_carrier delivery.carrier model_delivery_carrier base.group_sale_salesman 1 0 0 0
3 access_delivery_grid access_delivery_price_rule delivery.grid delivery.price.rule model_delivery_grid model_delivery_price_rule base.group_sale_salesman 1 0 0 0
access_delivery_grid_line delivery.grid.line model_delivery_grid_line base.group_sale_salesman 1 0 0 0
4 access_delivery_carrier_manager delivery.carrier model_delivery_carrier base.group_sale_manager 1 1 1 1
5 access_delivery_grid_manager access_delivery_price_rule_manager delivery.grid delivery.price.rule model_delivery_grid model_delivery_price_rule base.group_sale_manager 1 1 1 1
access_delivery_grid_line_manager delivery.grid.line model_delivery_grid_line base.group_sale_manager 1 1 1 1
6 access_delivery_carrier_partner_manager delivery.carrier partner_manager model_delivery_carrier base.group_partner_manager 1 0 0 0
7 access_delivery_carrier_stock_user delivery.carrier stock_user model_delivery_carrier stock.group_stock_user 1 0 0 0
8 access_delivery_carrier_stock_manager delivery.carrier model_delivery_carrier stock.group_stock_manager 1 1 1 1
9 access_delivery_grid_stock_manager access_delivery_price_rule_stock_manager delivery.grid delivery.price.rule model_delivery_grid model_delivery_price_rule stock.group_stock_manager 1 1 1 1
access_delivery_grid_line_stock_manager delivery.grid.line model_delivery_grid_line stock.group_stock_manager 1 1 1 1
-164
View File
@@ -1,164 +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.tools.translate import _
from openerp.exceptions import UserError
import openerp.addons.decimal_precision as dp
# Overloaded stock_picking to manage carriers :
class stock_picking(osv.osv):
_inherit = 'stock.picking'
def _cal_weight(self, cr, uid, ids, name, args, context=None):
res = {}
for picking in self.browse(cr, uid, ids, context=context):
res[picking.id] = sum(move.weight for move in picking.move_lines if move.state != 'cancel')
return res
def _get_picking_line(self, cr, uid, ids, context=None):
result = {}
for line in self.pool.get('stock.move').browse(cr, uid, ids, context=context):
result[line.picking_id.id] = True
return result.keys()
_columns = {
'carrier_id':fields.many2one("delivery.carrier","Carrier"),
'volume': fields.float('Volume', copy=False),
'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'),
store={
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 40),
'stock.move': (_get_picking_line, ['state', 'picking_id', 'product_id','product_uom_qty','product_uom'], 40),
}),
'carrier_tracking_ref': fields.char('Carrier Tracking Ref', copy=False),
'number_of_packages': fields.integer('Number of Packages', copy=False),
'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of measurement for Weight",),
}
def _prepare_shipping_invoice_line(self, cr, uid, picking, invoice, context=None):
"""Prepare the invoice line to add to the shipping costs to the shipping's
invoice.
:param browse_record picking: the stock picking being invoiced
:param browse_record invoice: the stock picking's invoice
:return: dict containing the values to create the invoice line,
or None to create nothing
"""
carrier_obj = self.pool.get('delivery.carrier')
grid_obj = self.pool.get('delivery.grid')
fpos_obj = self.pool['account.fiscal.position']
currency_obj = self.pool.get('res.currency')
if not picking.carrier_id or \
any(inv_line.product_id.id == picking.carrier_id.product_id.id
for inv_line in invoice.invoice_line_ids):
return None
grid_id = carrier_obj.grid_get(cr, uid, [picking.carrier_id.id],
picking.partner_id.id, context=context)
if not grid_id:
raise UserError(_('The carrier %s (id: %d) has no delivery grid!') % (picking.carrier_id.name,picking.carrier_id.id))
quantity = sum([line.product_uom_qty for line in picking.move_lines])
price = grid_obj.get_price_from_picking(cr, uid, grid_id,
invoice.amount_untaxed, picking.weight, picking.volume,
quantity, context=context)
if invoice.company_id.currency_id.id != invoice.currency_id.id:
price = currency_obj.compute(cr, uid, invoice.company_id.currency_id.id, invoice.currency_id.id,
price, context=dict(context or {}, date=invoice.date_invoice))
account_id = picking.carrier_id.product_id.property_account_income_id.id
if not account_id:
account_id = picking.carrier_id.product_id.categ_id\
.property_account_income_categ_id.id
taxes = picking.carrier_id.product_id.taxes_id
taxes_ids = [x.id for x in taxes]
partner = picking.partner_id or False
fpos = None
if picking.sale_id and picking.sale_id.fiscal_position_id:
fpos = picking.sale_id.fiscal_position_id
elif picking.partner_id:
fpos = partner.property_account_position_id
# TDE FIXME: spotted undefined variable
account_id = fpos_obj.map_account(cr, uid, fpos, account_id, context=context)
taxes_ids = fpos_obj.map_tax(cr, uid, fpos, taxes, context=context)
return {
'name': picking.carrier_id.name,
'invoice_id': invoice.id,
'uom_id': picking.carrier_id.product_id.uom_id.id,
'product_id': picking.carrier_id.product_id.id,
'account_id': account_id,
'price_unit': price,
'quantity': 1,
'invoice_line_tax_ids': [(6, 0, taxes_ids)],
}
def _invoice_create_line(self, cr, uid, moves, journal_id, inv_type='out_invoice', context=None):
invoice_line_obj = self.pool.get('account.invoice.line')
invoice_ids = super(stock_picking, self)._invoice_create_line(cr, uid, moves, journal_id, inv_type=inv_type, context=context)
delivey_invoices = {}
for move in moves:
for invoice in move.picking_id.sale_id.invoice_ids:
if invoice.id in invoice_ids:
delivey_invoices[invoice] = move.picking_id
if delivey_invoices:
for invoice, picking in delivey_invoices.items():
invoice_line = self._prepare_shipping_invoice_line(cr, uid, picking, invoice, context=context)
if invoice_line:
invoice_line_obj.create(cr, uid, invoice_line)
return invoice_ids
def _get_default_uom(self, cr, uid, context=None):
uom_categ_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'product.product_uom_categ_kgm')
return self.pool.get('product.uom').search(cr, uid, [('category_id', '=', uom_categ_id), ('factor', '=', 1)])[0]
_defaults = {
'weight_uom_id': lambda self, cr, uid, c: self._get_default_uom(cr, uid, c),
}
class stock_move(osv.osv):
_inherit = 'stock.move'
def _cal_move_weight(self, cr, uid, ids, name, args, context=None):
res = {}
for move in self.browse(cr, uid, ids, context=context):
weight = 0.00
if move.product_id.weight > 0.00:
weight = move.product_qty * move.product_id.weight
res[move.id] = weight
return res
_columns = {
'weight': fields.function(_cal_move_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'),
store={
'stock.move': (lambda self, cr, uid, ids, c=None: ids, ['product_id', 'product_uom_qty', 'product_uom'], 30),
}),
'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of Measure (Unit of Measure) is the unit of measurement for Weight",),
}
def action_confirm(self, cr, uid, ids, context=None):
"""
Pass the carrier to the picking from the sales order
(Should also work in case of Phantom BoMs when on explosion the original move is deleted)
"""
procs_to_check = []
for move in self.browse(cr, uid, ids, context=context):
if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.carrier_id:
procs_to_check += [move.procurement_id]
res = super(stock_move, self).action_confirm(cr, uid, ids, context=context)
pick_obj = self.pool.get("stock.picking")
for proc in procs_to_check:
pickings = list(set([x.picking_id.id for x in proc.move_ids if x.picking_id and not x.picking_id.carrier_id]))
if pickings:
pick_obj.write(cr, uid, pickings, {'carrier_id': proc.sale_line_id.order_id.carrier_id.id}, context=context)
return res
def _get_default_uom(self, cr, uid, context=None):
uom_categ_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'product.product_uom_categ_kgm')
return self.pool.get('product.uom').search(cr, uid, [('category_id', '=', uom_categ_id),('factor','=',1)])[0]
_defaults = {
'weight_uom_id': lambda self, cr, uid, c: self._get_default_uom(cr, uid, c),
}
-80
View File
@@ -1,80 +0,0 @@
-
In order to test Carrier Cost,
-
Create sale order with Normal Delivery Charges
-
!record {model: sale.order, id: sale_normal_delivery_charges}:
partner_id: base.res_partner_18
partner_invoice_id: base.res_partner_18
partner_shipping_id: base.res_partner_18
pricelist_id: product.list0
order_line:
- name: 'PC Assamble + 2GB RAM'
product_id: product.product_product_4
product_uom_qty: 1
product_uom: product.product_uom_unit
price_unit: 750.00
carrier_id: normal_delivery_carrier
-
I add delivery cost in Sale order.
-
!python {model: sale.order}: |
self.pool.get('product.product').write(cr, uid, [ref('product_product_delivery')], {'taxes_id': [(5, 0, 0)]}, context=context) # Removing taxes as this can cause problem (re. price_included)
self.delivery_set(cr, uid, [ref("sale_normal_delivery_charges")], context=context)
-
I check sale order after added delivery cost.
-
!python {model: sale.order.line}: |
from openerp.tools import float_compare
line_ids = self.search(cr, uid, [('order_id','=', ref('sale_normal_delivery_charges')), ('product_id','=', ref('product_product_delivery'))])
assert len(line_ids), "Delivery cost is not Added"
line_data = self.browse(cr ,uid ,line_ids[0] ,context)
assert float_compare(line_data.price_subtotal, 10, precision_digits=2) == 0, "Delivey cost is not correspond."
-
I confirm the sale order.
-
!python {model: sale.order}: |
self.action_confirm(cr, uid, ref("sale_normal_delivery_charges"), context=context)
-
Create one more sale order with Free Delivery Charges
-
!record {model: sale.order, id: sale_free_delivery_charges}:
partner_id: base.res_partner_4
partner_invoice_id: base.res_partner_address_13
partner_shipping_id: base.res_partner_address_13
pricelist_id: product.list0
order_line:
- name: 'Service on demand'
product_id: product.product_product_1
product_uom_qty: 24
product_uom: product.product_uom_hour
price_unit: 75.00
order_line:
- name: 'On Site Assistance'
product_id: product.product_product_2
product_uom_qty: 30
product_uom: product.product_uom_hour
price_unit: 38.25
carrier_id: free_delivery_carrier
-
I add free delivery cost in Sale order.
-
!python {model: sale.order}: |
self.delivery_set(cr, uid, [ref("sale_free_delivery_charges")], context=context)
-
I check sale order after added delivery cost.
-
!python {model: sale.order.line}: |
from openerp.tools import float_compare
line_ids = self.search(cr, uid, [('order_id','=', ref('sale_free_delivery_charges')), ('product_id','=', ref('product_product_delivery'))])
assert len(line_ids), "Delivery cost is not Added"
line_data = self.browse(cr ,uid ,line_ids[0] ,context)
assert float_compare(line_data.price_subtotal, 0, precision_digits=2) == 0, "Delivey cost is not correspond."
-
I set default delivery policy.
-
!record {model: sale.config.settings, id: default_delivery_policy}:
{}
-
!python {model: sale.config.settings}: |
self.execute(cr, uid, [ref('default_delivery_policy')], context=context)
@@ -1,73 +0,0 @@
-
test that the store fields of stock moves are computed with invoice before delivery flow
-
set a weight on ipod 16GB
-
!python {model: product.product, id: product.product_product_11}: |
self.write({'weight': 0.25})
-
create a SO with invoice before delivery
-
!record {model: sale.order, id: sale_prepaid}:
partner_id: base.res_partner_18
partner_invoice_id: base.res_partner_18
partner_shipping_id: base.res_partner_18
pricelist_id: product.list0
order_line:
- name: 'Ice Cream'
product_id: stock.product_icecream
product_uom_qty: 2
product_uom: product.product_uom_kgm
price_unit: 750.00
carrier_id: normal_delivery_carrier
-
I add delivery cost in Sale order.
-
!python {model: sale.order, id: sale_prepaid}: |
self.delivery_set()
-
I confirm the SO.
-
!python {model: sale.order}: |
self.action_confirm(cr, uid, ref("sale_prepaid"), context=context)
self.action_invoice_create(cr, uid, [ref("sale_prepaid")], context=context)
-
I check that the invoice was created
-
!python {model: sale.order, id: sale_prepaid}: |
assert len(self.invoice_ids) == 1, "Invoice not created."
-
I confirm the invoice
-
!python {model: sale.order, id: sale_prepaid}: |
invoice = self.invoice_ids[0]
invoice.signal_workflow('invoice_open')
-
I pay the invoice.
-
!python {model: sale.order, id: sale_prepaid}: |
invoice = self.invoice_ids[0]
invoice.signal_workflow('invoice_open')
journal = self.env['account.journal'].search([('type', '=', 'cash'),
('company_id', '=', self.company_id.id)],
limit=1)
invoice.pay_and_reconcile(journal, invoice.amount_total)
-
Check the SO after paying the invoice
-
!python {model: sale.order, id: sale_prepaid}: |
assert self.invoice_count != 0, 'order not invoiced'
assert self.invoice_status == 'invoiced', 'order is not invoiced'
assert len(self.picking_ids) == 1, 'pickings not generated'
-
check the stock moves
-
!python {model: sale.order, id: sale_prepaid}: |
move = self.picking_ids.move_lines
assert move.product_qty == 2, 'wrong product_qty'
assert move.weight == 2.0, 'wrong move weight'
-
ship
-
!python {model: sale.order, id: sale_prepaid}: |
picking = self.picking_ids.action_done()
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing detailsself.
from . import test_delivery_cost, test_delivery_stock_move
+125
View File
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
from openerp.tests import common
from openerp.tools import float_compare
class TestDeliveryCost(common.TransactionCase):
def setUp(self):
super(TestDeliveryCost, self).setUp()
self.SaleOrder = self.env['sale.order']
self.SaleOrderLine = self.env['sale.order.line']
self.AccountAccount = self.env['account.account']
self.SaleConfigSetting = self.env['sale.config.settings']
self.Product = self.env['product.product']
self.partner_18 = self.env.ref('base.res_partner_18')
self.pricelist = self.env.ref('product.list0')
self.product_4 = self.env.ref('product.product_product_4')
self.product_uom_unit = self.env.ref('product.product_uom_unit')
self.normal_delivery = self.env.ref('delivery.normal_delivery_carrier')
self.partner_4 = self.env.ref('base.res_partner_4')
self.partner_address_13 = self.env.ref('base.res_partner_address_13')
self.product_uom_hour = self.env.ref('product.product_uom_hour')
self.account_data = self.env.ref('account.data_account_type_revenue')
self.account_tag_operating = self.env.ref('account.account_tag_operating')
self.product_2 = self.env.ref('product.product_product_2')
self.product_category = self.env.ref('product.product_category_all')
self.free_delivery = self.env.ref('delivery.free_delivery_carrier')
def test_00_delivery_cost(self):
# In order to test Carrier Cost
# Create sale order with Normal Delivery Charges
self.sale_normal_delivery_charges = self.SaleOrder.create({
'partner_id': self.partner_18.id,
'partner_invoice_id': self.partner_18.id,
'partner_shipping_id': self.partner_18.id,
'pricelist_id': self.pricelist.id,
'order_line': [(0, 0, {
'name': 'PC Assamble + 2GB RAM',
'product_id': self.product_4.id,
'product_uom_qty': 1,
'product_uom': self.product_uom_unit.id,
'price_unit': 750.00,
})],
'carrier_id': self.normal_delivery.id
})
# I add delivery cost in Sale order
self.a_sale = self.AccountAccount.create({
'code': 'X2020',
'name': 'Product Sales - (test)',
'user_type_id': self.account_data.id,
'tag_ids': [(6, 0, {
self.account_tag_operating.id
})]
})
self.product_consultant = self.Product.create({
'sale_ok': True,
'list_price': 75.0,
'standard_price': 30.0,
'uom_id': self.product_uom_hour.id,
'uom_po_id': self.product_uom_hour.id,
'name': 'Service',
'categ_id': self.product_category.id,
'type': 'service'
})
# I add delivery cost in Sale order
self.sale_normal_delivery_charges.delivery_set()
# I check sale order after added delivery cost
line = self.SaleOrderLine.search([('order_id', '=', self.sale_normal_delivery_charges.id),
('product_id', '=', self.sale_normal_delivery_charges.carrier_id.product_id.id)])
self.assertEqual(len(line), 1, "Delivery cost is not Added")
self.assertEqual(float_compare(line.price_subtotal, 10, precision_digits=2), 0,
"Delivey cost is not correspond.")
# I confirm the sale order
self.sale_normal_delivery_charges.action_confirm()
# Create one more sale order with Free Delivery Charges
self.delivery_sale_order_cost = self.SaleOrder.create({
'partner_id': self.partner_4.id,
'partner_invoice_id': self.partner_address_13.id,
'partner_shipping_id': self.partner_address_13.id,
'pricelist_id': self.pricelist.id,
'order_line': [(0, 0, {
'name': 'Service on demand',
'product_id': self.product_consultant.id,
'product_uom_qty': 24,
'product_uom': self.product_uom_hour.id,
'price_unit': 75.00,
}), (0, 0, {
'name': 'On Site Assistance',
'product_id': self.product_2.id,
'product_uom_qty': 30,
'product_uom': self.product_uom_hour.id,
'price_unit': 38.25,
})],
'carrier_id': self.free_delivery.id
})
# I add free delivery cost in Sale order
self.delivery_sale_order_cost.delivery_set()
# I check sale order after adding delivery cost
line = self.SaleOrderLine.search([('order_id', '=', self.delivery_sale_order_cost.id),
('product_id', '=', self.delivery_sale_order_cost.carrier_id.product_id.id)])
self.assertEqual(len(line), 1, "Delivery cost is not Added")
self.assertEqual(float_compare(line.price_subtotal, 0, precision_digits=2), 0,
"Delivey cost is not correspond.")
# I set default delivery policy
self.default_delivery_policy = self.SaleConfigSetting.create({})
self.default_delivery_policy.execute()
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
from openerp.tests import common
class StockMoveInvoice(common.TransactionCase):
def setUp(self):
super(StockMoveInvoice, self).setUp()
self.ProductProduct = self.env['product.product']
self.SaleOrder = self.env['sale.order']
self.AccountJournal = self.env['account.journal']
self.partner_18 = self.env.ref('base.res_partner_18')
self.pricelist_id = self.env.ref('product.list0')
self.product_11 = self.env.ref('product.product_product_11')
self.product_icecream = self.env.ref('stock.product_icecream')
self.product_uom_kgm = self.env.ref('product.product_uom_kgm')
self.normal_delivery = self.env.ref('delivery.normal_delivery_carrier')
def test_01_delivery_stock_move(self):
# Test if the stored fields of stock moves are computed with invoice before delivery flow
# Set a weight on ipod 16GB
self.product_11.write({
'weight': 0.25,
})
self.sale_prepaid = self.SaleOrder.create({
'partner_id': self.partner_18.id,
'partner_invoice_id': self.partner_18.id,
'partner_shipping_id': self.partner_18.id,
'pricelist_id': self.pricelist_id.id,
'order_line': [(0, 0, {
'name': 'Ice Cream',
'product_id': self.product_icecream.id,
'product_uom_qty': 2,
'product_uom': self.product_uom_kgm.id,
'price_unit': 750.00,
})],
'carrier_id': self.normal_delivery.id
})
# I add delivery cost in Sale order
self.sale_prepaid.delivery_set()
# I confirm the SO.
self.sale_prepaid.action_confirm()
self.sale_prepaid.action_invoice_create()
# I check that the invoice was created
self.assertEqual(len(self.sale_prepaid.invoice_ids), 1, "Invoice not created.")
# I confirm the invoice
self.invoice = self.sale_prepaid.invoice_ids
self.invoice.signal_workflow('invoice_open')
# I pay the invoice.
self.invoice = self.sale_prepaid.invoice_ids
self.invoice.signal_workflow('invoice_open')
self.journal = self.AccountJournal.search([('type', '=', 'cash'), ('company_id', '=', self.sale_prepaid.company_id.id)], limit=1)
self.invoice.pay_and_reconcile(self.journal, self.invoice.amount_total)
# Check the SO after paying the invoice
self.assertNotEqual(self.sale_prepaid.invoice_count, 0, 'order not invoiced')
self.assertTrue(self.sale_prepaid.invoice_status == 'invoiced', 'order is not invoiced')
self.assertEqual(len(self.sale_prepaid.picking_ids), 1, 'pickings not generated')
# Check the stock moves
moves = self.sale_prepaid.picking_ids.move_lines
self.assertEqual(moves[0].product_qty, 2, 'wrong product_qty')
self.assertEqual(moves[0].weight, 2.0, 'wrong move weight')
# Ship
self.picking = self.sale_prepaid.picking_ids.action_done()
@@ -1,5 +1,5 @@
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Delivery Carriers -->
<menuitem id="menu_delivery" name="Delivery" parent="stock.menu_stock_config_settings" groups="stock.group_stock_manager" sequence="50"/>
<menuitem id="sale_menu_delivery" name="Delivery" parent="base.menu_sale_config" groups="base.group_sale_manager" sequence="12"/>
@@ -12,7 +12,7 @@
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="partner_id"/>
<field name="price" invisible="'order_id' not in context"/>
<field name="price"/>
</tree>
</field>
</record>
@@ -22,72 +22,59 @@
<field name="model">delivery.carrier</field>
<field name="arch" type="xml">
<form string="Carrier">
<header>
<field name="state" widget="statusbar" attrs="{'invisible':['|', ('delivery_type', '=', 'fixed'), ('delivery_type', '=', 'base_on_rule')]}" clickable="True"/>
</header>
<sheet>
<div class="oe_title" name="title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
</div>
<group>
<field name="active"/>
</group>
<group name="general">
<group string="General Information">
<field name="partner_id"/>
<field name="product_id"/>
<field name="delivery_type"/>
</group>
<group string="Pricing Information" name='pricing_info' attrs="{'invisible':[('delivery_type','!=','grid')]}">
<field name="normal_price" attrs="{'readonly':[('use_detailed_pricelist', '=', True)]}"/>
<label for="free_if_more_than"/>
<div>
<field name="free_if_more_than" attrs="{'readonly':[('use_detailed_pricelist', '=', True)]}"/>
<field name="amount" attrs="{'required':[('free_if_more_than','&lt;&gt;',False)], 'invisible':[('free_if_more_than','=',False)]}"/>
</div>
<field name="use_detailed_pricelist"/>
</group>
</group>
<field name="pricelist_ids" attrs="{'invisible':[('use_detailed_pricelist','=',False)]}" mode="tree">
<tree string="Delivery grids">
<field name="sequence"/>
<field name="name"/>
</tree>
<form string="Delivery grids">
<group>
<div class="oe_title" name="title">
<label for="name" string="Name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<group name="company">
<field name="partner_id" class="oe_inline"/>
<field name="product_type" invisible="1"/>
<field name="product_sale_ok" invisible="1"/>
</group>
</div>
<notebook>
<page string="Pricing">
<group>
<field name="name"/>
<field name="delivery_type" widget="radio"/>
</group>
<group>
<field name="active"/>
<field name="sequence"/>
</group>
</group>
<notebook>
<page string="Grid definition">
<field name="line_ids"/>
</page>
<page string="Destination">
<group attrs="{'invisible':[('delivery_type', '!=', 'fixed')]}">
<group>
<group>
<field name="country_ids" widget="many2many_tags"/>
<field name="state_ids" widget="many2many_tags"/>
</group>
<group>
<label for="zip_from" string="Zip"/>
<div>
<field name="zip_from" class="oe_inline"/>
-
<field name="zip_to" class="oe_inline"/>
</div>
</group>
<field name="fixed_price"/>
</group>
</page>
</notebook>
</form>
</field>
</sheet>
<group>
<div class="o_row">
<label for="free_if_more_than"/>
<field name="free_if_more_than"/>
<field nolabel="1" name="amount" attrs="{'required':[('free_if_more_than','!=', False)], 'invisible':[('free_if_more_than','=', False)]}"/>
</div>
</group>
</group>
<group name="general" attrs="{'invisible':[('delivery_type', '!=', 'base_on_rule')]}">
<field name="price_rule_ids" nolabel="1"/>
</group>
</page>
<page string="Destination">
<group>
<group>
<field name="country_ids" widget="many2many_tags"/>
<field name="state_ids" widget="many2many_tags"/>
</group>
<group></group>
<group>
<field name="zip_from"/>
</group>
<group>
<field name="zip_to"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
@@ -116,83 +103,20 @@
<menuitem action="action_delivery_carrier_form" id="menu_action_delivery_carrier_form" parent="menu_delivery" sequence="1"/>
<menuitem action="action_delivery_carrier_form" id="sale_menu_action_delivery_carrier_form" parent="sale_menu_delivery" sequence="1"/>
<!-- Delivery Grids -->
<record id="view_delivery_grid_tree" model="ir.ui.view">
<field name="name">delivery.grid.tree</field>
<field name="model">delivery.grid</field>
<record id="view_delivery_price_rule_form" model="ir.ui.view">
<field name="name">delivery.price.rule.form</field>
<field name="model">delivery.price.rule</field>
<field name="arch" type="xml">
<tree string="Delivery grids">
<field name="sequence"/>
<field name="carrier_id"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="view_delivery_grid_form" model="ir.ui.view">
<field name="name">delivery.grid.form</field>
<field name="model">delivery.grid</field>
<field name="arch" type="xml">
<form string="Delivery grids">
<group col="4">
<field name="name"/>
<field name="active"/>
<field name="carrier_id"/>
<field name="sequence"/>
</group>
<notebook>
<page string="Grid definition">
<field name="line_ids"/>
</page>
<page string="Destination">
<group string="Countries">
<field name="country_ids" nolabel="1"/>
</group>
<group string="States">
<field colspan="2" name="state_ids" nolabel="1"/>
<field name="zip_from"/>
<field name="zip_to"/>
</group>
</page>
</notebook>
</form>
</field>
</record>
<record id="action_delivery_grid_form" model="ir.actions.act_window">
<field name="name">Delivery Pricelist</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">delivery.grid</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a delivery price list for a specific region.
</p><p>
The delivery price list allows you to compute the cost and
sales price of the delivery according to the weight of the
products and other criteria. You can define several price lists
for each delivery method: per country or a zone in a specific
country defined by a postal code range.
</p>
</field>
</record>
<menuitem action="action_delivery_grid_form" id="menu_action_delivery_grid_form" parent="menu_delivery" sequence="2"/>
<menuitem action="action_delivery_grid_form" id="sale_menu_action_delivery_grid_form" parent="sale_menu_delivery" sequence="2"/>
<record id="view_delivery_grid_line_form" model="ir.ui.view">
<field name="name">delivery.grid.line.form</field>
<field name="model">delivery.grid.line</field>
<field name="arch" type="xml">
<form string="Grid Lines">
<form string="Price Rules">
<group>
<field name="name"/>
<field name="name" invisible="1"/>
</group>
<group>
<label for="type" string="Condition"/>
<div>
<field name="type" class="oe_inline"/>
<field name="operator" class="oe_inline"/>
<field name="max_value" class="oe_inline"/>
<label for="variable" string="Condition"/>
<div class="o_row">
<field name="variable"/>
<field name="operator"/>
<field name="max_value"/>
</div>
<label for="list_base_price" string="Sale Price"/>
<div>
@@ -202,23 +126,19 @@
*
<field name="variable_factor" class="oe_inline"/>
</div>
<field name="standard_price"/>
<field name="standard_price" invisible="1"/>
</group>
</form>
</field>
</record>
<record id="view_delivery_grid_line_tree" model="ir.ui.view">
<field name="name">delivery.grid.line.tree</field>
<field name="model">delivery.grid.line</field>
<record id="view_delivery_price_rule_tree" model="ir.ui.view">
<field name="name">delivery.price.rule.tree</field>
<field name="model">delivery.price.rule</field>
<field name="arch" type="xml">
<tree string="Grid Lines">
<tree string="Price Rules">
<field name="sequence" widget="handle" />
<field name="name"/>
<field name="type"/>
<field name="operator"/>
<field name="max_value" nolabel="1"/>
<field name="list_price"/>
<field name="standard_price"/>
<field name="list_base_price" string="Sale Price"/>
</tree>
</field>
</record>
@@ -238,7 +158,7 @@
<label for="carrier_tracking_ref"/>
<div>
<field name="carrier_tracking_ref" class="oe_inline"/>
<button type='object' class="fa fa-arrow-right oe_link" name="cancel_shipment" string="Cancel" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','=',False),('delivery_type','=','grid'),('delivery_type','=',False),('state','not in',('done'))]}"/>
<button type='object' class="fa fa-arrow-right oe_link" name="cancel_shipment" string="Cancel" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
</div>
<field name="carrier_price"/>
</group>
@@ -254,11 +174,11 @@
</xpath>
<xpath expr="//h1" position="before">
<div class="oe_right oe_button_box">
<button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking" attrs="{'invisible': ['|',('carrier_tracking_ref','=',False),('delivery_type','=','grid')]}" />
<button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking" attrs="{'invisible': ['|',('carrier_tracking_ref','=',False),('delivery_type','in', ['fixed', 'base_on_rule'])]}" />
</div>
</xpath>
<xpath expr="/form/header/button[last()]" position="after">
<button name="send_to_shipper" string="Send to Shipper" type="object" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','!=',False),('delivery_type','=','grid'),('delivery_type','=',False),('state','not in',('done'))]}"/>
<button name="send_to_shipper" string="Send to Shipper" type="object" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','!=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
</xpath>
</data>
</field>
@@ -327,7 +247,7 @@
<label for="carrier_tracking_ref"/>
<div>
<field name="carrier_tracking_ref" class="oe_inline"/>
<button type='object' class="fa fa-arrow-right oe_link" name="cancel_shipment" string="Cancel" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','=',False),('delivery_type','=','grid'),('delivery_type','=',False),('state','not in',('done'))]}"/>
<button type='object' class="fa fa-arrow-right oe_link" name="cancel_shipment" string="Cancel" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
</div>
<field name="carrier_price"/>
</group>
@@ -335,5 +255,4 @@
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,5 +1,4 @@
<openerp>
<data>
<odoo>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">res.partner.carrier.property.form.inherit</field>
@@ -7,10 +6,9 @@
<field name="inherit_id" ref="mail.view_emails_partner_info_form" />
<field name="arch" type="xml">
<field name="opt_out" position="after">
<field name="property_delivery_carrier"/>
<field name="property_delivery_carrier_id"/>
</field>
</field>
</record>
</data>
</openerp>
</odoo>
+2 -4
View File
@@ -1,5 +1,4 @@
<openerp>
<data>
<odoo>
<template id="report_shipping2" inherit_id="stock.report_picking">
<xpath expr="//div[@name='partner_header']" position="after">
<div name="invoice_partner" t-if="o.picking_type_id.code == 'outgoing' and o.sale_id and o.sale_id.partner_invoice_id">
@@ -21,5 +20,4 @@
</td>
</xpath>
</template>
</data>
</openerp>
</odoo>
@@ -333,18 +333,18 @@ $('.oe_website_sale').each(function () {
});
$(oe_website_sale).on('change', "select[name='country_id']", function () {
var $select = $("select[name='state_id']");
var $select = $("select[name='state_id']:enabled");
$select.find("option:not(:first)").hide();
var nb = $select.find("option[data-country_id="+($(this).val() || 0)+"]").show().size();
$select.parent().toggle(nb>1);
$select.parent().toggle(nb>=1);
});
$(oe_website_sale).find("select[name='country_id']").change();
$(oe_website_sale).on('change', "select[name='shipping_country_id']", function () {
var $select = $("select[name='shipping_state_id']");
var $select = $("select[name='shipping_state_id']:enabled");
$select.find("option:not(:first)").hide();
var nb = $select.find("option[data-country_id="+($(this).val() || 0)+"]").show().size();
$select.parent().toggle(nb>1);
$select.parent().toggle(nb>=1);
});
$(oe_website_sale).find("select[name='shipping_country_id']").change();
});
@@ -13,6 +13,7 @@ Delivery Costs
'views/website_sale_delivery.xml',
'views/website_sale_delivery_view.xml',
'security/ir.model.access.csv',
'demo/website_sale_delivery_data.xml'
],
'demo': [
'data/website_sale_delivery_demo.xml'
@@ -26,3 +26,7 @@ class website_sale(openerp.addons.website_sale.controllers.main.website_sale):
""" Transforms a list of order lines into a dict for google analytics """
order_lines_not_delivery = [line for line in order_lines if not line.is_delivery]
return super(website_sale, self).order_lines_2_google_api(order_lines_not_delivery)
def checkout_values(self, data=None):
values = super(website_sale, self).checkout_values(data)
return request.env['sale.order']._get_shipping_country(values)
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="delivery.free_delivery_carrier" model="delivery.carrier">
<field name="website_published" eval="True"/>
</record>
</odoo>
@@ -12,7 +12,7 @@ class delivery_carrier(orm.Model):
_inherit = ['delivery.carrier', 'website.published.mixin']
_columns = {
'website_description': fields.text('Description for Online Quotations'),
'website_description': fields.related('product_id', 'description_sale', type="text", string='Description for Online Quotations'),
}
_defaults = {
'website_published': False
@@ -91,8 +91,8 @@ class SaleOrder(orm.Model):
carrier_ids.insert(0, carrier_id)
if force_carrier_id or not carrier_id or not carrier_id in carrier_ids:
for delivery_id in carrier_ids:
grid_id = carrier_obj.grid_get(cr, SUPERUSER_ID, [delivery_id], order.partner_shipping_id.id)
if grid_id:
carrier = carrier_obj.verify_carrier(cr, SUPERUSER_ID, [delivery_id], order.partner_shipping_id)
if carrier:
carrier_id = delivery_id
break
order.write({'carrier_id': carrier_id})
@@ -157,3 +157,25 @@ class SaleOrder(orm.Model):
self._check_carrier_quotation(cr, uid, sale_order, context=context)
return values
def _get_shipping_country(self, cr, uid, values, context=None):
country_ids = set()
state_ids = set()
values['shipping_countries'] = values['countries']
values['shipping_states'] = values['states']
DeliveryCarrier = self.pool['delivery.carrier']
delivery_carrier_ids = DeliveryCarrier.search(cr, SUPERUSER_ID, [('website_published', '=', True)], context=context)
for carrier in DeliveryCarrier.browse(cr, SUPERUSER_ID, delivery_carrier_ids, context):
if not carrier.country_ids and not carrier.state_ids:
return values
# Authorized shipping countries
country_ids = country_ids|set(carrier.country_ids.ids)
# Authorized shipping countries without any state restriction
state_country_ids = [country.id for country in carrier.country_ids if country.id not in carrier.state_ids.mapped('country_id.id')]
# Authorized shipping states + all states from shipping countries without any state restriction
state_ids = state_ids|set(carrier.state_ids.ids)|set(values['states'].filtered(lambda r: r.country_id.id in state_country_ids).ids)
values['shipping_countries'] = values['countries'].filtered(lambda r: r.id in list(country_ids))
values['shipping_states'] = values['states'].filtered(lambda r: r.id in list(state_ids))
return values
@@ -7,4 +7,19 @@ $(document).ready(function () {
window.location.href = '/shop/payment?carrier_id=' + carrier_id;
});
$(".oe_website_sale select[name='shipping_id']").on('change', function () {
var value = $(this).val();
var $provider_free = $("select[name='country_id']:not(.o_provider_restricted), select[name='state_id']:not(.o_provider_restricted)");
var $provider_restricted = $("select[name='country_id'].o_provider_restricted, select[name='state_id'].o_provider_restricted");
if (value == 0) {
// Ship to the same address : only show shipping countries available for billing
$provider_free.hide().attr('disabled', true);
$provider_restricted.show().attr('disabled', false).change();
} else {
// Create a new address : show all countries available for billing
$provider_free.show().attr('disabled', false).change();
$provider_restricted.hide().attr('disabled', true);
}
});
});
@@ -51,5 +51,39 @@
</xpath>
</template>
<template id="country_state_shipping" name="Shipping Country and states" inherit_id="website_sale.checkout">
<!-- By default, with 'ship to the same address', we can only ship to restricted countries & states -->
<xpath expr="//select[@name='country_id']" position="attributes">
<attribute name="t-att-disabled">not shipping_id and 'disabled' or ''</attribute>
<attribute name="t-att-style">not shipping_id and 'display: none' or ''</attribute>
</xpath>
<xpath expr="//select[@name='country_id']" position="after">
<select name="country_id" class="form-control o_provider_restricted" t-att-style="shipping_id and 'display:none' or ''" t-att-disabled="shipping_id and 'disabled' or ''">
<option value="">Country...</option>
<t t-foreach="shipping_countries or []" t-as="country">
<option t-att-value="country.id" t-att-selected="country.id == checkout.get('country_id')"><t t-esc="country.name"/></option>
</t>
</select>
</xpath>
<xpath expr="//select[@name='state_id']" position="attributes">
<attribute name="t-att-disabled">not shipping_id and 'disabled' or ''</attribute>
<attribute name="t-att-style">not shipping_id and 'display: none' or ''</attribute>
</xpath>
<xpath expr="//select[@name='state_id']" position="after">
<select name="state_id" class="form-control o_provider_restricted" t-att-style="shipping_id and 'display:none' or ''" t-att-disabled="shipping_id and 'disabled' or ''">
<option value="">Select...</option>
<t t-foreach="shipping_states or []" t-as="state">
<option t-att-value="state.id" style="display:none;" t-att-data-country_id="state.country_id.id" t-att-selected="state.id == checkout.get('state_id')"><t t-esc="state.name"/></option>
</t>
</select>
</xpath>
<xpath expr="//select[@name='shipping_country_id']/t[@t-foreach]" position="attributes">
<attribute name="t-foreach">shipping_countries</attribute>
</xpath>
<xpath expr="//select[@name='shipping_state_id']/t[@t-foreach]" position="attributes">
<attribute name="t-foreach">shipping_states</attribute>
</xpath>
</template>
</data>
</openerp>
@@ -13,11 +13,11 @@
</button>
</div>
</div>
<group name="general" position="after">
<group name="website">
<field name="website_description" attrs="{'invisible':[('website_published', '=', False)]}"/>
<div name="title" position="after">
<group>
<field name="website_description" string="Description" placeholder="Description displayed on the eCommerce and on the Online Quotations."/>
</group>
</group>
</div>
</field>
</record>
</odoo>