diff --git a/modules/web_shop_shopify/CHANGELOG b/modules/web_shop_shopify/CHANGELOG
index 1791fb7e29..b8fe8143a5 100644
--- a/modules/web_shop_shopify/CHANGELOG
+++ b/modules/web_shop_shopify/CHANGELOG
@@ -1,3 +1,4 @@
+* Add carrier selection
* Use GraphQL API
* Add option to notify customer about fulfillment
* Support taxes from shipping product
diff --git a/modules/web_shop_shopify/carrier.py b/modules/web_shop_shopify/carrier.py
new file mode 100644
index 0000000000..dca9cdbddd
--- /dev/null
+++ b/modules/web_shop_shopify/carrier.py
@@ -0,0 +1,59 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import re
+
+from trytond.model import MatchMixin, ModelSQL, ModelView, fields
+from trytond.pool import PoolMeta
+
+
+class Carrier(metaclass=PoolMeta):
+ __name__ = 'carrier'
+
+ shopify_selections = fields.One2Many(
+ 'carrier.selection.shopify', 'carrier', "Shopify Selections",
+ help="Define the criteria that will match this carrier "
+ "with the Shopify shipping methods.")
+
+ def shopify_match(self, shop, shipping_line, pattern=None):
+ pattern = pattern.copy() if pattern is not None else {}
+ pattern.setdefault('shop', shop.id)
+ pattern.setdefault('code', shipping_line.get('code'))
+ pattern.setdefault('title', shipping_line.get('title'))
+ for selection in self.shopify_selections:
+ if selection.match(pattern):
+ return True
+ return False
+
+
+class SelectionShopify(MatchMixin, ModelSQL, ModelView):
+ __name__ = 'carrier.selection.shopify'
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.__access__.add('carrier')
+
+ carrier = fields.Many2One(
+ 'carrier', "Carrier", required=True, ondelete='CASCADE')
+ shop = fields.Many2One(
+ 'web.shop', "Shop",
+ domain=[
+ ('type', '=', 'shopify'),
+ ])
+ code = fields.Char(
+ "Code",
+ help="The code of the shipping line.")
+ title = fields.Char(
+ "Title",
+ help="A regular expression to match the shipping line title.\n"
+ "Leave empty to allow any title.")
+
+ def match(self, pattern, match_none=False):
+ if 'title' in pattern:
+ pattern = pattern.copy()
+ title = pattern.pop('title') or ''
+ if (self.title is not None
+ and not re.search(self.title, title, flags=re.I)):
+ return False
+ return super().match(pattern, match_none=match_none)
diff --git a/modules/web_shop_shopify/carrier.xml b/modules/web_shop_shopify/carrier.xml
new file mode 100644
index 0000000000..21119ec934
--- /dev/null
+++ b/modules/web_shop_shopify/carrier.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ carrier
+
+ carrier_form
+
+
+
+ carrier.selection.shopify
+ form
+ carrier_selection_shopify_form
+
+
+ carrier.selection.shopify
+ tree
+ carrier_selection_shopify_list
+
+
+
diff --git a/modules/web_shop_shopify/doc/design.rst b/modules/web_shop_shopify/doc/design.rst
index 06218fc64f..20f2f6712d 100644
--- a/modules/web_shop_shopify/doc/design.rst
+++ b/modules/web_shop_shopify/doc/design.rst
@@ -55,3 +55,17 @@ Shopify Inventory Item
The *Shopify Inventory Item* concept manages the Shopify inventory item of each
`Varient `.
+
+.. _model-carrier:
+
+Carrier
+=======
+
+When the *Carrier Module* is activated, the `Carrier `
+is extended to allow to define the :guilabel:`Code` of the Shopify shipping
+method.
+
+.. seealso::
+
+ The `Carrier ` concept is introduced by the
+ :doc:`Carrier Module `.
diff --git a/modules/web_shop_shopify/sale.py b/modules/web_shop_shopify/sale.py
index b587fb6261..d9408981f6 100644
--- a/modules/web_shop_shopify/sale.py
+++ b/modules/web_shop_shopify/sale.py
@@ -721,6 +721,14 @@ class Sale_ShipmentCost(metaclass=PoolMeta):
return []
return super().set_shipment_cost()
+ @classmethod
+ def shopify_fields(cls):
+ fields = super().shopify_fields()
+ shipping_line = fields.setdefault('shippingLine', {})
+ shipping_line.setdefault('code')
+ shipping_line.setdefault('title')
+ return fields
+
@classmethod
def get_from_shopify(cls, shop, order, sale=None):
pool = Pool()
@@ -729,11 +737,16 @@ class Sale_ShipmentCost(metaclass=PoolMeta):
sale = super().get_from_shopify(shop, order, sale=sale)
shipment_cost_method = None
- if order['shippingLines']:
+ if shipping_line := order['shippingLine']:
available_carriers = sale.on_change_with_available_carriers()
carrier = None
- if available_carriers:
- carrier = available_carriers[0]
+ for carrier in available_carriers:
+ if carrier.shopify_match(shop, shipping_line):
+ carrier = carrier
+ break
+ else:
+ if available_carriers:
+ carrier = available_carriers[0]
setattr_changed(sale, 'carrier', carrier)
if sale.carrier:
shipment_cost_method = 'order'
diff --git a/modules/web_shop_shopify/setup.py b/modules/web_shop_shopify/setup.py
index a20deb2e4f..50cc0c211d 100755
--- a/modules/web_shop_shopify/setup.py
+++ b/modules/web_shop_shopify/setup.py
@@ -53,6 +53,7 @@ requires.append(get_require_version('trytond'))
tests_require = [
get_require_version('proteus'),
get_require_version('trytond_account_payment_clearing'),
+ get_require_version('trytond_carrier'),
get_require_version('trytond_customs'),
get_require_version('trytond_product_image'),
get_require_version('trytond_product_image_attribute'),
diff --git a/modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst b/modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst
index acfc0f81e7..02723a51db 100644
--- a/modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst
+++ b/modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst
@@ -46,6 +46,7 @@ Activate modules::
>>> config = activate_modules([
... 'web_shop_shopify',
... 'account_payment_clearing',
+ ... 'carrier',
... 'customs',
... 'product_measurements',
... 'product_image',
@@ -280,7 +281,7 @@ Create products::
... 'https://downloads.tryton.org/tests/shopify/chair-white.jpg').read()
>>> variant2.save()
-Create carrier::
+Create carriers::
>>> carrier_template = ProductTemplate()
>>> carrier_template.name = 'Carrier Product'
@@ -294,12 +295,23 @@ Create carrier::
>>> carrier_product.cost_price = Decimal('2')
>>> carrier_product.save()
- >>> carrier = Carrier()
- >>> party = Party(name='Carrier')
+ >>> carrier1 = Carrier()
+ >>> party = Party(name="Carrier 1")
>>> party.save()
- >>> carrier.party = party
- >>> carrier.carrier_product = carrier_product
- >>> carrier.save()
+ >>> carrier1.party = party
+ >>> carrier1.carrier_product = carrier_product
+ >>> carrier1.save()
+
+ >>> carrier2 = Carrier()
+ >>> party = Party(name="Carrier 2")
+ >>> party.save()
+ >>> carrier2.party = party
+ >>> carrier2.carrier_product = carrier_product
+ >>> _ = carrier2.shopify_selections.new(code='SHIP')
+ >>> carrier2.save()
+
+ >>> CarrierSelection(carrier=carrier1).save()
+ >>> CarrierSelection(carrier=carrier2).save()
Fill warehouse::
@@ -562,7 +574,7 @@ Run fetch order::
'processing'
>>> payment.amount
Decimal('0')
- >>> assertEqual(sale.carrier, carrier)
+ >>> assertEqual(sale.carrier, carrier2)
>>> sale.state
'quotation'
>>> sale.party.name
diff --git a/modules/web_shop_shopify/tests/test_module.py b/modules/web_shop_shopify/tests/test_module.py
index 259044c3e0..7b521a4849 100644
--- a/modules/web_shop_shopify/tests/test_module.py
+++ b/modules/web_shop_shopify/tests/test_module.py
@@ -10,7 +10,7 @@ class WebShopShopifyTestCase(PartyCheckReplaceMixin, ModuleTestCase):
'Test Web Shop Shopify module'
module = 'web_shop_shopify'
extras = [
- 'customs', 'product_image', 'product_image_attribute',
+ 'carrier', 'customs', 'product_image', 'product_image_attribute',
'product_measurements', 'sale_discount', 'sale_invoice_grouping',
'sale_secondary_unit', 'sale_shipment_cost', 'stock_package_shipping']
diff --git a/modules/web_shop_shopify/tryton.cfg b/modules/web_shop_shopify/tryton.cfg
index 4ec83c4a36..a5b31fa861 100644
--- a/modules/web_shop_shopify/tryton.cfg
+++ b/modules/web_shop_shopify/tryton.cfg
@@ -13,6 +13,7 @@ depends:
stock
web_shop
extras_depend:
+ carrier
customs
product_image
product_image_attribute
@@ -27,6 +28,7 @@ xml:
product.xml
web.xml
stock.xml
+ carrier.xml
message.xml
[register]
@@ -58,6 +60,11 @@ model:
wizard:
party.Replace
+[register carrier]
+model:
+ carrier.Carrier
+ carrier.SelectionShopify
+
[register customs]
model:
product.ShopifyInventoryItem_Customs
diff --git a/modules/web_shop_shopify/view/carrier_form.xml b/modules/web_shop_shopify/view/carrier_form.xml
new file mode 100644
index 0000000000..65c67d8fb5
--- /dev/null
+++ b/modules/web_shop_shopify/view/carrier_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/web_shop_shopify/view/carrier_selection_shopify_form.xml b/modules/web_shop_shopify/view/carrier_selection_shopify_form.xml
new file mode 100644
index 0000000000..24818f631b
--- /dev/null
+++ b/modules/web_shop_shopify/view/carrier_selection_shopify_form.xml
@@ -0,0 +1,17 @@
+
+
+
diff --git a/modules/web_shop_shopify/view/carrier_selection_shopify_list.xml b/modules/web_shop_shopify/view/carrier_selection_shopify_list.xml
new file mode 100644
index 0000000000..bbbdf5089c
--- /dev/null
+++ b/modules/web_shop_shopify/view/carrier_selection_shopify_list.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+