first commit
This commit is contained in:
2
modules/web_shop_shopify/tests/__init__.py
Normal file
2
modules/web_shop_shopify/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
modules/web_shop_shopify/tests/__pycache__/tools.cpython-311.pyc
Normal file
BIN
modules/web_shop_shopify/tests/__pycache__/tools.cpython-311.pyc
Normal file
Binary file not shown.
753
modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst
Normal file
753
modules/web_shop_shopify/tests/scenario_web_shop_shopify.rst
Normal file
@@ -0,0 +1,753 @@
|
||||
=========================
|
||||
Web Shop Shopify Scenario
|
||||
=========================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> import os
|
||||
>>> import random
|
||||
>>> import string
|
||||
>>> import time
|
||||
>>> import urllib.request
|
||||
>>> from decimal import Decimal
|
||||
>>> from itertools import cycle
|
||||
>>> from unittest.mock import patch
|
||||
|
||||
>>> import shopify
|
||||
>>> from shopify.api_version import ApiVersion
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, create_tax, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company, get_company
|
||||
>>> from trytond.modules.product_image.product import ImageURLMixin
|
||||
>>> from trytond.modules.web_shop_shopify.common import gid2id, id2gid
|
||||
>>> from trytond.modules.web_shop_shopify.product import Template
|
||||
>>> from trytond.modules.web_shop_shopify.tests import tools
|
||||
>>> from trytond.modules.web_shop_shopify.web import Shop
|
||||
>>> from trytond.tests.tools import activate_modules, assertEqual, assertTrue
|
||||
|
||||
>>> FETCH_SLEEP, MAX_SLEEP = 1, 10
|
||||
|
||||
Patch image URL::
|
||||
|
||||
>>> get_image_url = patch.object(
|
||||
... ImageURLMixin, 'get_image_url').start()
|
||||
>>> get_image_url.side_effect = cycle([
|
||||
... 'https://downloads.tryton.org/tests/shopify/chair.jpg',
|
||||
... 'https://downloads.tryton.org/tests/shopify/chair-black.jpg',
|
||||
... 'https://downloads.tryton.org/tests/shopify/chair-white.jpg',
|
||||
... ])
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules([
|
||||
... 'web_shop_shopify',
|
||||
... 'account_payment_clearing',
|
||||
... 'carrier',
|
||||
... 'customs',
|
||||
... 'product_measurements',
|
||||
... 'product_image',
|
||||
... 'sale_discount',
|
||||
... 'sale_shipment_cost',
|
||||
... ],
|
||||
... create_company, create_chart)
|
||||
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> Carrier = Model.get('carrier')
|
||||
>>> CarrierSelection = Model.get('carrier.selection')
|
||||
>>> Category = Model.get('product.category')
|
||||
>>> Country = Model.get('country.country')
|
||||
>>> Cron = Model.get('ir.cron')
|
||||
>>> Inventory = Model.get('stock.inventory')
|
||||
>>> Journal = Model.get('account.journal')
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> Party = Model.get('party.party')
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> Product = Model.get('product.product')
|
||||
>>> ProductAttribute = Model.get('product.attribute')
|
||||
>>> ProductAttributeSet = Model.get('product.attribute.set')
|
||||
>>> ProductInventoryItem = Model.get('product.shopify_inventory_item')
|
||||
>>> ProductTemplate = Model.get('product.template')
|
||||
>>> Sale = Model.get('sale.sale')
|
||||
>>> ShopifyIdentifier = Model.get('web.shop.shopify_identifier')
|
||||
>>> Tariff = Model.get('customs.tariff.code')
|
||||
>>> Uom = Model.get('product.uom')
|
||||
>>> WebShop = Model.get('web.shop')
|
||||
|
||||
Set metafields to product::
|
||||
|
||||
>>> def get_shopify_metafields(self, shop):
|
||||
... return {
|
||||
... 'global.test': {
|
||||
... 'type': 'single_line_text_field',
|
||||
... 'value': self.name,
|
||||
... },
|
||||
... }
|
||||
|
||||
>>> Template.get_shopify_metafields = get_shopify_metafields
|
||||
|
||||
>>> def managed_metafields(self):
|
||||
... return {'global.test'}
|
||||
|
||||
>>> Shop.managed_metafields = managed_metafields
|
||||
|
||||
Create country::
|
||||
|
||||
>>> belgium = Country(name="Belgium", code='BE')
|
||||
>>> belgium.save()
|
||||
>>> china = Country(name="China", code='CN')
|
||||
>>> china.save()
|
||||
|
||||
Get company::
|
||||
|
||||
>>> company = get_company()
|
||||
|
||||
Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
|
||||
Create tax::
|
||||
|
||||
>>> tax = create_tax(Decimal('.10'))
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Create payment journal::
|
||||
|
||||
>>> shopify_account = Account(parent=accounts['receivable'].parent)
|
||||
>>> shopify_account.name = "Shopify"
|
||||
>>> shopify_account.type = accounts['receivable'].type
|
||||
>>> shopify_account.reconcile = True
|
||||
>>> shopify_account.save()
|
||||
|
||||
>>> payment_journal = PaymentJournal()
|
||||
>>> payment_journal.name = "Shopify"
|
||||
>>> payment_journal.process_method = 'shopify'
|
||||
>>> payment_journal.clearing_journal, = Journal.find([('code', '=', 'REV')])
|
||||
>>> payment_journal.clearing_account = shopify_account
|
||||
>>> payment_journal.save()
|
||||
|
||||
Define a web shop::
|
||||
|
||||
>>> web_shop = WebShop(name="Web Shop")
|
||||
>>> web_shop.type = 'shopify'
|
||||
>>> web_shop.shopify_url = os.getenv('SHOPIFY_URL')
|
||||
>>> web_shop.shopify_password = os.getenv('SHOPIFY_PASSWORD')
|
||||
>>> web_shop.shopify_version = sorted(ApiVersion.versions, reverse=True)[1]
|
||||
>>> shop_warehouse = web_shop.shopify_warehouses.new()
|
||||
>>> shop_warehouse.warehouse, = Location.find([('type', '=', 'warehouse')])
|
||||
>>> shopify_payment_journal = web_shop.shopify_payment_journals.new()
|
||||
>>> shopify_payment_journal.journal = payment_journal
|
||||
>>> web_shop.save()
|
||||
|
||||
>>> shopify.ShopifyResource.activate_session(shopify.Session(
|
||||
... web_shop.shopify_url,
|
||||
... web_shop.shopify_version,
|
||||
... web_shop.shopify_password))
|
||||
|
||||
>>> location = tools.get_location()
|
||||
|
||||
>>> shop_warehouse, = web_shop.shopify_warehouses
|
||||
>>> shop_warehouse.shopify_id = str(gid2id(location['id']))
|
||||
>>> web_shop.save()
|
||||
|
||||
Create categories::
|
||||
|
||||
>>> category1 = Category(name="Category 1")
|
||||
>>> category1.save()
|
||||
>>> sub_category = Category(name="Sub Category", parent=category1)
|
||||
>>> sub_category.save()
|
||||
>>> category2 = Category(name="Category 2")
|
||||
>>> category2.save()
|
||||
|
||||
>>> account_category = Category(name="Account Category")
|
||||
>>> account_category.accounting = True
|
||||
>>> account_category.account_expense = accounts['expense']
|
||||
>>> account_category.account_revenue = accounts['revenue']
|
||||
>>> account_category.customer_taxes.append(tax)
|
||||
>>> account_category.save()
|
||||
|
||||
>>> account_category_shipping = Category(name="Account Category Shipping")
|
||||
>>> account_category_shipping.accounting = True
|
||||
>>> account_category_shipping.account_expense = accounts['expense']
|
||||
>>> account_category_shipping.account_revenue = accounts['revenue']
|
||||
>>> account_category_shipping.save()
|
||||
|
||||
Create attribute set::
|
||||
|
||||
>>> attribute_set = ProductAttributeSet(name="Attributes")
|
||||
>>> attribute = attribute_set.attributes.new()
|
||||
>>> attribute.name = 'color'
|
||||
>>> attribute.string = "Color"
|
||||
>>> attribute.type_ = 'selection'
|
||||
>>> attribute.selection = "blue:Blue\nred:Red"
|
||||
>>> attribute_set.save()
|
||||
>>> attribute = attribute_set.attributes.new()
|
||||
>>> attribute.name = 'check'
|
||||
>>> attribute.string = "Check"
|
||||
>>> attribute.type_ = 'boolean'
|
||||
>>> attribute_set.save()
|
||||
>>> attribute1, attribute2 = attribute_set.attributes
|
||||
>>> attribute_set.shopify_option1 = attribute1
|
||||
>>> attribute_set.shopify_option2 = attribute2
|
||||
>>> attribute_set.save()
|
||||
|
||||
Create tariff codes::
|
||||
|
||||
>>> tariff1 = Tariff(code='170390')
|
||||
>>> tariff1.save()
|
||||
>>> tariff2 = Tariff(code='17039099', country=belgium)
|
||||
>>> tariff2.save()
|
||||
|
||||
Create products::
|
||||
|
||||
>>> unit, = Uom.find([('name', '=', "Unit")])
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = "Product 1"
|
||||
>>> template.default_uom = unit
|
||||
>>> template.type = 'goods'
|
||||
>>> template.salable = True
|
||||
>>> template.web_shop_description = "<p>Product description</p>"
|
||||
>>> template.shopify_handle = 'product-%s' % random.randint(0, 1000)
|
||||
>>> template.list_price = round(Decimal('9.99') / (1 + tax.rate), 4)
|
||||
>>> template.account_category = account_category
|
||||
>>> template.categories.append(Category(sub_category.id))
|
||||
>>> template.country_of_origin = china
|
||||
>>> _ = template.tariff_codes.new(tariff_code=tariff1)
|
||||
>>> _ = template.tariff_codes.new(tariff_code=tariff2)
|
||||
>>> template.weight = 10
|
||||
>>> template.weight_uom, = Uom.find([('name', '=', "Carat")])
|
||||
>>> template.save()
|
||||
>>> product1, = template.products
|
||||
>>> product1.suffix_code = 'PROD1'
|
||||
>>> product1.save()
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = "Product 2"
|
||||
>>> template.default_uom = unit
|
||||
>>> template.type = 'service'
|
||||
>>> template.salable = True
|
||||
>>> template.list_price = round(Decimal('20') / (1 + tax.rate), 4)
|
||||
>>> template.account_category = account_category
|
||||
>>> template.categories.append(Category(category2.id))
|
||||
>>> template.save()
|
||||
>>> product2, = template.products
|
||||
>>> product2.suffix_code = 'PROD2'
|
||||
>>> product2.save()
|
||||
|
||||
>>> variant = ProductTemplate()
|
||||
>>> variant.name = "Variant"
|
||||
>>> variant.code = "VAR"
|
||||
>>> variant.default_uom = unit
|
||||
>>> variant.type = 'goods'
|
||||
>>> variant.salable = True
|
||||
>>> variant.list_price = round(Decimal('50') / (1 + tax.rate), 4)
|
||||
>>> variant.attribute_set = attribute_set
|
||||
>>> variant.account_category = account_category
|
||||
>>> variant.categories.append(Category(category1.id))
|
||||
>>> variant.categories.append(Category(category2.id))
|
||||
>>> image = variant.images.new(web_shop=True)
|
||||
>>> image.image = urllib.request.urlopen(
|
||||
... 'https://downloads.tryton.org/tests/shopify/chair.jpg').read()
|
||||
>>> variant1, = variant.products
|
||||
>>> variant1.suffix_code = "1"
|
||||
>>> variant1.attributes = {
|
||||
... 'color': 'blue',
|
||||
... 'check': True,
|
||||
... }
|
||||
>>> variant2 = variant.products.new()
|
||||
>>> variant2.suffix_code = "2"
|
||||
>>> variant2.attributes = {
|
||||
... 'color': 'red',
|
||||
... 'check': False,
|
||||
... }
|
||||
>>> variant.save()
|
||||
>>> variant1, variant2 = variant.products
|
||||
|
||||
>>> image = variant1.images.new(web_shop=True, template=variant)
|
||||
>>> image.image = urllib.request.urlopen(
|
||||
... 'https://downloads.tryton.org/tests/shopify/chair-black.jpg').read()
|
||||
>>> variant1.save()
|
||||
|
||||
>>> image = variant2.images.new(web_shop=True, template=variant)
|
||||
>>> image.image = urllib.request.urlopen(
|
||||
... 'https://downloads.tryton.org/tests/shopify/chair-white.jpg').read()
|
||||
>>> variant2.save()
|
||||
|
||||
Create carriers::
|
||||
|
||||
>>> carrier_template = ProductTemplate()
|
||||
>>> carrier_template.name = 'Carrier Product'
|
||||
>>> carrier_template.default_uom = unit
|
||||
>>> carrier_template.type = 'service'
|
||||
>>> carrier_template.salable = True
|
||||
>>> carrier_template.list_price = Decimal('3')
|
||||
>>> carrier_template.account_category = account_category_shipping
|
||||
>>> carrier_template.save()
|
||||
>>> carrier_product, = carrier_template.products
|
||||
>>> carrier_product.cost_price = Decimal('2')
|
||||
>>> carrier_product.save()
|
||||
|
||||
>>> carrier1 = Carrier()
|
||||
>>> party = Party(name="Carrier 1")
|
||||
>>> party.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::
|
||||
|
||||
>>> inventory = Inventory()
|
||||
>>> inventory.location, = Location.find([('code', '=', 'STO')])
|
||||
>>> line = inventory.lines.new()
|
||||
>>> line.product = product1
|
||||
>>> line.quantity = 10
|
||||
>>> line = inventory.lines.new()
|
||||
>>> line.product = variant1
|
||||
>>> line.quantity = 5
|
||||
>>> inventory.click('confirm')
|
||||
>>> inventory.state
|
||||
'done'
|
||||
|
||||
Set categories, products and attributes to web shop::
|
||||
|
||||
>>> web_shop.categories.extend([
|
||||
... Category(category1.id),
|
||||
... Category(sub_category.id),
|
||||
... Category(category2.id)])
|
||||
>>> web_shop.products.extend([
|
||||
... Product(product1.id),
|
||||
... Product(product2.id),
|
||||
... Product(variant1.id),
|
||||
... Product(variant2.id)])
|
||||
>>> web_shop.save()
|
||||
|
||||
Run update product::
|
||||
|
||||
>>> cron_update_product, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_update_product'),
|
||||
... ])
|
||||
>>> cron_update_product.click('run_once')
|
||||
|
||||
>>> category1.reload()
|
||||
>>> len(category1.shopify_identifiers)
|
||||
1
|
||||
>>> category2.reload()
|
||||
>>> len(category2.shopify_identifiers)
|
||||
1
|
||||
|
||||
>>> product1.reload()
|
||||
>>> len(product1.shopify_identifiers)
|
||||
1
|
||||
>>> len(product1.template.shopify_identifiers)
|
||||
1
|
||||
>>> product2.reload()
|
||||
>>> len(product2.shopify_identifiers)
|
||||
1
|
||||
>>> len(product2.template.shopify_identifiers)
|
||||
1
|
||||
>>> variant1.reload()
|
||||
>>> len(variant1.shopify_identifiers)
|
||||
1
|
||||
>>> variant2.reload()
|
||||
>>> len(variant2.shopify_identifiers)
|
||||
1
|
||||
>>> variant.reload()
|
||||
>>> len(variant.shopify_identifiers)
|
||||
1
|
||||
>>> all(i.shopify_identifiers for i in variant.images)
|
||||
True
|
||||
|
||||
Run update inventory::
|
||||
|
||||
>>> cron_update_inventory, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_update_inventory'),
|
||||
... ])
|
||||
>>> cron_update_inventory.click('run_once')
|
||||
|
||||
Check inventory item::
|
||||
|
||||
>>> inventory_items = ProductInventoryItem.find([])
|
||||
>>> inventory_item_ids = [i.shopify_identifier
|
||||
... for inv in inventory_items for i in inv.shopify_identifiers]
|
||||
>>> for _ in range(MAX_SLEEP):
|
||||
... inventory_levels = tools.get_inventory_levels(location)
|
||||
... if inventory_levels and len(inventory_levels) == 2:
|
||||
... break
|
||||
... time.sleep(FETCH_SLEEP)
|
||||
>>> sorted(l['quantities'][0]['quantity'] for l in inventory_levels
|
||||
... if l['quantities'][0]['quantity']
|
||||
... and gid2id(l['item']['id']) in inventory_item_ids)
|
||||
[5, 10]
|
||||
|
||||
Remove a category, a product and an image::
|
||||
|
||||
>>> _ = web_shop.categories.pop(web_shop.categories.index(category2))
|
||||
>>> _ = web_shop.products.pop(web_shop.products.index(product2))
|
||||
>>> web_shop.save()
|
||||
>>> variant2.images.remove(variant2.images[0])
|
||||
>>> variant2.save()
|
||||
|
||||
Rename a category::
|
||||
|
||||
>>> sub_category.name = "Sub-category"
|
||||
>>> sub_category.save()
|
||||
>>> identifier, = sub_category.shopify_identifiers
|
||||
>>> bool(identifier.to_update)
|
||||
True
|
||||
|
||||
Update attribute::
|
||||
|
||||
>>> attribute, = [a for a in attribute_set.attributes if a.name == 'color']
|
||||
>>> attribute.selection += "\ngreen:Green"
|
||||
>>> attribute.save()
|
||||
|
||||
Run update product::
|
||||
|
||||
>>> cron_update_product, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_update_product'),
|
||||
... ])
|
||||
>>> cron_update_product.click('run_once')
|
||||
|
||||
>>> category1.reload()
|
||||
>>> len(category1.shopify_identifiers)
|
||||
1
|
||||
>>> category2.reload()
|
||||
>>> len(category2.shopify_identifiers)
|
||||
0
|
||||
|
||||
>>> sub_category.reload()
|
||||
>>> identifier, = sub_category.shopify_identifiers
|
||||
>>> bool(identifier.to_update)
|
||||
False
|
||||
|
||||
>>> product1.reload()
|
||||
>>> len(product1.shopify_identifiers)
|
||||
1
|
||||
>>> len(product1.template.shopify_identifiers)
|
||||
1
|
||||
>>> product2.reload()
|
||||
>>> len(product2.shopify_identifiers)
|
||||
0
|
||||
>>> identifier, = product2.template.shopify_identifiers
|
||||
>>> tools.get_product(identifier.shopify_identifier)['status']
|
||||
'ARCHIVED'
|
||||
>>> variant1.reload()
|
||||
>>> len(variant1.shopify_identifiers)
|
||||
1
|
||||
>>> variant2.reload()
|
||||
>>> len(variant2.shopify_identifiers)
|
||||
1
|
||||
>>> variant.reload()
|
||||
>>> len(variant.shopify_identifiers)
|
||||
1
|
||||
>>> all(i.shopify_identifiers for i in variant1.images)
|
||||
True
|
||||
>>> any(i.shopify_identifiers for i in variant2.images)
|
||||
False
|
||||
|
||||
Create an order on Shopify::
|
||||
|
||||
>>> customer_phone = '+32-495-555-' + (
|
||||
... ''.join(random.choice(string.digits) for _ in range(3)))
|
||||
>>> customer_address_phone = '+32-495-555-' + (
|
||||
... ''.join(random.choice(string.digits) for _ in range(3)))
|
||||
>>> customer = tools.create_customer({
|
||||
... 'lastName': "Customer",
|
||||
... 'email': (''.join(
|
||||
... random.choice(string.ascii_letters) for _ in range(10))
|
||||
... + '@example.com'),
|
||||
... 'phone': customer_phone,
|
||||
... 'locale': 'en-CA',
|
||||
... })
|
||||
|
||||
>>> order = tools.create_order({
|
||||
... 'customer': {
|
||||
... 'toAssociate': {
|
||||
... 'id': customer['id'],
|
||||
... },
|
||||
... },
|
||||
... 'shippingAddress': {
|
||||
... 'lastName': "Customer",
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... 'phone': customer_address_phone,
|
||||
... },
|
||||
... 'lineItems': [{
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
... product1.shopify_identifiers[0].shopify_identifier),
|
||||
... 'quantity': 1,
|
||||
... }, {
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
... product1.shopify_identifiers[0].shopify_identifier),
|
||||
... 'quantity': 1,
|
||||
... }, {
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
... variant1.shopify_identifiers[0].shopify_identifier),
|
||||
... 'quantity': 5,
|
||||
... }],
|
||||
... 'shippingLines': [{
|
||||
... 'code': 'SHIP',
|
||||
... 'title': "Shipping",
|
||||
... 'priceSet': {
|
||||
... 'shopMoney': {
|
||||
... 'amount': 4,
|
||||
... 'currencyCode': company.currency.code,
|
||||
... },
|
||||
... },
|
||||
... }],
|
||||
... 'discountCode': {
|
||||
... 'itemFixedDiscountCode': {
|
||||
... 'amountSet': {
|
||||
... 'shopMoney': {
|
||||
... 'amount': 15,
|
||||
... 'currencyCode': company.currency.code,
|
||||
... },
|
||||
... },
|
||||
... 'code': "CODE",
|
||||
... },
|
||||
... },
|
||||
... 'financialStatus': 'AUTHORIZED',
|
||||
... 'transactions': [{
|
||||
... 'kind': 'AUTHORIZATION',
|
||||
... 'status': 'SUCCESS',
|
||||
... 'amountSet': {
|
||||
... 'shopMoney': {
|
||||
... 'amount': 258.98,
|
||||
... 'currencyCode': company.currency.code,
|
||||
... },
|
||||
... },
|
||||
... 'test': True,
|
||||
... }],
|
||||
... })
|
||||
>>> order['totalPriceSet']['presentmentMoney']['amount']
|
||||
'258.98'
|
||||
>>> order['displayFinancialStatus']
|
||||
'AUTHORIZED'
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'UNFULFILLED'
|
||||
|
||||
Run fetch order::
|
||||
|
||||
>>> with config.set_context(shopify_orders=[gid2id(order['id'])]):
|
||||
... cron_fetch_order, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_fetch_order'),
|
||||
... ])
|
||||
... cron_fetch_order.click('run_once')
|
||||
|
||||
>>> sale, = Sale.find([])
|
||||
>>> sale.shopify_tax_adjustment
|
||||
Decimal('0.01')
|
||||
>>> len(sale.lines)
|
||||
4
|
||||
>>> sorted([l.unit_price for l in sale.lines])
|
||||
[Decimal('4.0000'), Decimal('8.5727'), Decimal('8.5727'), Decimal('42.9309')]
|
||||
>>> any(l.product == carrier_product for l in sale.lines)
|
||||
True
|
||||
>>> sale.total_amount
|
||||
Decimal('258.98')
|
||||
>>> len(sale.payments)
|
||||
1
|
||||
>>> payment, = sale.payments
|
||||
>>> payment.state
|
||||
'processing'
|
||||
>>> payment.amount
|
||||
Decimal('0')
|
||||
>>> assertEqual(sale.carrier, carrier2)
|
||||
>>> sale.state
|
||||
'quotation'
|
||||
>>> sale.party.name
|
||||
'Customer'
|
||||
>>> sale.party.lang.code
|
||||
'en'
|
||||
>>> assertTrue(sale.party.email)
|
||||
>>> assertEqual(sale.party.phone.replace(' ', ''), customer_phone.replace('-', ''))
|
||||
>>> address, = sale.party.addresses
|
||||
>>> address_contact_mechanism, = address.contact_mechanisms
|
||||
>>> assertEqual(
|
||||
... address_contact_mechanism.value.replace(' ', ''),
|
||||
... customer_address_phone.replace('-', ''))
|
||||
>>> len(sale.party.contact_mechanisms)
|
||||
3
|
||||
>>> assertTrue(sale.web_status_url)
|
||||
|
||||
Capture full amount::
|
||||
|
||||
>>> transaction = tools.capture_order(
|
||||
... order['id'], 258.98, order['transactions'][0]['id'])
|
||||
|
||||
>>> with config.set_context(shopify_orders=[gid2id(order['id'])]):
|
||||
... cron_update_order, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_update_order'),
|
||||
... ])
|
||||
... cron_update_order.click('run_once')
|
||||
|
||||
>>> sale.reload()
|
||||
>>> len(sale.payments)
|
||||
1
|
||||
>>> payment, = sale.payments
|
||||
>>> payment.state
|
||||
'succeeded'
|
||||
>>> sale.state
|
||||
'processing'
|
||||
>>> len(sale.invoices)
|
||||
0
|
||||
>>> len(sale.party.contact_mechanisms)
|
||||
3
|
||||
|
||||
Make a partial shipment::
|
||||
|
||||
>>> shipment, = sale.shipments
|
||||
>>> move, = [m for m in shipment.inventory_moves if m.product == variant1]
|
||||
>>> move.quantity = 3
|
||||
>>> shipment.click('pick')
|
||||
>>> shipment.click('pack')
|
||||
>>> shipment.click('do')
|
||||
>>> shipment.state
|
||||
'done'
|
||||
|
||||
>>> sale.reload()
|
||||
>>> len(sale.invoices)
|
||||
0
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'PARTIALLY_FULFILLED'
|
||||
>>> len(order['fulfillments'])
|
||||
1
|
||||
>>> order['displayFinancialStatus']
|
||||
'PAID'
|
||||
|
||||
Ship and cancel remaining shipment::
|
||||
|
||||
>>> shipment, = [s for s in sale.shipments if s.state != 'done']
|
||||
>>> shipment.click('pick')
|
||||
>>> shipment.click('pack')
|
||||
>>> shipment.click('ship')
|
||||
>>> shipment.state
|
||||
'shipped'
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'FULFILLED'
|
||||
>>> len(order['fulfillments'])
|
||||
2
|
||||
|
||||
>>> shipment.click('cancel')
|
||||
>>> shipment.state
|
||||
'cancelled'
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'PARTIALLY_FULFILLED'
|
||||
>>> len(order['fulfillments'])
|
||||
2
|
||||
|
||||
>>> shipment_exception = sale.click('handle_shipment_exception')
|
||||
>>> shipment_exception.form.recreate_moves.extend(
|
||||
... shipment_exception.form.ignore_moves.find())
|
||||
>>> shipment_exception.execute('handle')
|
||||
|
||||
Cancel remaining shipment::
|
||||
|
||||
>>> shipment, = [s for s in sale.shipments if s.state not in {'done', 'cancelled'}]
|
||||
>>> shipment.click('cancel')
|
||||
>>> shipment.state
|
||||
'cancelled'
|
||||
|
||||
>>> sale.reload()
|
||||
>>> sale.shipment_state
|
||||
'exception'
|
||||
>>> len(sale.invoices)
|
||||
0
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'PARTIALLY_FULFILLED'
|
||||
>>> len(order['fulfillments'])
|
||||
2
|
||||
>>> order['displayFinancialStatus']
|
||||
'PAID'
|
||||
|
||||
Ignore shipment exception::
|
||||
|
||||
>>> shipment_exception = sale.click('handle_shipment_exception')
|
||||
>>> shipment_exception.form.ignore_moves.extend(
|
||||
... shipment_exception.form.ignore_moves.find())
|
||||
>>> shipment_exception.execute('handle')
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'FULFILLED'
|
||||
>>> len(order['fulfillments'])
|
||||
2
|
||||
>>> order['displayFinancialStatus']
|
||||
'PARTIALLY_REFUNDED'
|
||||
|
||||
>>> sale.reload()
|
||||
>>> invoice, = sale.invoices
|
||||
>>> invoice.total_amount
|
||||
Decimal('164.53')
|
||||
>>> payment, = sale.payments
|
||||
>>> payment.state
|
||||
'succeeded'
|
||||
|
||||
Correct taxes as partial invoice can get rounding gap::
|
||||
|
||||
>>> tax_line, = invoice.taxes
|
||||
>>> tax_line.amount += payment.amount - invoice.total_amount
|
||||
>>> invoice.save()
|
||||
>>> assertEqual(invoice.total_amount, payment.amount)
|
||||
|
||||
Post invoice::
|
||||
|
||||
>>> invoice.click('post')
|
||||
>>> invoice.state
|
||||
'paid'
|
||||
>>> sale.reload()
|
||||
>>> sale.state
|
||||
'done'
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> bool(order['closed'])
|
||||
True
|
||||
|
||||
Clean up::
|
||||
|
||||
>>> tools.delete_order(order['id'])
|
||||
>>> for product in ShopifyIdentifier.find(
|
||||
... [('record', 'like', 'product.template,%')]):
|
||||
... tools.delete_product(id2gid('Product', product.shopify_identifier))
|
||||
>>> for category in ShopifyIdentifier.find(
|
||||
... [('record', 'like', 'product.category,%')]):
|
||||
... tools.delete_collection(id2gid('Collection', category.shopify_identifier))
|
||||
>>> for _ in range(MAX_SLEEP):
|
||||
... try:
|
||||
... tools.delete_customer(customer['id'])
|
||||
... except Exception:
|
||||
... time.sleep(FETCH_SLEEP)
|
||||
... else:
|
||||
... break
|
||||
|
||||
>>> shopify.ShopifyResource.clear_session()
|
||||
@@ -0,0 +1,296 @@
|
||||
=====================================
|
||||
Web Shop Shopify Product Kit Scenario
|
||||
=====================================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> import os
|
||||
>>> import random
|
||||
>>> import string
|
||||
>>> import time
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> import shopify
|
||||
>>> from shopify.api_version import ApiVersion
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company, get_company
|
||||
>>> from trytond.modules.web_shop_shopify.common import gid2id, id2gid
|
||||
>>> from trytond.modules.web_shop_shopify.tests import tools
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
|
||||
>>> FETCH_SLEEP, MAX_SLEEP = 1, 10
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules([
|
||||
... 'web_shop_shopify',
|
||||
... 'product_kit',
|
||||
... ],
|
||||
... create_company, create_chart)
|
||||
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> Category = Model.get('product.category')
|
||||
>>> Cron = Model.get('ir.cron')
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> Product = Model.get('product.product')
|
||||
>>> ProductTemplate = Model.get('product.template')
|
||||
>>> Sale = Model.get('sale.sale')
|
||||
>>> ShopifyIdentifier = Model.get('web.shop.shopify_identifier')
|
||||
>>> Uom = Model.get('product.uom')
|
||||
>>> WebShop = Model.get('web.shop')
|
||||
|
||||
Get company::
|
||||
|
||||
>>> company = get_company()
|
||||
|
||||
Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Create payment journal::
|
||||
|
||||
>>> shopify_account = Account(parent=accounts['receivable'].parent)
|
||||
>>> shopify_account.name = "Shopify"
|
||||
>>> shopify_account.type = accounts['receivable'].type
|
||||
>>> shopify_account.reconcile = True
|
||||
>>> shopify_account.save()
|
||||
|
||||
>>> payment_journal = PaymentJournal()
|
||||
>>> payment_journal.name = "Shopify"
|
||||
>>> payment_journal.process_method = 'shopify'
|
||||
>>> payment_journal.save()
|
||||
|
||||
Define a web shop::
|
||||
|
||||
>>> web_shop = WebShop(name="Web Shop")
|
||||
>>> web_shop.type = 'shopify'
|
||||
>>> web_shop.shopify_url = os.getenv('SHOPIFY_URL')
|
||||
>>> web_shop.shopify_password = os.getenv('SHOPIFY_PASSWORD')
|
||||
>>> web_shop.shopify_version = sorted(ApiVersion.versions, reverse=True)[1]
|
||||
>>> shop_warehouse = web_shop.shopify_warehouses.new()
|
||||
>>> shop_warehouse.warehouse, = Location.find([('type', '=', 'warehouse')])
|
||||
>>> shopify_payment_journal = web_shop.shopify_payment_journals.new()
|
||||
>>> shopify_payment_journal.journal = payment_journal
|
||||
>>> web_shop.save()
|
||||
|
||||
>>> shopify.ShopifyResource.activate_session(shopify.Session(
|
||||
... web_shop.shopify_url,
|
||||
... web_shop.shopify_version,
|
||||
... web_shop.shopify_password))
|
||||
|
||||
>>> location = tools.get_location()
|
||||
|
||||
>>> shop_warehouse, = web_shop.shopify_warehouses
|
||||
>>> shop_warehouse.shopify_id = str(gid2id(location['id']))
|
||||
>>> web_shop.save()
|
||||
|
||||
Create categories::
|
||||
|
||||
>>> category = Category(name="Category")
|
||||
>>> category.save()
|
||||
|
||||
>>> account_category = Category(name="Account Category")
|
||||
>>> account_category.accounting = True
|
||||
>>> account_category.account_expense = accounts['expense']
|
||||
>>> account_category.account_revenue = accounts['revenue']
|
||||
>>> account_category.save()
|
||||
|
||||
Create product kit::
|
||||
|
||||
>>> unit, = Uom.find([('name', '=', "Unit")])
|
||||
>>> meter, = Uom.find([('name', '=', "Meter")])
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = "Component 1"
|
||||
>>> template.default_uom = unit
|
||||
>>> template.type = 'goods'
|
||||
>>> template.save()
|
||||
>>> component1, = template.products
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = "Component 2"
|
||||
>>> template.default_uom = meter
|
||||
>>> template.type = 'goods'
|
||||
>>> template.save()
|
||||
>>> component2, = template.products
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = "Product Kit"
|
||||
>>> template.default_uom = unit
|
||||
>>> template.type = 'kit'
|
||||
>>> template.salable = True
|
||||
>>> template.list_price = Decimal('100.0000')
|
||||
>>> template.account_category = account_category
|
||||
>>> template.categories.append(Category(category.id))
|
||||
>>> template.save()
|
||||
>>> product, = template.products
|
||||
>>> product.suffix_code = 'PROD'
|
||||
>>> product.save()
|
||||
|
||||
>>> _ = template.components.new(product=component1, quantity=2)
|
||||
>>> _ = template.components.new(product=component2, quantity=5)
|
||||
>>> template.save()
|
||||
|
||||
Set categories, products and attributes to web shop::
|
||||
|
||||
>>> web_shop.categories.append(Category(category.id))
|
||||
>>> web_shop.products.append(Product(product.id))
|
||||
>>> web_shop.save()
|
||||
|
||||
Run update product::
|
||||
|
||||
>>> cron_update_product, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_update_product'),
|
||||
... ])
|
||||
>>> cron_update_product.click('run_once')
|
||||
|
||||
Create an order on Shopify::
|
||||
|
||||
>>> customer = tools.create_customer({
|
||||
... 'lastName': "Customer",
|
||||
... 'email': (''.join(
|
||||
... random.choice(string.ascii_letters) for _ in range(10))
|
||||
... + '@example.com'),
|
||||
... 'addresses': [{
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... }],
|
||||
... })
|
||||
|
||||
>>> order = tools.create_order({
|
||||
... 'customerId': customer['id'],
|
||||
... 'lineItems': [{
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
... product.shopify_identifiers[0].shopify_identifier),
|
||||
... 'quantity': 3,
|
||||
... }],
|
||||
... 'financialStatus': 'AUTHORIZED',
|
||||
... 'transactions': [{
|
||||
... 'kind': 'AUTHORIZATION',
|
||||
... 'status': 'SUCCESS',
|
||||
... 'amountSet': {
|
||||
... 'shopMoney': {
|
||||
... 'amount': 300,
|
||||
... 'currencyCode': company.currency.code,
|
||||
... },
|
||||
... },
|
||||
... 'test': True,
|
||||
... }],
|
||||
... })
|
||||
>>> order['totalPriceSet']['presentmentMoney']['amount']
|
||||
'300.0'
|
||||
>>> order['displayFinancialStatus']
|
||||
'AUTHORIZED'
|
||||
|
||||
>>> transaction = tools.capture_order(
|
||||
... order['id'], 300, order['transactions'][0]['id'])
|
||||
|
||||
Run fetch order::
|
||||
|
||||
>>> with config.set_context(shopify_orders=[gid2id(order['id'])]):
|
||||
... cron_fetch_order, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_fetch_order'),
|
||||
... ])
|
||||
... for _ in range(MAX_SLEEP):
|
||||
... cron_fetch_order.click('run_once')
|
||||
... if Sale.find([]):
|
||||
... break
|
||||
... time.sleep(FETCH_SLEEP)
|
||||
|
||||
>>> sale, = Sale.find([])
|
||||
>>> sale.total_amount
|
||||
Decimal('300.00')
|
||||
>>> sale_line, = sale.lines
|
||||
>>> sale_line.quantity
|
||||
3.0
|
||||
|
||||
Make a partial shipment of components::
|
||||
|
||||
>>> shipment, = sale.shipments
|
||||
>>> for move in shipment.inventory_moves:
|
||||
... if move.product == component1:
|
||||
... move.quantity = 4
|
||||
... else:
|
||||
... move.quantity = 0
|
||||
>>> shipment.click('pick')
|
||||
>>> shipment.click('pack')
|
||||
>>> shipment.click('do')
|
||||
>>> shipment.state
|
||||
'done'
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'PARTIALLY_FULFILLED'
|
||||
>>> fulfillment, = order['fulfillments']
|
||||
>>> fulfillment_line_item, = fulfillment['fulfillmentLineItems']['nodes']
|
||||
>>> fulfillment_line_item['quantity']
|
||||
2
|
||||
|
||||
Make a partial shipment for a single component::
|
||||
|
||||
>>> sale.reload()
|
||||
>>> _, shipment = sale.shipments
|
||||
>>> for move in shipment.inventory_moves:
|
||||
... if move.product == component1:
|
||||
... move.quantity = 0
|
||||
... else:
|
||||
... move.quantity = 10
|
||||
>>> shipment.click('pick')
|
||||
>>> shipment.click('pack')
|
||||
>>> shipment.click('do')
|
||||
>>> shipment.state
|
||||
'done'
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'PARTIALLY_FULFILLED'
|
||||
>>> fulfillment, = order['fulfillments']
|
||||
>>> fulfillment_line_item, = fulfillment['fulfillmentLineItems']['nodes']
|
||||
>>> fulfillment_line_item['quantity']
|
||||
2
|
||||
|
||||
Ship remaining::
|
||||
|
||||
>>> sale.reload()
|
||||
>>> _, _, shipment = sale.shipments
|
||||
>>> shipment.click('pick')
|
||||
>>> shipment.click('pack')
|
||||
>>> shipment.click('do')
|
||||
>>> shipment.state
|
||||
'done'
|
||||
|
||||
>>> order = tools.get_order(order['id'])
|
||||
>>> order['displayFulfillmentStatus']
|
||||
'FULFILLED'
|
||||
|
||||
Clean up::
|
||||
|
||||
>>> tools.delete_order(order['id'])
|
||||
>>> for product in ShopifyIdentifier.find(
|
||||
... [('record', 'like', 'product.template,%')]):
|
||||
... tools.delete_product(id2gid('Product', product.shopify_identifier))
|
||||
>>> for category in ShopifyIdentifier.find(
|
||||
... [('record', 'like', 'product.category,%')]):
|
||||
... tools.delete_collection(id2gid('Collection', category.shopify_identifier))
|
||||
>>> for _ in range(MAX_SLEEP):
|
||||
... try:
|
||||
... tools.delete_customer(customer['id'])
|
||||
... except Exception:
|
||||
... time.sleep(FETCH_SLEEP)
|
||||
... else:
|
||||
... break
|
||||
|
||||
>>> shopify.ShopifyResource.clear_session()
|
||||
@@ -0,0 +1,242 @@
|
||||
========================================
|
||||
Web Shop Shopify Secondary Unit Scenario
|
||||
========================================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> import os
|
||||
>>> import random
|
||||
>>> import string
|
||||
>>> import time
|
||||
>>> from decimal import Decimal
|
||||
|
||||
>>> import shopify
|
||||
>>> from shopify.api_version import ApiVersion
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.account.tests.tools import (
|
||||
... create_chart, create_fiscalyear, get_accounts)
|
||||
>>> from trytond.modules.account_invoice.tests.tools import (
|
||||
... set_fiscalyear_invoice_sequences)
|
||||
>>> from trytond.modules.company.tests.tools import create_company, get_company
|
||||
>>> from trytond.modules.web_shop_shopify.common import gid2id, id2gid
|
||||
>>> from trytond.modules.web_shop_shopify.tests import tools
|
||||
>>> from trytond.tests.tools import activate_modules, assertEqual
|
||||
|
||||
>>> FETCH_SLEEP, MAX_SLEEP = 1, 10
|
||||
|
||||
Activate modules::
|
||||
|
||||
>>> config = activate_modules([
|
||||
... 'web_shop_shopify',
|
||||
... 'sale_secondary_unit',
|
||||
... ],
|
||||
... create_company, create_chart)
|
||||
|
||||
>>> Account = Model.get('account.account')
|
||||
>>> Category = Model.get('product.category')
|
||||
>>> Cron = Model.get('ir.cron')
|
||||
>>> Location = Model.get('stock.location')
|
||||
>>> PaymentJournal = Model.get('account.payment.journal')
|
||||
>>> Product = Model.get('product.product')
|
||||
>>> ProductTemplate = Model.get('product.template')
|
||||
>>> Sale = Model.get('sale.sale')
|
||||
>>> ShopifyIdentifier = Model.get('web.shop.shopify_identifier')
|
||||
>>> Uom = Model.get('product.uom')
|
||||
>>> WebShop = Model.get('web.shop')
|
||||
|
||||
Get company::
|
||||
|
||||
>>> company = get_company()
|
||||
|
||||
Get accounts::
|
||||
|
||||
>>> accounts = get_accounts()
|
||||
|
||||
Create fiscal year::
|
||||
|
||||
>>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear())
|
||||
>>> fiscalyear.click('create_period')
|
||||
|
||||
Create payment journal::
|
||||
|
||||
>>> shopify_account = Account(parent=accounts['receivable'].parent)
|
||||
>>> shopify_account.name = "Shopify"
|
||||
>>> shopify_account.type = accounts['receivable'].type
|
||||
>>> shopify_account.reconcile = True
|
||||
>>> shopify_account.save()
|
||||
|
||||
>>> payment_journal = PaymentJournal()
|
||||
>>> payment_journal.name = "Shopify"
|
||||
>>> payment_journal.process_method = 'shopify'
|
||||
>>> payment_journal.save()
|
||||
|
||||
Define a web shop::
|
||||
|
||||
>>> web_shop = WebShop(name="Web Shop")
|
||||
>>> web_shop.type = 'shopify'
|
||||
>>> web_shop.shopify_url = os.getenv('SHOPIFY_URL')
|
||||
>>> web_shop.shopify_password = os.getenv('SHOPIFY_PASSWORD')
|
||||
>>> web_shop.shopify_version = sorted(ApiVersion.versions, reverse=True)[1]
|
||||
>>> shop_warehouse = web_shop.shopify_warehouses.new()
|
||||
>>> shop_warehouse.warehouse, = Location.find([('type', '=', 'warehouse')])
|
||||
>>> shopify_payment_journal = web_shop.shopify_payment_journals.new()
|
||||
>>> shopify_payment_journal.journal = payment_journal
|
||||
>>> web_shop.save()
|
||||
|
||||
>>> shopify.ShopifyResource.activate_session(shopify.Session(
|
||||
... web_shop.shopify_url,
|
||||
... web_shop.shopify_version,
|
||||
... web_shop.shopify_password))
|
||||
|
||||
>>> location = tools.get_location()
|
||||
|
||||
>>> shop_warehouse, = web_shop.shopify_warehouses
|
||||
>>> shop_warehouse.shopify_id = str(gid2id(location['id']))
|
||||
>>> web_shop.save()
|
||||
|
||||
Create categories::
|
||||
|
||||
>>> category = Category(name="Category")
|
||||
>>> category.save()
|
||||
|
||||
>>> account_category = Category(name="Account Category")
|
||||
>>> account_category.accounting = True
|
||||
>>> account_category.account_expense = accounts['expense']
|
||||
>>> account_category.account_revenue = accounts['revenue']
|
||||
>>> account_category.save()
|
||||
|
||||
Create product::
|
||||
|
||||
>>> unit, = Uom.find([('name', '=', "Unit")])
|
||||
>>> unit.digits = 2
|
||||
>>> unit.rounding = 0.01
|
||||
>>> unit.save()
|
||||
>>> cm, = Uom.find([('name', '=', "Centimeter")])
|
||||
>>> cm, = cm.duplicate(default={'digits': 0, 'rounding': 1})
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = "Product 1"
|
||||
>>> template.default_uom = unit
|
||||
>>> template.type = 'goods'
|
||||
>>> template.salable = True
|
||||
>>> template.sale_secondary_uom = cm
|
||||
>>> template.sale_secondary_uom_factor = 25
|
||||
>>> template.list_price = Decimal('100.0000')
|
||||
>>> template.account_category = account_category
|
||||
>>> template.categories.append(Category(category.id))
|
||||
>>> template.save()
|
||||
>>> product, = template.products
|
||||
>>> product.suffix_code = 'PROD'
|
||||
>>> product.save()
|
||||
|
||||
Set categories, products and attributes to web shop::
|
||||
|
||||
>>> web_shop.categories.append(Category(category.id))
|
||||
>>> web_shop.products.append(Product(product.id))
|
||||
>>> web_shop.save()
|
||||
|
||||
Run update product::
|
||||
|
||||
>>> cron_update_product, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_update_product'),
|
||||
... ])
|
||||
>>> cron_update_product.click('run_once')
|
||||
|
||||
Create an order on Shopify::
|
||||
|
||||
>>> customer = tools.create_customer({
|
||||
... 'lastName': "Customer",
|
||||
... 'email': (''.join(
|
||||
... random.choice(string.ascii_letters) for _ in range(10))
|
||||
... + '@example.com'),
|
||||
... 'addresses': [{
|
||||
... 'address1': "Street",
|
||||
... 'city': "City",
|
||||
... 'countryCode': 'BE',
|
||||
... }],
|
||||
... })
|
||||
|
||||
>>> order = tools.create_order({
|
||||
... 'customerId': customer['id'],
|
||||
... 'lineItems': [{
|
||||
... 'variantId': id2gid(
|
||||
... 'ProductVariant',
|
||||
... product.shopify_identifiers[0].shopify_identifier),
|
||||
... 'quantity': 50,
|
||||
... }],
|
||||
... 'shippingLines': [{
|
||||
... 'code': 'SHIP',
|
||||
... 'title': "Shipping",
|
||||
... 'priceSet': {
|
||||
... 'shopMoney': {
|
||||
... 'amount': 2,
|
||||
... 'currencyCode': company.currency.code,
|
||||
... },
|
||||
... },
|
||||
... }],
|
||||
... 'financialStatus': 'AUTHORIZED',
|
||||
... 'transactions': [{
|
||||
... 'kind': 'AUTHORIZATION',
|
||||
... 'status': 'SUCCESS',
|
||||
... 'amountSet': {
|
||||
... 'shopMoney': {
|
||||
... 'amount': 202,
|
||||
... 'currencyCode': company.currency.code,
|
||||
... },
|
||||
... },
|
||||
... 'test': True,
|
||||
... }],
|
||||
... })
|
||||
>>> order['totalPriceSet']['presentmentMoney']['amount']
|
||||
'202.0'
|
||||
>>> order['displayFinancialStatus']
|
||||
'AUTHORIZED'
|
||||
|
||||
Run fetch order::
|
||||
|
||||
>>> with config.set_context(shopify_orders=[gid2id(order['id'])]):
|
||||
... cron_fetch_order, = Cron.find([
|
||||
... ('method', '=', 'web.shop|shopify_fetch_order'),
|
||||
... ])
|
||||
... for _ in range(MAX_SLEEP):
|
||||
... cron_fetch_order.click('run_once')
|
||||
... if Sale.find([]):
|
||||
... break
|
||||
... time.sleep(FETCH_SLEEP)
|
||||
|
||||
>>> sale, = Sale.find([])
|
||||
>>> len(sale.lines)
|
||||
2
|
||||
>>> sale.total_amount
|
||||
Decimal('202.00')
|
||||
>>> line, = [l for l in sale.lines if l.product]
|
||||
>>> line.quantity
|
||||
2.0
|
||||
>>> assertEqual(line.unit, unit)
|
||||
>>> line.unit_price
|
||||
Decimal('100.0000')
|
||||
>>> line.secondary_quantity
|
||||
50.0
|
||||
>>> assertEqual(line.secondary_unit, cm)
|
||||
>>> line.secondary_unit_price
|
||||
Decimal('4.0000')
|
||||
|
||||
Clean up::
|
||||
|
||||
>>> tools.delete_order(order['id'])
|
||||
>>> for product in ShopifyIdentifier.find(
|
||||
... [('record', 'like', 'product.template,%')]):
|
||||
... tools.delete_product(id2gid('Product', product.shopify_identifier))
|
||||
>>> for category in ShopifyIdentifier.find(
|
||||
... [('record', 'like', 'product.category,%')]):
|
||||
... tools.delete_collection(id2gid('Collection', category.shopify_identifier))
|
||||
>>> for _ in range(MAX_SLEEP):
|
||||
... try:
|
||||
... tools.delete_customer(customer['id'])
|
||||
... except Exception:
|
||||
... time.sleep(FETCH_SLEEP)
|
||||
... else:
|
||||
... break
|
||||
|
||||
>>> shopify.ShopifyResource.clear_session()
|
||||
60
modules/web_shop_shopify/tests/test_graphql.py
Normal file
60
modules/web_shop_shopify/tests/test_graphql.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# 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 unittest
|
||||
|
||||
from trytond.modules.web_shop_shopify.graphql import deep_merge, selection
|
||||
|
||||
|
||||
class GraphQLTestCase(unittest.TestCase):
|
||||
"Test GraphQL library"
|
||||
|
||||
def test_deep_merge(self):
|
||||
"Test deep_merge"
|
||||
a = {
|
||||
'id': None,
|
||||
'firstName': None,
|
||||
'birthday': {
|
||||
'month': None,
|
||||
},
|
||||
}
|
||||
b = {
|
||||
'id': None,
|
||||
'lastName': None,
|
||||
'birthday': {
|
||||
'day': None,
|
||||
},
|
||||
}
|
||||
self.assertEqual(deep_merge(a, b), {
|
||||
'id': None,
|
||||
'firstName': None,
|
||||
'lastName': None,
|
||||
'birthday': {
|
||||
'month': None,
|
||||
'day': None,
|
||||
},
|
||||
})
|
||||
|
||||
def test_selection(self):
|
||||
"Test selection"
|
||||
for fields, result in [
|
||||
({'id': None}, '{\nid\n}'),
|
||||
({
|
||||
'id': None,
|
||||
'firstName': None,
|
||||
'lastName': None},
|
||||
'{\nid\nfirstName\nlastName\n}'),
|
||||
({
|
||||
'id': None,
|
||||
'firstName': None,
|
||||
'lastName': None,
|
||||
'birthday': {
|
||||
'month': None,
|
||||
'day': None,
|
||||
},
|
||||
},
|
||||
'{\nid\nfirstName\nlastName\n'
|
||||
'birthday {\nmonth\nday\n}\n}'),
|
||||
]:
|
||||
with self.subTest(fields=fields):
|
||||
self.assertEqual(selection(fields), result)
|
||||
27
modules/web_shop_shopify/tests/test_module.py
Normal file
27
modules/web_shop_shopify/tests/test_module.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.modules.party.tests import PartyCheckReplaceMixin
|
||||
from trytond.modules.web_shop_shopify.common import gid2id, id2gid
|
||||
from trytond.tests.test_tryton import ModuleTestCase
|
||||
|
||||
|
||||
class WebShopShopifyTestCase(PartyCheckReplaceMixin, ModuleTestCase):
|
||||
'Test Web Shop Shopify module'
|
||||
module = 'web_shop_shopify'
|
||||
extras = [
|
||||
'carrier', 'customs', 'product_image', 'product_image_attribute',
|
||||
'product_kit', 'product_measurements', 'sale_discount',
|
||||
'sale_invoice_grouping', 'sale_secondary_unit', 'sale_shipment_cost',
|
||||
'stock_package_shipping']
|
||||
|
||||
def test_id2gid(self):
|
||||
"Test ID to GID"
|
||||
self.assertEqual(id2gid('Product', '123'), 'gid://shopify/Product/123')
|
||||
|
||||
def test_gid2id(self):
|
||||
"Test GID to ID"
|
||||
self.assertEqual(gid2id('gid://shopify/Product/123'), 123)
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
18
modules/web_shop_shopify/tests/test_scenario.py
Normal file
18
modules/web_shop_shopify/tests/test_scenario.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# 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 os
|
||||
|
||||
from trytond.tests.test_tryton import TEST_NETWORK, load_doc_tests
|
||||
|
||||
|
||||
def load_tests(*args, **kwargs):
|
||||
if (not TEST_NETWORK
|
||||
or not (os.getenv('SHOPIFY_PASSWORD')
|
||||
and os.getenv('SHOPIFY_URL'))):
|
||||
kwargs.setdefault('skips', set()).update({
|
||||
'scenario_web_shop_shopify.rst',
|
||||
'scenario_web_shop_shopify_secondary_unit.rst',
|
||||
'scenario_web_shop_shopify_product_kit.rst',
|
||||
})
|
||||
return load_doc_tests(__name__, __file__, *args, **kwargs)
|
||||
216
modules/web_shop_shopify/tests/tools.py
Normal file
216
modules/web_shop_shopify/tests/tools.py
Normal file
@@ -0,0 +1,216 @@
|
||||
# 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 trytond.modules.web_shop_shopify.common import id2gid
|
||||
from trytond.modules.web_shop_shopify.shopify_retry import GraphQLException
|
||||
|
||||
|
||||
def get_location():
|
||||
return shopify.GraphQL().execute('''{
|
||||
locations(first:1) {
|
||||
nodes {
|
||||
id
|
||||
}
|
||||
}
|
||||
}''')['data']['locations']['nodes'][0]
|
||||
|
||||
|
||||
def get_inventory_levels(location):
|
||||
return shopify.GraphQL().execute('''query InventoryLevels($id: ID!) {
|
||||
location(id: $id) {
|
||||
inventoryLevels(first: 250) {
|
||||
nodes {
|
||||
item {
|
||||
id
|
||||
}
|
||||
quantities(names: ["available"]) {
|
||||
name
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'id': location['id'],
|
||||
})['data']['location']['inventoryLevels']['nodes']
|
||||
|
||||
|
||||
def get_product(id):
|
||||
return shopify.GraphQL().execute('''query Product($id: ID!) {
|
||||
product(id: $id) {
|
||||
status
|
||||
}
|
||||
}''', {
|
||||
'id': id2gid('Product', id),
|
||||
})['data']['product']
|
||||
|
||||
|
||||
def delete_product(id):
|
||||
result = shopify.GraphQL().execute('''mutation productDelete($id: ID!) {
|
||||
productDelete(input: {id: $id}) {
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'id': id,
|
||||
})['data']['productDelete']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
|
||||
|
||||
def delete_collection(id):
|
||||
result = shopify.GraphQL().execute('''mutation collectionDelete($id: ID!) {
|
||||
collectionDelete(input: {id: $id}) {
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'id': id,
|
||||
})['data']['collectionDelete']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
|
||||
|
||||
def create_customer(customer):
|
||||
result = shopify.GraphQL().execute('''mutation customerCreate(
|
||||
$input: CustomerInput!) {
|
||||
customerCreate(input: $input) {
|
||||
customer {
|
||||
id
|
||||
}
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'input': customer,
|
||||
})['data']['customerCreate']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
return result['customer']
|
||||
|
||||
|
||||
def delete_customer(id):
|
||||
result = shopify.GraphQL().execute('''mutation customerDelete($id: ID!) {
|
||||
customerDelete(input: {id: $id}) {
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'id': id,
|
||||
})['data']['customerDelete']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
|
||||
|
||||
def create_order(order):
|
||||
result = shopify.GraphQL().execute('''mutation orderCreate(
|
||||
$order: OrderCreateOrderInput!) {
|
||||
orderCreate(order: $order) {
|
||||
order {
|
||||
id
|
||||
totalPriceSet {
|
||||
presentmentMoney {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
displayFinancialStatus
|
||||
displayFulfillmentStatus
|
||||
transactions(first: 250) {
|
||||
id
|
||||
}
|
||||
fulfillments {
|
||||
id
|
||||
}
|
||||
closed
|
||||
}
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'order': order,
|
||||
})['data']['orderCreate']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
return result['order']
|
||||
|
||||
|
||||
def get_order(id):
|
||||
return shopify.GraphQL().execute('''query Order($id: ID!) {
|
||||
order(id: $id) {
|
||||
id
|
||||
totalPriceSet {
|
||||
presentmentMoney {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
displayFinancialStatus
|
||||
displayFulfillmentStatus
|
||||
transactions(first: 250) {
|
||||
id
|
||||
}
|
||||
fulfillments {
|
||||
id
|
||||
fulfillmentLineItems(first: 10) {
|
||||
nodes {
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
closed
|
||||
}
|
||||
}''', {
|
||||
'id': id,
|
||||
})['data']['order']
|
||||
|
||||
|
||||
def capture_order(id, amount, parent_transaction_id):
|
||||
result = shopify.GraphQL().execute('''mutation orderCapture(
|
||||
$input: OrderCaptureInput!) {
|
||||
orderCapture(input: $input) {
|
||||
transaction {
|
||||
id
|
||||
}
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'input': {
|
||||
'amount': amount,
|
||||
'id': id,
|
||||
'parentTransactionId': parent_transaction_id,
|
||||
},
|
||||
})['data']['orderCapture']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
return result['transaction']
|
||||
|
||||
|
||||
def delete_order(id):
|
||||
result = shopify.GraphQL().execute('''mutation orderDelete($orderId: ID!) {
|
||||
orderDelete(orderId: $orderId) {
|
||||
userErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}''', {
|
||||
'orderId': id,
|
||||
})['data']['orderDelete']
|
||||
if errors := result.get('userErrors'):
|
||||
raise GraphQLException({'errors': errors})
|
||||
Reference in New Issue
Block a user