details: https://code.tryton.org/tryton/commit/1d15a4b5bb79
branch: default
user: Cédric Krier <[email protected]>
date: Thu Jul 03 16:49:46 2025 +0200
description:
Add carrier selection for Shopify order
Closes #14102
diffstat:
modules/web_shop_shopify/CHANGELOG | 1 +
modules/web_shop_shopify/carrier.py | 59
++++++++++
modules/web_shop_shopify/carrier.xml | 23 +++
modules/web_shop_shopify/doc/design.rst | 14 ++
modules/web_shop_shopify/sale.py | 19 ++-
modules/web_shop_shopify/setup.py | 1 +
modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst | 26 +++-
modules/web_shop_shopify/tests/test_module.py | 2 +-
modules/web_shop_shopify/tryton.cfg | 7 +
modules/web_shop_shopify/view/carrier_form.xml | 8 +
modules/web_shop_shopify/view/carrier_selection_shopify_form.xml | 17 ++
modules/web_shop_shopify/view/carrier_selection_shopify_list.xml | 9 +
12 files changed, 175 insertions(+), 11 deletions(-)
diffs (315 lines):
diff -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/CHANGELOG
--- a/modules/web_shop_shopify/CHANGELOG Mon Dec 01 16:46:23 2025 +0100
+++ b/modules/web_shop_shopify/CHANGELOG Thu Jul 03 16:49:46 2025 +0200
@@ -1,3 +1,4 @@
+* Add carrier selection
* Use GraphQL API
* Add option to notify customer about fulfillment
* Support taxes from shipping product
diff -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/carrier.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web_shop_shopify/carrier.py Thu Jul 03 16:49:46 2025 +0200
@@ -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 -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/carrier.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web_shop_shopify/carrier.xml Thu Jul 03 16:49:46 2025 +0200
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tryton>
+ <data depends="carrier">
+ <record model="ir.ui.view" id="carrier_view_form">
+ <field name="model">carrier</field>
+ <field name="inherit" ref="carrier.carrier_view_form"/>
+ <field name="name">carrier_form</field>
+ </record>
+
+ <record model="ir.ui.view" id="carrier_selection_shopify_view_form">
+ <field name="model">carrier.selection.shopify</field>
+ <field name="type">form</field>
+ <field name="name">carrier_selection_shopify_form</field>
+ </record>
+ <record model="ir.ui.view" id="carrier_selection_shopify_view_list">
+ <field name="model">carrier.selection.shopify</field>
+ <field name="type">tree</field>
+ <field name="name">carrier_selection_shopify_list</field>
+ </record>
+ </data>
+</tryton>
diff -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/doc/design.rst
--- a/modules/web_shop_shopify/doc/design.rst Mon Dec 01 16:46:23 2025 +0100
+++ b/modules/web_shop_shopify/doc/design.rst Thu Jul 03 16:49:46 2025 +0200
@@ -55,3 +55,17 @@
The *Shopify Inventory Item* concept manages the Shopify inventory item of each
`Varient <product:model-product.product>`.
+
+.. _model-carrier:
+
+Carrier
+=======
+
+When the *Carrier Module* is activated, the `Carrier <carrier:model-carrier>`
+is extended to allow to define the :guilabel:`Code` of the Shopify shipping
+method.
+
+.. seealso::
+
+ The `Carrier <carrier:model-carrier>` concept is introduced by the
+ :doc:`Carrier Module <carrier:index>`.
diff -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/sale.py
--- a/modules/web_shop_shopify/sale.py Mon Dec 01 16:46:23 2025 +0100
+++ b/modules/web_shop_shopify/sale.py Thu Jul 03 16:49:46 2025 +0200
@@ -722,6 +722,14 @@
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()
Tax = pool.get('account.tax')
@@ -729,11 +737,16 @@
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 -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/setup.py
--- a/modules/web_shop_shopify/setup.py Mon Dec 01 16:46:23 2025 +0100
+++ b/modules/web_shop_shopify/setup.py Thu Jul 03 16:49:46 2025 +0200
@@ -53,6 +53,7 @@
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 -r 420f26f83887 -r 1d15a4b5bb79
modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst
--- a/modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst Mon Dec
01 16:46:23 2025 +0100
+++ b/modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst Thu Jul
03 16:49:46 2025 +0200
@@ -46,6 +46,7 @@
>>> config = activate_modules([
... 'web_shop_shopify',
... 'account_payment_clearing',
+ ... 'carrier',
... 'customs',
... 'product_measurements',
... 'product_image',
@@ -280,7 +281,7 @@
...
'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 @@
>>> 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 @@
'processing'
>>> payment.amount
Decimal('0')
- >>> assertEqual(sale.carrier, carrier)
+ >>> assertEqual(sale.carrier, carrier2)
>>> sale.state
'quotation'
>>> sale.party.name
diff -r 420f26f83887 -r 1d15a4b5bb79
modules/web_shop_shopify/tests/test_module.py
--- a/modules/web_shop_shopify/tests/test_module.py Mon Dec 01 16:46:23
2025 +0100
+++ b/modules/web_shop_shopify/tests/test_module.py Thu Jul 03 16:49:46
2025 +0200
@@ -10,7 +10,7 @@
'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 -r 420f26f83887 -r 1d15a4b5bb79 modules/web_shop_shopify/tryton.cfg
--- a/modules/web_shop_shopify/tryton.cfg Mon Dec 01 16:46:23 2025 +0100
+++ b/modules/web_shop_shopify/tryton.cfg Thu Jul 03 16:49:46 2025 +0200
@@ -13,6 +13,7 @@
stock
web_shop
extras_depend:
+ carrier
customs
product_image
product_image_attribute
@@ -27,6 +28,7 @@
product.xml
web.xml
stock.xml
+ carrier.xml
message.xml
[register]
@@ -58,6 +60,11 @@
wizard:
party.Replace
+[register carrier]
+model:
+ carrier.Carrier
+ carrier.SelectionShopify
+
[register customs]
model:
product.ShopifyInventoryItem_Customs
diff -r 420f26f83887 -r 1d15a4b5bb79
modules/web_shop_shopify/view/carrier_form.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web_shop_shopify/view/carrier_form.xml Thu Jul 03 16:49:46
2025 +0200
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<data>
+ <xpath expr="/form" position="inside">
+ <field name="shopify_selections" colspan="4"/>
+ </xpath>
+</data>
diff -r 420f26f83887 -r 1d15a4b5bb79
modules/web_shop_shopify/view/carrier_selection_shopify_form.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web_shop_shopify/view/carrier_selection_shopify_form.xml Thu Jul
03 16:49:46 2025 +0200
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<form cursor="shop" col="2">
+ <label name="carrier"/>
+ <field name="carrier"/>
+
+ <separator string="Criteria" id="criteria" colspan="2"/>
+ <label name="shop"/>
+ <field name="shop"/>
+
+ <label name="code"/>
+ <field name="code"/>
+
+ <label name="title"/>
+ <field name="title"/>
+</form>
diff -r 420f26f83887 -r 1d15a4b5bb79
modules/web_shop_shopify/view/carrier_selection_shopify_list.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/web_shop_shopify/view/carrier_selection_shopify_list.xml Thu Jul
03 16:49:46 2025 +0200
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree>
+ <field name="carrier" expand="2"/>
+ <field name="shop" expand="1"/>
+ <field name="code" expand="1"/>
+ <field name="title" expand="1"/>
+</tree>