Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync HSN code #346

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-12-11 15:41:11.020935",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_group",
"hsnsac_code"
],
"fields": [
{
"fieldname": "item_group",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Group",
"options": "Item Group"
},
{
"fieldname": "hsnsac_code",
"fieldtype": "Data",
"in_list_view": 1,
"label": "HSN/SAC Code"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-12-11 15:45:34.268557",
"modified_by": "Administrator",
"module": "shopify",
"name": "Shopify Item Group HSN Mapping",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class ShopifyItemGroupHSNMapping(Document):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ frappe.ui.form.on("Shopify Setting", {
},

refresh: function (frm) {
frm.add_custom_button(__("Import Item Group"), function () {
frm.trigger("shopify_import_item_group");
});
frm.add_custom_button(__("Import Products"), function () {
frappe.set_route("shopify-import-products");
});
Expand All @@ -37,6 +40,12 @@ frappe.ui.form.on("Shopify Setting", {
frm.trigger("setup_queries");
},

shopify_import_item_group: function (frm) {
frappe.call({
method: "ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.import_all_product_groups",
});
},

setup_queries: function (frm) {
const warehouse_query = () => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"inventory_sync_frequency",
"fetch_shopify_locations",
"shopify_warehouse_mapping",
"shopify_item_group_hsn_mapping",
"sync_old_orders_section",
"sync_old_orders",
"column_break_45",
Expand Down Expand Up @@ -387,12 +388,18 @@
"fieldtype": "Link",
"label": "Default Shipping Charges Account",
"options": "Account"
},
{
"fieldname": "shopify_item_group_hsn_mapping",
"fieldtype": "Table",
"label": "Shopify Item Group HSN Mapping",
"options": "Shopify Item Group HSN Mapping"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-10-24 10:38:49.247431",
"modified": "2024-12-11 15:45:53.201261",
"modified_by": "Administrator",
"module": "shopify",
"name": "Shopify Setting",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

from ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_item import ecommerce_item
from ecommerce_integrations.shopify.connection import temp_shopify_session
from ecommerce_integrations.shopify.constants import MODULE_NAME
from ecommerce_integrations.shopify.constants import MODULE_NAME, SETTING_DOCTYPE
from ecommerce_integrations.shopify.product import ShopifyProduct

# constants
SYNC_JOB_NAME = "shopify.job.sync.all.products"
REALTIME_KEY = "shopify.key.sync.all.products"
SYNC_PRODUCT_GROUP_JOB_NAME = "shopify.job.sync.all.product.groups"
SYNC_PRODUCT_GROUP_REALTIME_KEY = "shopify.key.sync.all.product.groups"


@frappe.whitelist()
Expand Down Expand Up @@ -126,6 +128,41 @@ def import_all_products():
)


@frappe.whitelist()
def import_all_product_groups():
frappe.enqueue(
queue_sync_all_product_groups,
queue="long",
job_name=SYNC_PRODUCT_GROUP_JOB_NAME,
key=SYNC_PRODUCT_GROUP_REALTIME_KEY,
)


def queue_sync_all_product_groups(*args, **kwargs):
_sync = True
collection = _fetch_products_from_shopify(limit=100)
savepoint = "shopify_product_groups_sync"
setting = frappe.get_doc(SETTING_DOCTYPE)
while _sync:
for product in collection:
try:
frappe.db.savepoint(savepoint)
shopify_product = ShopifyProduct(product.id, setting=setting)
shopify_product.sync_product(sync_product_group=True)
except Exception:
frappe.log_error(title="queue_sync_all_product_groups", message=frappe.get_traceback())
frappe.db.rollback(save_point=savepoint)
continue

if collection.has_next_page():
frappe.db.commit() # prevents too many write request error
collection = _fetch_products_from_shopify(from_=collection.next_page_url)
else:
_sync = False
setting.save()
return True


def queue_sync_all_products(*args, **kwargs):
start_time = process_time()

Expand Down
112 changes: 95 additions & 17 deletions ecommerce_integrations/shopify/product.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from typing import Optional

import frappe
import shopify
from frappe import _, msgprint
from frappe.query_builder import DocType
from frappe.utils import cint, cstr
from frappe.utils.nestedset import get_root_of
from shopify.resources import Product, Variant
Expand All @@ -26,12 +28,13 @@ def __init__(
variant_id: str | None = None,
sku: str | None = None,
has_variants: int | None = 0,
setting: object | None = None,
):
self.product_id = str(product_id)
self.variant_id = str(variant_id) if variant_id else None
self.sku = str(sku) if sku else None
self.has_variants = has_variants
self.setting = frappe.get_doc(SETTING_DOCTYPE)
self.setting = setting if setting else frappe.get_doc(SETTING_DOCTYPE)

if not self.setting.is_enabled():
frappe.throw(_("Can not create Shopify product when integration is disabled."))
Expand All @@ -54,11 +57,22 @@ def get_erpnext_item(self):
)

@temp_shopify_session
def sync_product(self):
if not self.is_synced():
def sync_product(self, sync_product_group=False):
if not self.is_synced() or sync_product_group:
shopify_product = Product.find(self.product_id)
product_dict = shopify_product.to_dict()
self._make_item(product_dict)
product_dict = self._update_hsn_sac_code(product_dict)
if sync_product_group:
self._sync_product_group(product_dict)
else:
self._make_item(product_dict)

def _sync_product_group(self, product_dict):
self._get_item_group(
product_type=product_dict.get("product_type"),
sync_product_group=True,
hsnsac_code=product_dict.get("gst_hsn_code"),
)

def _make_item(self, product_dict):
_add_weight_details(product_dict)
Expand Down Expand Up @@ -127,7 +141,9 @@ def _create_item(self, product_dict, warehouse, has_variant=0, attributes=None,
"item_code": cstr(product_dict.get("item_code")) or cstr(product_dict.get("id")),
"item_name": product_dict.get("title", "").strip(),
"description": product_dict.get("body_html") or product_dict.get("title"),
"item_group": self._get_item_group(product_dict.get("product_type")),
"item_group": self._get_item_group(
product_dict.get("product_type"), hsnsac_code=product_dict.get("gst_hsn_code")
),
"has_variants": has_variant,
"attributes": attributes or [],
"stock_uom": product_dict.get("uom") or _("Nos"),
Expand All @@ -137,6 +153,7 @@ def _create_item(self, product_dict, warehouse, has_variant=0, attributes=None,
"weight_uom": WEIGHT_TO_ERPNEXT_UOM_MAP[product_dict.get("weight_unit")],
"weight_per_unit": product_dict.get("weight"),
"default_supplier": self._get_supplier(product_dict),
"gst_hsn_code": product_dict.get("gst_hsn_code"),
}

integration_item_code = product_dict["id"] # shopify product_id
Expand Down Expand Up @@ -174,6 +191,7 @@ def _create_item_variants(self, product_dict, warehouse, attributes):
"item_price": variant.get("price"),
"weight_unit": variant.get("weight_unit"),
"weight": variant.get("weight"),
"gst_hsn_code": product_dict.get("gst_hsn_code"),
}

for i, variant_attr in enumerate(SHOPIFY_VARIANTS_ATTR_LIST):
Expand All @@ -187,22 +205,49 @@ def _create_item_variants(self, product_dict, warehouse, attributes):
)
self._create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name)

def _update_hsn_sac_code(self, product_dict):
try:
for variant in product_dict.get("variants"):
if variant.get("inventory_item_id"):
inventory_item = shopify.InventoryItem.find(variant.get("inventory_item_id"))
if hasattr(inventory_item, "harmonized_system_code"):
product_dict["gst_hsn_code"] = inventory_item.harmonized_system_code
break
except Exception:
frappe.log_error(title="_update_hsn_sac_code", message=frappe.get_traceback())
if not (product_dict.get("gst_hsn_code")):
hsnsac_code = frappe.db.get_value(
"Shopify Item Group HSN Mapping",
{
"parent": SETTING_DOCTYPE,
"item_group": product_dict.get("product_type"),
"hsnsac_code": ["is", "set"],
},
"hsnsac_code",
)
if hsnsac_code:
product_dict["gst_hsn_code"] = hsnsac_code

return product_dict

def _get_attribute_value(self, 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,
attribute_value = frappe.db.get_all(
"Item Attribute Value",
filters={"parent": attribute["attribute"]},
or_filters={"abbr": variant_attr_val, "attribute_value": variant_attr_val},
fields=["attribute_value"],
as_list=True,
)
return attribute_value[0][0] if len(attribute_value) > 0 else cint(variant_attr_val)

def _get_item_group(self, product_type=None):
def _get_item_group(self, product_type=None, hsnsac_code=None, sync_product_group=False):
parent_item_group = get_root_of("Item Group")

if not product_type:
return parent_item_group

if frappe.db.get_value("Item Group", product_type, "name"):
self._sync_item_group_hsn_sac_code(sync_product_group, product_type, hsnsac_code, False)
return product_type
item_group = frappe.get_doc(
{
Expand All @@ -212,16 +257,49 @@ def _get_item_group(self, product_type=None):
"is_group": "No",
}
).insert()
self._sync_item_group_hsn_sac_code(sync_product_group, product_type, hsnsac_code)
return item_group.name

def _update_hsn_code_in_item_group(self, product_type, hsnsac_code):
try:
if not (frappe.db.get_value("Item Group", product_type, "gst_hsn_code")) and hsnsac_code:
frappe.db.set_value("Item Group", product_type, "gst_hsn_code", hsnsac_code)
except Exception:
pass

def _update_in_item_group_mapping(self, sync_product_group, product_type, hsnsac_code):
has_value_flag = False
for row in self.setting.shopify_item_group_hsn_mapping:
if row.get("item_group") == product_type:
if hsnsac_code and row.get("hsnsac_code") != hsnsac_code:
row.hsnsac_code = hsnsac_code
has_value_flag = True
break
if not has_value_flag:
self._sync_item_group_hsn_sac_code(sync_product_group, product_type, hsnsac_code)

def _sync_item_group_hsn_sac_code(self, sync_product_group, item_group, hsnsac_code=None, is_new=True):
if sync_product_group:
if is_new:
self.setting.append(
"shopify_item_group_hsn_mapping",
{"item_group": item_group, "hsnsac_code": hsnsac_code},
)
else:
self._update_hsn_code_in_item_group(item_group, hsnsac_code)
self._update_in_item_group_mapping(sync_product_group, item_group, hsnsac_code)

def _get_supplier(self, product_dict):
if product_dict.get("vendor"):
supplier = frappe.db.sql(
f"""select name from tabSupplier
where name = %s or {SUPPLIER_ID_FIELD} = %s """,
(product_dict.get("vendor"), product_dict.get("vendor").lower()),
as_list=1,
)
Supplier = DocType("Supplier")
supplier = (
frappe.qb.from_(Supplier)
.select(Supplier.name)
.where(
(Supplier.name == product_dict.get("vendor"))
| (Supplier[SUPPLIER_ID_FIELD] == product_dict.get("vendor").lower())
)
).run(as_list=True)

if supplier:
return product_dict.get("vendor")
Expand Down
7 changes: 7 additions & 0 deletions ecommerce_integrations/shopify/tests/test_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ def test_sync_single_product(self):
ecommerce_item_exists = frappe.db.exists("Ecommerce Item", {"erpnext_item_code": item.name})
self.assertTrue(bool(ecommerce_item_exists))

def test_sync_item_group(self):
self.fake("products/6704435495065", body=self.load_fixture("variant_product"))
product = ShopifyProduct(product_id="6704435495065")
product.sync_product(sync_product_group=1)
self.assertEqual(frappe.get_last_doc("Item Group").name, "shirt")
self.assertEqual(product.setting.shopify_item_group_hsn_mapping[0].item_group, "shirt")

def test_sync_product_with_variants(self):
self.fake("products/6704435495065", body=self.load_fixture("variant_product"))

Expand Down
1 change: 1 addition & 0 deletions ecommerce_integrations/shopify/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def setUpClass(cls):
"erpnext_warehouse": "_Test Warehouse 2 - _TC",
},
],
"shopify_item_group_hsn_mapping": [],
}
).save(ignore_permissions=True)

Expand Down
Loading