forked from Mapan/odoo17e
299 lines
14 KiB
Python
299 lines
14 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
import logging
|
|
import requests
|
|
|
|
from datetime import timedelta
|
|
|
|
from odoo import fields, models, _
|
|
from odoo.exceptions import ValidationError
|
|
|
|
from .shiprocket_request import ShipRocket
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DeliverCarrier(models.Model):
|
|
_inherit = 'delivery.carrier'
|
|
|
|
delivery_type = fields.Selection(
|
|
selection_add=[('shiprocket', 'Shiprocket')],
|
|
ondelete={'shiprocket': lambda recs: recs.write({'delivery_type': 'fixed', 'fixed_price': 0})}
|
|
)
|
|
shiprocket_email = fields.Char(string="Shiprocket Email",
|
|
help="Enter your Username from Shiprocket account (API).")
|
|
shiprocket_password = fields.Char(string="Shiprocket Password",
|
|
help="Enter your Password from Shiprocket account (API).")
|
|
shiprocket_access_token = fields.Text(
|
|
string="Shiprocket Access Token",
|
|
help="Generate access token using Shiprocket credentials", copy=False
|
|
)
|
|
shiprocket_token_valid_upto = fields.Datetime(
|
|
string="Token Expiry", copy=False,
|
|
help="Shiprocket token expires in 10 days. Token will be auto generate based on this token expiry date."
|
|
)
|
|
shiprocket_channel_id = fields.Many2one(
|
|
'shiprocket.channel',
|
|
string="Shiprocket Channel",
|
|
domain="[('shiprocket_email', '=', shiprocket_email)]",
|
|
help="Get all the integrated channels from your Shiprocket account."
|
|
"This channel id is used to select or specify a custom channel at the time of Shiprocket order creation."
|
|
)
|
|
shiprocket_courier_ids = fields.Many2many(
|
|
'shiprocket.courier',
|
|
string="Shiprocket Couriers", copy=False,
|
|
domain="[('shiprocket_email', '=', shiprocket_email)]",
|
|
help="Get all the integrated Couriers from your Shiprocket account."
|
|
"Based on the courier selections the rate will be fetched from the Shiprocket."
|
|
)
|
|
shiprocket_default_package_type_id = fields.Many2one(
|
|
"stock.package.type",
|
|
string="Package Type",
|
|
help="Shiprocket requires package dimensions for getting accurate rate, "
|
|
"you can define these in a package type that you set as default"
|
|
)
|
|
shiprocket_payment_method = fields.Selection(
|
|
[('prepaid', 'Prepaid'), ('cod', 'COD')],
|
|
default="prepaid",
|
|
string="Payment Method",
|
|
help="The method of payment. Can be either COD (Cash on delivery) Or Prepaid while creating Shiprocket order."
|
|
)
|
|
shiprocket_manifests_generate = fields.Boolean(
|
|
string="Generate Manifest",
|
|
help="A manifest is a document that is required by some carriers to streamline the pickup process."
|
|
"particularly when shipping out a high-volume of ecommerce orders."
|
|
)
|
|
shiprocket_pickup_request = fields.Boolean(
|
|
string="Pickup Request", default=True,
|
|
help="Create a pickup request for your order shipment using Validate button of the Delivery Order."
|
|
)
|
|
|
|
def action_shiprocket_test_connection(self):
|
|
"""
|
|
Test connection by generate access token from shiprocket email and password.
|
|
"""
|
|
self.ensure_one()
|
|
if self.delivery_type == 'shiprocket':
|
|
sr = ShipRocket(self, self.log_xml)
|
|
response_json = sr._authorize_generate_token()
|
|
if response_json.get('token'):
|
|
self.write({
|
|
'shiprocket_access_token': response_json['token'],
|
|
'shiprocket_token_valid_upto': fields.datetime.now() + timedelta(days=9)
|
|
})
|
|
message_type = 'success'
|
|
message = _("Access token is generated successfully!")
|
|
else:
|
|
if response_json.get('message'):
|
|
error_message = response_json['message']
|
|
else:
|
|
error_message = _("Authentication failed! Please check your credentials.")
|
|
message_type = 'danger'
|
|
message = error_message
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'title': _("Shiprocket Notification"),
|
|
'type': message_type,
|
|
'message': message,
|
|
'next': {'type': 'ir.actions.act_window_close'},
|
|
}
|
|
}
|
|
|
|
def action_get_channels(self):
|
|
"""
|
|
Fetch the dictionary of channel(s) configured by the customer on its
|
|
shiprocket account and create shiprocket channel record(s) in odoo.
|
|
"""
|
|
for carrier in self:
|
|
if carrier.delivery_type != 'shiprocket':
|
|
continue
|
|
sr = ShipRocket(carrier, self.log_xml)
|
|
channels = sr._fetch_shiprocket_channels()
|
|
if not channels:
|
|
raise ValidationError(_('Failed to fetch Shiprocket Channel(s), Please try again later.'))
|
|
# multiple shipping method(s) can use same channels
|
|
current_channels = self.env['shiprocket.channel'].search([('shiprocket_email', '=', carrier.shiprocket_email)])
|
|
new_channel_vals = []
|
|
for name, code in channels.items():
|
|
existing_channels = current_channels.filtered(lambda c: c.channel_code == code)
|
|
# remove channel if already exists
|
|
current_channels -= existing_channels
|
|
if not existing_channels:
|
|
new_channel_vals.append({
|
|
'name': name,
|
|
'channel_code': code,
|
|
'shiprocket_email': carrier.shiprocket_email
|
|
})
|
|
if new_channel_vals:
|
|
self.env['shiprocket.channel'].create(new_channel_vals)
|
|
# delete channel(s) if not exists anymore
|
|
current_channels.unlink()
|
|
|
|
def action_get_couriers(self):
|
|
"""
|
|
Fetch shiprocket carriers from shiprocket account.
|
|
create record(s) of shiprocket courier(s) in odoo.
|
|
"""
|
|
for carrier in self:
|
|
if carrier.delivery_type != 'shiprocket':
|
|
continue
|
|
sr = ShipRocket(carrier, self.log_xml)
|
|
couriers_list = sr._fetch_shiprocket_couriers()
|
|
if not couriers_list:
|
|
raise ValidationError(_('Failed to fetch Shiprocket Couriers(s), Please try again later.'))
|
|
# multiple shipping method(s) can use same couriers
|
|
current_couriers = self.env['shiprocket.courier'].search([('shiprocket_email', '=', carrier.shiprocket_email)])
|
|
new_courier_vals = []
|
|
for courier in couriers_list:
|
|
existing_couriers = current_couriers.filtered(lambda c: c.courier_code == courier.get('id'))
|
|
# remove courier if already exists
|
|
current_couriers -= existing_couriers
|
|
if not existing_couriers:
|
|
new_courier_vals.append({
|
|
'name': courier.get('name'),
|
|
'courier_code': courier.get('id'),
|
|
'shiprocket_email': carrier.shiprocket_email
|
|
})
|
|
if new_courier_vals:
|
|
self.env['shiprocket.courier'].create(new_courier_vals)
|
|
# delete courier(s) if not exists anymore
|
|
current_couriers.unlink()
|
|
|
|
def _shiprocket_convert_weight(self, weight):
|
|
"""
|
|
Returns the weight in KG for a shiprocket order.
|
|
"""
|
|
self.ensure_one()
|
|
weight_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
|
|
return weight_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_kgm'), round=False)
|
|
|
|
def _shiprocket_converted_amount(self, order, price_inr):
|
|
"""
|
|
Returns the converted amount from the INR amount based on order's currency.
|
|
"""
|
|
return self.env.ref('base.INR')._convert(
|
|
price_inr,
|
|
order.currency_id,
|
|
order.company_id,
|
|
fields.Date.context_today(self)
|
|
)
|
|
|
|
def shiprocket_rate_shipment(self, order):
|
|
"""
|
|
Returns shipping rate for the order and chosen shipping method.
|
|
"""
|
|
sr = ShipRocket(self, self.log_xml)
|
|
result = sr._rate_request(
|
|
order.partner_shipping_id,
|
|
order.warehouse_id.partner_id or order.warehouse_id.company_id.partner_id,
|
|
order
|
|
)
|
|
if result.get('error_found'):
|
|
return {'success': False, 'price': 0.0, 'error_message': result['error_found'], 'warning_message': False}
|
|
price = float(result.get('price'))
|
|
if order.currency_id.id != self.env.ref('base.INR').id:
|
|
price = self._shiprocket_converted_amount(order, price)
|
|
return {
|
|
'success': True,
|
|
'price': price,
|
|
'error_message': False,
|
|
'warning_message': result.get('warning_message')
|
|
}
|
|
|
|
def shiprocket_send_shipping(self, pickings):
|
|
"""
|
|
Send shipment to shiprocket. Once the shiprocket order is
|
|
generated, it will post the message(s) with tracking link,
|
|
shipping label pdf and manifest pdf.
|
|
"""
|
|
sr = ShipRocket(self, self.log_xml)
|
|
def _get_document_data(url):
|
|
""" Returns the document content for label and manifest. """
|
|
try:
|
|
document_response = requests.get(url, timeout=30)
|
|
document_response.raise_for_status()
|
|
_logger.info('Document downloaded successfully from %s', url)
|
|
return document_response.content
|
|
except requests.exceptions.HTTPError as e:
|
|
_logger.warning('Document download failed from %s - %s', url, e)
|
|
except requests.exceptions.ConnectionError as e:
|
|
_logger.warning('Connection error while downloading %s - %s', url, e)
|
|
res = []
|
|
for picking in pickings:
|
|
shippings = sr._send_shipping(picking)
|
|
picking.shiprocket_orders = " + ".join(shippings.get('order_ids'))
|
|
res.append({
|
|
'tracking_number': " + ".join(shippings.get('tracking_numbers')),
|
|
'exact_price': shippings.get('exact_price')
|
|
})
|
|
for pack in shippings['all_pack'].values():
|
|
response = pack.get('response')
|
|
courier_name = response.get('courier_name')
|
|
carrier_tracking_ref = response.get('awb_code')
|
|
if response.get('warning_message'):
|
|
picking.message_post(body='%s' % (response['warning_message']))
|
|
if response.get('label_url'):
|
|
label_data = _get_document_data(response['label_url'])
|
|
attachments = [("%s-%s.pdf" % (courier_name, carrier_tracking_ref), label_data)]
|
|
log_message = _("Label generated of %s with Tracking Number: %s",
|
|
courier_name, carrier_tracking_ref)
|
|
picking.message_post(body=log_message, attachments=attachments)
|
|
# if shiprocket_pickup_request is enable then only shiprocket generate manifest(s).
|
|
if self.shiprocket_manifests_generate and response.get('manifest_url'):
|
|
manifest_data = _get_document_data(response['manifest_url'])
|
|
attachments = [("Manifest - %s-%s.pdf" % (courier_name, carrier_tracking_ref), manifest_data)]
|
|
log_message = _("Manifest generated of %s", courier_name)
|
|
picking.message_post(body=log_message, attachments=attachments)
|
|
# when carrier is in test mode, need to cancel shiprocket order.
|
|
if not self.prod_environment:
|
|
# Need carrier_tracking_ref to cancel shipment
|
|
picking.carrier_tracking_ref = " + ".join(shippings.get('tracking_numbers'))
|
|
self.shiprocket_cancel_shipment(picking)
|
|
return res
|
|
|
|
def shiprocket_get_tracking_link(self, picking):
|
|
"""
|
|
Returns the tracking links for a picking.
|
|
Shiprocket returns one tracking link for one package.
|
|
"""
|
|
tracking_urls = []
|
|
tracking_numbers = picking.carrier_tracking_ref and picking.carrier_tracking_ref.split(' + ') or []
|
|
for tracking_number in tracking_numbers:
|
|
track_url = (str(tracking_number), "https://shiprocket.co/tracking/%s" % (tracking_number))
|
|
tracking_urls.append(track_url)
|
|
return len(tracking_urls) == 1 and tracking_urls[0][1] or json.dumps(tracking_urls)
|
|
|
|
def shiprocket_cancel_shipment(self, picking):
|
|
"""
|
|
Cancel shipment using shiprocket requests.
|
|
To Refunds for canceled shipment(s) or order(s) will be promptly processed:
|
|
- Cancel the Shiprocket order(s), if pickup request enable.
|
|
- Cancel the Shiprocket shipment(s), if pickup request disable.
|
|
post message if order is already canceled.
|
|
"""
|
|
sr = ShipRocket(self, self.log_xml)
|
|
pickup_request = picking.carrier_id.shiprocket_pickup_request
|
|
shiprocket_orders = []
|
|
if pickup_request:
|
|
if not picking.shiprocket_orders:
|
|
picking.message_post(body=_('Shiprocket order(s) not found to cancel the shipment!'))
|
|
else:
|
|
shiprocket_orders = picking.shiprocket_orders.split(' + ')
|
|
elif not picking.carrier_tracking_ref:
|
|
picking.message_post(body=_('AWB number(s) not found to cancel the shipment!'))
|
|
else:
|
|
shiprocket_orders = picking.carrier_tracking_ref.split(' + ')
|
|
if shiprocket_orders:
|
|
cancel_order = sr._send_cancelling(shiprocket_orders, pickup_request=pickup_request)
|
|
for order, response in cancel_order.items():
|
|
if response.get('status') == 200 or response.get('message'):
|
|
msg = 'Order #' if pickup_request else 'AWB #'
|
|
msg += order + ' - ' + response.get('message') or _('Order canceled successfully!')
|
|
picking.message_post(body=msg)
|
|
# To avoid the duplicates values in Tracking Reference
|
|
if not self.prod_environment:
|
|
picking.carrier_tracking_ref = ''
|