diff --git a/delivery_fedex/__init__.py b/delivery_fedex/__init__.py new file mode 100755 index 00000000..d6948c82 --- /dev/null +++ b/delivery_fedex/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +# from . import controllers +from . import models diff --git a/delivery_fedex/__manifest__.py b/delivery_fedex/__manifest__.py new file mode 100755 index 00000000..b722622f --- /dev/null +++ b/delivery_fedex/__manifest__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +{ + 'name': "FedEx Integration", + + 'summary': """ + A module to integrate FedEx Delivery, Tracking, and Rates into Odoo """, + + # 'description': """ + # Long description of module's purpose + # """, + + 'sequence': -10, + + 'application': True, + + 'author': "Yousef Sheta", + 'website': "https://github.com/TrueYouface", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml + # for the full list + 'category': 'Delivery', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['base', 'delivery_integration_base'], + + # always loaded + 'data': [ + # 'security/ir.model.access.csv', + 'views/delivery_fedex_view.xml', + # 'views/stock_picking_views.xml', + ], + # only loaded in demonstration mode + # 'demo': [ + # 'demo/demo.xml', + # ], +} diff --git a/delivery_fedex/controllers/__init__.py b/delivery_fedex/controllers/__init__.py new file mode 100755 index 00000000..457bae27 --- /dev/null +++ b/delivery_fedex/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers \ No newline at end of file diff --git a/delivery_fedex/controllers/controllers.py b/delivery_fedex/controllers/controllers.py new file mode 100755 index 00000000..29c889a1 --- /dev/null +++ b/delivery_fedex/controllers/controllers.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# from odoo import http + + +# class ModuleTemplate1(http.Controller): +# @http.route('/module_template_1/module_template_1', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/module_template_1/module_template_1/objects', auth='public') +# def list(self, **kw): +# return http.request.render('module_template_1.listing', { +# 'root': '/module_template_1/module_template_1', +# 'objects': http.request.env['module_template_1.module_template_1'].search([]), +# }) + +# @http.route('/module_template_1/module_template_1/objects/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('module_template_1.object', { +# 'object': obj +# }) diff --git a/delivery_fedex/demo/demo.xml b/delivery_fedex/demo/demo.xml new file mode 100755 index 00000000..5f015912 --- /dev/null +++ b/delivery_fedex/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/delivery_fedex/models/__init__.py b/delivery_fedex/models/__init__.py new file mode 100755 index 00000000..bba1fbcf --- /dev/null +++ b/delivery_fedex/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +# from . import stock_picking +from . import deliver_carrier diff --git a/delivery_fedex/models/deliver_carrier.py b/delivery_fedex/models/deliver_carrier.py new file mode 100644 index 00000000..25f4ef35 --- /dev/null +++ b/delivery_fedex/models/deliver_carrier.py @@ -0,0 +1,505 @@ +# Copyright 2024 Yousef Sheta (https://github.com/TrueYouface) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from .fedex_request import FedexRequest +import requests +import json +import phonenumbers +from odoo.exceptions import ValidationError + + +class DeliveryCarrier(models.Model): + _inherit = "delivery.carrier" + + delivery_type = fields.Selection(selection_add=[("fedex", "Fedex")]) + + fedex_grant_type = fields.Selection( + string="Grant Type", + selection=[ + ("client_credentials", "client_credentials"), + ("csp_credentials", "csp_credentials"), + ("client_pc_credentials", "client_pc_credentials"), + ], + ) + fedex_client_id = fields.Char(string="Client ID") + fedex_client_secret = fields.Char(string="Secret ID") + fedex_account_number = fields.Char( + string="FedEx Account Number", + ) + # fedex_default_package_type_id = fields.Many2one( + # string="FedEx Package Type", + # comodel_name="stock.package.type", + # ) + fedex_developer_key = fields.Char(string="Developer Key") + fedex_developer_password = fields.Char(string="Password") + fedex_document_stock_type = fields.Selection( + string="Commercial Invoice Type", + selection=[ + ("PAPER_4X6", "PAPER_4X6"), + ("PAPER_4X6.75", "PAPER_4X6.75"), + ("PAPER_4X8", "PAPER_4X8"), + ("PAPER_4X9", "PAPER_4X9"), + ("PAPER_7X4.75", "PAPER_7X4.75"), + ("PAPER_8.5X11_BOTTOM_HALF_LABEL", "PAPER_8.5X11_BOTTOM_HALF_LABEL"), + ("PAPER_8.5X11_TOP_HALF_LABEL", "PAPER_8.5X11_TOP_HALF_LABEL"), + ("PAPER_LETTER", "PAPER_LETTER"), + ("STOCK_4X6", "STOCK_4X6"), + ("STOCK_4X6.75", "STOCK_4X6.75"), + ("STOCK_4X6.75_LEADING_DOC_TAB", "STOCK_4X6.75_LEADING_DOC_TAB"), + ("STOCK_4X6.75_TRAILING_DOC_TAB", "STOCK_4X6.75_TRAILING_DOC_TAB"), + ("STOCK_4X8", "STOCK_4X8"), + ("STOCK_4X9", "STOCK_4X9"), + ("STOCK_4X9_LEADING_DOC_TAB", "STOCK_4X9_LEADING_DOC_TAB"), + ("STOCK_4X9_TRAILING_DOC_TAB", "STOCK_4X9_TRAILING_DOC_TAB"), + ], + ) + fedex_dropoff_type = fields.Selection( + string="FedEx Drop-Off Type", + selection=[ + ("BUSINESS_SERVICE_CENTER", "BUSINESS_SERVICE_CENTER"), + ("DROP_BOX", "DROP_BOX"), + ("REGULAR_PICKUP", "REGULAR_PICKUP"), + ("REQUEST_COURIER", "REQUEST_COURIER"), + ("STATION", "STATION"), + ], + ) + # fedex_duty_payment = fields.Selection( + # string="Fedex Duty Payment", + # selection=[ + # ("SENDER", "Sender"), + # ("RECIPIENT", "Recipient"), + # ] + # ) + fedex_extra_data_rate_request = fields.Text( + string="Extra Data for Rate", + help="""The extra data in FedEx is organized like the inside of a json file. + This functionality is advanced/technical and should only be used if you know what you are doing. + + Example of valid value: ``` + "ShipmentDetails": {"Pieces": {"Piece": {"AdditionalInformation": "extra info"}}} + ``` + + With the above example, the AdditionalInformation of each piece will be updated. + More info on https://www.fedex.com/en-us/developer/web-services/process.html#documentation""", + ) + fedex_extra_data_Return_request = fields.Text( + string="Extra Data for Return", + help="""The extra data in FedEx is organized like the inside of a json file. + This functionality is advanced/technical and should only be used if you know what you are doing. + + Example of valid value: ``` + "ShipmentDetails": {"Pieces": {"Piece": {"AdditionalInformation": "extra info"}}} + ``` + + With the above example, the AdditionalInformation of each piece will be updated. + More info on https://www.fedex.com/en-us/developer/web-services/process.html#documentation""", + ) + fedex_extra_data_ship_request = fields.Text( + string="Extra Data for Ship", + help="""The extra data in FedEx is organized like the inside of a json file. + This functionality is advanced/technical and should only be used if you know what you are doing. + + Example of valid value: ``` + "ShipmentDetails": {"Pieces": {"Piece": {"AdditionalInformation": "extra info"}}} + ``` + + With the above example, the AdditionalInformation of each piece will be updated. + More info on https://www.fedex.com/en-us/developer/web-services/process.html#documentation""", + ) + fedex_label_file_type = fields.Selection( + string="FEDEX Label File Type", + selection=[ + ("PDF", "PDF"), + ("EPL2", "EPL2"), + ("PNG", "PNG"), + ("ZPLII", "ZPLII"), + ], + ) + fedex_label_stock_type = fields.Selection( + string="Label Type", + selection=[ + ("PAPER_4X6", "PAPER_4X6"), + ("PAPER_4X6.75", "PAPER_4X6.75"), + ("PAPER_4X8", "PAPER_4X8"), + ("PAPER_4X9", "PAPER_4X9"), + ("PAPER_7X4.75", "PAPER_7X4.75"), + ("PAPER_8.5X11_BOTTOM_HALF_LABEL", "PAPER_8.5X11_BOTTOM_HALF_LABEL"), + ("PAPER_8.5X11_TOP_HALF_LABEL", "PAPER_8.5X11_TOP_HALF_LABEL"), + ("PAPER_LETTER", "PAPER_LETTER"), + ("STOCK_4X6", "STOCK_4X6"), + ("STOCK_4X6.75", "STOCK_4X6.75"), + ("STOCK_4X6.75_LEADING_DOC_TAB", "STOCK_4X6.75_LEADING_DOC_TAB"), + ("STOCK_4X6.75_TRAILING_DOC_TAB", "STOCK_4X6.75_TRAILING_DOC_TAB"), + ("STOCK_4X8", "STOCK_4X8"), + ("STOCK_4X9", "STOCK_4X9"), + ("STOCK_4X9_LEADING_DOC_TAB", "STOCK_4X9_LEADING_DOC_TAB"), + ("STOCK_4X9_TRAILING_DOC_TAB", "STOCK_4X9_TRAILING_DOC_TAB"), + ], + ) + fedex_locations_radius_value = fields.Integer( + string="Fedex Locations Radius", help="Maximum locations distance radius." + ) + fedex_locations_radius_unit = fields.Many2one( + string="Fedex Locations Radius Unit", comodel_name="uom.uom" + ) + fedex_locations_radius_unit_name = fields.Char(string="Fedex Radius Unit Name") + fedex_meter_number = fields.Char(string="Meter Number") + fedex_saturday_delivery = fields.Boolean( + string="FedEx Saturday Delivery", + default=False, + help="""Special service:Saturday Delivery, can be requested on following days. + Thursday: + 1.FEDEX_2_DAY. + Friday: + 1.PRIORITY_OVERNIGHT. + 2.FIRST_OVERNIGHT. + 3.INTERNATIONAL_PRIORITY. + (To Select Countries)""", + ) + fedex_service_type = fields.Selection( + string="Fedex Service Type", + selection=[ + ("INTERNATIONAL_ECONOMY", "INTERNATIONAL_ECONOMY"), + ("INTERNATIONAL_PRIORITY", "INTERNATIONAL_PRIORITY"), + ("FEDEX_INTERNATIONAL_PRIORITY", "FEDEX_INTERNATIONAL_PRIORITY"), + ( + "FEDEX_INTERNATIONAL_PRIORITY_EXPRESS", + "FEDEX_INTERNATIONAL_PRIORITY_EXPRESS", + ), + ("FEDEX_GROUND", "FEDEX_GROUND"), + ("FEDEX_2_DAY", "FEDEX_2_DAY"), + ("FEDEX_2_DAY_AM", "FEDEX_2_DAY_AM"), + ("FEDEX_3_DAY_FREIGHT", "FEDEX_3_DAY_FREIGHT"), + ("FIRST_OVERNIGHT", "FIRST_OVERNIGHT"), + ("PRIORITY_OVERNIGHT", "PRIORITY_OVERNIGHT"), + ("STANDARD_OVERNIGHT", "STANDARD_OVERNIGHT"), + ("FEDEX_NEXT_DAY_EARLY_MORNING", "FEDEX_NEXT_DAY_EARLY_MORNING"), + ("FEDEX_NEXT_DAY_MID_MORNING", "FEDEX_NEXT_DAY_MID_MORNING"), + ("FEDEX_NEXT_DAY_AFTERNOON", "FEDEX_NEXT_DAY_AFTERNOON"), + ("FEDEX_NEXT_DAY_END_OF_DAY", "FEDEX_NEXT_DAY_END_OF_DAY"), + ("FEDEX_EXPRESS_SAVER", "FEDEX_EXPRESS_SAVER"), + ("FEDEX_REGIONAL_ECONOMY", "FEDEX_REGIONAL_ECONOMY"), + ("FEDEX_FIRST", "FEDEX_FIRST"), + ("FEDEX_PRIORITY_EXPRESS", "FEDEX_PRIORITY_EXPRESS"), + ("FEDEX_PRIORITY", "FEDEX_PRIORITY"), + ("FEDEX_PRIORITY_EXPRESS_FREIGHT", "FEDEX_PRIORITY_EXPRESS_FREIGHT"), + ("FEDEX_PRIORITY_FREIGHT", "FEDEX_PRIORITY_FREIGHT"), + ("FEDEX_ECONOMY_SELECT", "FEDEX_ECONOMY_SELECT"), + ("FEDEX_INTERNATIONAL_CONNECT_PLUS", "FEDEX_INTERNATIONAL_CONNECT_PLUS"), + ], + ) + fedex_use_locations = fields.Boolean( + string="Use Fedex Locations", + help="Allows the ecommerce user to choose a pick-up point as delivery address.", + ) + fedex_weight_unit = fields.Selection( + string="Fedex Weight Unit", + default="LB", + selection=[("KG", "KG"), ("LB", "LB")] + ) + fedex_label_format = fields.Selection( + string="Label Format", + selection=[("URL_ONLY", "URL_ONLY"), ("LABEL", "LABEL")], + required=True, + ) + fedex_pickup_type = fields.Selection( + string="Pick-up Type", + selection=[ + ("CONTACT_FEDEX_TO_SCHEDULE", "CONTACT_FEDEX_TO_SCHEDULE"), + ("DROPOFF_AT_FEDEX_LOCATION", "DROPOFF_AT_FEDEX_LOCATION"), + ("USE_SCHEDULED_PICKUP", "USE_SCHEDULED_PICKUP"), + ], + ) + fedex_package_type = fields.Selection( + string="Fedex Package Type", + selection=[ + ("YOUR_PACKAGING", "YOUR_PACKAGING"), + ("FEDEX_ENVELOPE", "FEDEX_ENVELOPE"), + ("FEDEX_BOX", "FEDEX_BOX"), + ("FEDEX_SMALL_BOX", "FEDEX_SMALL_BOX"), + ("FEDEX_MEDIUM_BOX", "FEDEX_MEDIUM_BOX"), + ("FEDEX_LARGE_BOX", "FEDEX_LARGE_BOX"), + ("FEDEX_EXTRA_LARGE_BOX", "FEDEX_EXTRA_LARGE_BOX"), + ("FEDEX_10KG_BOX", "FEDEX_10KG_BOX"), + ("FEDEX_25KG_BOX", "FEDEX_25KG_BOX"), + ("FEDEX_PAK", "FEDEX_PAK"), + ("FEDEX_TUBE", "FEDEX_TUBE"), + ], + ) + + def shorten_address(self, street, max_length=35): + # Step 1: Define common abbreviations + abbreviations = { + "Suite": "Ste", + "Avenue": "Ave", + "Boulevard": "Blvd", + "Street": "St", + "Drive": "Dr", + "Road": "Rd", + "Court": "Ct", + "Place": "Pl" + } + + # Step 2: Apply abbreviations + for long, short in abbreviations.items(): + street = street.replace(long, short) + + # Step 3: Trim to the maximum length + if len(street) > max_length: + street = street[:max_length].rstrip() # Remove trailing spaces + + return street + + def _fedex_payment_type(self): + if self.payment_type == 'sender_pays': + return "SENDER" + else: + return "RECIPIENT" + + def get_fedex_credentials(self): + url = "https://apis-sandbox.fedex.com/oauth/token" + payload = { + "grant_type": self.fedex_grant_type, + "client_id": self.fedex_client_id, + "client_secret": self.fedex_client_secret, + } + headers = {"Content-Type": "application/x-www-form-urlencoded"} + response = requests.post(url, data=payload, headers=headers) + print(response.text) + token = json.loads(response.text)["access_token"] + return token + + def get_shipment_weight(self, picking): + weight = picking.picking_total_weight + val = weight * 2.22 + return val + + def _fedex_phone_number(self, partner, field, priority="mobile"): + priority_field = getattr(partner, priority) + number = priority_field or (partner.phone or partner.mobile) + if number: + try: + parsed_number = phonenumbers.parse( + number, partner.country_id.code or None + ) + # Extract number and extension + return getattr(parsed_number, field) + except ValidationError as e: + raise ValidationError(_("Error parsing phone number: %s" % str(e))) + else: + raise ValidationError( + _( + "%s\nPartner's phone number is missing." + " It's a required field for dispatch." % partner.name + ) + ) + + def _prepare_fedex_shipping(self, picking): + vals = { + "accountNumber": { + "value": self.fedex_account_number + }, + "labelResponseOptions": self.fedex_label_format, + "requestedShipment": { + "shipper": { + "contact": { + "companyName": picking.company_id.partner_id.name, + "phoneNumber": self._fedex_phone_number( + picking.company_id.partner_id, "national_number" + ), + "emailAddress": picking.company_id.partner_id.email, + # "phoneExtension": self._fedex_phone_number( + # picking.company_id.partner_id, "extension" + # ) + }, + "address": { + "countryCode": picking.company_id.country_id.code, + "city": picking.company_id.partner_id.city, + "streetLines": [ + self.shorten_address(picking.company_id.partner_id.street) + ], + "postalCode": picking.company_id.zip + } + }, + "recipients": [ + { + "contact": { + "name": picking.partner_id.name, + "phoneNumber": self._fedex_phone_number( + picking.partner_id, "national_number" + ), + "emailAddress": picking.partner_id.email, + # "phoneExtension": self._fedex_phone_number( + # picking.partner_id, "extension" + # ) + }, + "address": { + "countryCode": picking.partner_id.country_id.code, + "city": picking.partner_id.city, + "streetLines": [ + self.shorten_address(picking.partner_id.street) + ], + "postalCode": picking.partner_id.zip + } + } + ], + "pickupType": self.fedex_pickup_type, + "serviceType": self.fedex_service_type, + "packagingType": self.fedex_package_type, + # "totalWeight": self.get_shipment_weight(picking), + "shippingChargesPayment": { + "paymentType": self._fedex_payment_type() + }, + "labelSpecification": { + "imageType": self.fedex_label_file_type, + "labelStockType": self.fedex_label_stock_type + }, + "customsClearanceDetail": { + "dutiesPayment": { + "paymentType": self._fedex_payment_type() + }, + "commodities": [ + { + "description": picking.note, + "countryOfManufacture": picking.company_id.country_id.code, + "quantity": picking.number_of_packages, + "quantityUnits": "PCS", + "unitPrice": { + "amount": 100, + "currency": "USD" + }, + "customsValue": { + "amount": picking.carrier_price, + "currency": picking.shipping_currency_id + }, + "weight": { + "units": self.fedex_weight_unit, + "value": picking.weight + } + } + ] + }, + "requestedPackageLineItems": [ + { + "weight": { + "units": self.fedex_weight_unit, + "value": picking.weight + } + } + ] + } + } + vals_test = { + "labelResponseOptions": "LABEL", + "requestedShipment": { + "shipper": { + "contact": { + "phoneNumber": 1234567890, + "companyName": "Shipper Company Name", + "emailAddress": "sample@company.com", + "phoneExtension": "90" + }, + "address": { + "streetLines": [ + "SHIPPER STREET LINE 1" + ], + "city": "Ankara", + "postalCode": "06935", + "countryCode": "TR" + } + }, + "recipients": [ + { + "contact": { + "phoneNumber": 1234567890, + "companyName": "Recipient Company Name", + "emailAddress":"sample@company.com", + "phoneExtension":"966" + }, + "address": { + "streetLines": [ + "RECIPIENT STREET LINE 1", + "RECIPIENT STREET LINE 2", + "RECIPIENT STREET LINE 3" + ], + "city": "Riyadh", + "stateOrProvinceCode": "BC", + "postalCode": "06800", + "countryCode": "SA" + } + } + ], + "serviceType": "INTERNATIONAL_ECONOMY", + "packagingType": "FEDEX_BOX", + "pickupType": "DROPOFF_AT_FEDEX_LOCATION", + "shippingChargesPayment": { + "paymentType": "SENDER" + }, + "labelSpecification": { + "imageType": "PDF", + "labelStockType": "STOCK_4X6" + }, + "customsClearanceDetail": { + "dutiesPayment": { + "paymentType": "SENDER" + }, + "commodities": [ + { + "description": "Commodity description", + "countryOfManufacture": "US", + "quantity": 3, + "quantityUnits": "PCS", + "unitPrice": { + "amount": 100, + "currency": "USD" + }, + "customsValue": { + "amount": 300, + "currency": "USD" + }, + "weight": { + "units": "LB", + "value": 20 + } + } + ] + }, + "requestedPackageLineItems": [ + { + "weight": { + "value": 20, + "units": "LB" + } + } + ] + }, + "accountNumber": { + "value": "740561073" + } + } + return vals + + def fedex_send_shipping(self, pickings): + url = "https://apis-sandbox.fedex.com/ship/v1/shipments" + headers = { + "Content-Type": "application/json", + "X-locale": "en_US", + "Authorization": "Bearer " + self.get_fedex_credentials(), + } + result = [] + for picking in pickings: + payload = self._prepare_fedex_shipping( + picking + ) + response = requests.post(url, data=payload, headers=headers) + response_data = response.json() + if response_data["errors"]: + print(response_data) + break + else: + # Extract the masterTrackingNumber + master_tracking_number = response_data["output"] + print(master_tracking_number) + return True diff --git a/delivery_fedex/models/fedex_request.py b/delivery_fedex/models/fedex_request.py new file mode 100644 index 00000000..6d4124dc --- /dev/null +++ b/delivery_fedex/models/fedex_request.py @@ -0,0 +1,29 @@ +# Copyright 2024 Yousef Sheta (https://github.com/TrueYouface) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models, fields, api +import requests, json + +# logger = logging.getLogger(__name__) + +FEDEX_API_URL = { + "test": "https://apis-sandbox.fedex.com", + "prod": "https://eschenker.dbschenker.com", +} + + +class FedexRequest: + + def __init__(self): + + return + + + + def _shipping_api_credentials(self): + credentials = {"applicationArea": {"accessKey": self.access_key}} + if self.user: + credentials["applicationArea"]["userId"] = self.user + if self.group_id: + credentials["applicationArea"]["groupId"] = self.group_id + return credentials \ No newline at end of file diff --git a/delivery_fedex/models/stock_picking.py b/delivery_fedex/models/stock_picking.py new file mode 100644 index 00000000..db2c97bf --- /dev/null +++ b/delivery_fedex/models/stock_picking.py @@ -0,0 +1,14 @@ +# Copyright 2021 Studio73 - Ethan Hildick +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, models, fields + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + fedex_tracking_number = fields.Char( + string='Tracking Number', + readonly=True + ) + diff --git a/delivery_fedex/security/ir.model.access.csv b/delivery_fedex/security/ir.model.access.csv new file mode 100755 index 00000000..86a14e8f --- /dev/null +++ b/delivery_fedex/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_module_template_1_module_template_1,module_template_1.module_template_1,model_module_template_1_module_template_1,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/delivery_fedex/views/delivery_fedex_view.xml b/delivery_fedex/views/delivery_fedex_view.xml new file mode 100755 index 00000000..9f585410 --- /dev/null +++ b/delivery_fedex/views/delivery_fedex_view.xml @@ -0,0 +1,104 @@ + + + + + + delivery.carrier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/delivery_fedex/views/stock_picking_views.xml b/delivery_fedex/views/stock_picking_views.xml new file mode 100644 index 00000000..c5ddbd7d --- /dev/null +++ b/delivery_fedex/views/stock_picking_views.xml @@ -0,0 +1,26 @@ + + + + fedex.view.picking.form + stock.picking + + + +