changeset c1591e56fd06 in modules/web_shop_shopify:default
details: 
https://hg.tryton.org/modules/web_shop_shopify?cmd=changeset&node=c1591e56fd06
description:
        Replace deprecated fulfillment by fulfillment order

        issue11746
        review421851003
diffstat:

 CHANGELOG     |   2 +
 doc/usage.rst |  10 +++++--
 message.xml   |   3 ++
 setup.py      |   3 +-
 stock.py      |  78 +++++++++++++++++++++++++++++++++++++++++-----------------
 web.py        |   2 +-
 6 files changed, 69 insertions(+), 29 deletions(-)

diffs (192 lines):

diff -r 90ea9f496a0b -r c1591e56fd06 CHANGELOG
--- a/CHANGELOG Mon Oct 10 18:51:28 2022 +0200
+++ b/CHANGELOG Mon Oct 10 18:52:53 2022 +0200
@@ -1,3 +1,5 @@
+* Replace deprecated fulfillment by fulfillment order
+
 Version 6.4.0 - 2022-05-02
 * Bug fixes (see mercurial logs for details)
 * Support webhook for orders
diff -r 90ea9f496a0b -r c1591e56fd06 doc/usage.rst
--- a/doc/usage.rst     Mon Oct 10 18:51:28 2022 +0200
+++ b/doc/usage.rst     Mon Oct 10 18:52:53 2022 +0200
@@ -7,9 +7,9 @@
 Configure Shopify Web Shop
 ==========================
 
-First you must create a new `private app
-<https://help.shopify.com/en/manual/apps/private-apps>`_ for your Shopify
-store with, as a minimum, the following permissions:
+First you must create a new `custom app
+<https://help.shopify.com/en/manual/apps/app-types#custom-apps>`_ for your
+Shopify store with, as a minimum, the following permissions:
 
    * Fulfillment services: Read and write
 
@@ -21,6 +21,10 @@
 
    * Products: Read and write
 
+   * Assigned fulfillment: Read and write
+
+   * Merchant-managed fulfillment orders: Read and write
+
 You also need to copy the password that is generated.
 
 When setting the :doc:`Web Shop <web_shop:index>`'s type to  "Shopify", you
diff -r 90ea9f496a0b -r c1591e56fd06 message.xml
--- a/message.xml       Mon Oct 10 18:51:28 2022 +0200
+++ b/message.xml       Mon Oct 10 18:52:53 2022 +0200
@@ -67,5 +67,8 @@
         <record model="ir.message" id="msg_sale_line_without_product">
             <field name="text">To process the sale "%(sale)s" you must set a 
product on the line "%(line)s".</field>
         </record>
+        <record model="ir.message" id="msg_fulfillment_order_line_not_found">
+            <field name="text">Failed to find fulfillment order for 
%(quantity)s of move "%(move)s".</field>
+        </record>
     </data>
 </tryton>
diff -r 90ea9f496a0b -r c1591e56fd06 setup.py
--- a/setup.py  Mon Oct 10 18:51:28 2022 +0200
+++ b/setup.py  Mon Oct 10 18:52:53 2022 +0200
@@ -61,8 +61,7 @@
             break
 if local_version:
     version += '+' + '.'.join(local_version)
-requires = [
-    'ShopifyAPI<12.0.0', 'pyactiveresource', 'python-sql', 'python-dateutil']
+requires = ['ShopifyAPI', 'pyactiveresource', 'python-sql', 'python-dateutil']
 for dep in info.get('depends', []):
     if not re.match(r'(ir|res)(\W|$)', dep):
         requires.append(get_require_version('trytond_%s' % dep))
diff -r 90ea9f496a0b -r c1591e56fd06 stock.py
--- a/stock.py  Mon Oct 10 18:51:28 2022 +0200
+++ b/stock.py  Mon Oct 10 18:52:53 2022 +0200
@@ -1,12 +1,17 @@
 # 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 shopify
+
+from collections import defaultdict
 
-from trytond.i18n import lazy_gettext
+import shopify
+from shopify.resources.fulfillment import FulfillmentV2
+
+from trytond.i18n import gettext, lazy_gettext
 from trytond.model import ModelSQL, ModelView, Unique, fields
 from trytond.pool import Pool, PoolMeta
 
 from .common import IdentifierMixin
+from .exceptions import ShopifyError
 
 
 class ShipmentOut(metaclass=PoolMeta):
@@ -24,21 +29,28 @@
             # Fulfillment can not be modified
             return
         else:
-            fulfillment = shopify.Fulfillment(
-                prefix_options={'order_id': sale.shopify_identifier})
+            fulfillment = FulfillmentV2()
         for shop_warehouse in sale.web_shop.shopify_warehouses:
             if shop_warehouse.warehouse == self.warehouse:
-                fulfillment.location_id = int(shop_warehouse.shopify_id)
+                location_id = int(shop_warehouse.shopify_id)
                 break
-        line_items = []
+        else:
+            location_id = None
+        fulfillment_orders = shopify.FulfillmentOrders.find(
+            order_id=sale.shopify_identifier)
+        line_items = defaultdict(list)
         for move in self.outgoing_moves:
             if move.sale == sale:
-                line_item = move.get_shopify()
-                if line_item:
-                    line_items.append(line_item)
+                for order_id, line_item in move.get_shopify(
+                        fulfillment_orders, location_id):
+                    line_items[order_id].append(line_item)
         if not line_items:
             return
-        fulfillment.line_items = line_items
+        fulfillment.line_items_by_fulfillment_order = [{
+                'fulfillment_order_id': order_id,
+                'fulfillment_order_line_items': line_items,
+                }
+            for order_id, line_items in line_items.items()]
         return fulfillment
 
     def get_shopify_identifier(self, sale):
@@ -116,25 +128,45 @@
     def get_shopify(self, sale):
         fulfillment = super().get_shopify(sale)
         if fulfillment and self.packages:
-            fulfillment.tracking_numbers = [
-                p.shipping_reference for p in self.packages
-                if p.shipping_reference]
-            fulfillment.tracking_urls = [
-                p.shipping_tracking_url for p in self.packages
-                if p.shipping_tracking_url]
+            tracking_info = []
+            for package in self.packages:
+                tracking_info.append({
+                        'number': package.shipping_reference,
+                        'url': package.shipping_tracking_url,
+                        })
+            fulfillment.tracking_info = tracking_info
         return fulfillment
 
 
 class Move(metaclass=PoolMeta):
     __name__ = 'stock.move'
 
-    def get_shopify(self):
+    def get_shopify(self, fulfillment_orders, location_id):
         pool = Pool()
         SaleLine = pool.get('sale.line')
         Uom = pool.get('product.uom')
-        if isinstance(self.origin, SaleLine):
-            return {
-                'id': self.origin.shopify_identifier,
-                'quantity': int(Uom.compute_qty(
-                        self.uom, self.quantity, self.origin.unit)),
-                }
+        if not isinstance(self.origin, SaleLine):
+            return
+        identifier = self.origin.shopify_identifier
+        quantity = int(Uom.compute_qty(
+                self.uom, self.quantity, self.origin.unit))
+        for fulfillment_order in fulfillment_orders:
+            if fulfillment_order.assigned_location_id != location_id:
+                continue
+            for line_item in fulfillment_order.line_items:
+                if line_item.line_item_id == identifier:
+                    qty = min(quantity, line_item.fulfillable_quantity)
+                    qty = quantity
+                    yield fulfillment_order.id, {
+                        'id': line_item.id,
+                        'quantity': qty,
+                        }
+                    quantity -= qty
+                    if quantity <= 0:
+                        return
+        else:
+            raise ShopifyError(gettext(
+                    'web_shop_shopify.msg_fulfillment_order_line_not_found',
+                    quantity=quantity,
+                    move=self.rec_name,
+                    ))
diff -r 90ea9f496a0b -r c1591e56fd06 web.py
--- a/web.py    Mon Oct 10 18:51:28 2022 +0200
+++ b/web.py    Mon Oct 10 18:52:53 2022 +0200
@@ -36,7 +36,7 @@
     shopify_url = fields.Char("Shop URL", states=_states)
     shopify_version = fields.Selection(
         'get_shopify_versions', "Version", states=_states)
-    shopify_password = fields.Char("Password", states=_states, strip=False)
+    shopify_password = fields.Char("Access Token", states=_states, strip=False)
     shopify_webhook_shared_secret = fields.Char(
         "Webhook Shared Secret", strip=False,
         states={

Reply via email to