Initial import from Docker volume
This commit is contained in:
717
modules/web_shop_shopify/web.py
Executable file
717
modules/web_shop_shopify/web.py
Executable file
@@ -0,0 +1,717 @@
|
||||
# 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 datetime as dt
|
||||
import urllib.parse
|
||||
from decimal import Decimal
|
||||
|
||||
import pyactiveresource
|
||||
import shopify
|
||||
from shopify.api_version import ApiVersion
|
||||
|
||||
from trytond.cache import Cache
|
||||
from trytond.config import config
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
MatchMixin, ModelSQL, ModelView, Unique, fields, sequence_ordered)
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
from trytond.tools import grouped_slice
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.url import http_host
|
||||
|
||||
from .common import IdentifierMixin, IdentifiersMixin
|
||||
from .exceptions import ShopifyError
|
||||
|
||||
EDIT_ORDER_DELAY = dt.timedelta(days=60 + 1)
|
||||
|
||||
|
||||
class Shop(metaclass=PoolMeta):
|
||||
__name__ = 'web.shop'
|
||||
|
||||
_states = {
|
||||
'required': Eval('type') == 'shopify',
|
||||
'invisible': Eval('type') != 'shopify',
|
||||
}
|
||||
|
||||
shopify_url = fields.Char("Shop URL", states=_states)
|
||||
shopify_version = fields.Selection(
|
||||
'get_shopify_versions', "Version", states=_states)
|
||||
shopify_password = fields.Char("Access Token", states=_states, strip=False)
|
||||
shopify_webhook_shared_secret = fields.Char(
|
||||
"Webhook Shared Secret", strip=False,
|
||||
states={
|
||||
'invisible': _states['invisible'],
|
||||
})
|
||||
shopify_webhook_endpoint_order = fields.Function(
|
||||
fields.Char(
|
||||
"Webhook Order Endpoint",
|
||||
help="The URL to be called by Shopify for Order events."),
|
||||
'on_change_with_shopify_webhook_endpoint_order')
|
||||
shopify_warehouses = fields.One2Many(
|
||||
'web.shop-stock.location', 'shop', "Warehouses", states=_states)
|
||||
shopify_payment_journals = fields.One2Many(
|
||||
'web.shop.shopify_payment_journal', 'shop', "Payment Journals",
|
||||
states=_states)
|
||||
|
||||
del _states
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.type.selection.append(('shopify', "Shopify"))
|
||||
invisible = Eval('type') == 'shopify'
|
||||
for field in [cls.attributes, cls.attributes_removed, cls.warehouses]:
|
||||
if field.states.get('invisible'):
|
||||
field.states['invisible'] |= invisible
|
||||
else:
|
||||
field.states['invisible'] = invisible
|
||||
|
||||
@classmethod
|
||||
def get_shopify_versions(cls):
|
||||
return [(None, "")] + sorted(
|
||||
((v, v) for v in ApiVersion.versions), reverse=True)
|
||||
|
||||
@fields.depends('name')
|
||||
def on_change_with_shopify_webhook_endpoint_order(self, name=None):
|
||||
if not self.name:
|
||||
return
|
||||
url_part = {
|
||||
'database_name': Transaction().database.name,
|
||||
'shop': self.name,
|
||||
}
|
||||
return http_host() + (
|
||||
urllib.parse.quote(
|
||||
'/%(database_name)s/web_shop_shopify/webhook/%(shop)s/order' %
|
||||
url_part))
|
||||
|
||||
@classmethod
|
||||
def view_attributes(cls):
|
||||
return super().view_attributes() + [
|
||||
('//page[@id="shopify"]', 'states', {
|
||||
'invisible': Eval('type') != 'shopify',
|
||||
}),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def validate_fields(cls, shops, field_names):
|
||||
super().validate_fields(shops, field_names)
|
||||
if field_names & {'type', 'products'}:
|
||||
for shop in shops:
|
||||
if shop.type == 'shopify':
|
||||
for product in shop.products:
|
||||
shop._shopify_check_product(product)
|
||||
|
||||
def _shopify_check_product(self, product):
|
||||
if not product.template.shopify_uom:
|
||||
shopify_uom = product.template.get_shopify_uom()
|
||||
if shopify_uom.digits:
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.'
|
||||
'msg_product_shopify_uom_digits',
|
||||
product=product.rec_name))
|
||||
|
||||
@property
|
||||
def to_sync(self):
|
||||
result = super().to_sync
|
||||
if self.type == 'shopify':
|
||||
result = True
|
||||
return result
|
||||
|
||||
def get_sale(self, party=None):
|
||||
sale = super().get_sale(party=party)
|
||||
if self.type == 'shopify':
|
||||
sale.invoice_method = 'shipment'
|
||||
return sale
|
||||
|
||||
def shopify_session(self):
|
||||
return shopify.Session.temp(
|
||||
self.shopify_url, self.shopify_version, self.shopify_password)
|
||||
|
||||
def get_payment_journal(self, currency_code, pattern):
|
||||
for payment_journal in self.shopify_payment_journals:
|
||||
if (payment_journal.journal.currency.code == currency_code
|
||||
and payment_journal.match(pattern)):
|
||||
return payment_journal.journal
|
||||
|
||||
def managed_metafields(self):
|
||||
return set()
|
||||
|
||||
def __sync_metafields(self, resource, metafields):
|
||||
metafields = metafields.copy()
|
||||
managed_metafields = self.managed_metafields()
|
||||
assert metafields.keys() <= managed_metafields
|
||||
for metafield in resource.metafields():
|
||||
key = '.'.join([metafield.namespace, metafield.key])
|
||||
value = metafield.to_dict()
|
||||
if key not in metafields:
|
||||
if key in managed_metafields:
|
||||
metafield.destroy()
|
||||
elif metafields[key] != value:
|
||||
for k, v in metafields.pop(key).items():
|
||||
setattr(metafield, k, v)
|
||||
metafield.save()
|
||||
for key, value in metafields.items():
|
||||
namespace, key = key.split('.', 1)
|
||||
value['namespace'] = namespace
|
||||
value['key'] = key
|
||||
resource.add_metafield(shopify.Metafield(value))
|
||||
|
||||
@classmethod
|
||||
def shopify_update_product(cls, shops=None):
|
||||
"""Update Shopify Products
|
||||
|
||||
The transaction is committed after the creation of each new resource.
|
||||
"""
|
||||
pool = Pool()
|
||||
InventoryItem = pool.get('product.shopify_inventory_item')
|
||||
transaction = Transaction()
|
||||
if shops is None:
|
||||
shops = cls.search([
|
||||
('type', '=', 'shopify'),
|
||||
])
|
||||
for shop in shops:
|
||||
with shop.shopify_session():
|
||||
shopify_shop = shopify.Shop.current()
|
||||
shop_language = (
|
||||
shop.language.code if shop.language
|
||||
else transaction.language)
|
||||
categories = shop.get_categories()
|
||||
products, prices, taxes = shop.get_products()
|
||||
if shopify_shop.currency.lower() != shop.currency.code.lower():
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.msg_shop_currency_different',
|
||||
shop=shop.rec_name,
|
||||
shop_currency=shop.currency.code,
|
||||
shopify_currency=shopify_shop.currency))
|
||||
if (shopify_shop.primary_locale.lower()
|
||||
!= shop_language.lower()):
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.msg_shop_locale_different',
|
||||
shop=shop.rec_name,
|
||||
shop_language=shop_language,
|
||||
shopify_primary_locale=shopify_shop.primary_locale
|
||||
))
|
||||
|
||||
for category in categories:
|
||||
shop.__shopify_update_category(category)
|
||||
|
||||
categories = set(categories)
|
||||
inventory_items = InventoryItem.browse(products)
|
||||
for product, inventory_item in zip(products, inventory_items):
|
||||
price = prices[product.id]
|
||||
tax = taxes[product.id]
|
||||
|
||||
template = product.template
|
||||
if not template.shopify_uom:
|
||||
shop._shopify_check_product(product)
|
||||
template.shopify_uom = template.get_shopify_uom()
|
||||
template.save()
|
||||
|
||||
shop.__shopify_update_template(
|
||||
shopify_shop, categories, template,
|
||||
product, price, tax)
|
||||
shop.__shopify_update_product(
|
||||
shopify_shop, product, price, tax)
|
||||
shop.__shopify_update_inventory_item(inventory_item)
|
||||
|
||||
for category in shop.categories_removed:
|
||||
shop.__shopify_remove_category(category)
|
||||
shop.categories_removed = []
|
||||
|
||||
products = set(products)
|
||||
for product in shop.products_removed:
|
||||
template = product.template
|
||||
if set(template.products).isdisjoint(products):
|
||||
shop.__shopify_remove_template(template)
|
||||
else:
|
||||
shop.__shopify_remove_product(product)
|
||||
shop.products_removed = []
|
||||
cls.save(shops)
|
||||
|
||||
def __shopify_update_category(self, category):
|
||||
if not category.is_shopify_to_update(self):
|
||||
return
|
||||
custom_collection = category.get_shopify(self)
|
||||
if not custom_collection.save():
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.msg_custom_collection_fail',
|
||||
category=category.rec_name,
|
||||
error="\n".join(
|
||||
custom_collection.errors.full_messages())))
|
||||
identifier = category.set_shopify_identifier(
|
||||
self, custom_collection.id)
|
||||
if identifier.to_update:
|
||||
identifier.to_update = False
|
||||
identifier.save()
|
||||
Transaction().commit()
|
||||
|
||||
self.__sync_metafields(
|
||||
custom_collection, category.get_shopify_metafields(self))
|
||||
|
||||
def __shopify_remove_category(self, category):
|
||||
shopify_id = category.get_shopify_identifier(self)
|
||||
if shopify_id:
|
||||
if shopify.CustomCollection.exists(shopify_id):
|
||||
shopify.CustomCollection.find(shopify_id).destroy()
|
||||
category.set_shopify_identifier(self)
|
||||
|
||||
def __shopify_update_template(
|
||||
self, shopify_shop, categories, template, product, price, tax):
|
||||
if not template.is_shopify_to_update(self):
|
||||
return
|
||||
shopify_product = template.get_shopify(self)
|
||||
new = shopify_product.is_new()
|
||||
if new:
|
||||
shopify_product.variants = [
|
||||
product.get_shopify(
|
||||
self, price, tax,
|
||||
shop_taxes_included=shopify_shop.taxes_included,
|
||||
shop_weight_unit=shopify_shop.weight_unit)]
|
||||
else:
|
||||
# Set fake value for missing new options
|
||||
for j, variant in enumerate(shopify_product.variants):
|
||||
for i, _ in range(len(shopify_product.options), 1):
|
||||
name = 'option%i' % i
|
||||
if not getattr(variant, name, None):
|
||||
setattr(variant, name, '_option%i-%i' % (i, j))
|
||||
if not shopify_product.save():
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.msg_product_fail',
|
||||
template=template.rec_name,
|
||||
error="\n".join(shopify_product.errors.full_messages())))
|
||||
identifier = template.set_shopify_identifier(
|
||||
self, shopify_product.id)
|
||||
if identifier.to_update:
|
||||
identifier.to_update = False
|
||||
identifier.save()
|
||||
if new:
|
||||
variant, = shopify_product.variants
|
||||
product.set_shopify_identifier(self, variant.id)
|
||||
Transaction().commit()
|
||||
|
||||
self.__sync_metafields(
|
||||
shopify_product, template.get_shopify_metafields(self))
|
||||
|
||||
collection_ids = {
|
||||
c.id for c in shopify_product.collections()}
|
||||
for category in template.categories_all:
|
||||
while category:
|
||||
if category in categories:
|
||||
custom_collection = (
|
||||
shopify.CustomCollection.find(
|
||||
category.get_shopify_identifier(
|
||||
self)))
|
||||
if custom_collection.id in collection_ids:
|
||||
collection_ids.remove(
|
||||
custom_collection.id)
|
||||
else:
|
||||
shopify_product.add_to_collection(
|
||||
custom_collection)
|
||||
category = category.parent
|
||||
for collection_id in collection_ids:
|
||||
collection = shopify.CustomCollection.find(
|
||||
collection_id)
|
||||
shopify_product.remove_from_collection(collection)
|
||||
|
||||
self.__shopify_update_images(template, shopify_product)
|
||||
|
||||
def __shopify_remove_template(self, template):
|
||||
shopify_id = template.get_shopify_identifier(self)
|
||||
if not shopify_id:
|
||||
return
|
||||
if shopify.Product.exists(shopify_id):
|
||||
shopify.Product.find(shopify_id).destroy()
|
||||
template.set_shopify_identifier(self)
|
||||
for product in template.products:
|
||||
product.set_shopify_identifier(self)
|
||||
if getattr(template, 'images', None):
|
||||
for image in template.images:
|
||||
image.set_shopify_identifier(self)
|
||||
|
||||
def __shopify_update_images(self, template, shopify_product):
|
||||
if not getattr(template, 'images', None):
|
||||
return
|
||||
transaction = Transaction()
|
||||
image_ids = set()
|
||||
for i, image in enumerate(filter(
|
||||
lambda i: i.web_shop,
|
||||
template.images_used), 1):
|
||||
product_image = image.get_shopify(self)
|
||||
new_image = not product_image.id
|
||||
product_image.position = i
|
||||
if not product_image.save():
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify'
|
||||
'.msg_product_image_fail',
|
||||
image=image.rec_name,
|
||||
template=template.rec_name,
|
||||
error="\n".join(
|
||||
product_image.errors
|
||||
.full_messages())))
|
||||
image_ids.add(product_image.id)
|
||||
if new_image:
|
||||
image.set_shopify_identifier(
|
||||
self, product_image.id)
|
||||
transaction.commit()
|
||||
|
||||
for image in shopify_product.images:
|
||||
if image.id not in image_ids:
|
||||
image.destroy()
|
||||
|
||||
def __shopify_update_product(self, shopify_shop, product, price, tax):
|
||||
update_extra = {'price': str(price), 'tax': str(tax)}
|
||||
if not product.is_shopify_to_update(self, **update_extra):
|
||||
return
|
||||
variant = product.get_shopify(
|
||||
self, price, tax,
|
||||
shop_taxes_included=shopify_shop.taxes_included,
|
||||
shop_weight_unit=shopify_shop.weight_unit)
|
||||
if not variant.save():
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.msg_variant_fail',
|
||||
product=product.rec_name,
|
||||
error="\n".join(variant.errors.full_messages())
|
||||
))
|
||||
identifier = product.set_shopify_identifier(self, variant.id)
|
||||
if identifier.to_update or identifier.to_update_extra != update_extra:
|
||||
identifier.to_update = False
|
||||
identifier.to_update_extra = update_extra
|
||||
identifier.save()
|
||||
Transaction().commit()
|
||||
|
||||
self.__sync_metafields(variant, product.get_shopify_metafields(self))
|
||||
|
||||
def __shopify_update_inventory_item(self, inventory_item):
|
||||
if not inventory_item.is_shopify_to_update(self):
|
||||
return
|
||||
shopify_inventory_item = inventory_item.get_shopify(self)
|
||||
if shopify_inventory_item:
|
||||
if not shopify_inventory_item.save():
|
||||
raise ShopifyError(gettext(
|
||||
'web_shop_shopify.msg_inventory_item_fail',
|
||||
product=inventory_item.product.rec_name,
|
||||
error="\n".join(
|
||||
inventory_item.errors.full_messages())))
|
||||
identifier = inventory_item.set_shopify_identifier(
|
||||
self, shopify_inventory_item.id if
|
||||
shopify_inventory_item.tracked else None)
|
||||
if identifier and identifier.to_update:
|
||||
identifier.to_update = False
|
||||
identifier.save()
|
||||
Transaction().commit()
|
||||
|
||||
def __shopify_remove_product(self, product):
|
||||
shopify_id = product.get_shopify_identifier(self)
|
||||
if shopify_id:
|
||||
if shopify.Variant.exists(shopify_id):
|
||||
shopify.Variant.find(shopify_id).destroy()
|
||||
product.set_shopify_identifier(self)
|
||||
|
||||
@classmethod
|
||||
def shopify_update_inventory(cls, shops=None):
|
||||
"""Update Shopify Inventory"""
|
||||
pool = Pool()
|
||||
Product = pool.get('product.product')
|
||||
if shops is None:
|
||||
shops = cls.search([
|
||||
('type', '=', 'shopify'),
|
||||
])
|
||||
for shop in shops:
|
||||
for shop_warehouse in shop.shopify_warehouses:
|
||||
location_id = shop_warehouse.shopify_id
|
||||
if not location_id:
|
||||
continue
|
||||
location_id = int(location_id)
|
||||
with Transaction().set_context(
|
||||
shop.get_context(),
|
||||
**shop_warehouse.get_shopify_inventory_context()):
|
||||
products = Product.browse([
|
||||
p for p in shop.products if p.shopify_uom])
|
||||
with shop.shopify_session():
|
||||
shop.__shopify_update_inventory(products, location_id)
|
||||
|
||||
def __shopify_update_inventory(self, products, location_id):
|
||||
pool = Pool()
|
||||
InventoryItem = pool.get('product.shopify_inventory_item')
|
||||
inventory_items = InventoryItem.browse(products)
|
||||
product2quantity = {p.id: int(p.shopify_quantity) for p in products}
|
||||
shopify2product = {
|
||||
i.get_shopify_identifier(self): i.id for i in inventory_items}
|
||||
shopify2product.pop(None, None)
|
||||
product2shopify = {v: k for k, v in shopify2product.items()}
|
||||
|
||||
location = shopify.Location.find(location_id)
|
||||
for i, inventory_level in enumerate(
|
||||
location.inventory_levels(limit=250, no_iter_next=False)):
|
||||
inventory_item_id = inventory_level.inventory_item_id
|
||||
product_id = shopify2product.get(inventory_item_id)
|
||||
if product_id is None:
|
||||
continue
|
||||
quantity = product2quantity.pop(product_id)
|
||||
if inventory_level.available != quantity:
|
||||
try:
|
||||
shopify.InventoryLevel.set(
|
||||
location_id, inventory_item_id, quantity)
|
||||
except pyactiveresource.connection.ResourceNotFound:
|
||||
pass
|
||||
|
||||
for product_id, quantity in product2quantity.items():
|
||||
inventory_item_id = product2shopify.get(product_id)
|
||||
if inventory_item_id is None:
|
||||
continue
|
||||
try:
|
||||
shopify.InventoryLevel.set(
|
||||
location_id, inventory_item_id, quantity)
|
||||
except pyactiveresource.connection.ResourceNotFound:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def shopify_fetch_order(cls, shops=None):
|
||||
"""Fetch new Shopify Order"""
|
||||
pool = Pool()
|
||||
Sale = pool.get('sale.sale')
|
||||
Payment = pool.get('account.payment')
|
||||
context = Transaction().context
|
||||
if shops is None:
|
||||
shops = cls.search([
|
||||
('type', '=', 'shopify'),
|
||||
])
|
||||
cls.lock(shops)
|
||||
for shop in shops:
|
||||
last_sales = Sale.search([
|
||||
('web_shop', '=', shop.id),
|
||||
], order=[('shopify_identifier_signed', 'DESC')], limit=1)
|
||||
if last_sales:
|
||||
last_sale, = last_sales
|
||||
last_order_id = last_sale.shopify_identifier
|
||||
else:
|
||||
last_order_id = ''
|
||||
with shop.shopify_session():
|
||||
if 'shopify_orders' in context:
|
||||
orders = shopify.Order.find(
|
||||
ids=context['shopify_orders'],
|
||||
limit=250, no_iter_next=False)
|
||||
else:
|
||||
orders = shopify.Order.find(
|
||||
status='open', since_id=last_order_id,
|
||||
limit=250, no_iter_next=False)
|
||||
sales = []
|
||||
for i, order in enumerate(orders):
|
||||
sales.append(Sale.get_from_shopify(shop, order))
|
||||
Sale.save(sales)
|
||||
for sale, order in zip(sales, orders):
|
||||
sale.shopify_tax_adjustment = (
|
||||
Decimal(order.total_price) - sale.total_amount)
|
||||
Sale.save(sales)
|
||||
Sale.quote(sales)
|
||||
for sale, order in zip(sales, orders):
|
||||
Payment.get_from_shopify(sale, order)
|
||||
|
||||
@classmethod
|
||||
def shopify_update_order(cls, shops=None):
|
||||
"""Update existing sale from Shopify"""
|
||||
pool = Pool()
|
||||
Sale = pool.get('sale.sale')
|
||||
if shops is None:
|
||||
shops = cls.search([
|
||||
('type', '=', 'shopify'),
|
||||
])
|
||||
cls.lock(shops)
|
||||
now = dt.datetime.now()
|
||||
for shop in shops:
|
||||
sales = Sale.search([
|
||||
('web_shop', '=', shop.id),
|
||||
('shopify_identifier', '!=', None),
|
||||
['OR',
|
||||
('state', 'in',
|
||||
['quotation', 'confirmed', 'processing']),
|
||||
('create_date', '>=', now - EDIT_ORDER_DELAY),
|
||||
],
|
||||
])
|
||||
for sub_sales in grouped_slice(sales, count=250):
|
||||
cls._shopify_update_order(shop, list(sub_sales))
|
||||
|
||||
@classmethod
|
||||
def _shopify_update_order(cls, shop, sales):
|
||||
assert shop.type == 'shopify'
|
||||
assert all(s.web_shop == shop for s in sales)
|
||||
with shop.shopify_session():
|
||||
orders = shopify.Order.find(
|
||||
ids=','.join(str(s.shopify_identifier) for s in sales),
|
||||
status='any')
|
||||
id2order = {o.id: o for o in orders}
|
||||
|
||||
to_update = []
|
||||
orders = []
|
||||
for sale in sales:
|
||||
try:
|
||||
order = id2order[sale.shopify_identifier]
|
||||
except KeyError:
|
||||
continue
|
||||
to_update.append(sale)
|
||||
orders.append(order)
|
||||
cls.shopify_update_sale(to_update, orders)
|
||||
|
||||
@classmethod
|
||||
def shopify_update_sale(cls, sales, orders):
|
||||
"""Update sales based on Shopify orders"""
|
||||
pool = Pool()
|
||||
Amendment = pool.get('sale.amendment')
|
||||
Payment = pool.get('account.payment')
|
||||
Sale = pool.get('sale.sale')
|
||||
assert len(sales) == len(orders)
|
||||
to_update = {}
|
||||
for sale, order in zip(sales, orders):
|
||||
assert sale.shopify_identifier == order.id
|
||||
shop = sale.web_shop
|
||||
with shop.shopify_session():
|
||||
sale = Sale.get_from_shopify(shop, order, sale=sale)
|
||||
if sale._changed_values:
|
||||
sale.untaxed_amount_cache = None
|
||||
sale.tax_amount_cache = None
|
||||
sale.total_amount_cache = None
|
||||
to_update[sale] = order
|
||||
Payment.get_from_shopify(sale, order)
|
||||
Sale.save(to_update.keys())
|
||||
for sale, order in to_update.items():
|
||||
sale.shopify_tax_adjustment = (
|
||||
Decimal(order.current_total_price) - sale.total_amount)
|
||||
Sale.store_cache(to_update.keys())
|
||||
Amendment._clear_sale(to_update.keys())
|
||||
Sale.__queue__.process(to_update.keys())
|
||||
|
||||
|
||||
class ShopShopifyIdentifier(IdentifierMixin, ModelSQL, ModelView):
|
||||
"Shopify Identifier"
|
||||
__name__ = 'web.shop.shopify_identifier'
|
||||
|
||||
record = fields.Reference("Record", 'get_records', required=True)
|
||||
web_shop = fields.Many2One(
|
||||
'web.shop', "Web Shop", required=True, ondelete='CASCADE')
|
||||
to_update = fields.Boolean("To Update")
|
||||
to_update_extra = fields.Dict(None, "To Update Extra")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.shopify_identifier_signed.states = {
|
||||
'required': True,
|
||||
}
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('record_web_shop_unique',
|
||||
Unique(t, t.record, t.shopify_identifier_signed),
|
||||
'web_shop_shopify.msg_identifier_record_web_shop_unique'),
|
||||
]
|
||||
cls._buttons.update({
|
||||
'set_to_update': {},
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def get_records(cls):
|
||||
pool = Pool()
|
||||
Model = pool.get('ir.model')
|
||||
get_name = Model.get_name
|
||||
models = (klass.__name__ for _, klass in pool.iterobject()
|
||||
if issubclass(klass, IdentifiersMixin))
|
||||
return [(m, get_name(m)) for m in models]
|
||||
|
||||
@classmethod
|
||||
def set_to_update(cls, identifiers):
|
||||
cls.write(identifiers, {'to_update': True})
|
||||
|
||||
|
||||
class Shop_Warehouse(ModelView, metaclass=PoolMeta):
|
||||
__name__ = 'web.shop-stock.location'
|
||||
|
||||
shopify_stock_skip_warehouse = fields.Boolean(
|
||||
"Only storage zone",
|
||||
help="Check to use only the quantity of the storage zone.")
|
||||
shopify_id = fields.Selection(
|
||||
'get_shopify_locations', "Shopify ID")
|
||||
_shopify_locations_cache = Cache(
|
||||
__name__ + '.get_shopify_locations',
|
||||
duration=config.getint(
|
||||
'web_shop_shopify', 'locations_cache', default=15 * 60),
|
||||
context=False)
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__access__.add('shop')
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('shopify_id_unique',
|
||||
Unique(t, t.shopify_id),
|
||||
'web_shop_shopify.msg_location_id_unique'),
|
||||
]
|
||||
|
||||
@fields.depends(
|
||||
'shop', '_parent_shop.shopify_url', '_parent_shop.shopify_version',
|
||||
'_parent_shop.shopify_password')
|
||||
def get_shopify_locations(self):
|
||||
locations = [(None, "")]
|
||||
if self.shop:
|
||||
locations_cache = self._shopify_locations_cache.get(self.shop.id)
|
||||
if locations_cache is not None:
|
||||
return locations_cache
|
||||
try:
|
||||
with self.shop.shopify_session():
|
||||
locations += [
|
||||
(str(l.id), l.name)
|
||||
for l in shopify.Location.find(no_iter_next=False)]
|
||||
self._shopify_locations_cache.set(self.shop.id, locations)
|
||||
except (AttributeError,
|
||||
shopify.VersionNotFoundError,
|
||||
pyactiveresource.connection.Error):
|
||||
pass
|
||||
return locations
|
||||
|
||||
def get_shopify_inventory_context(self):
|
||||
return {
|
||||
'locations': [self.warehouse.id],
|
||||
'stock_skip_warehouse': self.shopify_stock_skip_warehouse,
|
||||
'with_childs': True,
|
||||
}
|
||||
|
||||
|
||||
class Shop_Attribute(metaclass=PoolMeta):
|
||||
__name__ = 'web.shop-product.attribute'
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
domain = [
|
||||
('type', '!=', 'shopify'),
|
||||
]
|
||||
if cls.shop.domain:
|
||||
cls.shop.domain = [cls.shop.domain, domain]
|
||||
else:
|
||||
cls.shop.domain = domain
|
||||
|
||||
|
||||
class ShopShopifyPaymentJournal(
|
||||
sequence_ordered(), MatchMixin, ModelSQL, ModelView):
|
||||
"Shopify Payment Journal"
|
||||
__name__ = 'web.shop.shopify_payment_journal'
|
||||
|
||||
shop = fields.Many2One(
|
||||
'web.shop', "Shop", required=True, ondelete='CASCADE',
|
||||
domain=[
|
||||
('type', '=', 'shopify'),
|
||||
])
|
||||
gateway = fields.Char(
|
||||
"Gateway",
|
||||
help="The payment gateway name for which the journal must be used.")
|
||||
journal = fields.Many2One(
|
||||
'account.payment.journal', "Journal", required=True,
|
||||
domain=[
|
||||
('process_method', '=', 'shopify'),
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__access__.add('shop')
|
||||
|
||||
# TODO: add wizard to export translations
|
||||
Reference in New Issue
Block a user