Browse Source

Add new model for shipment pick-ups

Using this model, the user can select a number of pickings and
request them to be picked up from a user-specified address.
Harald Welte 9 months ago
  1. 4
  2. 2
  3. 21
  4. 75
  5. 2
  6. 63


@ -3,13 +3,15 @@
'category': 'Website/Shipping Logistics',
'summary': 'Integrate shipping via directly within Odoo',
'website': '',
'version': '0.1',
'version': '0.2',
'author': 'Harald Welte',
'depends': ['odoo_shipping_service_apps', 'shipment_packaging'],
'data': [


@ -1 +1 @@
import res_config, shipcloud, shipcloud_delivery_carrier, shipcloud_shipping_service
import res_config, shipcloud, shipcloud_delivery_carrier, shipcloud_shipping_service, shipcloud_pickup


@ -115,6 +115,12 @@ class api(object):
res = self._transport.rest_delete('/shipments/%s' % shipment_id, None, sandbox)
return res
def create_pickup(self, pickup):
# Assume if the user passed a sandbox API key, we use it for create shipment requests
sandbox = True if self._transport._auth_sandbox else False
res = self._transport.rest_post('/pickup_requests', pickup, sandbox)
return res
def gen_customs_item(origin, desc, hts, qty, value, net_weight):
"""Generate a dict for a customs_declaration.item in accordance with
@ -218,3 +224,18 @@ def gen_quote_req(shipment):
sh['package'] = _filter_dict(sh['package'], permitted_pkg_keys)
return _filter_dict(sh, permitted_sh_keys)
def gen_pickup(addr, earliest, latest, shipment_ids, carrier='ups'):
"""Generate a dict for a pickup request in accordance with"""
permitted_addr_keys = [ 'company', 'first_name', 'last_name', 'care_of',
'street', 'street_no', 'city', 'zip_code', 'state', 'country', 'phone' ]
pickup = {
'carrier': carrier,
'pickup_time': {
'earliest': earliest,
'latest': latest,
'pickup_address': _filter_dict(addr, permitted_addr_keys)
return pickup


@ -0,0 +1,75 @@
from openerp import api, fields, models
import logging
from openerp.exceptions import Warning
from shipcloud_delivery_carrier import build_sc_addr
import shipcloud
_logger = logging.getLogger(__name__)
class SCPickup(models.Model):
_name = "delivery.carrier.pickup"
# carrier who should be picking up
carrier = fields.Selection([('ups','UPS'), ('dpd','DPD'), ('hermes','Hermes'), ('gls','GLS')],
string='Carrier', required=True, default='ups')
# pickings (shipments) to be picked up
pickings = fields.Many2many('stock.picking', string='Pickings', required=True)
# address from where to pick up
address = fields.Many2one('res.partner', string='Pick-up address', required=True)
# earliest pick-up time
earliest = fields.Datetime('Earliest', required=True,
# latest pick-up time
latest = fields.Datetime('Latest', required=True)
# draft: not yet requested from shipcloud; confirmed: confirmed by shipcloud
state = fields.Selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done','Done')],
string='Status', default='draft', readonly=True, required=True, copy=False)
sc_pickup_id = fields.Char('Shipcloud Pick-up ID', readonly=True, copy=False)
def _shipcloud_api(self):
config = self._get_config()
api_key = config['sc_api_key_prod']
sandbox_api_key = None if config['sc_api_use_prod'] else config['sc_api_key_sandbox']
return shipcloud.api(api_key, sandbox_api_key)
def button_request(self):
# consistency check
for p in self.pickings:
if p.carrier_id.delivery_type != 'sc':
raise Warning("Picking %s doesn't use shipcloud" % (
if not p.label_genrated:
raise Warning("Picking %s doesn't have a label yet" % (
if not p.sc_shipment_id:
raise Warning("Picking %s has not shipcloud label/id" % (
# collect the SC shipment IDs of all the pickings
sc_ship_ids = []
for p in self.pickings:
sc_ship_ids.append({'id': p.sc_shipment_id})
sc_addr = build_sc_addr(p.address)
sc_earliest = self.earliest.astimezone().isoformat()"earliest: %s -> %s" % (self.earliest, sc_earliest))
sc_latest = self.latest.astimezone().isoformat()
sc_pickup = shipcloud.gen_pickup(sc_addr, sc_earliest, sc_latest, sc_ship_ids, self.carrier)
# actually use the SC API to request the pickup
api = self._shipcloud_api()
result = api.create_pickup(sc_pickup)
except shipcloud.ApiError as err:
raise Warning(err)
self.sc_pickup_id = result['id']
self.state = 'confirmed'
# somehow this didn't work as a new (v8) style ORM ?!?
def default_get(self, cr, user, fields_list, context=None):
if context is None:
context = {}
res = super(SCPickup, self).default_get(cr, user, fields_list, context=context)
if context.get('active_model') == 'stock.picking' and context.get('active_ids'):
picking_ids = context['active_ids']
res['pickings'] = picking_ids
# intentionally no default pick-up address to avoid the mistakes of
# leaving the default and hence scheduling a pick-up from the wrong address
return res


@ -0,0 +1,2 @@
delivery_carrier_pickup,delivery.carrier.pickup all,odoo_shipcloud.model_delivery_carrier_pickup,stock.group_stock_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 delivery_carrier_pickup delivery.carrier.pickup all odoo_shipcloud.model_delivery_carrier_pickup stock.group_stock_user 1 1 1 0


@ -0,0 +1,63 @@
<?xml version="1.0"?>
<record id="view_pickup_form" model="ir.ui.view">
<field name="name">delivery.carrier.pickup.form</field>
<field name="model">delivery.carrier.pickup</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Pick-Up">
<button string="Request pick-up" name="button_request" states="draft" type="object" class="oe_highlight" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,done"/>
<div class="oe_title">
<h1>Pick-up request</h1>
<group name="main_field_group">
<field name="carrier"/>
<field name="address"/>
<field name="earliest"/>
<field name="latest"/>
<group name="pickings_group">
<field name="pickings"/>
<group name="misc">
<field name="sc_pickup_id"/>
<record id="delivery_carrier_pickup_tree" model="ir.ui.view">
<field name="name">delivery.carrier.pickup.tree</field>
<field name="model">delivery.carrier.pickup</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="pickup">
<field name="carrier"/>
<field name="earliest"/>
<field name="latest"/>
<field name="address"/>
<field name="state"/>
<record model="ir.actions.act_window" id="action_delivery_carrier_pickup">
<field name="name">Pick-Up</field>
<field name="res_model">delivery.carrier.pickup</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="delivery_carrier_pickup_tree"/>
<menuitem id="menu_shipping_pickup" parent="stock.menu_stock_warehouse_mgmt" string="Pick-Up" action="action_delivery_carrier_pickup"/>
<act_window id="action_create_pickup" res_model="delivery.carrier.pickup" src_model="stock.picking" target="new" multi="False" key2="client_action_multi" view_mode="form" name="Schedule Pick-Up"/>