[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:
committed by
David Monjoie
parent
bb1200adc7
commit
a223111b92
@@ -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
|
||||
|
||||
@@ -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 <= 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 > 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">>=</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 >= 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">>=</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>
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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."),
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,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),
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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','<>',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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user