@@ -1,3 +1,4 @@
|
||||
* Add carrier selection
|
||||
* Use GraphQL API
|
||||
* Add option to notify customer about fulfillment
|
||||
* Support taxes from shipping product
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
@@ -55,3 +55,17 @@ Shopify Inventory Item
|
||||
|
||||
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>`.
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user