@@ -1,3 +1,4 @@
|
||||
* Add support for pick-up delivery method
|
||||
* Log the update of sales
|
||||
* Add support for gift card products
|
||||
* Add support for Python 3.14
|
||||
|
||||
@@ -49,6 +49,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
<record model="ir.message" id="msg_inventory_set_fail">
|
||||
<field name="text">Failed to set inventory with error:
|
||||
%(error)s</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_fulfillment_prepared_for_pickup_fail">
|
||||
<field name="text">Failed to prepare for pickup fulfillments for shipments "%(shipments)s" and sales "%(sales)s" with error:
|
||||
%(error)s</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_fulfillment_fail">
|
||||
|
||||
@@ -251,6 +251,9 @@ class Sale(IdentifierMixin, metaclass=PoolMeta):
|
||||
'id': None,
|
||||
},
|
||||
},
|
||||
'deliveryMethod': {
|
||||
'methodType': None,
|
||||
},
|
||||
'lineItems(first: 100)': {
|
||||
'nodes': {
|
||||
'lineItem': {
|
||||
@@ -264,6 +267,7 @@ class Sale(IdentifierMixin, metaclass=PoolMeta):
|
||||
'endCursor': None,
|
||||
},
|
||||
},
|
||||
'status': None,
|
||||
},
|
||||
'pageInfo': {
|
||||
'hasNextPage': None,
|
||||
@@ -348,23 +352,9 @@ class Sale(IdentifierMixin, metaclass=PoolMeta):
|
||||
else:
|
||||
invoice_address = None
|
||||
else:
|
||||
shipment_address = sale.party.address_get(type='delivery')
|
||||
shipment_address = None
|
||||
invoice_address = sale.party.address_get(type='invoice')
|
||||
|
||||
if shipment_address:
|
||||
setattr_changed(sale, 'shipment_address', shipment_address)
|
||||
if invoice_address or shipment_address:
|
||||
setattr_changed(
|
||||
sale, 'invoice_address', invoice_address or shipment_address)
|
||||
|
||||
if not party.addresses:
|
||||
address = Address(party=party)
|
||||
address.save()
|
||||
if not sale.shipment_address:
|
||||
sale.shipment_address = address
|
||||
if not sale.invoice_address:
|
||||
sale.invoice_address = address
|
||||
|
||||
setattr_changed(sale, 'reference', order['name'])
|
||||
setattr_changed(sale, 'shopify_status_url', order['statusPageUrl'])
|
||||
setattr_changed(sale, 'comment', order['note'])
|
||||
@@ -419,6 +409,10 @@ class Sale(IdentifierMixin, metaclass=PoolMeta):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if fulfillment_order['status'] != 'CANCELLED':
|
||||
method_type = fulfillment_order['deliveryMethod']['methodType']
|
||||
if method_type == 'PICK_UP':
|
||||
shipment_address = sale.warehouse.address
|
||||
shopify_line_items = graphql.iterate(
|
||||
QUERY_FULFILLMENT_ORDER % {
|
||||
'fields': graphql.selection({
|
||||
@@ -436,6 +430,17 @@ class Sale(IdentifierMixin, metaclass=PoolMeta):
|
||||
line2warehouses[gid2id(line_item['lineItem']['id'])].add(
|
||||
warehouse)
|
||||
|
||||
if shipment_address:
|
||||
setattr_changed(sale, 'shipment_address', shipment_address)
|
||||
if invoice_address or shipment_address:
|
||||
setattr_changed(
|
||||
sale, 'invoice_address', invoice_address or shipment_address)
|
||||
|
||||
if not party.addresses and not sale.invoice_address:
|
||||
address = Address(party=party)
|
||||
address.save()
|
||||
sale.invoice_address = address
|
||||
|
||||
id2line = {
|
||||
l.shopify_identifier: l for l in getattr(sale, 'lines', [])
|
||||
if l.shopify_identifier}
|
||||
|
||||
@@ -13,12 +13,24 @@ from trytond.pool import Pool, PoolMeta
|
||||
from . import graphql
|
||||
from .common import IdentifierMixin, id2gid
|
||||
from .exceptions import ShopifyError
|
||||
from .shopify_retry import GraphQLException
|
||||
|
||||
QUERY_FULFILLMENT_ORDERS = '''\
|
||||
query FulfillmentOrders($orderId: ID!) {
|
||||
order(id: $orderId) %(fields)s
|
||||
}'''
|
||||
|
||||
MUTATION_FULFILLMENT_ORDER_LINE_ITEMS_PREPARED_FOR_PICKUP = '''\
|
||||
mutation fulfillmentOrderLineItemsPreparedForPickup(\
|
||||
$input: FulfillmentOrderLineItemsPreparedForPickupInput!) {
|
||||
fulfillmentOrderLineItemsPreparedForPickup(input: $input) {
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}'''
|
||||
|
||||
|
||||
class ShipmentOut(metaclass=PoolMeta):
|
||||
__name__ = 'stock.shipment.out'
|
||||
@@ -145,6 +157,52 @@ class ShipmentOut(metaclass=PoolMeta):
|
||||
shipment=shipment.rec_name))
|
||||
super().draft(shipments)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('packed')
|
||||
def pack(cls, shipments):
|
||||
super().pack(shipments)
|
||||
if pickup := [
|
||||
s for s in shipments
|
||||
if s.delivery_address == s.warehouse.address]:
|
||||
cls.__queue__._shopify_prepared_for_pickup(pickup)
|
||||
|
||||
@classmethod
|
||||
def _shopify_prepared_for_pickup(cls, shipments):
|
||||
mutation = MUTATION_FULFILLMENT_ORDER_LINE_ITEMS_PREPARED_FOR_PICKUP
|
||||
fullfilments = defaultdict(set)
|
||||
for shipment in shipments:
|
||||
if shipment.state == 'packed':
|
||||
for record in shipment.shopify_identifiers:
|
||||
fullfilments[record.sale.web_shop].add(record)
|
||||
for shop, records in fullfilments.items():
|
||||
with shop.shopify_session():
|
||||
fullfilment_ids = list({
|
||||
id2gid('FulfillmentOrder', r.shopify_identifier)
|
||||
for r in records})
|
||||
try:
|
||||
result = shopify.GraphQL().execute(
|
||||
mutation, {
|
||||
'input': list(fullfilment_ids),
|
||||
}
|
||||
)['data']['fulfillmentOrderLineItemsPreparedForPickup']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
except GraphQLException as e:
|
||||
shipments = ", ".join(
|
||||
r.shipment.rec_name for r in records[:5])
|
||||
sales = ", ".join(r.sale.rec_name for r in records[:5])
|
||||
if len(records) > 5:
|
||||
shipments += "..."
|
||||
sales += "..."
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify'
|
||||
'.msg_fulfillment_prepared_for_pickup_fail',
|
||||
shipments=shipments,
|
||||
sales=sales,
|
||||
error="\n".join(
|
||||
err['message'] for err in e.errors))) from e
|
||||
|
||||
|
||||
class ShipmentShopifyIdentifier(IdentifierMixin, ModelSQL, ModelView):
|
||||
__name__ = 'stock.shipment.shopify_identifier'
|
||||
|
||||
@@ -36,6 +36,7 @@ Activate modules::
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> Category = Model.get('product.category')
|
||||
>>> Cron = Model.get('ir.cron')
|
||||
>>> Country = Model.get('country.country')
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> Product = Model.get('product.product')
|
||||
@@ -45,6 +46,11 @@ Activate modules::
|
||||
>>> Uom = Model.get('product.uom')
|
||||
>>> WebShop = Model.get('web.shop')
|
||||
|
||||
Create country::
|
||||
|
||||
>>> belgium = Country(name="Belgium", code='BE')
|
||||
>>> belgium.save()
|
||||
|
||||
Get company::
|
||||
|
||||
>>> company = get_company()
|
||||
@@ -161,16 +167,17 @@ Create an order on Shopify::
|
||||
... 'lastName': "Customer",
|
||||
... 'email': (''.join(
|
||||
... random.choice(string.ascii_letters) for _ in range(10))
|
||||
... + '@example.com'),
|
||||
... 'addresses': [{
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... }],
|
||||
... + '@example.com')
|
||||
... })
|
||||
|
||||
>>> order = tools.create_order({
|
||||
... 'customerId': customer['id'],
|
||||
... 'shippingAddress': {
|
||||
... 'lastName': "Customer",
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... },
|
||||
... 'lineItems': [{
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
|
||||
@@ -36,6 +36,7 @@ Activate modules::
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> Category = Model.get('product.category')
|
||||
>>> Cron = Model.get('ir.cron')
|
||||
>>> Country = Model.get('country.country')
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> Product = Model.get('product.product')
|
||||
@@ -45,6 +46,11 @@ Activate modules::
|
||||
>>> Uom = Model.get('product.uom')
|
||||
>>> WebShop = Model.get('web.shop')
|
||||
|
||||
Create country::
|
||||
|
||||
>>> belgium = Country(name="Belgium", code='BE')
|
||||
>>> belgium.save()
|
||||
|
||||
Get company::
|
||||
|
||||
>>> company = get_company()
|
||||
@@ -149,16 +155,17 @@ Create an order on Shopify::
|
||||
... 'lastName': "Customer",
|
||||
... 'email': (''.join(
|
||||
... random.choice(string.ascii_letters) for _ in range(10))
|
||||
... + '@example.com'),
|
||||
... 'addresses': [{
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... }],
|
||||
... + '@example.com')
|
||||
... })
|
||||
|
||||
>>> order = tools.create_order({
|
||||
... 'customerId': customer['id'],
|
||||
... 'shippingAddress': {
|
||||
... 'lastName': "Customer",
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... },
|
||||
... 'lineItems': [{
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
|
||||
Reference in New Issue
Block a user