354 lines
13 KiB
Python
354 lines
13 KiB
Python
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)
|
|
|