refactor!: drop shopify integration from ERPNext (#26700)

BREAKING CHANGE: remove Shopify integration.

The integration is moved to a separate app with additional enhancements.

The app is still FOSS and licensed under the same license as ERPNext. Any
data migration required is taken care of after installing the app and
enabling it.

New app: https://github.com/frappe/ecommerce_integrations
This commit is contained in:
Ankush 2021-08-02 21:57:52 +05:30 committed by GitHub
parent 78762850ee
commit efb037b566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 13 additions and 3177 deletions

View File

@ -1,353 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe import _
import json
from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime
from erpnext.erpnext_integrations.utils import validate_webhooks_request
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
@frappe.whitelist(allow_guest=True)
@validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret')
def store_request_data(order=None, event=None):
if frappe.request:
order = json.loads(frappe.request.data)
event = frappe.request.headers.get('X-Shopify-Topic')
dump_request_data(order, event)
def sync_sales_order(order, request_id=None, old_order_sync=False):
frappe.set_user('Administrator')
shopify_settings = frappe.get_doc("Shopify Settings")
frappe.flags.request_id = request_id
if not frappe.db.get_value("Sales Order", filters={"shopify_order_id": cstr(order['id'])}):
try:
validate_customer(order, shopify_settings)
validate_item(order, shopify_settings)
create_order(order, shopify_settings, old_order_sync=old_order_sync)
except Exception as e:
make_shopify_log(status="Error", exception=e)
else:
make_shopify_log(status="Success")
def prepare_sales_invoice(order, request_id=None):
frappe.set_user('Administrator')
shopify_settings = frappe.get_doc("Shopify Settings")
frappe.flags.request_id = request_id
try:
sales_order = get_sales_order(cstr(order['id']))
if sales_order:
create_sales_invoice(order, shopify_settings, sales_order)
make_shopify_log(status="Success")
except Exception as e:
make_shopify_log(status="Error", exception=e, rollback=True)
def prepare_delivery_note(order, request_id=None):
frappe.set_user('Administrator')
shopify_settings = frappe.get_doc("Shopify Settings")
frappe.flags.request_id = request_id
try:
sales_order = get_sales_order(cstr(order['id']))
if sales_order:
create_delivery_note(order, shopify_settings, sales_order)
make_shopify_log(status="Success")
except Exception as e:
make_shopify_log(status="Error", exception=e, rollback=True)
def get_sales_order(shopify_order_id):
sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id})
if sales_order:
so = frappe.get_doc("Sales Order", sales_order)
return so
def validate_customer(order, shopify_settings):
customer_id = order.get("customer", {}).get("id")
if customer_id:
if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"):
create_customer(order.get("customer"), shopify_settings)
def validate_item(order, shopify_settings):
for item in order.get("line_items"):
if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"):
sync_item_from_shopify(shopify_settings, item)
def create_order(order, shopify_settings, old_order_sync=False, company=None):
so = create_sales_order(order, shopify_settings, company)
if so:
if order.get("financial_status") == "paid":
create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync)
if order.get("fulfillments") and not old_order_sync:
create_delivery_note(order, shopify_settings, so)
def create_sales_order(shopify_order, shopify_settings, company=None):
product_not_exists = []
customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer", {}).get("id")}, "name")
so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name")
if not so:
items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at')))
if not items:
message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
message += "\n" + ", ".join(product_not_exists)
make_shopify_log(status="Error", exception=message, rollback=True)
return ''
so = frappe.get_doc({
"doctype": "Sales Order",
"naming_series": shopify_settings.sales_order_series or "SO-Shopify-",
"shopify_order_id": shopify_order.get("id"),
"shopify_order_number": shopify_order.get("name"),
"customer": customer or shopify_settings.default_customer,
"transaction_date": getdate(shopify_order.get("created_at")) or nowdate(),
"delivery_date": getdate(shopify_order.get("created_at")) or nowdate(),
"company": shopify_settings.company,
"selling_price_list": shopify_settings.price_list,
"ignore_pricing_rule": 1,
"items": items,
"taxes": get_order_taxes(shopify_order, shopify_settings),
"apply_discount_on": "Grand Total",
"discount_amount": get_discounted_amount(shopify_order),
})
if company:
so.update({
"company": company,
"status": "Draft"
})
so.flags.ignore_mandatory = True
so.save(ignore_permissions=True)
so.submit()
else:
so = frappe.get_doc("Sales Order", so)
frappe.db.commit()
return so
def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False):
if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\
and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice):
if old_order_sync:
posting_date = getdate(shopify_order.get('created_at'))
else:
posting_date = nowdate()
si = make_sales_invoice(so.name, ignore_permissions=True)
si.shopify_order_id = shopify_order.get("id")
si.shopify_order_number = shopify_order.get("name")
si.set_posting_time = 1
si.posting_date = posting_date
si.due_date = posting_date
si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
si.flags.ignore_mandatory = True
set_cost_center(si.items, shopify_settings.cost_center)
si.insert(ignore_mandatory=True)
si.submit()
make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
frappe.db.commit()
def set_cost_center(items, cost_center):
for item in items:
item.cost_center = cost_center
def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
payment_entry.flags.ignore_mandatory = True
payment_entry.reference_no = doc.name
payment_entry.posting_date = posting_date or nowdate()
payment_entry.reference_date = posting_date or nowdate()
payment_entry.insert(ignore_permissions=True)
payment_entry.submit()
def create_delivery_note(shopify_order, shopify_settings, so):
if not cint(shopify_settings.sync_delivery_note):
return
for fulfillment in shopify_order.get("fulfillments"):
if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\
and so.docstatus==1:
dn = make_delivery_note(so.name)
dn.shopify_order_id = fulfillment.get("order_id")
dn.shopify_order_number = shopify_order.get("name")
dn.set_posting_time = 1
dn.posting_date = getdate(fulfillment.get("created_at"))
dn.shopify_fulfillment_id = fulfillment.get("id")
dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-"
dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings)
dn.flags.ignore_mandatory = True
dn.save()
dn.submit()
frappe.db.commit()
def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings):
return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\
if get_item_code(item) == dn_item.item_code]
def get_discounted_amount(order):
discounted_amount = 0.0
for discount in order.get("discount_codes"):
discounted_amount += flt(discount.get("amount"))
return discounted_amount
def get_order_items(order_items, shopify_settings, delivery_date):
items = []
all_product_exists = True
product_not_exists = []
for shopify_item in order_items:
if not shopify_item.get('product_exists'):
all_product_exists = False
product_not_exists.append({'title':shopify_item.get('title'),
'shopify_order_id': shopify_item.get('id')})
continue
if all_product_exists:
item_code = get_item_code(shopify_item)
items.append({
"item_code": item_code,
"item_name": shopify_item.get("name"),
"rate": shopify_item.get("price"),
"delivery_date": delivery_date,
"qty": shopify_item.get("quantity"),
"stock_uom": shopify_item.get("uom") or _("Nos"),
"warehouse": shopify_settings.warehouse
})
else:
items = []
return items
def get_item_code(shopify_item):
item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code")
if not item_code:
item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code")
if not item_code:
item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code")
return item_code
def get_order_taxes(shopify_order, shopify_settings):
taxes = []
for tax in shopify_order.get("tax_lines"):
taxes.append({
"charge_type": _("On Net Total"),
"account_head": get_tax_account_head(tax),
"description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0),
"rate": tax.get("rate") * 100.00,
"included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0,
"cost_center": shopify_settings.cost_center
})
taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings)
return taxes
def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
"""Shipping lines represents the shipping details,
each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines:
if shipping_charge.get("price"):
taxes.append({
"charge_type": _("Actual"),
"account_head": get_tax_account_head(shipping_charge),
"description": shipping_charge["title"],
"tax_amount": shipping_charge["price"],
"cost_center": shopify_settings.cost_center
})
for tax in shipping_charge.get("tax_lines"):
taxes.append({
"charge_type": _("Actual"),
"account_head": get_tax_account_head(tax),
"description": tax["title"],
"tax_amount": tax["price"],
"cost_center": shopify_settings.cost_center
})
return taxes
def get_tax_account_head(tax):
tax_title = tax.get("title").encode("utf-8")
tax_account = frappe.db.get_value("Shopify Tax Account", \
{"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account")
if not tax_account:
frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))
return tax_account
@frappe.whitelist(allow_guest=True)
def sync_old_orders():
frappe.set_user('Administrator')
shopify_settings = frappe.get_doc('Shopify Settings')
if not shopify_settings.sync_missing_orders:
return
url = get_url(shopify_settings)
session = get_request_session()
try:
res = session.get(url, headers=get_header(shopify_settings))
res.raise_for_status()
orders = res.json()["orders"]
for order in orders:
if is_sync_complete(shopify_settings, order):
stop_sync(shopify_settings)
return
sync_sales_order(order=order, old_order_sync=True)
last_order_id = order.get('id')
if last_order_id:
shopify_settings.load_from_db()
shopify_settings.last_order_id = last_order_id
shopify_settings.save()
frappe.db.commit()
except Exception as e:
raise e
def stop_sync(shopify_settings):
shopify_settings.sync_missing_orders = 0
shopify_settings.last_order_id = ''
shopify_settings.save()
frappe.db.commit()
def get_url(shopify_settings):
last_order_id = shopify_settings.last_order_id
if not last_order_id:
if shopify_settings.sync_based_on == 'Date':
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
get_datetime(shopify_settings.from_date)), shopify_settings)
else:
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
shopify_settings.from_order_id), shopify_settings)
else:
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
return url
def is_sync_complete(shopify_settings, order):
if shopify_settings.sync_based_on == 'Date':
return getdate(shopify_settings.to_date) < getdate(order.get('created_at'))
else:
return cstr(order.get('id')) == cstr(shopify_settings.to_order_id)

View File

@ -1,22 +0,0 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Shopify Log', {
refresh: function(frm) {
if (frm.doc.request_data && frm.doc.status=='Error'){
frm.add_custom_button('Resync', function() {
frappe.call({
method:"erpnext.erpnext_integrations.doctype.shopify_log.shopify_log.resync",
args:{
method:frm.doc.method,
name: frm.doc.name,
request_data: frm.doc.request_data
},
callback: function(r){
frappe.msgprint(__("Order rescheduled for sync"))
}
})
}).addClass('btn-primary');
}
}
});

View File

@ -1,268 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-03-14 10:02:06.227184",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Queued",
"fieldname": "status",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "method",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Method",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "message",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Message",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "traceback",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Traceback",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "request_data",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Request Data",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-20 16:23:36.862381",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Shopify Log",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from frappe.model.document import Document
from erpnext.erpnext_integrations.utils import get_webhook_address
class ShopifyLog(Document):
pass
def make_shopify_log(status="Queued", exception=None, rollback=False):
# if name not provided by log calling method then fetch existing queued state log
make_new = False
if not frappe.flags.request_id:
make_new = True
if rollback:
frappe.db.rollback()
if make_new:
log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True)
else:
log = log = frappe.get_doc("Shopify Log", frappe.flags.request_id)
log.message = get_message(exception)
log.traceback = frappe.get_traceback()
log.status = status
log.save(ignore_permissions=True)
frappe.db.commit()
def get_message(exception):
message = None
if hasattr(exception, 'message'):
message = exception.message
elif hasattr(exception, '__str__'):
message = exception.__str__()
else:
message = "Something went wrong while syncing"
return message
def dump_request_data(data, event="create/order"):
event_mapper = {
"orders/create": get_webhook_address(connector_name='shopify_connection', method="sync_sales_order", exclude_uri=True),
"orders/paid" : get_webhook_address(connector_name='shopify_connection', method="prepare_sales_invoice", exclude_uri=True),
"orders/fulfilled": get_webhook_address(connector_name='shopify_connection', method="prepare_delivery_note", exclude_uri=True)
}
log = frappe.get_doc({
"doctype": "Shopify Log",
"request_data": json.dumps(data, indent=1),
"method": event_mapper[event]
}).insert(ignore_permissions=True)
frappe.db.commit()
frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True,
**{"order": data, "request_id": log.name})
@frappe.whitelist()
def resync(method, name, request_data):
frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False)
frappe.enqueue(method=method, queue='short', timeout=300, is_async=True,
**{"order": json.loads(request_data), "request_id": name})

View File

@ -1,12 +0,0 @@
frappe.listview_settings['Shopify Log'] = {
add_fields: ["status"],
get_indicator: function(doc) {
if(doc.status==="Success"){
return [__("Success"), "green", "status,=,Success"];
} else if(doc.status ==="Error"){
return [__("Error"), "red", "status,=,Error"];
} else if(doc.status ==="Queued"){
return [__("Queued"), "orange", "status,=,Queued"];
}
}
}

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Shopify Log", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Shopify Log
() => frappe.tests.make('Shopify Log', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Shopify Log')
class TestShopifyLog(unittest.TestCase):
pass

View File

@ -1,90 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext_integrations.shopify_settings");
frappe.ui.form.on("Shopify Settings", "onload", function(frm){
frappe.call({
method:"erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings.get_series",
callback:function(r){
$.each(r.message, function(key, value){
set_field_options(key, value);
});
}
});
erpnext_integrations.shopify_settings.setup_queries(frm);
})
frappe.ui.form.on("Shopify Settings", "app_type", function(frm) {
frm.toggle_reqd("api_key", (frm.doc.app_type == "Private"));
frm.toggle_reqd("password", (frm.doc.app_type == "Private"));
})
frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
if(!frm.doc.__islocal && frm.doc.enable_shopify === 1){
frm.toggle_reqd("price_list", true);
frm.toggle_reqd("warehouse", true);
frm.toggle_reqd("taxes", true);
frm.toggle_reqd("company", true);
frm.toggle_reqd("cost_center", true);
frm.toggle_reqd("cash_bank_account", true);
frm.toggle_reqd("sales_order_series", true);
frm.toggle_reqd("customer_group", true);
frm.toggle_reqd("shared_secret", true);
frm.toggle_reqd("sales_invoice_series", frm.doc.sync_sales_invoice);
frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
}
})
$.extend(erpnext_integrations.shopify_settings, {
setup_queries: function(frm) {
frm.fields_dict["warehouse"].get_query = function(doc) {
return {
filters:{
"company": doc.company,
"is_group": "No"
}
}
}
frm.fields_dict["taxes"].grid.get_field("tax_account").get_query = function(doc){
return {
"query": "erpnext.controllers.queries.tax_account_query",
"filters": {
"account_type": ["Tax", "Chargeable", "Expense Account"],
"company": doc.company
}
}
}
frm.fields_dict["cash_bank_account"].get_query = function(doc) {
return {
filters: [
["Account", "account_type", "in", ["Cash", "Bank"]],
["Account", "root_type", "=", "Asset"],
["Account", "is_group", "=",0],
["Account", "company", "=", doc.company]
]
}
}
frm.fields_dict["cost_center"].get_query = function(doc) {
return {
filters:{
"company": doc.company,
"is_group": "No"
}
}
}
frm.fields_dict["price_list"].get_query = function() {
return {
filters:{
"selling": 1
}
}
}
}
})

View File

@ -1,353 +0,0 @@
{
"actions": [],
"creation": "2015-05-18 05:21:07.270859",
"doctype": "DocType",
"document_type": "System",
"engine": "InnoDB",
"field_order": [
"status_html",
"enable_shopify",
"app_type",
"column_break_4",
"last_sync_datetime",
"section_break_2",
"shopify_url",
"api_key",
"column_break_3",
"password",
"shared_secret",
"access_token",
"section_break_38",
"webhooks",
"section_break_15",
"default_customer",
"column_break_19",
"customer_group",
"company_dependent_settings",
"company",
"cash_bank_account",
"column_break_20",
"cost_center",
"erp_settings",
"price_list",
"update_price_in_erpnext_price_list",
"column_break_26",
"warehouse",
"section_break_25",
"sales_order_series",
"column_break_27",
"sync_delivery_note",
"delivery_note_series",
"sync_sales_invoice",
"sales_invoice_series",
"section_break_22",
"html_16",
"taxes",
"syncing_details_section",
"sync_missing_orders",
"sync_based_on",
"column_break_41",
"from_date",
"to_date",
"from_order_id",
"to_order_id",
"last_order_id"
],
"fields": [
{
"fieldname": "status_html",
"fieldtype": "HTML",
"label": "status html",
"read_only": 1
},
{
"default": "0",
"fieldname": "enable_shopify",
"fieldtype": "Check",
"label": "Enable Shopify"
},
{
"default": "Private",
"fieldname": "app_type",
"fieldtype": "Data",
"in_list_view": 1,
"label": "App Type",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "last_sync_datetime",
"fieldtype": "Datetime",
"label": "Last Sync Datetime",
"read_only": 1
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"description": "eg: frappe.myshopify.com",
"fieldname": "shopify_url",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Shop URL",
"reqd": 1
},
{
"depends_on": "eval:doc.app_type==\"Private\"",
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.app_type==\"Private\"",
"fieldname": "password",
"fieldtype": "Password",
"label": "Password"
},
{
"fieldname": "shared_secret",
"fieldtype": "Data",
"label": "Shared secret"
},
{
"fieldname": "access_token",
"fieldtype": "Data",
"hidden": 1,
"label": "Access Token",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "section_break_38",
"fieldtype": "Section Break",
"label": "Webhooks Details"
},
{
"fieldname": "webhooks",
"fieldtype": "Table",
"label": "Webhooks",
"options": "Shopify Webhook Detail",
"read_only": 1
},
{
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"label": "Customer Settings"
},
{
"description": "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order",
"fieldname": "default_customer",
"fieldtype": "Link",
"label": "Default Customer",
"options": "Customer"
},
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"description": "Customer Group will set to selected group while syncing customers from Shopify",
"fieldname": "customer_group",
"fieldtype": "Link",
"label": "Customer Group",
"options": "Customer Group"
},
{
"fieldname": "company_dependent_settings",
"fieldtype": "Section Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "For Company",
"options": "Company"
},
{
"description": "Cash Account will used for Sales Invoice creation",
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
"options": "Account"
},
{
"fieldname": "column_break_20",
"fieldtype": "Column Break"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "erp_settings",
"fieldtype": "Section Break"
},
{
"fieldname": "price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List"
},
{
"default": "0",
"fieldname": "update_price_in_erpnext_price_list",
"fieldtype": "Check",
"label": "Update Price from Shopify To ERPNext Price List"
},
{
"fieldname": "column_break_26",
"fieldtype": "Column Break"
},
{
"description": "Default Warehouse to to create Sales Order and Delivery Note",
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
"options": "Warehouse"
},
{
"fieldname": "section_break_25",
"fieldtype": "Section Break"
},
{
"fieldname": "sales_order_series",
"fieldtype": "Select",
"label": "Sales Order Series"
},
{
"fieldname": "column_break_27",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "sync_delivery_note",
"fieldtype": "Check",
"label": "Import Delivery Notes from Shopify on Shipment"
},
{
"depends_on": "eval:doc.sync_delivery_note==1",
"fieldname": "delivery_note_series",
"fieldtype": "Select",
"label": "Delivery Note Series"
},
{
"default": "0",
"fieldname": "sync_sales_invoice",
"fieldtype": "Check",
"label": "Import Sales Invoice from Shopify if Payment is marked"
},
{
"depends_on": "eval:doc.sync_sales_invoice==1",
"fieldname": "sales_invoice_series",
"fieldtype": "Select",
"label": "Sales Invoice Series"
},
{
"fieldname": "section_break_22",
"fieldtype": "Section Break"
},
{
"fieldname": "html_16",
"fieldtype": "HTML",
"options": "Map Shopify Taxes / Shipping Charges to ERPNext Account"
},
{
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Shopify Tax Account",
"options": "Shopify Tax Account"
},
{
"collapsible": 1,
"fieldname": "syncing_details_section",
"fieldtype": "Section Break",
"label": "Syncing Missing Orders"
},
{
"depends_on": "eval:doc.sync_missing_orders",
"fieldname": "last_order_id",
"fieldtype": "Data",
"label": "Last Order Id",
"read_only": 1
},
{
"fieldname": "column_break_41",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "On checking this Order from the ",
"fieldname": "sync_missing_orders",
"fieldtype": "Check",
"label": "Sync Missing Old Shopify Orders"
},
{
"depends_on": "eval:doc.sync_missing_orders",
"fieldname": "sync_based_on",
"fieldtype": "Select",
"label": "Sync Based On",
"mandatory_depends_on": "eval:doc.sync_missing_orders",
"options": "\nDate\nShopify Order Id"
},
{
"depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
},
{
"depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
},
{
"depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
"fieldname": "from_order_id",
"fieldtype": "Data",
"label": "From Order Id",
"mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
},
{
"depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
"fieldname": "to_order_id",
"fieldtype": "Data",
"label": "To Order Id",
"mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
}
],
"issingle": 1,
"links": [],
"modified": "2021-03-02 17:35:41.953317",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Shopify Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -1,144 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_request_session
from requests.exceptions import HTTPError
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.erpnext_integrations.utils import get_webhook_address
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
class ShopifySettings(Document):
def validate(self):
if self.enable_shopify == 1:
setup_custom_fields()
self.validate_access_credentials()
self.register_webhooks()
else:
self.unregister_webhooks()
def validate_access_credentials(self):
if not (self.get_password(raise_exception=False) and self.api_key and self.shopify_url):
frappe.msgprint(_("Missing value for Password, API Key or Shopify URL"), raise_exception=frappe.ValidationError)
def register_webhooks(self):
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
"format": "json"
}
}), headers=get_header(self))
res.raise_for_status()
self.update_webhook_table(method, res.json())
except HTTPError as e:
error_message = res.json().get('errors', e)
make_shopify_log(status="Warning", exception=error_message, rollback=True)
except Exception as e:
make_shopify_log(status="Warning", exception=e, rollback=True)
def unregister_webhooks(self):
session = get_request_session()
deleted_webhooks = []
for d in self.webhooks:
url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
deleted_webhooks.append(d)
except HTTPError as e:
error_message = res.json().get('errors', e)
make_shopify_log(status="Warning", exception=error_message, rollback=True)
except Exception as e:
frappe.log_error(message=e, title='Shopify Webhooks Issue')
for d in deleted_webhooks:
self.remove(d)
def update_webhook_table(self, method, res):
self.append("webhooks", {
"webhook_id": res['webhook']['id'],
"method": method
})
def get_shopify_url(path, settings):
if settings.app_type == "Private":
return 'https://{}:{}@{}/{}'.format(settings.api_key, settings.get_password('password'), settings.shopify_url, path)
else:
return 'https://{}/{}'.format(settings.shopify_url, path)
def get_header(settings):
header = {'Content-Type': 'application/json'}
return header
@frappe.whitelist()
def get_series():
return {
"sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-Shopify-",
"sales_invoice_series" : frappe.get_meta("Sales Invoice").get_options("naming_series") or "SI-Shopify-",
"delivery_note_series" : frappe.get_meta("Delivery Note").get_options("naming_series") or "DN-Shopify-"
}
def setup_custom_fields():
custom_fields = {
"Customer": [
dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
],
"Supplier": [
dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
],
"Address": [
dict(fieldname='shopify_address_id', label='Shopify Address Id',
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
],
"Item": [
dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
dict(fieldname='shopify_product_id', label='Shopify Product Id',
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
dict(fieldname='shopify_description', label='Shopify Description',
fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
],
"Sales Order": [
dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
dict(fieldname='shopify_order_number', label='Shopify Order Number',
fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
],
"Delivery Note":[
dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
dict(fieldname='shopify_order_number', label='Shopify Order Number',
fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1),
dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
],
"Sales Invoice": [
dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
dict(fieldname='shopify_order_number', label='Shopify Order Number',
fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
]
}
create_custom_fields(custom_fields)

View File

@ -1,71 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe import _
def create_customer(shopify_customer, shopify_settings):
import frappe.utils.nestedset
cust_name = (shopify_customer.get("first_name") + " " + (shopify_customer.get("last_name") \
and shopify_customer.get("last_name") or "")) if shopify_customer.get("first_name")\
else shopify_customer.get("email")
try:
customer = frappe.get_doc({
"doctype": "Customer",
"name": shopify_customer.get("id"),
"customer_name" : cust_name,
"shopify_customer_id": shopify_customer.get("id"),
"sync_with_shopify": 1,
"customer_group": shopify_settings.customer_group,
"territory": frappe.utils.nestedset.get_root_of("Territory"),
"customer_type": _("Individual")
})
customer.flags.ignore_mandatory = True
customer.insert(ignore_permissions=True)
if customer:
create_customer_address(customer, shopify_customer)
frappe.db.commit()
except Exception as e:
raise e
def create_customer_address(customer, shopify_customer):
addresses = shopify_customer.get("addresses", [])
if not addresses and "default_address" in shopify_customer:
addresses.append(shopify_customer["default_address"])
for i, address in enumerate(addresses):
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
try :
frappe.get_doc({
"doctype": "Address",
"shopify_address_id": address.get("id"),
"address_title": address_title,
"address_type": address_type,
"address_line1": address.get("address1") or "Address 1",
"address_line2": address.get("address2"),
"city": address.get("city") or "City",
"state": address.get("province"),
"pincode": address.get("zip"),
"country": address.get("country"),
"phone": address.get("phone"),
"email_id": shopify_customer.get("email"),
"links": [{
"link_doctype": "Customer",
"link_name": customer.name
}]
}).insert(ignore_mandatory=True)
except Exception as e:
raise e
def get_address_title_and_type(customer_name, index):
address_type = _("Billing")
address_title = customer_name
if frappe.db.get_value("Address", "{0}-{1}".format(customer_name.strip(), address_type)):
address_title = "{0}-{1}".format(customer_name.strip(), index)
return address_title, address_type

View File

@ -1,309 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext import get_default_company
from frappe.utils import cstr, cint, get_request_session
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
res = session.get(url, headers=get_header(shopify_settings))
res.raise_for_status()
shopify_item = res.json()["product"]
make_item(shopify_settings.warehouse, shopify_item)
except Exception as e:
raise e
def make_item(warehouse, shopify_item):
add_item_weight(shopify_item)
if has_variants(shopify_item):
attributes = create_attribute(shopify_item)
create_item(shopify_item, warehouse, 1, attributes)
create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list)
else:
shopify_item["variant_id"] = shopify_item['variants'][0]["id"]
create_item(shopify_item, warehouse)
def add_item_weight(shopify_item):
shopify_item["weight"] = shopify_item['variants'][0]["weight"]
shopify_item["weight_unit"] = shopify_item['variants'][0]["weight_unit"]
def has_variants(shopify_item):
if len(shopify_item.get("options")) >= 1 and "Default Title" not in shopify_item.get("options")[0]["values"]:
return True
return False
def create_attribute(shopify_item):
attribute = []
# shopify item dict
for attr in shopify_item.get('options'):
if not frappe.db.get_value("Item Attribute", attr.get("name"), "name"):
frappe.get_doc({
"doctype": "Item Attribute",
"attribute_name": attr.get("name"),
"item_attribute_values": [
{
"attribute_value": attr_value,
"abbr":attr_value
}
for attr_value in attr.get("values")
]
}).insert()
attribute.append({"attribute": attr.get("name")})
else:
# check for attribute values
item_attr = frappe.get_doc("Item Attribute", attr.get("name"))
if not item_attr.numeric_values:
set_new_attribute_values(item_attr, attr.get("values"))
item_attr.save()
attribute.append({"attribute": attr.get("name")})
else:
attribute.append({
"attribute": attr.get("name"),
"from_range": item_attr.get("from_range"),
"to_range": item_attr.get("to_range"),
"increment": item_attr.get("increment"),
"numeric_values": item_attr.get("numeric_values")
})
return attribute
def set_new_attribute_values(item_attr, values):
for attr_value in values:
if not any((d.abbr.lower() == attr_value.lower() or d.attribute_value.lower() == attr_value.lower())\
for d in item_attr.item_attribute_values):
item_attr.append("item_attribute_values", {
"attribute_value": attr_value,
"abbr": attr_value
})
def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_of=None):
item_dict = {
"doctype": "Item",
"shopify_product_id": shopify_item.get("id"),
"shopify_variant_id": shopify_item.get("variant_id"),
"variant_of": variant_of,
"sync_with_shopify": 1,
"is_stock_item": 1,
"item_code": cstr(shopify_item.get("item_code")) or cstr(shopify_item.get("id")),
"item_name": shopify_item.get("title", '').strip(),
"description": shopify_item.get("body_html") or shopify_item.get("title"),
"shopify_description": shopify_item.get("body_html") or shopify_item.get("title"),
"item_group": get_item_group(shopify_item.get("product_type")),
"has_variants": has_variant,
"attributes":attributes or [],
"stock_uom": shopify_item.get("uom") or _("Nos"),
"stock_keeping_unit": shopify_item.get("sku") or get_sku(shopify_item),
"default_warehouse": warehouse,
"image": get_item_image(shopify_item),
"weight_uom": shopify_item.get("weight_unit"),
"weight_per_unit": shopify_item.get("weight"),
"default_supplier": get_supplier(shopify_item),
"item_defaults": [
{
"company": get_default_company()
}
]
}
if not is_item_exists(item_dict, attributes, variant_of=variant_of):
item_details = get_item_details(shopify_item)
name = ''
if not item_details:
new_item = frappe.get_doc(item_dict)
new_item.insert(ignore_permissions=True, ignore_mandatory=True)
name = new_item.name
if not name:
name = item_details.name
if not has_variant:
add_to_price_list(shopify_item, name)
frappe.db.commit()
def create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list):
template_item = frappe.db.get_value("Item", filters={"shopify_product_id": shopify_item.get("id")},
fieldname=["name", "stock_uom"], as_dict=True)
if template_item:
for variant in shopify_item.get("variants"):
shopify_item_variant = {
"id" : variant.get("id"),
"item_code": variant.get("id"),
"title": variant.get("title"),
"product_type": shopify_item.get("product_type"),
"sku": variant.get("sku"),
"uom": template_item.stock_uom or _("Nos"),
"item_price": variant.get("price"),
"variant_id": variant.get("id"),
"weight_unit": variant.get("weight_unit"),
"weight": variant.get("weight")
}
for i, variant_attr in enumerate(shopify_variants_attr_list):
if variant.get(variant_attr):
attributes[i].update({"attribute_value": get_attribute_value(variant.get(variant_attr), attributes[i])})
create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name)
def get_attribute_value(variant_attr_val, attribute):
attribute_value = frappe.db.sql("""select attribute_value from `tabItem Attribute Value`
where parent = %s and (abbr = %s or attribute_value = %s)""", (attribute["attribute"], variant_attr_val,
variant_attr_val), as_list=1)
return attribute_value[0][0] if len(attribute_value)>0 else cint(variant_attr_val)
def get_item_group(product_type=None):
import frappe.utils.nestedset
parent_item_group = frappe.utils.nestedset.get_root_of("Item Group")
if product_type:
if not frappe.db.get_value("Item Group", product_type, "name"):
item_group = frappe.get_doc({
"doctype": "Item Group",
"item_group_name": product_type,
"parent_item_group": parent_item_group,
"is_group": "No"
}).insert()
return item_group.name
else:
return product_type
else:
return parent_item_group
def get_sku(item):
if item.get("variants"):
return item.get("variants")[0].get("sku")
return ""
def add_to_price_list(item, name):
shopify_settings = frappe.db.get_value("Shopify Settings", None, ["price_list", "update_price_in_erpnext_price_list"], as_dict=1)
if not shopify_settings.update_price_in_erpnext_price_list:
return
item_price_name = frappe.db.get_value("Item Price",
{"item_code": name, "price_list": shopify_settings.price_list}, "name")
if not item_price_name:
frappe.get_doc({
"doctype": "Item Price",
"price_list": shopify_settings.price_list,
"item_code": name,
"price_list_rate": item.get("item_price") or item.get("variants")[0].get("price")
}).insert()
else:
item_rate = frappe.get_doc("Item Price", item_price_name)
item_rate.price_list_rate = item.get("item_price") or item.get("variants")[0].get("price")
item_rate.save()
def get_item_image(shopify_item):
if shopify_item.get("image"):
return shopify_item.get("image").get("src")
return None
def get_supplier(shopify_item):
if shopify_item.get("vendor"):
supplier = frappe.db.sql("""select name from tabSupplier
where name = %s or shopify_supplier_id = %s """, (shopify_item.get("vendor"),
shopify_item.get("vendor").lower()), as_list=1)
if not supplier:
supplier = frappe.get_doc({
"doctype": "Supplier",
"supplier_name": shopify_item.get("vendor"),
"shopify_supplier_id": shopify_item.get("vendor").lower(),
"supplier_group": get_supplier_group()
}).insert()
return supplier.name
else:
return shopify_item.get("vendor")
else:
return ""
def get_supplier_group():
supplier_group = frappe.db.get_value("Supplier Group", _("Shopify Supplier"))
if not supplier_group:
supplier_group = frappe.get_doc({
"doctype": "Supplier Group",
"supplier_group_name": _("Shopify Supplier")
}).insert()
return supplier_group.name
return supplier_group
def get_item_details(shopify_item):
item_details = {}
item_details = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")},
["name", "stock_uom", "item_name"], as_dict=1)
if item_details:
return item_details
else:
item_details = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("id")},
["name", "stock_uom", "item_name"], as_dict=1)
return item_details
def is_item_exists(shopify_item, attributes=None, variant_of=None):
if variant_of:
name = variant_of
else:
name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")})
if name:
item = frappe.get_doc("Item", name)
item.flags.ignore_mandatory=True
if not variant_of and not item.shopify_product_id:
item.shopify_product_id = shopify_item.get("shopify_product_id")
item.shopify_variant_id = shopify_item.get("shopify_variant_id")
item.save()
return True
if item.shopify_product_id and attributes and attributes[0].get("attribute_value"):
if not variant_of:
variant_of = frappe.db.get_value("Item",
{"shopify_product_id": item.shopify_product_id}, "variant_of")
# create conditions for all item attributes,
# as we are putting condition basis on OR it will fetch all items matching either of conditions
# thus comparing matching conditions with len(attributes)
# which will give exact matching variant item.
conditions = ["(iv.attribute='{0}' and iv.attribute_value = '{1}')"\
.format(attr.get("attribute"), attr.get("attribute_value")) for attr in attributes]
conditions = "( {0} ) and iv.parent = it.name ) = {1}".format(" or ".join(conditions), len(attributes))
parent = frappe.db.sql(""" select * from tabItem it where
( select count(*) from `tabItem Variant Attribute` iv
where {conditions} and it.variant_of = %s """.format(conditions=conditions) ,
variant_of, as_list=1)
if parent:
variant = frappe.get_doc("Item", parent[0][0])
variant.flags.ignore_mandatory = True
variant.shopify_product_id = shopify_item.get("shopify_product_id")
variant.shopify_variant_id = shopify_item.get("shopify_variant_id")
variant.save()
return False
if item.shopify_product_id and item.shopify_product_id != shopify_item.get("shopify_product_id"):
return False
return True
else:
return False

View File

@ -1,527 +0,0 @@
[
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Print Settings",
"fieldname": "compact_item_print",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "with_letterhead",
"label": "Compact Item Print",
"modified": "2016-06-06 15:18:17.025602",
"name": "Print Settings-compact_item_print",
"no_copy": 0,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Customer",
"fieldname": "shopify_customer_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "naming_series",
"label": "Shopify Customer Id",
"modified": "2016-01-15 17:25:28.991818",
"name": "Customer-shopify_customer_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Address",
"fieldname": "shopify_address_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "fax",
"label": "Shopify Address Id",
"modified": "2016-01-15 17:50:52.213743",
"name": "Address-shopify_address_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Sales Order",
"fieldname": "shopify_order_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "title",
"label": "Shopify Order Id",
"modified": "2016-01-18 09:55:50.764524",
"name": "Sales Order-shopify_order_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Item",
"fieldname": "shopify_product_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "item_code",
"label": "Shopify Product Id",
"modified": "2016-01-19 15:44:16.132952",
"name": "Item-shopify_product_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Sales Invoice",
"fieldname": "shopify_order_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "naming_series",
"label": "Shopify Order Id",
"modified": "2016-01-19 16:30:12.261797",
"name": "Sales Invoice-shopify_order_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Delivery Note",
"fieldname": "shopify_order_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "title",
"label": "Shopify Order Id",
"modified": "2016-01-19 16:30:31.201198",
"name": "Delivery Note-shopify_order_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Item",
"fieldname": "stock_keeping_unit",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "stock_uom",
"label": "Stock Keeping Unit",
"modified": "2015-11-10 09:29:10.854943",
"name": "Item-stock_keeping_unit",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": "0",
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Item",
"fieldname": "sync_with_shopify",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "is_stock_item",
"label": "Sync With Shopify",
"modified": "2015-10-12 15:54:31.997714",
"name": "Item-sync_with_shopify",
"no_copy": 0,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Customer",
"fieldname": "sync_with_shopify",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "is_frozen",
"label": "Sync With Shopify",
"modified": "2015-10-01 17:31:55.758826",
"name": "Customer-sync_with_shopify",
"no_copy": 0,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Item",
"fieldname": "shopify_variant_id",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "item_code",
"label": "Variant Id",
"modified": "2015-11-09 18:26:50.825858",
"name": "Item-shopify_variant_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Item",
"fieldname": "sync_qty_with_shopify",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "item_code",
"label": "Sync Quantity With Shopify",
"modified": "2015-12-29 08:37:46.183295",
"name": "Item-sync_qty_with_shopify",
"no_copy": 0,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Delivery Note",
"fieldname": "shopify_fulfillment_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "title",
"label": "Shopify Fulfillment Id",
"modified": "2016-01-20 23:50:35.609543",
"name": "Delivery Note-shopify_fulfillment_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Supplier",
"fieldname": "shopify_supplier_id",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "supplier_name",
"label": "Shopify Supplier Id",
"modified": "2016-02-01 15:41:25.818306",
"name": "Supplier-shopify_supplier_id",
"no_copy": 1,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
},
{
"allow_on_submit": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Item",
"fieldname": "shopify_description",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"insert_after": "section_break_11",
"label": "shopify_description",
"modified": "2016-06-15 12:15:36.325581",
"name": "Item-shopify_description",
"no_copy": 0,
"options": null,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"unique": 0,
"width": null
}
]

View File

@ -1,59 +0,0 @@
{
"customer": {
"id": 2324518599,
"email": "andrew@wyatt.co.in",
"accepts_marketing": false,
"created_at": "2016-01-20T17:18:35+05:30",
"updated_at": "2016-01-20T17:22:23+05:30",
"first_name": "Andrew",
"last_name": "Wyatt",
"orders_count": 0,
"state": "disabled",
"total_spent": "0.00",
"last_order_id": null,
"note": "",
"verified_email": true,
"multipass_identifier": null,
"tax_exempt": false,
"tags": "",
"last_order_name": null,
"default_address": {
"id": 2476804295,
"first_name": "Andrew",
"last_name": "Wyatt",
"company": "Wyatt Inc.",
"address1": "B-11, Betahouse",
"address2": "Street 11, Sector 52",
"city": "Manhattan",
"province": "New York",
"country": "United States",
"zip": "10027",
"phone": "145-112211",
"name": "Andrew Wyatt",
"province_code": "NY",
"country_code": "US",
"country_name": "United States",
"default": true
},
"addresses": [
{
"id": 2476804295,
"first_name": "Andrew",
"last_name": "Wyatt",
"company": "Wyatt Inc.",
"address1": "B-11, Betahouse",
"address2": "Street 11, Sector 52",
"city": "Manhattan",
"province": "New York",
"country": "United States",
"zip": "10027",
"phone": "145-112211",
"name": "Andrew Wyatt",
"province_code": "NY",
"country_code": "US",
"country_name": "United States",
"default": true
}
]
}
}

View File

@ -1,125 +0,0 @@
{
"product": {
"id": 4059739520,
"title": "Shopify Test Item",
"body_html": "<div>Hold back Spin Medallion-Set of 2</div>\n<div></div>\n<div>Finish: Plated/ Powder Coated</div>\n<div>Material: Iron</div>\n<div>Color Finish: Satin Silver, Brown Oil Rubbed, Roman Bronze</div>\n<div>Qty: 1 Set</div>",
"vendor": "Boa casa",
"product_type": "Curtain Accessories",
"created_at": "2016-01-18T17:16:37+05:30",
"handle": "1001624-01",
"updated_at": "2016-01-20T17:26:44+05:30",
"published_at": "2016-01-18T17:16:37+05:30",
"template_suffix": null,
"published_scope": "global",
"tags": "Category_Curtain Accessories, Type_Holdback",
"variants": [{
"id": 13917612359,
"product_id": 4059739520,
"title": "Test BALCK Item",
"price": "499.00",
"sku": "",
"position": 1,
"grams": 0,
"inventory_policy": "continue",
"compare_at_price": null,
"fulfillment_service": "manual",
"inventory_management": "shopify",
"option1": "BLACK",
"option2": null,
"option3": null,
"created_at": "2016-01-18T17:16:37+05:30",
"updated_at": "2016-01-20T17:26:44+05:30",
"requires_shipping": true,
"taxable": true,
"barcode": "",
"inventory_quantity": -1,
"old_inventory_quantity": -1,
"image_id": 8539321735,
"weight": 0,
"weight_unit": "kg"
}, {
"id": 13917612423,
"product_id": 4059739520,
"title": "Test BLUE Item",
"price": "499.00",
"sku": "",
"position": 2,
"grams": 0,
"inventory_policy": "continue",
"compare_at_price": null,
"fulfillment_service": "manual",
"inventory_management": "shopify",
"option1": "BLUE",
"option2": null,
"option3": null,
"created_at": "2016-01-18T17:16:37+05:30",
"updated_at": "2016-01-20T17:26:44+05:30",
"requires_shipping": true,
"taxable": true,
"barcode": "",
"inventory_quantity": -1,
"old_inventory_quantity": -1,
"image_id": null,
"weight": 0,
"weight_unit": "kg"
}, {
"id": 13917612487,
"product_id": 4059739520,
"title": "Test White Item",
"price": "499.00",
"sku": "",
"position": 3,
"grams": 0,
"inventory_policy": "continue",
"compare_at_price": null,
"fulfillment_service": "manual",
"inventory_management": "shopify",
"option1": "White",
"option2": null,
"option3": null,
"created_at": "2016-01-18T17:16:37+05:30",
"updated_at": "2016-01-18T17:16:37+05:30",
"requires_shipping": true,
"taxable": true,
"barcode": "",
"inventory_quantity": 0,
"old_inventory_quantity": 0,
"image_id": null,
"weight": 0,
"weight_unit": "kg"
}],
"options": [{
"id": 4985027399,
"product_id": 4059739520,
"name": "Colour",
"position": 1,
"values": [
"BLACK",
"BLUE",
"White"
]
}],
"images": [{
"id": 8539321735,
"product_id": 4059739520,
"position": 1,
"created_at": "2016-01-18T17:16:37+05:30",
"updated_at": "2016-01-18T17:16:37+05:30",
"src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
"variant_ids": [
13917612359
]
}],
"image": {
"id": 8539321735,
"product_id": 4059739520,
"position": 1,
"created_at": "2016-01-18T17:16:37+05:30",
"updated_at": "2016-01-18T17:16:37+05:30",
"src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
"variant_ids": [
13917612359
]
}
}
}

View File

@ -1,270 +0,0 @@
{
"order": {
"id": 2414345735,
"email": "andrew@wyatt.co.in",
"closed_at": null,
"created_at": "2016-01-20T17:26:39+05:30",
"updated_at": "2016-01-20T17:27:15+05:30",
"number": 5,
"note": "",
"token": "660fed25987517b733644a8c9ec7c8e0",
"gateway": "manual",
"test": false,
"total_price": "1018.00",
"subtotal_price": "998.00",
"total_weight": 0,
"total_tax": "0.00",
"taxes_included": false,
"currency": "INR",
"financial_status": "paid",
"confirmed": true,
"total_discounts": "0.00",
"total_line_items_price": "998.00",
"cart_token": null,
"buyer_accepts_marketing": false,
"name": "#1005",
"referring_site": null,
"landing_site": null,
"cancelled_at": null,
"cancel_reason": null,
"total_price_usd": "15.02",
"checkout_token": null,
"reference": null,
"user_id": 55391175,
"location_id": null,
"source_identifier": null,
"source_url": null,
"processed_at": "2016-01-20T17:26:39+05:30",
"device_id": null,
"browser_ip": null,
"landing_site_ref": null,
"order_number": 1005,
"discount_codes": [],
"note_attributes": [],
"payment_gateway_names": [
"manual"
],
"processing_method": "manual",
"checkout_id": null,
"source_name": "shopify_draft_order",
"fulfillment_status": "fulfilled",
"tax_lines": [],
"tags": "",
"contact_email": "andrew@wyatt.co.in",
"line_items": [
{
"id": 4125768135,
"variant_id": 13917612359,
"title": "Shopify Test Item",
"quantity": 1,
"price": "499.00",
"grams": 0,
"sku": "",
"variant_title": "Roman BALCK 1",
"vendor": "Boa casa",
"fulfillment_service": "manual",
"product_id": 4059739527,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Roman BALCK 1",
"variant_inventory_management": "shopify",
"properties": [],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": []
},
{
"id": 4125768199,
"variant_id": 13917612423,
"title": "Shopify Test Item",
"quantity": 1,
"price": "499.00",
"grams": 0,
"sku": "",
"variant_title": "Satin BLUE 1",
"vendor": "Boa casa",
"fulfillment_service": "manual",
"product_id": 4059739527,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "Satin BLUE 1",
"variant_inventory_management": "shopify",
"properties": [],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": []
}
],
"shipping_lines": [
{
"id": 2108906247,
"title": "International Shipping",
"price": "20.00",
"code": "International Shipping",
"source": "shopify",
"phone": null,
"tax_lines": []
}
],
"billing_address": {
"first_name": "Andrew",
"address1": "B-11, Betahouse",
"phone": "145-112211",
"city": "Manhattan",
"zip": "10027",
"province": "New York",
"country": "United States",
"last_name": "Wyatt",
"address2": "Street 11, Sector 52",
"company": "Wyatt Inc.",
"latitude": 40.8138912,
"longitude": -73.96243270000001,
"name": "Andrew Wyatt",
"country_code": "US",
"province_code": "NY"
},
"shipping_address": {
"first_name": "Andrew",
"address1": "B-11, Betahouse",
"phone": "145-112211",
"city": "Manhattan",
"zip": "10027",
"province": "New York",
"country": "United States",
"last_name": "Wyatt",
"address2": "Street 11, Sector 52",
"company": "Wyatt Inc.",
"latitude": 40.8138912,
"longitude": -73.96243270000001,
"name": "Andrew Wyatt",
"country_code": "US",
"province_code": "NY"
},
"fulfillments": [
{
"id": 1849629255,
"order_id": 2414345735,
"status": "success",
"created_at": "2016-01-20T17:27:15+05:30",
"service": "manual",
"updated_at": "2016-01-20T17:27:15+05:30",
"tracking_company": null,
"tracking_number": null,
"tracking_numbers": [],
"tracking_url": null,
"tracking_urls": [],
"receipt": {},
"line_items": [
{
"id": 4125768199,
"variant_id": 13917612423,
"title": "1001624/01",
"quantity": 1,
"price": "499.00",
"grams": 0,
"sku": "",
"variant_title": "Satin Silver",
"vendor": "Boa casa",
"fulfillment_service": "manual",
"product_id": 4059739527,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "1001624/01 - Satin Silver",
"variant_inventory_management": "shopify",
"properties": [],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": []
}
]
},
{
"id": 1849628167,
"order_id": 2414345735,
"status": "success",
"created_at": "2016-01-20T17:26:58+05:30",
"service": "manual",
"updated_at": "2016-01-20T17:26:58+05:30",
"tracking_company": null,
"tracking_number": null,
"tracking_numbers": [],
"tracking_url": null,
"tracking_urls": [],
"receipt": {},
"line_items": [
{
"id": 4125768135,
"variant_id": 13917612359,
"title": "1001624/01",
"quantity": 1,
"price": "499.00",
"grams": 0,
"sku": "",
"variant_title": "Roman Bronze",
"vendor": "Boa casa",
"fulfillment_service": "manual",
"product_id": 4059739527,
"requires_shipping": true,
"taxable": true,
"gift_card": false,
"name": "1001624/01 - Roman Bronze",
"variant_inventory_management": "shopify",
"properties": [],
"product_exists": true,
"fulfillable_quantity": 0,
"total_discount": "0.00",
"fulfillment_status": "fulfilled",
"tax_lines": []
}
]
}
],
"refunds": [],
"customer": {
"id": 2324518599,
"email": "andrew@wyatt.co.in",
"accepts_marketing": false,
"created_at": "2016-01-20T17:18:35+05:30",
"updated_at": "2016-01-20T17:26:39+05:30",
"first_name": "Andrew",
"last_name": "Wyatt",
"orders_count": 1,
"state": "disabled",
"total_spent": "1018.00",
"last_order_id": 2414345735,
"note": "",
"verified_email": true,
"multipass_identifier": null,
"tax_exempt": false,
"tags": "",
"last_order_name": "#1005",
"default_address": {
"id": 2476804295,
"first_name": "Andrew",
"last_name": "Wyatt",
"company": "Wyatt Inc.",
"address1": "B-11, Betahouse",
"address2": "Street 11, Sector 52",
"city": "Manhattan",
"province": "New York",
"country": "United States",
"zip": "10027",
"phone": "145-112211",
"name": "Andrew Wyatt",
"province_code": "NY",
"country_code": "US",
"country_name": "United States",
"default": true
}
}
}
}

View File

@ -1,23 +0,0 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Shopify Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Shopify Settings
() => frappe.tests.make('Shopify Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -1,107 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest, os, json
from frappe.utils import cstr, cint
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
from frappe.core.doctype.data_import.data_import import import_doc
class ShopifySettings(unittest.TestCase):
@classmethod
def setUpClass(cls):
frappe.set_user("Administrator")
cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
if not cls.allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
# use the fixture data
import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
frappe.reload_doctype("Customer")
frappe.reload_doctype("Sales Order")
frappe.reload_doctype("Delivery Note")
frappe.reload_doctype("Sales Invoice")
cls.setup_shopify()
@classmethod
def tearDownClass(cls):
if not cls.allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
@classmethod
def setup_shopify(cls):
shopify_settings = frappe.get_doc("Shopify Settings")
shopify_settings.taxes = []
shopify_settings.update({
"app_type": "Private",
"shopify_url": "test.myshopify.com",
"api_key": "17702c7c4452b9c5d235240b6e7a39da",
"password": "17702c7c4452b9c5d235240b6e7a39da",
"shared_secret": "17702c7c4452b9c5d235240b6e7a39da",
"price_list": "_Test Price List",
"warehouse": "_Test Warehouse - _TC",
"cash_bank_account": "Cash - _TC",
"account": "Cash - _TC",
"customer_group": "_Test Customer Group",
"cost_center": "Main - _TC",
"taxes": [
{
"shopify_tax": "International Shipping",
"tax_account":"Legal Expenses - _TC"
}
],
"enable_shopify": 0,
"sales_order_series": "SO-",
"sync_sales_invoice": 1,
"sales_invoice_series": "SINV-",
"sync_delivery_note": 1,
"delivery_note_series": "DN-"
}).save(ignore_permissions=True)
cls.shopify_settings = shopify_settings
def test_order(self):
# Create Customer
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
shopify_customer = json.load(shopify_customer)
create_customer(shopify_customer.get("customer"), self.shopify_settings)
# Create Item
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
shopify_item = json.load(shopify_item)
make_item("_Test Warehouse - _TC", shopify_item.get("product"))
# Create Order
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
shopify_order = json.load(shopify_order)
create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company")
sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))})
self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
# Check for customer
shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
# Check sales invoice
sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
# Check delivery note
delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments")))

View File

@ -1,133 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2015-10-05 16:55:20.455371",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "shopify_tax",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Shopify Tax/Shipping Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "tax_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "ERPNext Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-04-09 11:36:49.272815",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Shopify Tax Account",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ShopifyTaxAccount(Document):
pass

View File

@ -1,103 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-04-10 17:06:22.697427",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "webhook_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Webhook ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "method",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Method",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-04-11 12:43:09.456449",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Shopify Webhook Detail",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class ShopifyWebhookDetail(Document):
pass

View File

@ -40,16 +40,6 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Shopify Settings",
"link_to": "Shopify Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@ -113,4 +103,4 @@
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": []
}
}

View File

@ -30,16 +30,6 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Shopify Settings",
"link_to": "Shopify Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@ -79,4 +69,4 @@
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": []
}
}

View File

@ -341,7 +341,6 @@ scheduler_events = {
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
"erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
],
"hourly_long": [

View File

@ -45,7 +45,6 @@ erpnext.patches.v11_0.make_location_from_warehouse
erpnext.patches.v11_0.make_asset_finance_book_against_old_entries
erpnext.patches.v11_0.check_buying_selling_in_currency_exchange
erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019
erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07
erpnext.patches.v11_0.rename_overproduction_percent_field
erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom
erpnext.patches.v11_0.inter_state_field_for_gst
@ -143,7 +142,6 @@ erpnext.patches.v12_0.add_variant_of_in_item_attribute_table
erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account
erpnext.patches.v12_0.create_default_energy_point_rules
erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order
erpnext.patches.v12_0.set_default_shopify_app_type
erpnext.patches.v12_0.set_cwip_and_delete_asset_settings
erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
@ -244,7 +242,6 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
erpnext.patches.v13_0.update_member_email_address
erpnext.patches.v13_0.update_custom_fields_for_shopify
erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
@ -299,3 +296,4 @@ erpnext.patches.v13_0.update_subscription_status_in_memberships
erpnext.patches.v13_0.update_amt_in_work_order_required_items
erpnext.patches.v13_0.update_export_type_for_gst
erpnext.patches.v13_0.update_tds_check_field #3
erpnext.patches.v13_0.shopify_deprecation_warning

View File

@ -1,43 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe.installer import remove_from_installed_apps
def execute():
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_tax_account')
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_log')
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_webhook_detail')
if 'erpnext_shopify' in frappe.get_installed_apps():
remove_from_installed_apps('erpnext_shopify')
frappe.delete_doc("Module Def", 'erpnext_shopify')
frappe.db.commit()
frappe.db.sql("truncate `tabShopify Log`")
setup_app_type()
else:
disable_shopify()
def setup_app_type():
try:
shopify_settings = frappe.get_doc("Shopify Settings")
shopify_settings.app_type = 'Private'
shopify_settings.update_price_in_erpnext_price_list = 0 if getattr(shopify_settings, 'push_prices_to_shopify', None) else 1
shopify_settings.flags.ignore_mandatory = True
shopify_settings.ignore_permissions = True
shopify_settings.save()
except Exception:
frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
frappe.log_error(frappe.get_traceback())
def disable_shopify():
# due to frappe.db.set_value wrongly written and enable_shopify being default 1
# Shopify Settings isn't properly configured and leads to error
shopify = frappe.get_doc('Shopify Settings')
if shopify.app_type == "Public" or shopify.app_type == None or \
(shopify.enable_shopify and not (shopify.shopify_url or shopify.api_key)):
frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)

View File

@ -1,6 +0,0 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private')

View File

@ -0,0 +1,10 @@
import click
def execute():
click.secho(
"Shopify Integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
"Please install the app to continue using the integration: https://github.com/frappe/ecommerce_integrations",
fg="yellow",
)

View File

@ -1,10 +0,0 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields
def execute():
if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'):
setup_custom_fields()