├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── README.md ├── ci ├── fix-mariadb.sh └── my_config.h.patch ├── erpnext_shopify ├── __init__.py ├── after_install.py ├── api.py ├── billing.py ├── change_log │ ├── current │ │ └── readme.md │ └── v1 │ │ └── v1_3_0.md ├── config │ ├── __init__.py │ ├── docs.py │ └── setup.py ├── docs │ ├── assets │ │ └── img │ │ │ ├── broker1.2.png │ │ │ ├── erpnext-config-for-private-app.png │ │ │ ├── home.png │ │ │ ├── permission.png │ │ │ ├── setup-shopify-settings.png │ │ │ ├── shopify-new-private-app.png │ │ │ ├── shopify-private-apps-page.png │ │ │ └── sync.png │ ├── contents.html │ ├── contents.py │ ├── current │ │ ├── api │ │ │ ├── config │ │ │ │ ├── erpnext_shopify.config.docs.html │ │ │ │ ├── erpnext_shopify.config.html │ │ │ │ ├── erpnext_shopify.config.setup.html │ │ │ │ ├── index.html │ │ │ │ └── index.txt │ │ │ ├── erpnext_shopify.__init__.html │ │ │ ├── erpnext_shopify.exceptions.html │ │ │ ├── erpnext_shopify.hooks.html │ │ │ ├── erpnext_shopify.utils.html │ │ │ ├── erpnext_shopify │ │ │ │ ├── erpnext_shopify.erpnext_shopify.html │ │ │ │ ├── index.html │ │ │ │ └── index.txt │ │ │ ├── index.html │ │ │ └── index.txt │ │ ├── index.html │ │ └── models │ │ │ ├── erpnext_shopify │ │ │ ├── index.html │ │ │ ├── index.txt │ │ │ ├── shopify_settings.html │ │ │ └── shopify_tax_account.html │ │ │ ├── index.html │ │ │ └── index.txt │ ├── index.html │ ├── index.txt │ ├── install.md │ ├── license.html │ └── user │ │ └── index.md ├── erpnext_shopify │ ├── __init__.py │ └── doctype │ │ ├── __init__.py │ │ ├── shopify_log │ │ ├── __init__.py │ │ ├── shopify_log.js │ │ ├── shopify_log.json │ │ ├── shopify_log.py │ │ ├── shopify_log_list.js │ │ └── test_shopify_log.py │ │ ├── shopify_settings │ │ ├── __init__.py │ │ ├── shopify_settings.js │ │ ├── shopify_settings.json │ │ ├── shopify_settings.py │ │ ├── test_data │ │ │ ├── shopify_customer.json │ │ │ ├── shopify_item.json │ │ │ └── shopify_order.json │ │ ├── test_shopify_settings.js │ │ └── test_shopify_settings.py │ │ └── shopify_tax_account │ │ ├── __init__.py │ │ ├── shopify_tax_account.json │ │ └── shopify_tax_account.py ├── exceptions.py ├── fixtures │ └── custom_field.json ├── hooks.py ├── modules.txt ├── patches.txt ├── patches │ ├── V1_0 │ │ ├── __init__.py │ │ ├── create_weight_uom.py │ │ └── set_variant_id.py │ ├── V2_0 │ │ ├── __init__.py │ │ ├── add_field_shopify_description.py │ │ ├── refactor_id.py │ │ ├── reset_inclusive_taxes_and_totals.py │ │ ├── set_default_supplier.py │ │ └── set_shopify_supplier_id.py │ └── __init__.py ├── shopify_requests.py ├── sync_customers.py ├── sync_orders.py ├── sync_products.py ├── templates │ ├── __init__.py │ ├── emails │ │ └── billing.md │ ├── generators │ │ └── __init__.py │ └── pages │ │ └── __init__.py ├── utils.py └── webhooks.py ├── license.txt ├── requirements.txt ├── setup.py └── test_sites ├── apps.txt ├── current_site.txt ├── languages.txt └── test_site └── site_config.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: trusty 3 | 4 | addons: 5 | apt: 6 | sources: 7 | - google-chrome 8 | packages: 9 | - google-chrome-stable 10 | 11 | python: 12 | - "2.7" 13 | 14 | services: 15 | - mysql 16 | 17 | install: 18 | - pip install flake8==3.3.0 19 | - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics 20 | - sudo rm /etc/apt/sources.list.d/docker.list 21 | - sudo apt-get purge -y mysql-common mysql-server mysql-client 22 | - nvm install v7.10.0 23 | - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py 24 | - sudo python install.py --develop --user travis --without-bench-setup 25 | - sudo pip install -e ~/bench 26 | 27 | - rm $TRAVIS_BUILD_DIR/.git/shallow 28 | - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ 29 | 30 | before_script: 31 | - wget http://chromedriver.storage.googleapis.com/2.27/chromedriver_linux64.zip 32 | - unzip chromedriver_linux64.zip 33 | - sudo apt-get install libnss3 34 | - sudo apt-get --only-upgrade install google-chrome-stable 35 | - sudo cp chromedriver /usr/local/bin/. 36 | - sudo chmod +x /usr/local/bin/chromedriver 37 | - export DISPLAY=:99.0 38 | - sh -e /etc/init.d/xvfb start 39 | - sleep 3 40 | - mysql -u root -ptravis -e 'create database test_frappe' 41 | - echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis 42 | - echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis 43 | 44 | - cd ~/frappe-bench 45 | - bench get-app erpnext https://github.com/frappe/erpnext.git --branch master 46 | - bench get-app erpnext_shopify $TRAVIS_BUILD_DIR 47 | - bench reinstall --yes 48 | - bench build 49 | - bench scheduler disable 50 | - bench start & 51 | - sleep 10 52 | 53 | jobs: 54 | include: 55 | - stage: test 56 | script: 57 | - set -e 58 | - bench run-tests 59 | env: Server Side Test -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include erpnext_shopify *.css 8 | recursive-include erpnext_shopify *.csv 9 | recursive-include erpnext_shopify *.html 10 | recursive-include erpnext_shopify *.ico 11 | recursive-include erpnext_shopify *.js 12 | recursive-include erpnext_shopify *.json 13 | recursive-include erpnext_shopify *.md 14 | recursive-include erpnext_shopify *.png 15 | recursive-include erpnext_shopify *.py 16 | recursive-include erpnext_shopify *.svg 17 | recursive-include erpnext_shopify *.txt 18 | recursive-exclude erpnext_shopify *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PLEASE NOTE! 2 | 3 | **this app is not being maintained any longer. 4 | Functionality was merged into the [core codebase of ERPNext](https://github.com/frappe/erpnext/tree/develop/erpnext/erpnext_integrations/connectors)**. 5 | 6 | ___ 7 | 8 | ## ERPNext Shopify 9 | 10 | Shopify connector for ERPNext 11 | 12 | #### License 13 | 14 | GNU GPL v3.0 15 | -------------------------------------------------------------------------------- /ci/fix-mariadb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # stolen from http://cgit.drupalcode.org/octopus/commit/?id=db4f837 4 | includedir=`mysql_config --variable=pkgincludedir` 5 | thiscwd=`pwd` 6 | _THIS_DB_VERSION=`mysql -V 2>&1 | tr -d "\n" | cut -d" " -f6 | awk '{ print $1}' | cut -d"-" -f1 | awk '{ print $1}' | sed "s/[\,']//g"` 7 | if [ "$_THIS_DB_VERSION" = "5.5.40" ] && [ ! -e "$includedir-$_THIS_DB_VERSION-fixed.log" ] ; then 8 | cd $includedir 9 | sudo patch -p1 < $thiscwd/ci/my_config.h.patch &> /dev/null 10 | sudo touch $includedir-$_THIS_DB_VERSION-fixed.log 11 | fi 12 | -------------------------------------------------------------------------------- /ci/my_config.h.patch: -------------------------------------------------------------------------------- 1 | diff -burp a/my_config.h b/my_config.h 2 | --- a/my_config.h 2014-10-09 19:32:46.000000000 -0400 3 | +++ b/my_config.h 2014-10-09 19:35:12.000000000 -0400 4 | @@ -641,17 +641,4 @@ 5 | #define SIZEOF_TIME_T 8 6 | /* #undef TIME_T_UNSIGNED */ 7 | 8 | -/* 9 | - stat structure (from ) is conditionally defined 10 | - to have different layout and size depending on the defined macros. 11 | - The correct macro is defined in my_config.h, which means it MUST be 12 | - included first (or at least before - so, practically, 13 | - before including any system headers). 14 | - 15 | - __GLIBC__ is defined in 16 | -*/ 17 | -#ifdef __GLIBC__ 18 | -#error MUST be included first! 19 | -#endif 20 | - 21 | #endif 22 | 23 | -------------------------------------------------------------------------------- /erpnext_shopify/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, absolute_import 2 | 3 | __version__ = "2.0.26" 4 | -------------------------------------------------------------------------------- /erpnext_shopify/after_install.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 2 | # License: GNU General Public License v3. See license.txt 3 | 4 | from __future__ import unicode_literals 5 | import frappe 6 | 7 | def create_weight_uom(): 8 | for unit in ['g', 'kg', 'lb', 'oz']: 9 | if not frappe.db.get_value("UOM", unit.title(), "name"): 10 | uom = frappe.new_doc("UOM") 11 | uom.uom_name = unit.title() 12 | uom.insert(ignore_permissions=True) -------------------------------------------------------------------------------- /erpnext_shopify/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe import _ 8 | from .exceptions import ShopifyError 9 | from .sync_orders import sync_orders 10 | from .sync_customers import sync_customers 11 | from .sync_products import sync_products, update_item_stock_qty 12 | from .utils import disable_shopify_sync_on_exception, make_shopify_log 13 | from frappe.utils.background_jobs import enqueue 14 | 15 | @frappe.whitelist() 16 | def sync_shopify(): 17 | "Enqueue longjob for syncing shopify" 18 | enqueue("erpnext_shopify.api.sync_shopify_resources", queue='long', timeout=1500) 19 | frappe.msgprint(_("Queued for syncing. It may take a few minutes to an hour if this is your first sync.")) 20 | 21 | @frappe.whitelist() 22 | def sync_shopify_resources(): 23 | shopify_settings = frappe.get_doc("Shopify Settings") 24 | 25 | make_shopify_log(title="Sync Job Queued", status="Queued", method=frappe.local.form_dict.cmd, message="Sync Job Queued") 26 | 27 | if shopify_settings.enable_shopify: 28 | try : 29 | now_time = frappe.utils.now() 30 | validate_shopify_settings(shopify_settings) 31 | frappe.local.form_dict.count_dict = {} 32 | sync_products(shopify_settings.price_list, shopify_settings.warehouse) 33 | sync_customers() 34 | sync_orders() 35 | update_item_stock_qty() 36 | frappe.db.set_value("Shopify Settings", None, "last_sync_datetime", now_time) 37 | 38 | make_shopify_log(title="Sync Completed", status="Success", method=frappe.local.form_dict.cmd, 39 | message= "Updated {customers} customer(s), {products} item(s), {orders} order(s)".format(**frappe.local.form_dict.count_dict)) 40 | 41 | except Exception as e: 42 | if e.args[0] and hasattr(e.args[0], "startswith") and e.args[0].startswith("402"): 43 | make_shopify_log(title="Shopify has suspended your account", status="Error", 44 | method="sync_shopify_resources", message=_("""Shopify has suspended your account till 45 | you complete the payment. We have disabled ERPNext Shopify Sync. Please enable it once 46 | your complete the payment at Shopify."""), exception=True) 47 | 48 | disable_shopify_sync_on_exception() 49 | 50 | else: 51 | make_shopify_log(title="sync has terminated", status="Error", method="sync_shopify_resources", 52 | message=frappe.get_traceback(), exception=True) 53 | 54 | elif frappe.local.form_dict.cmd == "erpnext_shopify.api.sync_shopify": 55 | make_shopify_log( 56 | title="Shopify connector is disabled", 57 | status="Error", 58 | method="sync_shopify_resources", 59 | message=_("""Shopify connector is not enabled. Click on 'Connect to Shopify' to connect ERPNext and your Shopify store."""), 60 | exception=True) 61 | 62 | def validate_shopify_settings(shopify_settings): 63 | """ 64 | This will validate mandatory fields and access token or app credentials 65 | by calling validate() of shopify settings. 66 | """ 67 | try: 68 | shopify_settings.save() 69 | except ShopifyError: 70 | disable_shopify_sync_on_exception() 71 | 72 | @frappe.whitelist() 73 | def get_log_status(): 74 | log = frappe.db.sql("""select name, status from `tabShopify Log` 75 | order by modified desc limit 1""", as_dict=1) 76 | if log: 77 | if log[0].status=="Queued": 78 | message = _("Last sync request is queued") 79 | alert_class = "alert-warning" 80 | elif log[0].status=="Error": 81 | message = _("Last sync request was failed, check here" 82 | .format(log[0].name)) 83 | alert_class = "alert-danger" 84 | else: 85 | message = _("Last sync request was successful") 86 | alert_class = "alert-success" 87 | 88 | return { 89 | "text": message, 90 | "alert_class": alert_class 91 | } 92 | -------------------------------------------------------------------------------- /erpnext_shopify/billing.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from .shopify_requests import post_request 3 | from frappe.limits import get_usage_info 4 | from frappe.utils import getdate, markdown 5 | from frappe.email import get_system_managers 6 | 7 | def send_payment_notification_to_user(): 8 | if not frappe.db.get_single_value('Shopify Settings', 'enable_shopify'): 9 | return 10 | 11 | if frappe.db.get_single_value('Shopify Settings', 'app_type') == 'Private': 12 | return 13 | 14 | if frappe.db.get_single_value("Global Defaults", "default_currency") == 'INR': 15 | return 16 | 17 | confirmation_url = create_shopify_application_charges() 18 | if confirmation_url: 19 | send_billing_reminder(confirmation_url) 20 | 21 | def create_shopify_application_charges(): 22 | """ 23 | response : 24 | { 25 | "application_charge": { 26 | "api_client_id": 1203780, 27 | "charge_type": None, 28 | "confirmation_url": "https://testerps.myshopify.com/admin/charges/46006316/confirm_application_charge?signature=BAhpBCwAvgI%3D--24633d18d865a2e3f62e19a9d1cd88f14e00d038", 29 | "created_at": "2018-01-11T15:47:04+05:30", 30 | "decorated_return_url": "https://apps.shopify.com/erpnext?charge_id=46006316", 31 | "id": 46006316, 32 | "name": "test plan", 33 | "price": "0.50", 34 | "return_url": "https://apps.shopify.com/erpnext", 35 | "status": "pending", 36 | "test": True, 37 | "updated_at": "2018-01-11T15:47:04+05:30" 38 | } 39 | } 40 | """ 41 | billing_url = 'admin/application_charges.json' 42 | data = prepare_data() 43 | 44 | if not data: 45 | return 46 | 47 | try: 48 | res = post_request(billing_url, data) 49 | return res["application_charge"]["confirmation_url"] 50 | except Exception: 51 | pass 52 | 53 | def prepare_data(): 54 | usage_info = get_usage_info() 55 | 56 | if not usage_info: 57 | return 58 | 59 | site_creation_date = frappe.get_doc('User', 'Administrator').creation.date() 60 | site_creation_days = (getdate(site_creation_date) - getdate()).days 61 | 62 | if usage_info.days_to_expiry == 1 and site_creation_days <= 30: 63 | plan = "P-{0}".format(usage_info.limits.users) 64 | 65 | return { 66 | "application_charge": { 67 | "name": "ERPNext Subscription: P-{0}".format(usage_info.limits.users), 68 | "price": get_plan_wise_prices(plan), 69 | "return_url": usage_info.upgrade_url 70 | } 71 | } 72 | 73 | def get_plan_wise_prices(plan): 74 | return { 75 | "P-5": 299, 76 | "P-10": 449, 77 | "P-15": 599, 78 | "P-25": 899, 79 | "P-50": 1499, 80 | "P-100": 1999, 81 | "P-200": 2599, 82 | "P-1000": 3999, 83 | }[plan] 84 | 85 | def send_billing_reminder(confirmation_url): 86 | system_manager = get_system_managers()[0] 87 | usage_info = get_usage_info() 88 | data = { 89 | 'site': frappe.local.site, 90 | 'full_name': frappe.db.get_value('User', system_manager, 'concat(ifnull(first_name, ""), ifnull(last_name, ""))'), 91 | 'support_email': 'support@erpnext.com', 92 | 'confirmation_url': confirmation_url, 93 | 'expires_on': usage_info.expires_on 94 | } 95 | 96 | stats = frappe.render_template('erpnext_shopify/templates/emails/billing.md', data, is_path=True) 97 | frappe.sendmail(recipients=[system_manager], subject='Your Shopify-ERPNext subscription is about to expire', message=markdown(stats)) 98 | -------------------------------------------------------------------------------- /erpnext_shopify/change_log/current/readme.md: -------------------------------------------------------------------------------- 1 | Leave change log files in this folder for user release notes. 2 | 3 | (this file is just a place holder, don't delete it) 4 | -------------------------------------------------------------------------------- /erpnext_shopify/change_log/v1/v1_3_0.md: -------------------------------------------------------------------------------- 1 | - Fix: Don't truncate a variant's attribute into an abbreviation 2 | - Stop syncing a Customer or Supplier if it is removed from Shopify 3 | -------------------------------------------------------------------------------- /erpnext_shopify/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/config/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/config/docs.py: -------------------------------------------------------------------------------- 1 | source_link = "https://github.com/frappe/erpnext_shopify" 2 | docs_base_url = "https://frappe.github.io/erpnext_shopify" 3 | headline = "ERPNext Shopify Connector" 4 | sub_heading = "Sync transactions between Shopify and ERPNext" 5 | long_description = """ERPNext Shopify Connector will sync data between your Shopify and ERPNext accounts. 6 |
7 |
    8 |
  1. It will sync Products and Cutomers between Shopify and ERPNext
  2. 9 |
  3. It will push Orders from Shopify to ERPNext 10 |
      11 |
    • 12 | If the Order has been paid for in Shopify, it will create a Sales Invoice in ERPNext and record the corresponding Payment Entry 13 |
    • 14 |
    • 15 | If the Order has been fulfilled in Shopify, it will create a draft Delivery Note in ERPNext 16 |
    • 17 |
    18 |
  4. 19 |
""" 20 | docs_version = "1.0.0" 21 | 22 | def get_context(context): 23 | context.title = "ERPNext Shopify Connector" 24 | -------------------------------------------------------------------------------- /erpnext_shopify/config/setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from frappe import _ 3 | 4 | def get_data(): 5 | return [ 6 | { 7 | "label": _("Integrations"), 8 | "icon": "icon-star", 9 | "items": [ 10 | { 11 | "type": "doctype", 12 | "name": "Shopify Settings", 13 | "description": _("Connect Shopify with ERPNext"), 14 | "hide_count": True 15 | } 16 | ] 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/broker1.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/broker1.2.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/erpnext-config-for-private-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/erpnext-config-for-private-app.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/home.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/permission.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/setup-shopify-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/setup-shopify-settings.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/shopify-new-private-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/shopify-new-private-app.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/shopify-private-apps-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/shopify-private-apps-page.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/assets/img/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/assets/img/sync.png -------------------------------------------------------------------------------- /erpnext_shopify/docs/contents.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Table of Contents

4 |
5 | 6 | {% include "templates/includes/full_index.html" %} 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/contents.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | from __future__ import unicode_literals 5 | import frappe 6 | from frappe.website.utils import get_full_index 7 | 8 | def get_context(context): 9 | context.full_index = get_full_index(extn = True) 10 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/config/erpnext_shopify.config.docs.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

21 | 22 | 23 | erpnext_shopify.config.docs.get_context 24 | (context) 25 |

26 |

No docs

27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/config/erpnext_shopify.config.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/config/erpnext_shopify.config.setup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

21 | 22 | 23 | erpnext_shopify.config.setup.get_data 24 | () 25 |

26 |

No docs

27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/config/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Version 1.0 8 | 9 | 10 | Source 12 | 13 |
14 | 15 |

Package Contents

16 | 17 | {index} 18 | 19 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/config/index.txt: -------------------------------------------------------------------------------- 1 | erpnext_shopify.config.docs 2 | erpnext_shopify.config 3 | erpnext_shopify.config.setup -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify.__init__.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify.exceptions.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

Class ShopifyError

19 | 20 |

Inherits from frappe.exceptions.ValidationError 21 | 22 |

23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify.hooks.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify.utils.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

21 | 22 | 23 | erpnext_shopify.utils.create_webhook 24 | (topic, address) 25 |

26 |

No docs

27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

37 | 38 | 39 | erpnext_shopify.utils.create_webhooks 40 | () 41 |

42 |

No docs

43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |

53 | 54 | 55 | erpnext_shopify.utils.delete_request 56 | (path) 57 |

58 |

No docs

59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |

69 | 70 | 71 | erpnext_shopify.utils.delete_webhooks 72 | () 73 |

74 |

No docs

75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |

85 | 86 | 87 | erpnext_shopify.utils.disable_shopify_sync 88 | (item) 89 |

90 |

Disable Item if not exist on shopify

91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |

101 | 102 | 103 | erpnext_shopify.utils.get_address_type 104 | (i) 105 |

106 |

No docs

107 |
108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |

117 | 118 | 119 | erpnext_shopify.utils.get_country 120 | () 121 |

122 |

No docs

123 |
124 |
125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |

133 | 134 | 135 | erpnext_shopify.utils.get_header 136 | (settings) 137 |

138 |

No docs

139 |
140 |
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |

149 | 150 | 151 | erpnext_shopify.utils.get_request 152 | (path, settings=None) 153 |

154 |

No docs

155 |
156 |
157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 |

165 | 166 | 167 | erpnext_shopify.utils.get_shopify_customers 168 | () 169 |

170 |

No docs

171 |
172 |
173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

181 | 182 | 183 | erpnext_shopify.utils.get_shopify_item_image 184 | (shopify_id) 185 |

186 |

No docs

187 |
188 |
189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |

197 | 198 | 199 | erpnext_shopify.utils.get_shopify_items 200 | () 201 |

202 |

No docs

203 |
204 |
205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 |

213 | 214 | 215 | erpnext_shopify.utils.get_shopify_orders 216 | () 217 |

218 |

No docs

219 |
220 |
221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 |

229 | 230 | 231 | erpnext_shopify.utils.get_shopify_settings 232 | () 233 |

234 |

No docs

235 |
236 |
237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 |

245 | 246 | 247 | erpnext_shopify.utils.get_shopify_url 248 | (path, settings) 249 |

250 |

No docs

251 |
252 |
253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 |

261 | 262 | 263 | erpnext_shopify.utils.get_total_pages 264 | (resource) 265 |

266 |

No docs

267 |
268 |
269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 |

277 | 278 | 279 | erpnext_shopify.utils.get_webhooks 280 | () 281 |

282 |

No docs

283 |
284 |
285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 |

293 | 294 | 295 | erpnext_shopify.utils.post_request 296 | (path, data) 297 |

298 |

No docs

299 |
300 |
301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 |

309 | 310 | 311 | erpnext_shopify.utils.put_request 312 | (path, data) 313 |

314 |

No docs

315 |
316 |
317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 |

325 | 326 | 327 | erpnext_shopify.utils.shopify_webhook 328 | (f) 329 |

330 |

A decorator thats checks and validates a Shopify Webhook request.

331 |
332 |
333 | 334 | 335 | 336 | 337 | 338 | 339 |

Public API 340 |
/api/method/erpnext_shopify.utils.webhook_handler 341 |

342 |

343 | 344 | 345 | erpnext_shopify.utils.webhook_handler 346 | () 347 |

348 |

No docs

349 |
350 |
351 | 352 | 353 | 354 | 355 | 356 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify/erpnext_shopify.erpnext_shopify.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Version 1.0 5 | 6 | 7 | Source 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Version 1.0 8 | 9 | 10 | Source 12 | 13 |
14 | 15 |

Package Contents

16 | 17 | {index} 18 | 19 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/erpnext_shopify/index.txt: -------------------------------------------------------------------------------- 1 | erpnext_shopify.erpnext_shopify -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | Version 1.0 9 | 10 | 11 | Source 13 | 14 |
15 | 16 |

Contents

17 | 18 | 19 | 20 | {index} -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/api/index.txt: -------------------------------------------------------------------------------- 1 | erpnext_shopify.__init__ 2 | erpnext_shopify.exceptions 3 | erpnext_shopify.hooks 4 | erpnext_shopify.utils -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | Version 1.0 9 | 10 | 11 | Source 13 | 14 |
15 | 16 | 17 | 18 | 21 | 24 | 25 | 26 | 29 | 32 | 33 | 34 | 37 | 40 | 41 |
19 | App Name 20 | 22 | erpnext_shopify 23 |
27 | Publisher 28 | 30 | Frappe Technologies Pvt. Ltd. 31 |
35 | Version 36 | 38 | 1.0.2 39 |
42 | 43 |

Contents

44 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/models/erpnext_shopify/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Version 1.0 8 | 9 | 10 | Source 12 | 13 |
14 | 15 |

DocTypes for erpnext_shopify

16 | 17 | {index} 18 | 19 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/models/erpnext_shopify/index.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/current/models/erpnext_shopify/index.txt -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/models/erpnext_shopify/shopify_tax_account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | Version 1.0 11 | 12 | 13 | Source 15 | 16 |
17 | 18 | 19 | Child Table 20 | 21 | 22 |

Table Name: tabShopify Tax Account

23 | 24 | 25 | 26 | 27 |

Fields

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 70 | 74 | 84 | 85 | 86 | 87 |
SrFieldnameTypeLabelOptions
1shopify_tax 45 | Data 47 | Shopify Tax/Shipping Title 48 | 49 |
2column_break_2 57 | Column Break 59 | 60 | 61 |
3tax_account 69 | Link 71 | ERPNext Account 72 | 73 | 75 | 76 | 77 | 78 | 79 | Account 80 | 81 | 82 | 83 |
88 | 89 | 90 | 91 | 92 |

Child Table Of

93 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/models/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Version 1.0 8 | 9 | 10 | Source 12 | 13 |
14 | 15 |

Browse DocTypes by Module

16 | 17 |

Contents

18 | 19 | {index} 20 | 21 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/current/models/index.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/docs/current/models/index.txt -------------------------------------------------------------------------------- /erpnext_shopify/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |

ERPNext Shopify Connector

14 |

Sync transactions between Shopify and ERPNext

15 |
16 |
17 |
18 | 20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 |
31 |

ERPNext Shopify Connector will sync data between your Shopify and ERPNext accounts. 32 |

33 | 34 |
    35 |
  1. It will sync Products and Cutomers between Shopify and ERPNext
  2. 36 |
  3. It will push Orders from Shopify to ERPNext 37 |
      38 |
    • 39 | If the Order has been paid for in Shopify, it will create a Sales Invoice in ERPNext and record the corresponding Payment Entry 40 |
    • 41 |
    • 42 | If the Order has been fulfilled in Shopify, it will create a draft Delivery Note in ERPNext 43 |
    • 44 |
    45 |
  4. 46 |
47 | 48 |
49 |
50 |
51 | 52 | 53 | 54 |
55 |
56 |
57 |

Install

58 |

From your site

59 |

To install this app, login to your site and click on "Installer". Search for ERPNext Shopify and click on "Install"

60 |

Using Bench

61 |

Go to your bench folder and setup the new app

62 |
$ bench get-app erpnext_shopify https://github.com/frappe/erpnext_shopify
63 | $ bench new-site testsite
64 | $ bench --site testsite install-app erpnext_shopify
65 |

Login to your site to configure the app.

66 |

Detailed Installation Steps

67 |
68 |
69 |

Author

70 | 71 |

Frappe Technologies Pvt. Ltd. (info@frappe.io)

72 |
73 |
74 |
75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/index.txt: -------------------------------------------------------------------------------- 1 | assets 2 | contents 3 | current 4 | install 5 | license 6 | user 7 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/install.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Installation 4 | 5 | ERPNext Shopify is based on the Frappe Framework, a full stack web framework based on Python, MariaDB, Redis, Node. 6 | 7 | To intall ERPNext Shopify, you will have to install the Frappe Bench, the command-line, package manager and site manager for Frappe Framework. For more details, read the Bench README. 8 | 9 | After you have installed Frappe Bench, go to you bench folder, which is `frappe.bench` by default and setup **erpnext_shopify**. 10 | 11 | bench get-app erpnext_shopify {{ source_link }} 12 | 13 | Then create a new site to install the app. 14 | 15 | bench new-site mysite 16 | 17 | This will create a new folder in your `/sites` directory and create a new database for this site. 18 | 19 | Next, install erpnext_shopify in this site 20 | 21 | bench --site mysite install-app erpnext_shopify 22 | 23 | To run this locally, run 24 | 25 | bench start 26 | 27 | Fire up your browser and go to http://localhost:8000 and you should see the login screen. Login as **Administrator** and **admin** (or the password you set at the time of `new-site`) and you are set. 28 | 29 | 30 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/license.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

MIT

4 | 5 |

License: GNU GPL v3.0

6 | 7 | 8 | -------------------------------------------------------------------------------- /erpnext_shopify/docs/user/index.md: -------------------------------------------------------------------------------- 1 | # ERPNext Shopify Connector 2 | 3 | This app synchronizes the following data between your Shopify and ERPNext accounts 4 | 5 | 1. Products 6 | 1. Customers 7 | 1. Orders, payments and order fulfillment from Shopify into ERPNext 8 | 9 | --- 10 | 11 | ## Setup 12 | 13 | 1. [Install]({{ docs_base_url }}/index.html#install) ERPNext Shopify app in your ERPNext site 14 | 1. Connect your Shopify account to ERPNext 15 | 1. Connect via the Public ERPNext App in Shopify's App Store (recommended) 16 | 1. Connect by creating a Private App 17 | 18 | #### Connect via the Public ERPNext App 19 | 20 | 1. Login to your Shopify account and install [ERPNext app](https://apps.shopify.com/erpnext-connector-1) from the Shopify App Store 21 | 1. On installing the app, you will be redirected to **ERPNext Shopify Connector** page where you will need to fill in your ERPNext credentials and then click on Submit 22 | 23 | 1. Next, you will be taken to the Permissions page, where you will be asked to allow ERPNext to: 24 | - Modify Products, variants and collections 25 | - Modify Customer details and customer groups 26 | - Modify Orders, transactions and fulfillments 27 | 28 | 1. Next, login to your ERPNext site, go to Setup > Integrations > Shopify Settings and modify the connector's configuration 29 | 30 | #### Connect by creating a Private App 31 | 32 | 1. From within your Shopify account, go to Apps > Private Apps > Create a Private App 33 | 34 | 1. Give it a title and save the app. Shopify will generate a unique API Key and Password for this app 35 | 36 | 1. Login to your ERPNext site, then navigate to Setup > Integrations > Shopify Settings 37 | 1. Select the App Type as "Private", specify your Shopify account's URL, copy the private app's API Key and Password into the form and save the settings 38 | 39 | 40 | --- 41 | 42 | ## Shopify Settings 43 | 44 | > Setup > Integrations > Shopify Settings 45 | 46 | 1. Specify Price List and Warehouse to be used in the transactions 47 | 1. Specify which Cash/Bank Account to use for recording payments 48 | 1. Map Shopify Taxes and Shipping to ERPNext Accounts 49 | 1. Mention the Series to be used by the transactions created during sync 50 | 51 | 52 | 53 | --- 54 | 55 | ## Synchronization 56 | 57 | The connector app synchronizes data between Shopify and ERPNext automatically, every hour. However, you can initiate a manual sync by going to Setup > Integrations > Shopify Settings and clicking on **Sync Shopify** 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/erpnext_shopify/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/erpnext_shopify/doctype/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/erpnext_shopify/doctype/shopify_log/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_log/shopify_log.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('Shopify Log', { 5 | refresh: function(frm) { 6 | 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_log/shopify_log.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 0, 3 | "allow_import": 0, 4 | "allow_rename": 0, 5 | "creation": "2016-03-14 10:02:06.227184", 6 | "custom": 0, 7 | "docstatus": 0, 8 | "doctype": "DocType", 9 | "document_type": "System", 10 | "fields": [ 11 | { 12 | "allow_on_submit": 0, 13 | "bold": 0, 14 | "collapsible": 0, 15 | "fieldname": "title", 16 | "fieldtype": "Data", 17 | "hidden": 1, 18 | "ignore_user_permissions": 0, 19 | "ignore_xss_filter": 0, 20 | "in_filter": 0, 21 | "in_list_view": 0, 22 | "label": "Title", 23 | "length": 0, 24 | "no_copy": 0, 25 | "permlevel": 0, 26 | "precision": "", 27 | "print_hide": 0, 28 | "print_hide_if_no_value": 0, 29 | "read_only": 1, 30 | "report_hide": 0, 31 | "reqd": 0, 32 | "search_index": 0, 33 | "set_only_once": 0, 34 | "unique": 0 35 | }, 36 | { 37 | "allow_on_submit": 0, 38 | "bold": 0, 39 | "collapsible": 0, 40 | "default": "Queued", 41 | "fieldname": "status", 42 | "fieldtype": "Select", 43 | "hidden": 0, 44 | "ignore_user_permissions": 0, 45 | "ignore_xss_filter": 0, 46 | "in_filter": 0, 47 | "in_list_view": 0, 48 | "label": "Status", 49 | "length": 0, 50 | "no_copy": 0, 51 | "options": "\nQueued\nError\nSuccess", 52 | "permlevel": 0, 53 | "precision": "", 54 | "print_hide": 0, 55 | "print_hide_if_no_value": 0, 56 | "read_only": 1, 57 | "report_hide": 0, 58 | "reqd": 0, 59 | "search_index": 0, 60 | "set_only_once": 0, 61 | "unique": 0 62 | }, 63 | { 64 | "allow_on_submit": 0, 65 | "bold": 0, 66 | "collapsible": 0, 67 | "fieldname": "method", 68 | "fieldtype": "Small Text", 69 | "hidden": 0, 70 | "ignore_user_permissions": 0, 71 | "ignore_xss_filter": 0, 72 | "in_filter": 0, 73 | "in_list_view": 0, 74 | "label": "Method", 75 | "length": 0, 76 | "no_copy": 0, 77 | "permlevel": 0, 78 | "precision": "", 79 | "print_hide": 0, 80 | "print_hide_if_no_value": 0, 81 | "read_only": 1, 82 | "report_hide": 0, 83 | "reqd": 0, 84 | "search_index": 0, 85 | "set_only_once": 0, 86 | "unique": 0 87 | }, 88 | { 89 | "allow_on_submit": 0, 90 | "bold": 0, 91 | "collapsible": 0, 92 | "fieldname": "message", 93 | "fieldtype": "Code", 94 | "hidden": 0, 95 | "ignore_user_permissions": 0, 96 | "ignore_xss_filter": 0, 97 | "in_filter": 0, 98 | "in_list_view": 0, 99 | "label": "Message", 100 | "length": 0, 101 | "no_copy": 0, 102 | "permlevel": 0, 103 | "precision": "", 104 | "print_hide": 0, 105 | "print_hide_if_no_value": 0, 106 | "read_only": 1, 107 | "report_hide": 0, 108 | "reqd": 0, 109 | "search_index": 0, 110 | "set_only_once": 0, 111 | "unique": 0 112 | }, 113 | { 114 | "allow_on_submit": 0, 115 | "bold": 0, 116 | "collapsible": 0, 117 | "fieldname": "request_data", 118 | "fieldtype": "Code", 119 | "hidden": 0, 120 | "ignore_user_permissions": 0, 121 | "ignore_xss_filter": 0, 122 | "in_filter": 0, 123 | "in_list_view": 0, 124 | "label": "Request Data", 125 | "length": 0, 126 | "no_copy": 0, 127 | "permlevel": 0, 128 | "precision": "", 129 | "print_hide": 0, 130 | "print_hide_if_no_value": 0, 131 | "read_only": 1, 132 | "report_hide": 0, 133 | "reqd": 0, 134 | "search_index": 0, 135 | "set_only_once": 0, 136 | "unique": 0 137 | } 138 | ], 139 | "hide_heading": 0, 140 | "hide_toolbar": 0, 141 | "idx": 0, 142 | "in_create": 1, 143 | "in_dialog": 0, 144 | "is_submittable": 0, 145 | "issingle": 0, 146 | "istable": 0, 147 | "max_attachments": 0, 148 | "modified": "2016-03-23 02:01:28.042687", 149 | "modified_by": "Administrator", 150 | "module": "ERPNext Shopify", 151 | "name": "Shopify Log", 152 | "name_case": "", 153 | "owner": "Administrator", 154 | "permissions": [ 155 | { 156 | "amend": 0, 157 | "apply_user_permissions": 0, 158 | "cancel": 0, 159 | "create": 1, 160 | "delete": 1, 161 | "email": 1, 162 | "export": 1, 163 | "if_owner": 0, 164 | "import": 0, 165 | "permlevel": 0, 166 | "print": 1, 167 | "read": 1, 168 | "report": 1, 169 | "role": "Administrator", 170 | "set_user_permissions": 0, 171 | "share": 1, 172 | "submit": 0, 173 | "write": 1 174 | }, 175 | { 176 | "amend": 0, 177 | "apply_user_permissions": 0, 178 | "cancel": 0, 179 | "create": 1, 180 | "delete": 1, 181 | "email": 1, 182 | "export": 1, 183 | "if_owner": 0, 184 | "import": 0, 185 | "permlevel": 0, 186 | "print": 1, 187 | "read": 1, 188 | "report": 1, 189 | "role": "System Manager", 190 | "set_user_permissions": 0, 191 | "share": 1, 192 | "submit": 0, 193 | "write": 1 194 | } 195 | ], 196 | "read_only": 0, 197 | "read_only_onload": 0, 198 | "sort_field": "modified", 199 | "sort_order": "DESC", 200 | "title_field": "title" 201 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_log/shopify_log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe.model.document import Document 8 | 9 | class ShopifyLog(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_log/shopify_log_list.js: -------------------------------------------------------------------------------- 1 | frappe.listview_settings['Shopify Log'] = { 2 | add_fields: ["status"], 3 | get_indicator: function(doc) { 4 | if(doc.status==="Success"){ 5 | return [__("Success"), "green", "status,=,Success"]; 6 | } else if(doc.status ==="Error"){ 7 | return [__("Error"), "red", "status,=,Error"]; 8 | } else if(doc.status ==="Queued"){ 9 | return [__("Queued"), "orange", "status,=,Queued"]; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_log/test_shopify_log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 3 | # See license.txt 4 | from __future__ import unicode_literals 5 | 6 | import frappe 7 | import unittest 8 | 9 | # test_records = frappe.get_test_records('Shopify Log') 10 | 11 | class TestShopifyLog(unittest.TestCase): 12 | pass 13 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/erpnext_shopify/doctype/shopify_settings/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/shopify_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 2 | // License: GNU General Public License v3. See license.txt 3 | 4 | frappe.provide("erpnext_shopify.shopify_settings"); 5 | 6 | frappe.ui.form.on("Shopify Settings", "onload", function(frm, dt, dn){ 7 | frappe.call({ 8 | method:"erpnext_shopify.erpnext_shopify.doctype.shopify_settings.shopify_settings.get_series", 9 | callback:function(r){ 10 | $.each(r.message, function(key, value){ 11 | set_field_options(key, value) 12 | }) 13 | } 14 | }) 15 | erpnext_shopify.shopify_settings.setup_queries(frm); 16 | }) 17 | 18 | frappe.ui.form.on("Shopify Settings", "app_type", function(frm, dt, dn) { 19 | frm.toggle_reqd("api_key", (frm.doc.app_type == "Private")); 20 | frm.toggle_reqd("password", (frm.doc.app_type == "Private")); 21 | }) 22 | 23 | frappe.ui.form.on("Shopify Settings", "refresh", function(frm){ 24 | if(!frm.doc.__islocal && frm.doc.enable_shopify === 1){ 25 | frm.toggle_reqd("price_list", true); 26 | frm.toggle_reqd("warehouse", true); 27 | frm.toggle_reqd("taxes", true); 28 | frm.toggle_reqd("company", true); 29 | frm.toggle_reqd("cost_center", true); 30 | frm.toggle_reqd("cash_bank_account", true); 31 | frm.toggle_reqd("sales_order_series", true); 32 | frm.toggle_reqd("customer_group", true); 33 | 34 | frm.toggle_reqd("sales_invoice_series", frm.doc.sync_sales_invoice); 35 | frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note); 36 | 37 | frm.add_custom_button(__('Sync Shopify'), function() { 38 | frappe.call({ 39 | method:"erpnext_shopify.api.sync_shopify", 40 | }) 41 | }).addClass("btn-primary"); 42 | } 43 | 44 | if(!frm.doc.access_token && !frm.doc.api_key) { 45 | frm.add_custom_button(__("Connect to Shopify"), 46 | function(){ 47 | window.open("https://apps.shopify.com/erpnext"); 48 | }).addClass("btn-primary") 49 | } 50 | 51 | frm.add_custom_button(__("Shopify Log"), function(){ 52 | frappe.set_route("List", "Shopify Log"); 53 | }) 54 | 55 | frm.add_custom_button(__("Reset Last Sync Date"), function(){ 56 | var dialog = new frappe.ui.Dialog({ 57 | title: __("Reset Last Sync Date"), 58 | fields: [ 59 | {"fieldtype": "Datetime", "label": __("Date"), "fieldname": "last_sync_date", "reqd": 1 }, 60 | {"fieldtype": "Button", "label": __("Set last sync date"), "fieldname": "set_last_sync_date", "cssClass": "btn-primary"}, 61 | ] 62 | }); 63 | var args; 64 | dialog.fields_dict.set_last_sync_date.$input.click(function() { 65 | args = dialog.get_values(); 66 | if(!args) return; 67 | 68 | frm.set_value("last_sync_datetime", args['last_sync_date']); 69 | frm.save(); 70 | 71 | dialog.hide(); 72 | }); 73 | dialog.show(); 74 | }) 75 | 76 | 77 | frappe.call({ 78 | method: "erpnext_shopify.api.get_log_status", 79 | callback: function(r) { 80 | if(r.message){ 81 | frm.dashboard.set_headline_alert(r.message.text, r.message.alert_class) 82 | } 83 | } 84 | }) 85 | 86 | }) 87 | 88 | 89 | $.extend(erpnext_shopify.shopify_settings, { 90 | setup_queries: function(frm) { 91 | frm.fields_dict["warehouse"].get_query = function(doc) { 92 | return { 93 | filters:{ 94 | "company": doc.company, 95 | "is_group": "No" 96 | } 97 | } 98 | } 99 | 100 | frm.fields_dict["taxes"].grid.get_field("tax_account").get_query = function(doc, dt, dn){ 101 | return { 102 | "query": "erpnext.controllers.queries.tax_account_query", 103 | "filters": { 104 | "account_type": ["Tax", "Chargeable", "Expense Account"], 105 | "company": doc.company 106 | } 107 | } 108 | } 109 | 110 | frm.fields_dict["cash_bank_account"].get_query = function(doc) { 111 | return { 112 | filters: [ 113 | ["Account", "account_type", "in", ["Cash", "Bank"]], 114 | ["Account", "root_type", "=", "Asset"], 115 | ["Account", "is_group", "=",0], 116 | ["Account", "company", "=", doc.company] 117 | ] 118 | } 119 | } 120 | 121 | frm.fields_dict["cost_center"].get_query = function(doc) { 122 | return { 123 | filters:{ 124 | "company": doc.company, 125 | "is_group": "No" 126 | } 127 | } 128 | } 129 | 130 | frm.fields_dict["price_list"].get_query = function(doc) { 131 | return { 132 | filters:{ 133 | "selling": 1 134 | } 135 | } 136 | } 137 | } 138 | }) 139 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/shopify_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 0, 3 | "allow_guest_to_view": 0, 4 | "allow_import": 0, 5 | "allow_rename": 0, 6 | "beta": 0, 7 | "creation": "2015-05-18 05:21:07.270859", 8 | "custom": 0, 9 | "docstatus": 0, 10 | "doctype": "DocType", 11 | "document_type": "System", 12 | "editable_grid": 0, 13 | "fields": [ 14 | { 15 | "allow_bulk_edit": 0, 16 | "allow_on_submit": 0, 17 | "bold": 0, 18 | "collapsible": 0, 19 | "columns": 0, 20 | "fieldname": "status_html", 21 | "fieldtype": "HTML", 22 | "hidden": 0, 23 | "ignore_user_permissions": 0, 24 | "ignore_xss_filter": 0, 25 | "in_filter": 0, 26 | "in_global_search": 0, 27 | "in_list_view": 0, 28 | "in_standard_filter": 0, 29 | "label": "status html", 30 | "length": 0, 31 | "no_copy": 0, 32 | "options": "", 33 | "permlevel": 0, 34 | "precision": "", 35 | "print_hide": 0, 36 | "print_hide_if_no_value": 0, 37 | "read_only": 1, 38 | "remember_last_selected_value": 0, 39 | "report_hide": 0, 40 | "reqd": 0, 41 | "search_index": 0, 42 | "set_only_once": 0, 43 | "unique": 0 44 | }, 45 | { 46 | "allow_bulk_edit": 0, 47 | "allow_on_submit": 0, 48 | "bold": 0, 49 | "collapsible": 0, 50 | "columns": 0, 51 | "default": "1", 52 | "fieldname": "enable_shopify", 53 | "fieldtype": "Check", 54 | "hidden": 0, 55 | "ignore_user_permissions": 0, 56 | "ignore_xss_filter": 0, 57 | "in_filter": 0, 58 | "in_global_search": 0, 59 | "in_list_view": 0, 60 | "in_standard_filter": 0, 61 | "label": "Enable Shopify", 62 | "length": 0, 63 | "no_copy": 0, 64 | "permlevel": 0, 65 | "precision": "", 66 | "print_hide": 0, 67 | "print_hide_if_no_value": 0, 68 | "read_only": 0, 69 | "remember_last_selected_value": 0, 70 | "report_hide": 0, 71 | "reqd": 0, 72 | "search_index": 0, 73 | "set_only_once": 0, 74 | "unique": 0 75 | }, 76 | { 77 | "allow_bulk_edit": 0, 78 | "allow_on_submit": 0, 79 | "bold": 0, 80 | "collapsible": 0, 81 | "columns": 0, 82 | "default": "Public", 83 | "fieldname": "app_type", 84 | "fieldtype": "Select", 85 | "hidden": 0, 86 | "ignore_user_permissions": 0, 87 | "ignore_xss_filter": 0, 88 | "in_filter": 0, 89 | "in_global_search": 0, 90 | "in_list_view": 1, 91 | "in_standard_filter": 0, 92 | "label": "App Type", 93 | "length": 0, 94 | "no_copy": 0, 95 | "options": "Public\nPrivate", 96 | "permlevel": 0, 97 | "precision": "", 98 | "print_hide": 0, 99 | "print_hide_if_no_value": 0, 100 | "read_only": 0, 101 | "remember_last_selected_value": 0, 102 | "report_hide": 0, 103 | "reqd": 1, 104 | "search_index": 0, 105 | "set_only_once": 0, 106 | "unique": 0 107 | }, 108 | { 109 | "allow_bulk_edit": 0, 110 | "allow_on_submit": 0, 111 | "bold": 0, 112 | "collapsible": 0, 113 | "columns": 0, 114 | "fieldname": "column_break_4", 115 | "fieldtype": "Column Break", 116 | "hidden": 0, 117 | "ignore_user_permissions": 0, 118 | "ignore_xss_filter": 0, 119 | "in_filter": 0, 120 | "in_global_search": 0, 121 | "in_list_view": 0, 122 | "in_standard_filter": 0, 123 | "length": 0, 124 | "no_copy": 0, 125 | "permlevel": 0, 126 | "precision": "", 127 | "print_hide": 0, 128 | "print_hide_if_no_value": 0, 129 | "read_only": 0, 130 | "remember_last_selected_value": 0, 131 | "report_hide": 0, 132 | "reqd": 0, 133 | "search_index": 0, 134 | "set_only_once": 0, 135 | "unique": 0 136 | }, 137 | { 138 | "allow_bulk_edit": 0, 139 | "allow_on_submit": 0, 140 | "bold": 0, 141 | "collapsible": 0, 142 | "columns": 0, 143 | "fieldname": "last_sync_datetime", 144 | "fieldtype": "Datetime", 145 | "hidden": 0, 146 | "ignore_user_permissions": 0, 147 | "ignore_xss_filter": 0, 148 | "in_filter": 0, 149 | "in_global_search": 0, 150 | "in_list_view": 0, 151 | "in_standard_filter": 0, 152 | "label": "Last Sync Datetime", 153 | "length": 0, 154 | "no_copy": 0, 155 | "permlevel": 0, 156 | "precision": "", 157 | "print_hide": 0, 158 | "print_hide_if_no_value": 0, 159 | "read_only": 1, 160 | "remember_last_selected_value": 0, 161 | "report_hide": 0, 162 | "reqd": 0, 163 | "search_index": 0, 164 | "set_only_once": 0, 165 | "unique": 0 166 | }, 167 | { 168 | "allow_bulk_edit": 0, 169 | "allow_on_submit": 0, 170 | "bold": 0, 171 | "collapsible": 0, 172 | "columns": 0, 173 | "fieldname": "section_break_2", 174 | "fieldtype": "Section Break", 175 | "hidden": 0, 176 | "ignore_user_permissions": 0, 177 | "ignore_xss_filter": 0, 178 | "in_filter": 0, 179 | "in_global_search": 0, 180 | "in_list_view": 0, 181 | "in_standard_filter": 0, 182 | "length": 0, 183 | "no_copy": 0, 184 | "permlevel": 0, 185 | "precision": "", 186 | "print_hide": 0, 187 | "print_hide_if_no_value": 0, 188 | "read_only": 0, 189 | "remember_last_selected_value": 0, 190 | "report_hide": 0, 191 | "reqd": 0, 192 | "search_index": 0, 193 | "set_only_once": 0, 194 | "unique": 0 195 | }, 196 | { 197 | "allow_bulk_edit": 0, 198 | "allow_on_submit": 0, 199 | "bold": 0, 200 | "collapsible": 0, 201 | "columns": 0, 202 | "description": "eg: frappe.myshopify.com", 203 | "fieldname": "shopify_url", 204 | "fieldtype": "Data", 205 | "hidden": 0, 206 | "ignore_user_permissions": 0, 207 | "ignore_xss_filter": 0, 208 | "in_filter": 0, 209 | "in_global_search": 0, 210 | "in_list_view": 1, 211 | "in_standard_filter": 0, 212 | "label": "Shop URL", 213 | "length": 0, 214 | "no_copy": 0, 215 | "permlevel": 0, 216 | "precision": "", 217 | "print_hide": 0, 218 | "print_hide_if_no_value": 0, 219 | "read_only": 0, 220 | "remember_last_selected_value": 0, 221 | "report_hide": 0, 222 | "reqd": 1, 223 | "search_index": 0, 224 | "set_only_once": 0, 225 | "unique": 0 226 | }, 227 | { 228 | "allow_bulk_edit": 0, 229 | "allow_on_submit": 0, 230 | "bold": 0, 231 | "collapsible": 0, 232 | "columns": 0, 233 | "fieldname": "column_break_3", 234 | "fieldtype": "Column Break", 235 | "hidden": 0, 236 | "ignore_user_permissions": 0, 237 | "ignore_xss_filter": 0, 238 | "in_filter": 0, 239 | "in_global_search": 0, 240 | "in_list_view": 0, 241 | "in_standard_filter": 0, 242 | "length": 0, 243 | "no_copy": 0, 244 | "permlevel": 0, 245 | "precision": "", 246 | "print_hide": 0, 247 | "print_hide_if_no_value": 0, 248 | "read_only": 0, 249 | "remember_last_selected_value": 0, 250 | "report_hide": 0, 251 | "reqd": 0, 252 | "search_index": 0, 253 | "set_only_once": 0, 254 | "unique": 0 255 | }, 256 | { 257 | "allow_bulk_edit": 0, 258 | "allow_on_submit": 0, 259 | "bold": 0, 260 | "collapsible": 0, 261 | "columns": 0, 262 | "depends_on": "eval:doc.app_type==\"Private\"", 263 | "fieldname": "api_key", 264 | "fieldtype": "Data", 265 | "hidden": 0, 266 | "ignore_user_permissions": 0, 267 | "ignore_xss_filter": 0, 268 | "in_filter": 0, 269 | "in_global_search": 0, 270 | "in_list_view": 0, 271 | "in_standard_filter": 0, 272 | "label": "API Key", 273 | "length": 0, 274 | "no_copy": 0, 275 | "permlevel": 0, 276 | "precision": "", 277 | "print_hide": 0, 278 | "print_hide_if_no_value": 0, 279 | "read_only": 0, 280 | "remember_last_selected_value": 0, 281 | "report_hide": 0, 282 | "reqd": 0, 283 | "search_index": 0, 284 | "set_only_once": 0, 285 | "unique": 0 286 | }, 287 | { 288 | "allow_bulk_edit": 0, 289 | "allow_on_submit": 0, 290 | "bold": 0, 291 | "collapsible": 0, 292 | "columns": 0, 293 | "depends_on": "eval:doc.app_type==\"Private\"", 294 | "fieldname": "password", 295 | "fieldtype": "Password", 296 | "hidden": 0, 297 | "ignore_user_permissions": 0, 298 | "ignore_xss_filter": 0, 299 | "in_filter": 0, 300 | "in_global_search": 0, 301 | "in_list_view": 0, 302 | "in_standard_filter": 0, 303 | "label": "Password", 304 | "length": 0, 305 | "no_copy": 0, 306 | "permlevel": 0, 307 | "precision": "", 308 | "print_hide": 0, 309 | "print_hide_if_no_value": 0, 310 | "read_only": 0, 311 | "remember_last_selected_value": 0, 312 | "report_hide": 0, 313 | "reqd": 0, 314 | "search_index": 0, 315 | "set_only_once": 0, 316 | "unique": 0 317 | }, 318 | { 319 | "allow_bulk_edit": 0, 320 | "allow_on_submit": 0, 321 | "bold": 0, 322 | "collapsible": 0, 323 | "columns": 0, 324 | "fieldname": "webhook_address", 325 | "fieldtype": "Data", 326 | "hidden": 1, 327 | "ignore_user_permissions": 0, 328 | "ignore_xss_filter": 0, 329 | "in_filter": 0, 330 | "in_global_search": 0, 331 | "in_list_view": 0, 332 | "in_standard_filter": 0, 333 | "label": "Webhook Address", 334 | "length": 0, 335 | "no_copy": 0, 336 | "permlevel": 0, 337 | "precision": "", 338 | "print_hide": 0, 339 | "print_hide_if_no_value": 0, 340 | "read_only": 0, 341 | "remember_last_selected_value": 0, 342 | "report_hide": 0, 343 | "reqd": 0, 344 | "search_index": 0, 345 | "set_only_once": 0, 346 | "unique": 0 347 | }, 348 | { 349 | "allow_bulk_edit": 0, 350 | "allow_on_submit": 0, 351 | "bold": 0, 352 | "collapsible": 0, 353 | "columns": 0, 354 | "fieldname": "access_token", 355 | "fieldtype": "Data", 356 | "hidden": 1, 357 | "ignore_user_permissions": 0, 358 | "ignore_xss_filter": 0, 359 | "in_filter": 0, 360 | "in_global_search": 0, 361 | "in_list_view": 0, 362 | "in_standard_filter": 0, 363 | "label": "Access Token", 364 | "length": 0, 365 | "no_copy": 0, 366 | "permlevel": 0, 367 | "precision": "", 368 | "print_hide": 0, 369 | "print_hide_if_no_value": 0, 370 | "read_only": 1, 371 | "remember_last_selected_value": 0, 372 | "report_hide": 0, 373 | "reqd": 0, 374 | "search_index": 0, 375 | "set_only_once": 0, 376 | "unique": 0 377 | }, 378 | { 379 | "allow_bulk_edit": 0, 380 | "allow_on_submit": 0, 381 | "bold": 0, 382 | "collapsible": 0, 383 | "columns": 0, 384 | "fieldname": "erp_settings", 385 | "fieldtype": "Section Break", 386 | "hidden": 0, 387 | "ignore_user_permissions": 0, 388 | "ignore_xss_filter": 0, 389 | "in_filter": 0, 390 | "in_global_search": 0, 391 | "in_list_view": 0, 392 | "in_standard_filter": 0, 393 | "label": "", 394 | "length": 0, 395 | "no_copy": 0, 396 | "permlevel": 0, 397 | "precision": "", 398 | "print_hide": 0, 399 | "print_hide_if_no_value": 0, 400 | "read_only": 0, 401 | "remember_last_selected_value": 0, 402 | "report_hide": 0, 403 | "reqd": 0, 404 | "search_index": 0, 405 | "set_only_once": 0, 406 | "unique": 0 407 | }, 408 | { 409 | "allow_bulk_edit": 0, 410 | "allow_on_submit": 0, 411 | "bold": 0, 412 | "collapsible": 0, 413 | "columns": 0, 414 | "fieldname": "if_not_exists_create_item_to_shopify", 415 | "fieldtype": "Check", 416 | "hidden": 0, 417 | "ignore_user_permissions": 0, 418 | "ignore_xss_filter": 0, 419 | "in_filter": 0, 420 | "in_global_search": 0, 421 | "in_list_view": 0, 422 | "in_standard_filter": 0, 423 | "label": "If not exists create item to Shopify", 424 | "length": 0, 425 | "no_copy": 0, 426 | "permlevel": 0, 427 | "precision": "", 428 | "print_hide": 0, 429 | "print_hide_if_no_value": 0, 430 | "read_only": 0, 431 | "remember_last_selected_value": 0, 432 | "report_hide": 0, 433 | "reqd": 0, 434 | "search_index": 0, 435 | "set_only_once": 0, 436 | "unique": 0 437 | }, 438 | { 439 | "allow_bulk_edit": 0, 440 | "allow_on_submit": 0, 441 | "bold": 0, 442 | "collapsible": 0, 443 | "columns": 0, 444 | "fieldname": "column_break_8", 445 | "fieldtype": "Column Break", 446 | "hidden": 0, 447 | "ignore_user_permissions": 0, 448 | "ignore_xss_filter": 0, 449 | "in_filter": 0, 450 | "in_global_search": 0, 451 | "in_list_view": 0, 452 | "in_standard_filter": 0, 453 | "length": 0, 454 | "no_copy": 0, 455 | "permlevel": 0, 456 | "precision": "", 457 | "print_hide": 0, 458 | "print_hide_if_no_value": 0, 459 | "read_only": 0, 460 | "remember_last_selected_value": 0, 461 | "report_hide": 0, 462 | "reqd": 0, 463 | "search_index": 0, 464 | "set_only_once": 0, 465 | "unique": 0 466 | }, 467 | { 468 | "allow_bulk_edit": 0, 469 | "allow_on_submit": 0, 470 | "bold": 0, 471 | "collapsible": 0, 472 | "columns": 0, 473 | "default": "", 474 | "fieldname": "push_prices_to_shopify", 475 | "fieldtype": "Check", 476 | "hidden": 0, 477 | "ignore_user_permissions": 0, 478 | "ignore_xss_filter": 0, 479 | "in_filter": 0, 480 | "in_global_search": 0, 481 | "in_list_view": 0, 482 | "in_standard_filter": 0, 483 | "label": "Update item prices to Shopify from EPNext", 484 | "length": 0, 485 | "no_copy": 0, 486 | "permlevel": 0, 487 | "precision": "", 488 | "print_hide": 0, 489 | "print_hide_if_no_value": 0, 490 | "read_only": 0, 491 | "remember_last_selected_value": 0, 492 | "report_hide": 0, 493 | "reqd": 0, 494 | "search_index": 0, 495 | "set_only_once": 0, 496 | "unique": 0 497 | }, 498 | { 499 | "allow_bulk_edit": 0, 500 | "allow_on_submit": 0, 501 | "bold": 0, 502 | "collapsible": 0, 503 | "columns": 0, 504 | "description": "", 505 | "fieldname": "price_list", 506 | "fieldtype": "Link", 507 | "hidden": 0, 508 | "ignore_user_permissions": 0, 509 | "ignore_xss_filter": 0, 510 | "in_filter": 0, 511 | "in_global_search": 0, 512 | "in_list_view": 0, 513 | "in_standard_filter": 0, 514 | "label": "Price List", 515 | "length": 0, 516 | "no_copy": 0, 517 | "options": "Price List", 518 | "permlevel": 0, 519 | "precision": "", 520 | "print_hide": 0, 521 | "print_hide_if_no_value": 0, 522 | "read_only": 0, 523 | "remember_last_selected_value": 0, 524 | "report_hide": 0, 525 | "reqd": 0, 526 | "search_index": 0, 527 | "set_only_once": 0, 528 | "unique": 0 529 | }, 530 | { 531 | "allow_bulk_edit": 0, 532 | "allow_on_submit": 0, 533 | "bold": 0, 534 | "collapsible": 0, 535 | "columns": 0, 536 | "fieldname": "section_break_15", 537 | "fieldtype": "Section Break", 538 | "hidden": 0, 539 | "ignore_user_permissions": 0, 540 | "ignore_xss_filter": 0, 541 | "in_filter": 0, 542 | "in_global_search": 0, 543 | "in_list_view": 0, 544 | "in_standard_filter": 0, 545 | "length": 0, 546 | "no_copy": 0, 547 | "permlevel": 0, 548 | "precision": "", 549 | "print_hide": 0, 550 | "print_hide_if_no_value": 0, 551 | "read_only": 0, 552 | "remember_last_selected_value": 0, 553 | "report_hide": 0, 554 | "reqd": 0, 555 | "search_index": 0, 556 | "set_only_once": 0, 557 | "unique": 0 558 | }, 559 | { 560 | "allow_bulk_edit": 0, 561 | "allow_on_submit": 0, 562 | "bold": 0, 563 | "collapsible": 0, 564 | "columns": 0, 565 | "description": "If Shopify not contains a customer in Order, then while syncing Orders, the system will consider default customer for order", 566 | "fieldname": "default_customer", 567 | "fieldtype": "Link", 568 | "hidden": 0, 569 | "ignore_user_permissions": 0, 570 | "ignore_xss_filter": 0, 571 | "in_filter": 0, 572 | "in_global_search": 0, 573 | "in_list_view": 0, 574 | "in_standard_filter": 0, 575 | "label": "Default Customer", 576 | "length": 0, 577 | "no_copy": 0, 578 | "options": "Customer", 579 | "permlevel": 0, 580 | "precision": "", 581 | "print_hide": 0, 582 | "print_hide_if_no_value": 0, 583 | "read_only": 0, 584 | "remember_last_selected_value": 0, 585 | "report_hide": 0, 586 | "reqd": 0, 587 | "search_index": 0, 588 | "set_only_once": 0, 589 | "unique": 0 590 | }, 591 | { 592 | "allow_bulk_edit": 0, 593 | "allow_on_submit": 0, 594 | "bold": 0, 595 | "collapsible": 0, 596 | "columns": 0, 597 | "default": "", 598 | "description": "Customer Group will set to selected group while syncing customers from Shopify", 599 | "fieldname": "customer_group", 600 | "fieldtype": "Link", 601 | "hidden": 0, 602 | "ignore_user_permissions": 0, 603 | "ignore_xss_filter": 0, 604 | "in_filter": 0, 605 | "in_global_search": 0, 606 | "in_list_view": 0, 607 | "in_standard_filter": 0, 608 | "label": "Customer Group", 609 | "length": 0, 610 | "no_copy": 0, 611 | "options": "Customer Group", 612 | "permlevel": 0, 613 | "precision": "", 614 | "print_hide": 0, 615 | "print_hide_if_no_value": 0, 616 | "read_only": 0, 617 | "remember_last_selected_value": 0, 618 | "report_hide": 0, 619 | "reqd": 0, 620 | "search_index": 0, 621 | "set_only_once": 0, 622 | "unique": 0 623 | }, 624 | { 625 | "allow_bulk_edit": 0, 626 | "allow_on_submit": 0, 627 | "bold": 0, 628 | "collapsible": 0, 629 | "columns": 0, 630 | "fieldname": "company_dependent_settings", 631 | "fieldtype": "Section Break", 632 | "hidden": 0, 633 | "ignore_user_permissions": 0, 634 | "ignore_xss_filter": 0, 635 | "in_filter": 0, 636 | "in_global_search": 0, 637 | "in_list_view": 0, 638 | "in_standard_filter": 0, 639 | "length": 0, 640 | "no_copy": 0, 641 | "permlevel": 0, 642 | "precision": "", 643 | "print_hide": 0, 644 | "print_hide_if_no_value": 0, 645 | "read_only": 0, 646 | "remember_last_selected_value": 0, 647 | "report_hide": 0, 648 | "reqd": 0, 649 | "search_index": 0, 650 | "set_only_once": 0, 651 | "unique": 0 652 | }, 653 | { 654 | "allow_bulk_edit": 0, 655 | "allow_on_submit": 0, 656 | "bold": 0, 657 | "collapsible": 0, 658 | "columns": 0, 659 | "fieldname": "company", 660 | "fieldtype": "Link", 661 | "hidden": 0, 662 | "ignore_user_permissions": 0, 663 | "ignore_xss_filter": 0, 664 | "in_filter": 0, 665 | "in_global_search": 0, 666 | "in_list_view": 0, 667 | "in_standard_filter": 0, 668 | "label": "For Company", 669 | "length": 0, 670 | "no_copy": 0, 671 | "options": "Company", 672 | "permlevel": 0, 673 | "precision": "", 674 | "print_hide": 0, 675 | "print_hide_if_no_value": 0, 676 | "read_only": 0, 677 | "remember_last_selected_value": 0, 678 | "report_hide": 0, 679 | "reqd": 0, 680 | "search_index": 0, 681 | "set_only_once": 0, 682 | "unique": 0 683 | }, 684 | { 685 | "allow_bulk_edit": 0, 686 | "allow_on_submit": 0, 687 | "bold": 0, 688 | "collapsible": 0, 689 | "columns": 0, 690 | "description": "Cash Account will used for Sales Invoice creation", 691 | "fieldname": "cash_bank_account", 692 | "fieldtype": "Link", 693 | "hidden": 0, 694 | "ignore_user_permissions": 0, 695 | "ignore_xss_filter": 0, 696 | "in_filter": 0, 697 | "in_global_search": 0, 698 | "in_list_view": 0, 699 | "in_standard_filter": 0, 700 | "label": "Cash/Bank Account", 701 | "length": 0, 702 | "no_copy": 0, 703 | "options": "Account", 704 | "permlevel": 0, 705 | "precision": "", 706 | "print_hide": 0, 707 | "print_hide_if_no_value": 0, 708 | "read_only": 0, 709 | "remember_last_selected_value": 0, 710 | "report_hide": 0, 711 | "reqd": 0, 712 | "search_index": 0, 713 | "set_only_once": 0, 714 | "unique": 0 715 | }, 716 | { 717 | "allow_bulk_edit": 0, 718 | "allow_on_submit": 0, 719 | "bold": 0, 720 | "collapsible": 0, 721 | "columns": 0, 722 | "fieldname": "column_break_20", 723 | "fieldtype": "Column Break", 724 | "hidden": 0, 725 | "ignore_user_permissions": 0, 726 | "ignore_xss_filter": 0, 727 | "in_filter": 0, 728 | "in_global_search": 0, 729 | "in_list_view": 0, 730 | "in_standard_filter": 0, 731 | "length": 0, 732 | "no_copy": 0, 733 | "permlevel": 0, 734 | "precision": "", 735 | "print_hide": 0, 736 | "print_hide_if_no_value": 0, 737 | "read_only": 0, 738 | "remember_last_selected_value": 0, 739 | "report_hide": 0, 740 | "reqd": 0, 741 | "search_index": 0, 742 | "set_only_once": 0, 743 | "unique": 0 744 | }, 745 | { 746 | "allow_bulk_edit": 0, 747 | "allow_on_submit": 0, 748 | "bold": 0, 749 | "collapsible": 0, 750 | "columns": 0, 751 | "fieldname": "cost_center", 752 | "fieldtype": "Link", 753 | "hidden": 0, 754 | "ignore_user_permissions": 0, 755 | "ignore_xss_filter": 0, 756 | "in_filter": 0, 757 | "in_global_search": 0, 758 | "in_list_view": 0, 759 | "in_standard_filter": 0, 760 | "label": "Cost Center", 761 | "length": 0, 762 | "no_copy": 0, 763 | "options": "Cost Center", 764 | "permlevel": 0, 765 | "precision": "", 766 | "print_hide": 0, 767 | "print_hide_if_no_value": 0, 768 | "read_only": 0, 769 | "remember_last_selected_value": 0, 770 | "report_hide": 0, 771 | "reqd": 0, 772 | "search_index": 0, 773 | "set_only_once": 0, 774 | "unique": 0 775 | }, 776 | { 777 | "allow_bulk_edit": 0, 778 | "allow_on_submit": 0, 779 | "bold": 0, 780 | "collapsible": 0, 781 | "columns": 0, 782 | "default": "", 783 | "depends_on": "", 784 | "description": "Sync Item Quantity, from seleted Warehouse with Shopify", 785 | "fieldname": "warehouse", 786 | "fieldtype": "Link", 787 | "hidden": 0, 788 | "ignore_user_permissions": 0, 789 | "ignore_xss_filter": 0, 790 | "in_filter": 0, 791 | "in_global_search": 0, 792 | "in_list_view": 0, 793 | "in_standard_filter": 0, 794 | "label": "Warehouse", 795 | "length": 0, 796 | "no_copy": 0, 797 | "options": "Warehouse", 798 | "permlevel": 0, 799 | "precision": "", 800 | "print_hide": 0, 801 | "print_hide_if_no_value": 0, 802 | "read_only": 0, 803 | "remember_last_selected_value": 0, 804 | "report_hide": 0, 805 | "reqd": 0, 806 | "search_index": 0, 807 | "set_only_once": 0, 808 | "unique": 0 809 | }, 810 | { 811 | "allow_bulk_edit": 0, 812 | "allow_on_submit": 0, 813 | "bold": 0, 814 | "collapsible": 0, 815 | "columns": 0, 816 | "fieldname": "section_break_22", 817 | "fieldtype": "Section Break", 818 | "hidden": 0, 819 | "ignore_user_permissions": 0, 820 | "ignore_xss_filter": 0, 821 | "in_filter": 0, 822 | "in_global_search": 0, 823 | "in_list_view": 0, 824 | "in_standard_filter": 0, 825 | "label": "", 826 | "length": 0, 827 | "no_copy": 0, 828 | "permlevel": 0, 829 | "precision": "", 830 | "print_hide": 0, 831 | "print_hide_if_no_value": 0, 832 | "read_only": 0, 833 | "remember_last_selected_value": 0, 834 | "report_hide": 0, 835 | "reqd": 0, 836 | "search_index": 0, 837 | "set_only_once": 0, 838 | "unique": 0 839 | }, 840 | { 841 | "allow_bulk_edit": 0, 842 | "allow_on_submit": 0, 843 | "bold": 0, 844 | "collapsible": 0, 845 | "columns": 0, 846 | "fieldname": "html_16", 847 | "fieldtype": "HTML", 848 | "hidden": 0, 849 | "ignore_user_permissions": 0, 850 | "ignore_xss_filter": 0, 851 | "in_filter": 0, 852 | "in_global_search": 0, 853 | "in_list_view": 0, 854 | "in_standard_filter": 0, 855 | "length": 0, 856 | "no_copy": 0, 857 | "options": "Map Shopify Tax / Shipping to ERPNext Account", 858 | "permlevel": 0, 859 | "precision": "", 860 | "print_hide": 0, 861 | "print_hide_if_no_value": 0, 862 | "read_only": 0, 863 | "remember_last_selected_value": 0, 864 | "report_hide": 0, 865 | "reqd": 0, 866 | "search_index": 0, 867 | "set_only_once": 0, 868 | "unique": 0 869 | }, 870 | { 871 | "allow_bulk_edit": 0, 872 | "allow_on_submit": 0, 873 | "bold": 0, 874 | "collapsible": 0, 875 | "columns": 0, 876 | "fieldname": "taxes", 877 | "fieldtype": "Table", 878 | "hidden": 0, 879 | "ignore_user_permissions": 0, 880 | "ignore_xss_filter": 0, 881 | "in_filter": 0, 882 | "in_global_search": 0, 883 | "in_list_view": 0, 884 | "in_standard_filter": 0, 885 | "label": "Shopify Tax Account", 886 | "length": 0, 887 | "no_copy": 0, 888 | "options": "Shopify Tax Account", 889 | "permlevel": 0, 890 | "precision": "", 891 | "print_hide": 0, 892 | "print_hide_if_no_value": 0, 893 | "read_only": 0, 894 | "remember_last_selected_value": 0, 895 | "report_hide": 0, 896 | "reqd": 0, 897 | "search_index": 0, 898 | "set_only_once": 0, 899 | "unique": 0 900 | }, 901 | { 902 | "allow_bulk_edit": 0, 903 | "allow_on_submit": 0, 904 | "bold": 0, 905 | "collapsible": 0, 906 | "columns": 0, 907 | "fieldname": "section_break_25", 908 | "fieldtype": "Section Break", 909 | "hidden": 0, 910 | "ignore_user_permissions": 0, 911 | "ignore_xss_filter": 0, 912 | "in_filter": 0, 913 | "in_global_search": 0, 914 | "in_list_view": 0, 915 | "in_standard_filter": 0, 916 | "length": 0, 917 | "no_copy": 0, 918 | "permlevel": 0, 919 | "precision": "", 920 | "print_hide": 0, 921 | "print_hide_if_no_value": 0, 922 | "read_only": 0, 923 | "remember_last_selected_value": 0, 924 | "report_hide": 0, 925 | "reqd": 0, 926 | "search_index": 0, 927 | "set_only_once": 0, 928 | "unique": 0 929 | }, 930 | { 931 | "allow_bulk_edit": 0, 932 | "allow_on_submit": 0, 933 | "bold": 0, 934 | "collapsible": 0, 935 | "columns": 0, 936 | "fieldname": "sales_order_series", 937 | "fieldtype": "Select", 938 | "hidden": 0, 939 | "ignore_user_permissions": 0, 940 | "ignore_xss_filter": 0, 941 | "in_filter": 0, 942 | "in_global_search": 0, 943 | "in_list_view": 0, 944 | "in_standard_filter": 0, 945 | "label": "Sales Order Series", 946 | "length": 0, 947 | "no_copy": 0, 948 | "permlevel": 0, 949 | "precision": "", 950 | "print_hide": 0, 951 | "print_hide_if_no_value": 0, 952 | "read_only": 0, 953 | "remember_last_selected_value": 0, 954 | "report_hide": 0, 955 | "reqd": 0, 956 | "search_index": 0, 957 | "set_only_once": 0, 958 | "unique": 0 959 | }, 960 | { 961 | "allow_bulk_edit": 0, 962 | "allow_on_submit": 0, 963 | "bold": 0, 964 | "collapsible": 0, 965 | "columns": 0, 966 | "fieldname": "column_break_27", 967 | "fieldtype": "Column Break", 968 | "hidden": 0, 969 | "ignore_user_permissions": 0, 970 | "ignore_xss_filter": 0, 971 | "in_filter": 0, 972 | "in_global_search": 0, 973 | "in_list_view": 0, 974 | "in_standard_filter": 0, 975 | "length": 0, 976 | "no_copy": 0, 977 | "permlevel": 0, 978 | "precision": "", 979 | "print_hide": 0, 980 | "print_hide_if_no_value": 0, 981 | "read_only": 0, 982 | "remember_last_selected_value": 0, 983 | "report_hide": 0, 984 | "reqd": 0, 985 | "search_index": 0, 986 | "set_only_once": 0, 987 | "unique": 0 988 | }, 989 | { 990 | "allow_bulk_edit": 0, 991 | "allow_on_submit": 0, 992 | "bold": 0, 993 | "collapsible": 0, 994 | "columns": 0, 995 | "description": "", 996 | "fieldname": "sync_delivery_note", 997 | "fieldtype": "Check", 998 | "hidden": 0, 999 | "ignore_user_permissions": 0, 1000 | "ignore_xss_filter": 0, 1001 | "in_filter": 0, 1002 | "in_global_search": 0, 1003 | "in_list_view": 0, 1004 | "in_standard_filter": 0, 1005 | "label": "Import Delivery Notes from Shopify on Shipment", 1006 | "length": 0, 1007 | "no_copy": 0, 1008 | "permlevel": 0, 1009 | "precision": "", 1010 | "print_hide": 0, 1011 | "print_hide_if_no_value": 0, 1012 | "read_only": 0, 1013 | "remember_last_selected_value": 0, 1014 | "report_hide": 0, 1015 | "reqd": 0, 1016 | "search_index": 0, 1017 | "set_only_once": 0, 1018 | "unique": 0 1019 | }, 1020 | { 1021 | "allow_bulk_edit": 0, 1022 | "allow_on_submit": 0, 1023 | "bold": 0, 1024 | "collapsible": 0, 1025 | "columns": 0, 1026 | "depends_on": "eval:doc.sync_delivery_note==1", 1027 | "fieldname": "delivery_note_series", 1028 | "fieldtype": "Select", 1029 | "hidden": 0, 1030 | "ignore_user_permissions": 0, 1031 | "ignore_xss_filter": 0, 1032 | "in_filter": 0, 1033 | "in_global_search": 0, 1034 | "in_list_view": 0, 1035 | "in_standard_filter": 0, 1036 | "label": "Delivery Note Series", 1037 | "length": 0, 1038 | "no_copy": 0, 1039 | "permlevel": 0, 1040 | "precision": "", 1041 | "print_hide": 0, 1042 | "print_hide_if_no_value": 0, 1043 | "read_only": 0, 1044 | "remember_last_selected_value": 0, 1045 | "report_hide": 0, 1046 | "reqd": 0, 1047 | "search_index": 0, 1048 | "set_only_once": 0, 1049 | "unique": 0 1050 | }, 1051 | { 1052 | "allow_bulk_edit": 0, 1053 | "allow_on_submit": 0, 1054 | "bold": 0, 1055 | "collapsible": 0, 1056 | "columns": 0, 1057 | "description": "", 1058 | "fieldname": "sync_sales_invoice", 1059 | "fieldtype": "Check", 1060 | "hidden": 0, 1061 | "ignore_user_permissions": 0, 1062 | "ignore_xss_filter": 0, 1063 | "in_filter": 0, 1064 | "in_global_search": 0, 1065 | "in_list_view": 0, 1066 | "in_standard_filter": 0, 1067 | "label": "Import Sales Invoice from Shopify if Payment is marked", 1068 | "length": 0, 1069 | "no_copy": 0, 1070 | "permlevel": 0, 1071 | "precision": "", 1072 | "print_hide": 0, 1073 | "print_hide_if_no_value": 0, 1074 | "read_only": 0, 1075 | "remember_last_selected_value": 0, 1076 | "report_hide": 0, 1077 | "reqd": 0, 1078 | "search_index": 0, 1079 | "set_only_once": 0, 1080 | "unique": 0 1081 | }, 1082 | { 1083 | "allow_bulk_edit": 0, 1084 | "allow_on_submit": 0, 1085 | "bold": 0, 1086 | "collapsible": 0, 1087 | "columns": 0, 1088 | "depends_on": "eval:doc.sync_sales_invoice==1", 1089 | "fieldname": "sales_invoice_series", 1090 | "fieldtype": "Select", 1091 | "hidden": 0, 1092 | "ignore_user_permissions": 0, 1093 | "ignore_xss_filter": 0, 1094 | "in_filter": 0, 1095 | "in_global_search": 0, 1096 | "in_list_view": 0, 1097 | "in_standard_filter": 0, 1098 | "label": "Sales Invoice Series", 1099 | "length": 0, 1100 | "no_copy": 0, 1101 | "permlevel": 0, 1102 | "precision": "", 1103 | "print_hide": 0, 1104 | "print_hide_if_no_value": 0, 1105 | "read_only": 0, 1106 | "remember_last_selected_value": 0, 1107 | "report_hide": 0, 1108 | "reqd": 0, 1109 | "search_index": 0, 1110 | "set_only_once": 0, 1111 | "unique": 0 1112 | } 1113 | ], 1114 | "has_web_view": 0, 1115 | "hide_heading": 0, 1116 | "hide_toolbar": 0, 1117 | "idx": 0, 1118 | "image_view": 0, 1119 | "in_create": 0, 1120 | "is_submittable": 0, 1121 | "issingle": 1, 1122 | "istable": 0, 1123 | "max_attachments": 0, 1124 | "modified": "2017-11-24 17:06:49.725017", 1125 | "modified_by": "Administrator", 1126 | "module": "ERPNext Shopify", 1127 | "name": "Shopify Settings", 1128 | "name_case": "", 1129 | "owner": "Administrator", 1130 | "permissions": [ 1131 | { 1132 | "amend": 0, 1133 | "apply_user_permissions": 0, 1134 | "cancel": 0, 1135 | "create": 1, 1136 | "delete": 1, 1137 | "email": 1, 1138 | "export": 0, 1139 | "if_owner": 0, 1140 | "import": 0, 1141 | "permlevel": 0, 1142 | "print": 1, 1143 | "read": 1, 1144 | "report": 0, 1145 | "role": "System Manager", 1146 | "set_user_permissions": 0, 1147 | "share": 1, 1148 | "submit": 0, 1149 | "write": 1 1150 | } 1151 | ], 1152 | "quick_entry": 0, 1153 | "read_only": 0, 1154 | "read_only_onload": 0, 1155 | "show_name_in_global_search": 0, 1156 | "sort_field": "modified", 1157 | "sort_order": "DESC", 1158 | "track_changes": 0, 1159 | "track_seen": 0 1160 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/shopify_settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe import _ 8 | import requests.exceptions 9 | from frappe.model.document import Document 10 | from erpnext_shopify.shopify_requests import get_request 11 | from erpnext_shopify.exceptions import ShopifySetupError 12 | 13 | class ShopifySettings(Document): 14 | def validate(self): 15 | if self.enable_shopify == 1: 16 | self.validate_access_credentials() 17 | self.validate_access() 18 | 19 | def validate_access_credentials(self): 20 | if self.app_type == "Private": 21 | if not (self.get_password(raise_exception=False) and self.api_key and self.shopify_url): 22 | frappe.msgprint(_("Missing value for Password, API Key or Shopify URL"), raise_exception=ShopifySetupError) 23 | 24 | else: 25 | if not (self.access_token and self.shopify_url): 26 | frappe.msgprint(_("Access token or Shopify URL missing"), raise_exception=ShopifySetupError) 27 | 28 | def validate_access(self): 29 | try: 30 | get_request('/admin/products.json', {"api_key": self.api_key, 31 | "password": self.get_password(raise_exception=False), "shopify_url": self.shopify_url, 32 | "access_token": self.access_token, "app_type": self.app_type}) 33 | 34 | except requests.exceptions.HTTPError: 35 | # disable shopify! 36 | frappe.db.rollback() 37 | self.set("enable_shopify", 0) 38 | frappe.db.commit() 39 | 40 | frappe.throw(_("""Invalid Shopify app credentials or access token"""), ShopifySetupError) 41 | 42 | 43 | @frappe.whitelist() 44 | def get_series(): 45 | return { 46 | "sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-Shopify-", 47 | "sales_invoice_series" : frappe.get_meta("Sales Invoice").get_options("naming_series") or "SI-Shopify-", 48 | "delivery_note_series" : frappe.get_meta("Delivery Note").get_options("naming_series") or "DN-Shopify-" 49 | } 50 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/test_data/shopify_customer.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer": { 3 | "id": 2324518599, 4 | "email": "andrew@wyatt.co.in", 5 | "accepts_marketing": false, 6 | "created_at": "2016-01-20T17:18:35+05:30", 7 | "updated_at": "2016-01-20T17:22:23+05:30", 8 | "first_name": "Andrew", 9 | "last_name": "Wyatt", 10 | "orders_count": 0, 11 | "state": "disabled", 12 | "total_spent": "0.00", 13 | "last_order_id": null, 14 | "note": "", 15 | "verified_email": true, 16 | "multipass_identifier": null, 17 | "tax_exempt": false, 18 | "tags": "", 19 | "last_order_name": null, 20 | "default_address": { 21 | "id": 2476804295, 22 | "first_name": "Andrew", 23 | "last_name": "Wyatt", 24 | "company": "Wyatt Inc.", 25 | "address1": "B-11, Betahouse", 26 | "address2": "Street 11, Sector 52", 27 | "city": "Manhattan", 28 | "province": "New York", 29 | "country": "United States", 30 | "zip": "10027", 31 | "phone": "145-112211", 32 | "name": "Andrew Wyatt", 33 | "province_code": "NY", 34 | "country_code": "US", 35 | "country_name": "United States", 36 | "default": true 37 | }, 38 | "addresses": [ 39 | { 40 | "id": 2476804295, 41 | "first_name": "Andrew", 42 | "last_name": "Wyatt", 43 | "company": "Wyatt Inc.", 44 | "address1": "B-11, Betahouse", 45 | "address2": "Street 11, Sector 52", 46 | "city": "Manhattan", 47 | "province": "New York", 48 | "country": "United States", 49 | "zip": "10027", 50 | "phone": "145-112211", 51 | "name": "Andrew Wyatt", 52 | "province_code": "NY", 53 | "country_code": "US", 54 | "country_name": "United States", 55 | "default": true 56 | } 57 | ] 58 | } 59 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/test_data/shopify_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "product": { 3 | "id": 4059739520, 4 | "title": "Shopify Test Item", 5 | "body_html": "
Hold back Spin Medallion-Set of 2
\n
\n
Finish: Plated/ Powder Coated
\n
Material: Iron
\n
Color Finish: Satin Silver, Brown Oil Rubbed, Roman Bronze
\n
Qty: 1 Set
", 6 | "vendor": "Boa casa", 7 | "product_type": "Curtain Accessories", 8 | "created_at": "2016-01-18T17:16:37+05:30", 9 | "handle": "1001624-01", 10 | "updated_at": "2016-01-20T17:26:44+05:30", 11 | "published_at": "2016-01-18T17:16:37+05:30", 12 | "template_suffix": null, 13 | "published_scope": "global", 14 | "tags": "Category_Curtain Accessories, Type_Holdback", 15 | "variants": [{ 16 | "id": 13917612359, 17 | "product_id": 4059739520, 18 | "title": "Test BALCK Item", 19 | "price": "499.00", 20 | "sku": "", 21 | "position": 1, 22 | "grams": 0, 23 | "inventory_policy": "continue", 24 | "compare_at_price": null, 25 | "fulfillment_service": "manual", 26 | "inventory_management": "shopify", 27 | "option1": "BLACK", 28 | "option2": null, 29 | "option3": null, 30 | "created_at": "2016-01-18T17:16:37+05:30", 31 | "updated_at": "2016-01-20T17:26:44+05:30", 32 | "requires_shipping": true, 33 | "taxable": true, 34 | "barcode": "", 35 | "inventory_quantity": -1, 36 | "old_inventory_quantity": -1, 37 | "image_id": 8539321735, 38 | "weight": 0, 39 | "weight_unit": "kg" 40 | }, { 41 | "id": 13917612423, 42 | "product_id": 4059739520, 43 | "title": "Test BLUE Item", 44 | "price": "499.00", 45 | "sku": "", 46 | "position": 2, 47 | "grams": 0, 48 | "inventory_policy": "continue", 49 | "compare_at_price": null, 50 | "fulfillment_service": "manual", 51 | "inventory_management": "shopify", 52 | "option1": "BLUE", 53 | "option2": null, 54 | "option3": null, 55 | "created_at": "2016-01-18T17:16:37+05:30", 56 | "updated_at": "2016-01-20T17:26:44+05:30", 57 | "requires_shipping": true, 58 | "taxable": true, 59 | "barcode": "", 60 | "inventory_quantity": -1, 61 | "old_inventory_quantity": -1, 62 | "image_id": null, 63 | "weight": 0, 64 | "weight_unit": "kg" 65 | }, { 66 | "id": 13917612487, 67 | "product_id": 4059739520, 68 | "title": "Test White Item", 69 | "price": "499.00", 70 | "sku": "", 71 | "position": 3, 72 | "grams": 0, 73 | "inventory_policy": "continue", 74 | "compare_at_price": null, 75 | "fulfillment_service": "manual", 76 | "inventory_management": "shopify", 77 | "option1": "White", 78 | "option2": null, 79 | "option3": null, 80 | "created_at": "2016-01-18T17:16:37+05:30", 81 | "updated_at": "2016-01-18T17:16:37+05:30", 82 | "requires_shipping": true, 83 | "taxable": true, 84 | "barcode": "", 85 | "inventory_quantity": 0, 86 | "old_inventory_quantity": 0, 87 | "image_id": null, 88 | "weight": 0, 89 | "weight_unit": "kg" 90 | }], 91 | "options": [{ 92 | "id": 4985027399, 93 | "product_id": 4059739520, 94 | "name": "Colour", 95 | "position": 1, 96 | "values": [ 97 | "BLACK", 98 | "BLUE", 99 | "White" 100 | ] 101 | }], 102 | "images": [{ 103 | "id": 8539321735, 104 | "product_id": 4059739520, 105 | "position": 1, 106 | "created_at": "2016-01-18T17:16:37+05:30", 107 | "updated_at": "2016-01-18T17:16:37+05:30", 108 | "src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597", 109 | "variant_ids": [ 110 | 13917612359 111 | ] 112 | }], 113 | "image": { 114 | "id": 8539321735, 115 | "product_id": 4059739520, 116 | "position": 1, 117 | "created_at": "2016-01-18T17:16:37+05:30", 118 | "updated_at": "2016-01-18T17:16:37+05:30", 119 | "src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597", 120 | "variant_ids": [ 121 | 13917612359 122 | ] 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/test_data/shopify_order.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": { 3 | "id": 2414345735, 4 | "email": "andrew@wyatt.co.in", 5 | "closed_at": null, 6 | "created_at": "2016-01-20T17:26:39+05:30", 7 | "updated_at": "2016-01-20T17:27:15+05:30", 8 | "number": 5, 9 | "note": "", 10 | "token": "660fed25987517b733644a8c9ec7c8e0", 11 | "gateway": "manual", 12 | "test": false, 13 | "total_price": "1018.00", 14 | "subtotal_price": "998.00", 15 | "total_weight": 0, 16 | "total_tax": "0.00", 17 | "taxes_included": false, 18 | "currency": "INR", 19 | "financial_status": "paid", 20 | "confirmed": true, 21 | "total_discounts": "0.00", 22 | "total_line_items_price": "998.00", 23 | "cart_token": null, 24 | "buyer_accepts_marketing": false, 25 | "name": "#1005", 26 | "referring_site": null, 27 | "landing_site": null, 28 | "cancelled_at": null, 29 | "cancel_reason": null, 30 | "total_price_usd": "15.02", 31 | "checkout_token": null, 32 | "reference": null, 33 | "user_id": 55391175, 34 | "location_id": null, 35 | "source_identifier": null, 36 | "source_url": null, 37 | "processed_at": "2016-01-20T17:26:39+05:30", 38 | "device_id": null, 39 | "browser_ip": null, 40 | "landing_site_ref": null, 41 | "order_number": 1005, 42 | "discount_codes": [], 43 | "note_attributes": [], 44 | "payment_gateway_names": [ 45 | "manual" 46 | ], 47 | "processing_method": "manual", 48 | "checkout_id": null, 49 | "source_name": "shopify_draft_order", 50 | "fulfillment_status": "fulfilled", 51 | "tax_lines": [], 52 | "tags": "", 53 | "contact_email": "andrew@wyatt.co.in", 54 | "line_items": [ 55 | { 56 | "id": 4125768135, 57 | "variant_id": 13917612359, 58 | "title": "Shopify Test Item", 59 | "quantity": 1, 60 | "price": "499.00", 61 | "grams": 0, 62 | "sku": "", 63 | "variant_title": "Roman BALCK 1", 64 | "vendor": "Boa casa", 65 | "fulfillment_service": "manual", 66 | "product_id": 4059739527, 67 | "requires_shipping": true, 68 | "taxable": true, 69 | "gift_card": false, 70 | "name": "Roman BALCK 1", 71 | "variant_inventory_management": "shopify", 72 | "properties": [], 73 | "product_exists": true, 74 | "fulfillable_quantity": 0, 75 | "total_discount": "0.00", 76 | "fulfillment_status": "fulfilled", 77 | "tax_lines": [] 78 | }, 79 | { 80 | "id": 4125768199, 81 | "variant_id": 13917612423, 82 | "title": "Shopify Test Item", 83 | "quantity": 1, 84 | "price": "499.00", 85 | "grams": 0, 86 | "sku": "", 87 | "variant_title": "Satin BLUE 1", 88 | "vendor": "Boa casa", 89 | "fulfillment_service": "manual", 90 | "product_id": 4059739527, 91 | "requires_shipping": true, 92 | "taxable": true, 93 | "gift_card": false, 94 | "name": "Satin BLUE 1", 95 | "variant_inventory_management": "shopify", 96 | "properties": [], 97 | "product_exists": true, 98 | "fulfillable_quantity": 0, 99 | "total_discount": "0.00", 100 | "fulfillment_status": "fulfilled", 101 | "tax_lines": [] 102 | } 103 | ], 104 | "shipping_lines": [ 105 | { 106 | "id": 2108906247, 107 | "title": "International Shipping", 108 | "price": "20.00", 109 | "code": "International Shipping", 110 | "source": "shopify", 111 | "phone": null, 112 | "tax_lines": [] 113 | } 114 | ], 115 | "billing_address": { 116 | "first_name": "Andrew", 117 | "address1": "B-11, Betahouse", 118 | "phone": "145-112211", 119 | "city": "Manhattan", 120 | "zip": "10027", 121 | "province": "New York", 122 | "country": "United States", 123 | "last_name": "Wyatt", 124 | "address2": "Street 11, Sector 52", 125 | "company": "Wyatt Inc.", 126 | "latitude": 40.8138912, 127 | "longitude": -73.96243270000001, 128 | "name": "Andrew Wyatt", 129 | "country_code": "US", 130 | "province_code": "NY" 131 | }, 132 | "shipping_address": { 133 | "first_name": "Andrew", 134 | "address1": "B-11, Betahouse", 135 | "phone": "145-112211", 136 | "city": "Manhattan", 137 | "zip": "10027", 138 | "province": "New York", 139 | "country": "United States", 140 | "last_name": "Wyatt", 141 | "address2": "Street 11, Sector 52", 142 | "company": "Wyatt Inc.", 143 | "latitude": 40.8138912, 144 | "longitude": -73.96243270000001, 145 | "name": "Andrew Wyatt", 146 | "country_code": "US", 147 | "province_code": "NY" 148 | }, 149 | "fulfillments": [ 150 | { 151 | "id": 1849629255, 152 | "order_id": 2414345735, 153 | "status": "success", 154 | "created_at": "2016-01-20T17:27:15+05:30", 155 | "service": "manual", 156 | "updated_at": "2016-01-20T17:27:15+05:30", 157 | "tracking_company": null, 158 | "tracking_number": null, 159 | "tracking_numbers": [], 160 | "tracking_url": null, 161 | "tracking_urls": [], 162 | "receipt": {}, 163 | "line_items": [ 164 | { 165 | "id": 4125768199, 166 | "variant_id": 13917612423, 167 | "title": "1001624/01", 168 | "quantity": 1, 169 | "price": "499.00", 170 | "grams": 0, 171 | "sku": "", 172 | "variant_title": "Satin Silver", 173 | "vendor": "Boa casa", 174 | "fulfillment_service": "manual", 175 | "product_id": 4059739527, 176 | "requires_shipping": true, 177 | "taxable": true, 178 | "gift_card": false, 179 | "name": "1001624/01 - Satin Silver", 180 | "variant_inventory_management": "shopify", 181 | "properties": [], 182 | "product_exists": true, 183 | "fulfillable_quantity": 0, 184 | "total_discount": "0.00", 185 | "fulfillment_status": "fulfilled", 186 | "tax_lines": [] 187 | } 188 | ] 189 | }, 190 | { 191 | "id": 1849628167, 192 | "order_id": 2414345735, 193 | "status": "success", 194 | "created_at": "2016-01-20T17:26:58+05:30", 195 | "service": "manual", 196 | "updated_at": "2016-01-20T17:26:58+05:30", 197 | "tracking_company": null, 198 | "tracking_number": null, 199 | "tracking_numbers": [], 200 | "tracking_url": null, 201 | "tracking_urls": [], 202 | "receipt": {}, 203 | "line_items": [ 204 | { 205 | "id": 4125768135, 206 | "variant_id": 13917612359, 207 | "title": "1001624/01", 208 | "quantity": 1, 209 | "price": "499.00", 210 | "grams": 0, 211 | "sku": "", 212 | "variant_title": "Roman Bronze", 213 | "vendor": "Boa casa", 214 | "fulfillment_service": "manual", 215 | "product_id": 4059739527, 216 | "requires_shipping": true, 217 | "taxable": true, 218 | "gift_card": false, 219 | "name": "1001624/01 - Roman Bronze", 220 | "variant_inventory_management": "shopify", 221 | "properties": [], 222 | "product_exists": true, 223 | "fulfillable_quantity": 0, 224 | "total_discount": "0.00", 225 | "fulfillment_status": "fulfilled", 226 | "tax_lines": [] 227 | } 228 | ] 229 | } 230 | ], 231 | "refunds": [], 232 | "customer": { 233 | "id": 2324518599, 234 | "email": "andrew@wyatt.co.in", 235 | "accepts_marketing": false, 236 | "created_at": "2016-01-20T17:18:35+05:30", 237 | "updated_at": "2016-01-20T17:26:39+05:30", 238 | "first_name": "Andrew", 239 | "last_name": "Wyatt", 240 | "orders_count": 1, 241 | "state": "disabled", 242 | "total_spent": "1018.00", 243 | "last_order_id": 2414345735, 244 | "note": "", 245 | "verified_email": true, 246 | "multipass_identifier": null, 247 | "tax_exempt": false, 248 | "tags": "", 249 | "last_order_name": "#1005", 250 | "default_address": { 251 | "id": 2476804295, 252 | "first_name": "Andrew", 253 | "last_name": "Wyatt", 254 | "company": "Wyatt Inc.", 255 | "address1": "B-11, Betahouse", 256 | "address2": "Street 11, Sector 52", 257 | "city": "Manhattan", 258 | "province": "New York", 259 | "country": "United States", 260 | "zip": "10027", 261 | "phone": "145-112211", 262 | "name": "Andrew Wyatt", 263 | "province_code": "NY", 264 | "country_code": "US", 265 | "country_name": "United States", 266 | "default": true 267 | } 268 | } 269 | } 270 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/test_shopify_settings.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // rename this file from _test_[name] to test_[name] to activate 3 | // and remove above this line 4 | 5 | QUnit.test("test: Shopify Settings", function (assert) { 6 | let done = assert.async(); 7 | 8 | // number of asserts 9 | assert.expect(1); 10 | 11 | frappe.run_serially([ 12 | // insert a new Shopify Settings 13 | () => frappe.tests.make('Shopify Settings', [ 14 | // values to be set 15 | {key: 'value'} 16 | ]), 17 | () => { 18 | assert.equal(cur_frm.doc.key, 'value'); 19 | }, 20 | () => done() 21 | ]); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_settings/test_shopify_settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 3 | # See license.txt 4 | from __future__ import unicode_literals 5 | 6 | import os 7 | import json 8 | import frappe 9 | import unittest 10 | from frappe.utils import cint, cstr, flt 11 | from frappe.utils.fixtures import sync_fixtures 12 | from erpnext_shopify.sync_orders import create_order, valid_customer_and_product 13 | from erpnext_shopify.sync_products import make_item 14 | from erpnext_shopify.sync_customers import create_customer 15 | 16 | class ShopifySettings(unittest.TestCase): 17 | def setUp(self): 18 | frappe.set_user("Administrator") 19 | sync_fixtures("erpnext_shopify") 20 | frappe.reload_doctype("Customer") 21 | frappe.reload_doctype("Sales Order") 22 | frappe.reload_doctype("Delivery Note") 23 | frappe.reload_doctype("Sales Invoice") 24 | 25 | self.setup_shopify() 26 | 27 | def setup_shopify(self): 28 | shopify_settings = frappe.get_doc("Shopify Settings") 29 | shopify_settings.taxes = [] 30 | 31 | shopify_settings.update({ 32 | "app_type": "Private", 33 | "shopify_url": "test.myshopify.com", 34 | "api_key": "17702c7c4452b9c5d235240b6e7a39da", 35 | "password": "17702c7c4452b9c5d235240b6e7a39da", 36 | "price_list": "_Test Price List", 37 | "warehouse": "_Test Warehouse - _TC", 38 | "cash_bank_account": "Cash - _TC", 39 | "customer_group": "_Test Customer Group", 40 | "taxes": [ 41 | { 42 | "shopify_tax": "International Shipping", 43 | "tax_account":"Legal Expenses - _TC" 44 | } 45 | ], 46 | "enable_shopify": 0, 47 | "sales_order_series": "SO-", 48 | "sales_invoice_series": "SINV-", 49 | "delivery_note_series": "DN-" 50 | }).save(ignore_permissions=True) 51 | 52 | def tearDown(self): 53 | frappe.set_user("Administrator") 54 | 55 | records = { 56 | "Sales Invoice": [{"shopify_order_id": "2414345735"}], 57 | "Delivery Note": [{"shopify_order_id": "2414345735"}], 58 | "Sales Order": [{"shopify_order_id": "2414345735"}], 59 | "Item": [{"shopify_product_id" :"4059739520"},{"shopify_product_id": "13917612359"}, 60 | {"shopify_product_id": "13917612423"}, {"shopify_product_id":"13917612487"}], 61 | "Address": [{"shopify_address_id": "2476804295"}], 62 | "Customer": [{"shopify_customer_id": "2324518599"}] 63 | } 64 | 65 | for doctype in ["Sales Invoice", "Delivery Note", "Sales Order", "Item", "Address", "Customer"]: 66 | for filters in records[doctype]: 67 | for record in frappe.get_all(doctype, filters=filters): 68 | if doctype not in ["Customer", "Item", "Address"]: 69 | doc = frappe.get_doc(doctype, record.name) 70 | if doc.docstatus == 1: 71 | doc.cancel() 72 | frappe.delete_doc(doctype, record.name) 73 | 74 | def test_product(self): 75 | with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item: 76 | shopify_item = json.load(shopify_item) 77 | 78 | make_item("_Test Warehouse - _TC", shopify_item.get("product"), []) 79 | 80 | item = frappe.get_doc("Item", cstr(shopify_item.get("product").get("id"))) 81 | 82 | self.assertEqual(cstr(shopify_item.get("product").get("id")), item.shopify_product_id) 83 | self.assertEqual(item.sync_with_shopify, 1) 84 | 85 | #test variant price 86 | for variant in shopify_item.get("product").get("variants"): 87 | price = frappe.get_value("Item Price", 88 | {"price_list": "_Test Price List", "item_code": cstr(variant.get("id"))}, "price_list_rate") 89 | self.assertEqual(flt(variant.get("price")), flt(price)) 90 | 91 | def test_customer(self): 92 | with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer: 93 | shopify_customer = json.load(shopify_customer) 94 | 95 | create_customer(shopify_customer.get("customer"), []) 96 | 97 | customer = frappe.get_doc("Customer", {"shopify_customer_id": cstr(shopify_customer.get("customer").get("id"))}) 98 | 99 | self.assertEqual(customer.sync_with_shopify, 1) 100 | 101 | shopify_address = shopify_customer.get("customer").get("addresses")[0] 102 | address = frappe.get_doc("Address", {"customer": customer.name}) 103 | 104 | self.assertEqual(cstr(shopify_address.get("id")), address.shopify_address_id) 105 | 106 | def test_order(self): 107 | with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer: 108 | shopify_customer = json.load(shopify_customer) 109 | 110 | create_customer(shopify_customer.get("customer"), []) 111 | 112 | with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item: 113 | shopify_item = json.load(shopify_item) 114 | 115 | make_item("_Test Warehouse - _TC", shopify_item.get("product"), []) 116 | 117 | with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order: 118 | shopify_order = json.load(shopify_order) 119 | 120 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 121 | 122 | create_order(shopify_order.get("order"), shopify_settings, "_Test Company") 123 | 124 | sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))}) 125 | 126 | self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id) 127 | 128 | #check for customer 129 | shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id")) 130 | sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id") 131 | 132 | self.assertEqual(shopify_order_customer_id, sales_order_customer_id) 133 | 134 | #check sales invoice 135 | sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id}) 136 | self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total) 137 | 138 | #check delivery note 139 | delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note` 140 | where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0] 141 | 142 | self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments"))) -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_tax_account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/erpnext_shopify/doctype/shopify_tax_account/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_tax_account/shopify_tax_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_copy": 0, 3 | "allow_import": 0, 4 | "allow_rename": 0, 5 | "creation": "2015-10-05 16:55:20.455371", 6 | "custom": 0, 7 | "docstatus": 0, 8 | "doctype": "DocType", 9 | "document_type": "", 10 | "fields": [ 11 | { 12 | "allow_on_submit": 0, 13 | "bold": 0, 14 | "collapsible": 0, 15 | "fieldname": "shopify_tax", 16 | "fieldtype": "Data", 17 | "hidden": 0, 18 | "ignore_user_permissions": 0, 19 | "in_filter": 0, 20 | "in_list_view": 1, 21 | "label": "Shopify Tax/Shipping Title", 22 | "length": 0, 23 | "no_copy": 0, 24 | "permlevel": 0, 25 | "precision": "", 26 | "print_hide": 0, 27 | "read_only": 0, 28 | "report_hide": 0, 29 | "reqd": 1, 30 | "search_index": 0, 31 | "set_only_once": 0, 32 | "unique": 0 33 | }, 34 | { 35 | "allow_on_submit": 0, 36 | "bold": 0, 37 | "collapsible": 0, 38 | "fieldname": "column_break_2", 39 | "fieldtype": "Column Break", 40 | "hidden": 0, 41 | "ignore_user_permissions": 0, 42 | "in_filter": 0, 43 | "in_list_view": 0, 44 | "length": 0, 45 | "no_copy": 0, 46 | "permlevel": 0, 47 | "precision": "", 48 | "print_hide": 0, 49 | "read_only": 0, 50 | "report_hide": 0, 51 | "reqd": 0, 52 | "search_index": 0, 53 | "set_only_once": 0, 54 | "unique": 0 55 | }, 56 | { 57 | "allow_on_submit": 0, 58 | "bold": 0, 59 | "collapsible": 0, 60 | "fieldname": "tax_account", 61 | "fieldtype": "Link", 62 | "hidden": 0, 63 | "ignore_user_permissions": 0, 64 | "in_filter": 0, 65 | "in_list_view": 1, 66 | "label": "ERPNext Account", 67 | "length": 0, 68 | "no_copy": 0, 69 | "options": "Account", 70 | "permlevel": 0, 71 | "precision": "", 72 | "print_hide": 0, 73 | "read_only": 0, 74 | "report_hide": 0, 75 | "reqd": 1, 76 | "search_index": 0, 77 | "set_only_once": 0, 78 | "unique": 0 79 | } 80 | ], 81 | "hide_heading": 0, 82 | "hide_toolbar": 0, 83 | "in_create": 0, 84 | "in_dialog": 0, 85 | "is_submittable": 0, 86 | "issingle": 0, 87 | "istable": 1, 88 | "max_attachments": 0, 89 | "modified": "2015-11-16 06:29:57.757314", 90 | "modified_by": "Administrator", 91 | "module": "ERPNext Shopify", 92 | "name": "Shopify Tax Account", 93 | "name_case": "", 94 | "owner": "Administrator", 95 | "permissions": [], 96 | "read_only": 0, 97 | "read_only_onload": 0, 98 | "sort_field": "modified", 99 | "sort_order": "DESC" 100 | } -------------------------------------------------------------------------------- /erpnext_shopify/erpnext_shopify/doctype/shopify_tax_account/shopify_tax_account.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe.model.document import Document 8 | 9 | class ShopifyTaxAccount(Document): 10 | pass 11 | -------------------------------------------------------------------------------- /erpnext_shopify/exceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | 4 | class ShopifyError(frappe.ValidationError): pass 5 | class ShopifySetupError(frappe.ValidationError): pass -------------------------------------------------------------------------------- /erpnext_shopify/fixtures/custom_field.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allow_on_submit": 0, 4 | "collapsible": 0, 5 | "collapsible_depends_on": null, 6 | "default": null, 7 | "depends_on": null, 8 | "description": null, 9 | "docstatus": 0, 10 | "doctype": "Custom Field", 11 | "dt": "Print Settings", 12 | "fieldname": "compact_item_print", 13 | "fieldtype": "Check", 14 | "hidden": 0, 15 | "ignore_user_permissions": 0, 16 | "ignore_xss_filter": 0, 17 | "in_filter": 0, 18 | "in_list_view": 0, 19 | "insert_after": "with_letterhead", 20 | "label": "Compact Item Print", 21 | "modified": "2016-06-06 15:18:17.025602", 22 | "name": "Print Settings-compact_item_print", 23 | "no_copy": 0, 24 | "options": null, 25 | "permlevel": 0, 26 | "precision": "", 27 | "print_hide": 0, 28 | "print_hide_if_no_value": 0, 29 | "print_width": null, 30 | "read_only": 0, 31 | "report_hide": 0, 32 | "reqd": 0, 33 | "search_index": 0, 34 | "unique": 0, 35 | "width": null 36 | }, 37 | { 38 | "allow_on_submit": 0, 39 | "collapsible": 0, 40 | "collapsible_depends_on": null, 41 | "default": null, 42 | "depends_on": null, 43 | "description": null, 44 | "docstatus": 0, 45 | "doctype": "Custom Field", 46 | "dt": "Customer", 47 | "fieldname": "shopify_customer_id", 48 | "fieldtype": "Data", 49 | "hidden": 0, 50 | "ignore_user_permissions": 0, 51 | "ignore_xss_filter": 0, 52 | "in_filter": 0, 53 | "in_list_view": 0, 54 | "insert_after": "naming_series", 55 | "label": "Shopify Customer Id", 56 | "modified": "2016-01-15 17:25:28.991818", 57 | "name": "Customer-shopify_customer_id", 58 | "no_copy": 1, 59 | "options": null, 60 | "permlevel": 0, 61 | "precision": "", 62 | "print_hide": 1, 63 | "print_hide_if_no_value": 0, 64 | "print_width": null, 65 | "read_only": 1, 66 | "report_hide": 0, 67 | "reqd": 0, 68 | "search_index": 0, 69 | "unique": 0, 70 | "width": null 71 | }, 72 | { 73 | "allow_on_submit": 0, 74 | "collapsible": 0, 75 | "collapsible_depends_on": null, 76 | "default": null, 77 | "depends_on": null, 78 | "description": null, 79 | "docstatus": 0, 80 | "doctype": "Custom Field", 81 | "dt": "Address", 82 | "fieldname": "shopify_address_id", 83 | "fieldtype": "Data", 84 | "hidden": 0, 85 | "ignore_user_permissions": 0, 86 | "ignore_xss_filter": 0, 87 | "in_filter": 0, 88 | "in_list_view": 0, 89 | "insert_after": "fax", 90 | "label": "Shopify Address Id", 91 | "modified": "2016-01-15 17:50:52.213743", 92 | "name": "Address-shopify_address_id", 93 | "no_copy": 1, 94 | "options": null, 95 | "permlevel": 0, 96 | "precision": "", 97 | "print_hide": 1, 98 | "print_hide_if_no_value": 0, 99 | "print_width": null, 100 | "read_only": 1, 101 | "report_hide": 0, 102 | "reqd": 0, 103 | "search_index": 0, 104 | "unique": 0, 105 | "width": null 106 | }, 107 | { 108 | "allow_on_submit": 0, 109 | "collapsible": 0, 110 | "collapsible_depends_on": null, 111 | "default": null, 112 | "depends_on": null, 113 | "description": null, 114 | "docstatus": 0, 115 | "doctype": "Custom Field", 116 | "dt": "Sales Order", 117 | "fieldname": "shopify_order_id", 118 | "fieldtype": "Data", 119 | "hidden": 0, 120 | "ignore_user_permissions": 0, 121 | "ignore_xss_filter": 0, 122 | "in_filter": 0, 123 | "in_list_view": 0, 124 | "insert_after": "title", 125 | "label": "Shopify Order Id", 126 | "modified": "2016-01-18 09:55:50.764524", 127 | "name": "Sales Order-shopify_order_id", 128 | "no_copy": 1, 129 | "options": null, 130 | "permlevel": 0, 131 | "precision": "", 132 | "print_hide": 1, 133 | "print_hide_if_no_value": 0, 134 | "print_width": null, 135 | "read_only": 1, 136 | "report_hide": 0, 137 | "reqd": 0, 138 | "search_index": 0, 139 | "unique": 0, 140 | "width": null 141 | }, 142 | { 143 | "allow_on_submit": 0, 144 | "collapsible": 0, 145 | "collapsible_depends_on": null, 146 | "default": null, 147 | "depends_on": null, 148 | "description": null, 149 | "docstatus": 0, 150 | "doctype": "Custom Field", 151 | "dt": "Item", 152 | "fieldname": "shopify_product_id", 153 | "fieldtype": "Data", 154 | "hidden": 0, 155 | "ignore_user_permissions": 0, 156 | "ignore_xss_filter": 0, 157 | "in_filter": 0, 158 | "in_list_view": 0, 159 | "insert_after": "item_code", 160 | "label": "Shopify Product Id", 161 | "modified": "2016-01-19 15:44:16.132952", 162 | "name": "Item-shopify_product_id", 163 | "no_copy": 1, 164 | "options": null, 165 | "permlevel": 0, 166 | "precision": "", 167 | "print_hide": 1, 168 | "print_hide_if_no_value": 0, 169 | "print_width": null, 170 | "read_only": 1, 171 | "report_hide": 0, 172 | "reqd": 0, 173 | "search_index": 0, 174 | "unique": 0, 175 | "width": null 176 | }, 177 | { 178 | "allow_on_submit": 0, 179 | "collapsible": 0, 180 | "collapsible_depends_on": null, 181 | "default": null, 182 | "depends_on": null, 183 | "description": null, 184 | "docstatus": 0, 185 | "doctype": "Custom Field", 186 | "dt": "Sales Invoice", 187 | "fieldname": "shopify_order_id", 188 | "fieldtype": "Data", 189 | "hidden": 0, 190 | "ignore_user_permissions": 0, 191 | "ignore_xss_filter": 0, 192 | "in_filter": 0, 193 | "in_list_view": 0, 194 | "insert_after": "naming_series", 195 | "label": "Shopify Order Id", 196 | "modified": "2016-01-19 16:30:12.261797", 197 | "name": "Sales Invoice-shopify_order_id", 198 | "no_copy": 1, 199 | "options": null, 200 | "permlevel": 0, 201 | "precision": "", 202 | "print_hide": 1, 203 | "print_hide_if_no_value": 0, 204 | "print_width": null, 205 | "read_only": 1, 206 | "report_hide": 0, 207 | "reqd": 0, 208 | "search_index": 0, 209 | "unique": 0, 210 | "width": null 211 | }, 212 | { 213 | "allow_on_submit": 0, 214 | "collapsible": 0, 215 | "collapsible_depends_on": null, 216 | "default": null, 217 | "depends_on": null, 218 | "description": null, 219 | "docstatus": 0, 220 | "doctype": "Custom Field", 221 | "dt": "Delivery Note", 222 | "fieldname": "shopify_order_id", 223 | "fieldtype": "Data", 224 | "hidden": 0, 225 | "ignore_user_permissions": 0, 226 | "ignore_xss_filter": 0, 227 | "in_filter": 0, 228 | "in_list_view": 0, 229 | "insert_after": "title", 230 | "label": "Shopify Order Id", 231 | "modified": "2016-01-19 16:30:31.201198", 232 | "name": "Delivery Note-shopify_order_id", 233 | "no_copy": 1, 234 | "options": null, 235 | "permlevel": 0, 236 | "precision": "", 237 | "print_hide": 1, 238 | "print_hide_if_no_value": 0, 239 | "print_width": null, 240 | "read_only": 1, 241 | "report_hide": 0, 242 | "reqd": 0, 243 | "search_index": 0, 244 | "unique": 0, 245 | "width": null 246 | }, 247 | { 248 | "allow_on_submit": 0, 249 | "collapsible": 0, 250 | "collapsible_depends_on": null, 251 | "default": null, 252 | "depends_on": null, 253 | "description": null, 254 | "docstatus": 0, 255 | "doctype": "Custom Field", 256 | "dt": "Item", 257 | "fieldname": "stock_keeping_unit", 258 | "fieldtype": "Data", 259 | "hidden": 0, 260 | "ignore_user_permissions": 0, 261 | "ignore_xss_filter": 0, 262 | "in_filter": 0, 263 | "in_list_view": 0, 264 | "insert_after": "stock_uom", 265 | "label": "Stock Keeping Unit", 266 | "modified": "2015-11-10 09:29:10.854943", 267 | "name": "Item-stock_keeping_unit", 268 | "no_copy": 1, 269 | "options": null, 270 | "permlevel": 0, 271 | "precision": "", 272 | "print_hide": 0, 273 | "print_hide_if_no_value": 0, 274 | "print_width": null, 275 | "read_only": 1, 276 | "report_hide": 0, 277 | "reqd": 0, 278 | "search_index": 0, 279 | "unique": 0, 280 | "width": null 281 | }, 282 | { 283 | "allow_on_submit": 0, 284 | "collapsible": 0, 285 | "collapsible_depends_on": null, 286 | "default": "0", 287 | "depends_on": null, 288 | "description": null, 289 | "docstatus": 0, 290 | "doctype": "Custom Field", 291 | "dt": "Item", 292 | "fieldname": "sync_with_shopify", 293 | "fieldtype": "Check", 294 | "hidden": 0, 295 | "ignore_user_permissions": 0, 296 | "ignore_xss_filter": 0, 297 | "in_filter": 0, 298 | "in_list_view": 0, 299 | "insert_after": "is_stock_item", 300 | "label": "Sync With Shopify", 301 | "modified": "2015-10-12 15:54:31.997714", 302 | "name": "Item-sync_with_shopify", 303 | "no_copy": 0, 304 | "options": null, 305 | "permlevel": 0, 306 | "precision": "", 307 | "print_hide": 0, 308 | "print_hide_if_no_value": 0, 309 | "print_width": null, 310 | "read_only": 0, 311 | "report_hide": 0, 312 | "reqd": 0, 313 | "search_index": 0, 314 | "unique": 0, 315 | "width": null 316 | }, 317 | { 318 | "allow_on_submit": 0, 319 | "collapsible": 0, 320 | "collapsible_depends_on": null, 321 | "default": null, 322 | "depends_on": null, 323 | "description": null, 324 | "docstatus": 0, 325 | "doctype": "Custom Field", 326 | "dt": "Customer", 327 | "fieldname": "sync_with_shopify", 328 | "fieldtype": "Check", 329 | "hidden": 0, 330 | "ignore_user_permissions": 0, 331 | "ignore_xss_filter": 0, 332 | "in_filter": 0, 333 | "in_list_view": 0, 334 | "insert_after": "is_frozen", 335 | "label": "Sync With Shopify", 336 | "modified": "2015-10-01 17:31:55.758826", 337 | "name": "Customer-sync_with_shopify", 338 | "no_copy": 0, 339 | "options": null, 340 | "permlevel": 0, 341 | "precision": "", 342 | "print_hide": 0, 343 | "print_hide_if_no_value": 0, 344 | "print_width": null, 345 | "read_only": 0, 346 | "report_hide": 0, 347 | "reqd": 0, 348 | "search_index": 0, 349 | "unique": 0, 350 | "width": null 351 | }, 352 | { 353 | "allow_on_submit": 0, 354 | "collapsible": 0, 355 | "collapsible_depends_on": null, 356 | "default": null, 357 | "depends_on": null, 358 | "description": null, 359 | "docstatus": 0, 360 | "doctype": "Custom Field", 361 | "dt": "Item", 362 | "fieldname": "shopify_variant_id", 363 | "fieldtype": "Data", 364 | "hidden": 1, 365 | "ignore_user_permissions": 0, 366 | "ignore_xss_filter": 0, 367 | "in_filter": 0, 368 | "in_list_view": 0, 369 | "insert_after": "item_code", 370 | "label": "Variant Id", 371 | "modified": "2015-11-09 18:26:50.825858", 372 | "name": "Item-shopify_variant_id", 373 | "no_copy": 1, 374 | "options": null, 375 | "permlevel": 0, 376 | "precision": "", 377 | "print_hide": 1, 378 | "print_hide_if_no_value": 0, 379 | "print_width": null, 380 | "read_only": 1, 381 | "report_hide": 0, 382 | "reqd": 0, 383 | "search_index": 0, 384 | "unique": 0, 385 | "width": null 386 | }, 387 | { 388 | "allow_on_submit": 0, 389 | "collapsible": 0, 390 | "collapsible_depends_on": null, 391 | "default": null, 392 | "depends_on": null, 393 | "description": null, 394 | "docstatus": 0, 395 | "doctype": "Custom Field", 396 | "dt": "Item", 397 | "fieldname": "sync_qty_with_shopify", 398 | "fieldtype": "Check", 399 | "hidden": 0, 400 | "ignore_user_permissions": 0, 401 | "ignore_xss_filter": 0, 402 | "in_filter": 0, 403 | "in_list_view": 0, 404 | "insert_after": "item_code", 405 | "label": "Sync Quantity With Shopify", 406 | "modified": "2015-12-29 08:37:46.183295", 407 | "name": "Item-sync_qty_with_shopify", 408 | "no_copy": 0, 409 | "options": null, 410 | "permlevel": 0, 411 | "precision": "", 412 | "print_hide": 0, 413 | "print_hide_if_no_value": 0, 414 | "print_width": null, 415 | "read_only": 0, 416 | "report_hide": 0, 417 | "reqd": 0, 418 | "search_index": 0, 419 | "unique": 0, 420 | "width": null 421 | }, 422 | { 423 | "allow_on_submit": 0, 424 | "collapsible": 0, 425 | "collapsible_depends_on": null, 426 | "default": null, 427 | "depends_on": null, 428 | "description": null, 429 | "docstatus": 0, 430 | "doctype": "Custom Field", 431 | "dt": "Delivery Note", 432 | "fieldname": "shopify_fulfillment_id", 433 | "fieldtype": "Data", 434 | "hidden": 0, 435 | "ignore_user_permissions": 0, 436 | "ignore_xss_filter": 0, 437 | "in_filter": 0, 438 | "in_list_view": 0, 439 | "insert_after": "title", 440 | "label": "Shopify Fulfillment Id", 441 | "modified": "2016-01-20 23:50:35.609543", 442 | "name": "Delivery Note-shopify_fulfillment_id", 443 | "no_copy": 1, 444 | "options": null, 445 | "permlevel": 0, 446 | "precision": "", 447 | "print_hide": 1, 448 | "print_hide_if_no_value": 0, 449 | "print_width": null, 450 | "read_only": 1, 451 | "report_hide": 0, 452 | "reqd": 0, 453 | "search_index": 0, 454 | "unique": 0, 455 | "width": null 456 | }, 457 | { 458 | "allow_on_submit": 0, 459 | "collapsible": 0, 460 | "collapsible_depends_on": null, 461 | "default": null, 462 | "depends_on": null, 463 | "description": null, 464 | "docstatus": 0, 465 | "doctype": "Custom Field", 466 | "dt": "Supplier", 467 | "fieldname": "shopify_supplier_id", 468 | "fieldtype": "Data", 469 | "hidden": 1, 470 | "ignore_user_permissions": 0, 471 | "ignore_xss_filter": 0, 472 | "in_filter": 0, 473 | "in_list_view": 0, 474 | "insert_after": "supplier_name", 475 | "label": "Shopify Supplier Id", 476 | "modified": "2016-02-01 15:41:25.818306", 477 | "name": "Supplier-shopify_supplier_id", 478 | "no_copy": 1, 479 | "options": null, 480 | "permlevel": 0, 481 | "precision": "", 482 | "print_hide": 1, 483 | "print_hide_if_no_value": 0, 484 | "print_width": null, 485 | "read_only": 1, 486 | "report_hide": 0, 487 | "reqd": 0, 488 | "search_index": 0, 489 | "unique": 0, 490 | "width": null 491 | }, 492 | { 493 | "allow_on_submit": 0, 494 | "collapsible": 0, 495 | "collapsible_depends_on": null, 496 | "default": null, 497 | "depends_on": null, 498 | "description": null, 499 | "docstatus": 0, 500 | "doctype": "Custom Field", 501 | "dt": "Item", 502 | "fieldname": "shopify_description", 503 | "fieldtype": "Text Editor", 504 | "hidden": 0, 505 | "ignore_user_permissions": 0, 506 | "ignore_xss_filter": 0, 507 | "in_filter": 0, 508 | "in_list_view": 0, 509 | "insert_after": "section_break_11", 510 | "label": "shopify_description", 511 | "modified": "2016-06-15 12:15:36.325581", 512 | "name": "Item-shopify_description", 513 | "no_copy": 0, 514 | "options": null, 515 | "permlevel": 0, 516 | "precision": "", 517 | "print_hide": 1, 518 | "print_hide_if_no_value": 0, 519 | "print_width": null, 520 | "read_only": 0, 521 | "report_hide": 1, 522 | "reqd": 0, 523 | "search_index": 0, 524 | "unique": 0, 525 | "width": null 526 | } 527 | ] -------------------------------------------------------------------------------- /erpnext_shopify/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from . import __version__ as app_version 4 | 5 | app_name = "erpnext_shopify" 6 | app_title = "ERPNext Shopify" 7 | app_publisher = "Frappe Technologies Pvt. Ltd." 8 | app_description = "Shopify connector for ERPNext" 9 | app_icon = "octicon octicon-file-directory" 10 | app_color = "grey" 11 | app_email = "info@frappe.io" 12 | app_license = "MIT" 13 | 14 | fixtures = ["Custom Field"] 15 | # Includes in 16 | # ------------------ 17 | 18 | # include js, css files in header of desk.html 19 | # app_include_css = "/assets/erpnext_shopify/css/erpnext_shopify.css" 20 | # app_include_js = "/assets/erpnext_shopify/js/erpnext_shopify.js" 21 | 22 | # include js, css files in header of web template 23 | # web_include_css = "/assets/erpnext_shopify/css/erpnext_shopify.css" 24 | # web_include_js = "/assets/erpnext_shopify/js/erpnext_shopify.js" 25 | 26 | # Home Pages 27 | # ---------- 28 | 29 | # application home page (will override Website Settings) 30 | # home_page = "login" 31 | 32 | # website user home page (by Role) 33 | # role_home_page = { 34 | # "Role": "home_page" 35 | # } 36 | 37 | # Generators 38 | # ---------- 39 | 40 | # automatically create page for each record of this doctype 41 | # website_generators = ["Web Page"] 42 | 43 | # Installation 44 | # ------------ 45 | 46 | # before_install = "erpnext_shopify.install.before_install" 47 | after_install = "erpnext_shopify.after_install.create_weight_uom" 48 | 49 | # Desk Notifications 50 | # ------------------ 51 | # See frappe.core.notifications.get_notification_config 52 | 53 | # notification_config = "erpnext_shopify.notifications.get_notification_config" 54 | 55 | # Permissions 56 | # ----------- 57 | # Permissions evaluated in scripted ways 58 | 59 | # permission_query_conditions = { 60 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 61 | # } 62 | # 63 | # has_permission = { 64 | # "Event": "frappe.desk.doctype.event.event.has_permission", 65 | # } 66 | 67 | # Document Events 68 | # --------------- 69 | # Hook on document methods and events 70 | 71 | doc_events = { 72 | "Bin": { 73 | "on_update": "erpnext_shopify.sync_products.trigger_update_item_stock" 74 | } 75 | } 76 | 77 | # Scheduled Tasks 78 | # --------------- 79 | 80 | scheduler_events = { 81 | "hourly": [ 82 | "erpnext_shopify.api.sync_shopify" 83 | ], 84 | "daily": [ 85 | "erpnext_shopify.billing.send_payment_notification_to_user" 86 | ] 87 | } 88 | 89 | # Testing 90 | # ------- 91 | 92 | # before_tests = "erpnext_shopify.install.before_tests" 93 | 94 | # Overriding Whitelisted Methods 95 | # ------------------------------ 96 | # 97 | # override_whitelisted_methods = { 98 | # "frappe.desk.doctype.event.event.get_events": "erpnext_shopify.event.get_events" 99 | # } 100 | 101 | -------------------------------------------------------------------------------- /erpnext_shopify/modules.txt: -------------------------------------------------------------------------------- 1 | ERPNext Shopify -------------------------------------------------------------------------------- /erpnext_shopify/patches.txt: -------------------------------------------------------------------------------- 1 | erpnext_shopify.patches.V1_0.set_variant_id #2015-12-01 2 | erpnext_shopify.patches.V1_0.create_weight_uom 3 | erpnext_shopify.patches.V2_0.refactor_id 4 | erpnext_shopify.patches.V2_0.set_default_supplier 5 | erpnext_shopify.patches.V2_0.set_shopify_supplier_id 6 | execute:frappe.permissions.reset_perms("Shopify Log") 7 | erpnext_shopify.patches.V2_0.add_field_shopify_description 8 | erpnext_shopify.patches.V2_0.reset_inclusive_taxes_and_totals -------------------------------------------------------------------------------- /erpnext_shopify/patches/V1_0/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/patches/V1_0/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/patches/V1_0/create_weight_uom.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 2 | # License: GNU General Public License v3. See license.txt 3 | 4 | from __future__ import unicode_literals 5 | import frappe 6 | from erpnext_shopify.after_install import create_weight_uom 7 | 8 | def execute(): 9 | create_weight_uom() 10 | -------------------------------------------------------------------------------- /erpnext_shopify/patches/V1_0/set_variant_id.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 2 | # License: GNU General Public License v3. See license.txt 3 | 4 | from __future__ import unicode_literals 5 | import frappe 6 | from erpnext_shopify.shopify_requests import get_shopify_items 7 | from frappe.utils import cint 8 | from frappe import _ 9 | from erpnext_shopify.exceptions import ShopifyError 10 | import requests.exceptions 11 | from frappe.utils.fixtures import sync_fixtures 12 | 13 | def execute(): 14 | sync_fixtures("erpnext_shopify") 15 | frappe.reload_doctype("Item") 16 | 17 | shopify_settings = frappe.get_doc("Shopify Settings") 18 | if not shopify_settings.enable_shopify or not shopify_settings.password: 19 | return 20 | 21 | try: 22 | shopify_items = get_item_list() 23 | except ShopifyError: 24 | print("Could not run shopify patch 'set_variant_id' for site: {0}".format(frappe.local.site)) 25 | return 26 | 27 | if shopify_settings.shopify_url and shopify_items: 28 | for item in frappe.db.sql("""select name, item_code, shopify_id, has_variants, variant_of from tabItem 29 | where sync_with_shopify=1 and shopify_id is not null""", as_dict=1): 30 | 31 | if item.get("variant_of"): 32 | frappe.db.sql(""" update tabItem set shopify_variant_id=shopify_id 33 | where name = %s """, item.get("name")) 34 | 35 | elif not item.get("has_variants"): 36 | product = filter(lambda shopify_item: shopify_item['id'] == cint(item.get("shopify_id")), shopify_items) 37 | 38 | if product: 39 | frappe.db.sql(""" update tabItem set shopify_variant_id=%s 40 | where name = %s """, (product[0]["variants"][0]["id"], item.get("name"))) 41 | 42 | def get_item_list(): 43 | try: 44 | return get_shopify_items() 45 | except (requests.exceptions.HTTPError, ShopifyError) as e: 46 | frappe.throw(_("Something went wrong: {0}").format(frappe.get_traceback()), ShopifyError) 47 | 48 | -------------------------------------------------------------------------------- /erpnext_shopify/patches/V2_0/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/patches/V2_0/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/patches/V2_0/add_field_shopify_description.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from frappe.utils.fixtures import sync_fixtures 8 | 9 | def execute(): 10 | sync_fixtures("erpnext_shopify") -------------------------------------------------------------------------------- /erpnext_shopify/patches/V2_0/refactor_id.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 2 | # License: GNU General Public License v3. See license.txt 3 | import frappe 4 | from frappe.utils.fixtures import sync_fixtures 5 | from six import iteritems 6 | 7 | def execute(): 8 | sync_fixtures("erpnext_shopify") 9 | frappe.reload_doctype("Item") 10 | frappe.reload_doctype("Customer") 11 | frappe.reload_doctype("Sales Order") 12 | frappe.reload_doctype("Delivery Note") 13 | frappe.reload_doctype("Sales Invoice") 14 | 15 | for doctype, column in iteritems({"Customer": "shopify_customer_id", 16 | "Item": "shopify_product_id", 17 | "Sales Order": "shopify_order_id", 18 | "Delivery Note": "shopify_order_id", 19 | "Sales Invoice": "shopify_order_id"}): 20 | 21 | if "shopify_id" in frappe.db.get_table_columns(doctype): 22 | frappe.db.sql("update `tab%s` set %s=shopify_id" % (doctype, column)) -------------------------------------------------------------------------------- /erpnext_shopify/patches/V2_0/reset_inclusive_taxes_and_totals.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from erpnext_shopify.shopify_requests import get_shopify_orders, get_request 3 | from frappe.utils import cstr 4 | from frappe import _ 5 | 6 | def execute(): 7 | shopify_settings = frappe.db.get_value("Shopify Settings", None, 8 | ["enable_shopify", "shopify_url"], as_dict=1) 9 | 10 | if not (shopify_settings and shopify_settings.enable_shopify and shopify_settings.shopify_url): 11 | return 12 | 13 | try: 14 | shopify_orders = get_shopify_orders(ignore_filter_conditions=True) 15 | shopify_orders = build_shopify_order_dict(shopify_orders, key="id") 16 | except: 17 | return 18 | 19 | for so in frappe.db.sql("""select name, shopify_order_id, discount_amount from `tabSales Order` 20 | where shopify_order_id is not null and shopify_order_id != '' and 21 | docstatus=1 and discount_amount > 0""", as_dict=1): 22 | 23 | try: 24 | shopify_order = shopify_orders.get(so.shopify_order_id) or {} 25 | 26 | if so.shopify_order_id not in shopify_orders: 27 | shopify_order = get_request("/admin/orders/{0}.json".format(so.shopify_order_id))["order"] 28 | 29 | if shopify_order.get("taxes_included"): 30 | so = frappe.get_doc("Sales Order", so.name) 31 | 32 | setup_inclusive_taxes(so, shopify_order) 33 | so.calculate_taxes_and_totals() 34 | so.set_total_in_words() 35 | db_update(so) 36 | 37 | update_si_against_so(so, shopify_order) 38 | update_dn_against_so(so, shopify_order) 39 | 40 | frappe.db.commit() 41 | except Exception: 42 | pass 43 | 44 | def setup_inclusive_taxes(doc, shopify_order): 45 | doc.apply_discount_on = "Grand Total" 46 | shopify_taxes = get_shopify_tax_settigns(shopify_order) 47 | 48 | for tax in doc.taxes: 49 | if tax.account_head in shopify_taxes: 50 | tax.charge_type = _("On Net Total") 51 | tax.included_in_print_rate = 1 52 | # 53 | def update_si_against_so(so, shopify_order): 54 | si_name =frappe.db.sql_list("""select distinct t1.name 55 | from `tabSales Invoice` t1,`tabSales Invoice Item` t2 56 | where t1.name = t2.parent and t2.sales_order = %s and t1.docstatus = 1""", so.name) 57 | 58 | if si_name: 59 | si = frappe.get_doc("Sales Invoice", si_name[0]) 60 | 61 | si.docstatus = 2 62 | si.update_prevdoc_status() 63 | si.make_gl_entries_on_cancel() 64 | 65 | si.docstatus = 1 66 | setup_inclusive_taxes(si, shopify_order) 67 | si.calculate_taxes_and_totals() 68 | si.set_total_in_words() 69 | si.update_prevdoc_status() 70 | si.make_gl_entries() 71 | 72 | db_update(si) 73 | 74 | def update_dn_against_so(so, shopify_order): 75 | dn_name =frappe.db.sql_list("""select distinct t1.name 76 | from `tabDelivery Note` t1,`tabdelivery Note Item` t2 77 | where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 0""", so.name) 78 | 79 | if dn_name: 80 | dn = frappe.get_doc("Delivery Note", dn_name[0]) 81 | 82 | setup_inclusive_taxes(dn, shopify_order) 83 | dn.calculate_taxes_and_totals() 84 | dn.set_total_in_words() 85 | 86 | db_update(dn) 87 | 88 | def db_update(doc): 89 | doc.db_update() 90 | for df in doc.meta.get_table_fields(): 91 | for d in doc.get(df.fieldname): 92 | d.db_update() 93 | 94 | def build_shopify_order_dict(sequence, key): 95 | return dict((cstr(d[key]), dict(d, index=index)) for (index, d) in enumerate(sequence)) 96 | 97 | def get_shopify_tax_settigns(shopify_order): 98 | shopify_taxes = [] 99 | for tax in shopify_order.get("tax_lines"): 100 | shopify_taxes.extend(map(lambda d: d.tax_account if d.shopify_tax == tax["title"] else "", frappe.get_doc("Shopify Settings").taxes)) 101 | 102 | return set(shopify_taxes) -------------------------------------------------------------------------------- /erpnext_shopify/patches/V2_0/set_default_supplier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from erpnext_shopify.shopify_requests import get_shopify_items 8 | from erpnext_shopify.sync_products import get_supplier 9 | from erpnext_shopify.utils import is_shopify_enabled 10 | from frappe.utils.fixtures import sync_fixtures 11 | 12 | def execute(): 13 | if not is_shopify_enabled(): 14 | return 15 | 16 | sync_fixtures('erpnext_shopify') 17 | 18 | for index, shopify_item in enumerate(get_shopify_items(ignore_filter_conditions=True)): 19 | name = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")}, "name") 20 | supplier = get_supplier(shopify_item) 21 | 22 | if name and supplier: 23 | frappe.db.set_value("Item", name, "default_supplier", supplier, update_modified=False) 24 | 25 | if (index+1) % 100 == 0: 26 | frappe.db.commit() 27 | 28 | -------------------------------------------------------------------------------- /erpnext_shopify/patches/V2_0/set_shopify_supplier_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | from erpnext_shopify.utils import is_shopify_enabled 8 | from frappe.utils.fixtures import sync_fixtures 9 | 10 | def execute(): 11 | if not is_shopify_enabled(): 12 | return 13 | 14 | sync_fixtures('erpnext_shopify') 15 | 16 | fieldnames = frappe.db.sql("select fieldname from `tabCustom Field`", as_dict=1) 17 | 18 | if not any(field['fieldname'] == 'shopify_supplier_id' for field in fieldnames): 19 | return 20 | 21 | frappe.db.sql("""update tabSupplier set shopify_supplier_id=supplier_name """) 22 | frappe.db.commit() 23 | -------------------------------------------------------------------------------- /erpnext_shopify/patches/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/patches/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/shopify_requests.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | from frappe import _ 4 | import json, math, time, pytz 5 | from .exceptions import ShopifyError 6 | from frappe.utils import get_request_session, get_datetime, get_time_zone 7 | 8 | def check_api_call_limit(response): 9 | """ 10 | This article will show you how to tell your program to take small pauses 11 | to keep your app a few API calls shy of the API call limit and 12 | to guard you against a 429 - Too Many Requests error. 13 | 14 | ref : https://docs.shopify.com/api/introduction/api-call-limit 15 | """ 16 | if response.headers.get("HTTP_X_SHOPIFY_SHOP_API_CALL_LIMIT") == 39: 17 | time.sleep(10) # pause 10 seconds 18 | 19 | def get_shopify_settings(): 20 | d = frappe.get_doc("Shopify Settings") 21 | 22 | if d.shopify_url: 23 | if d.app_type == "Private" and d.password: 24 | d.password = d.get_password() 25 | return d.as_dict() 26 | else: 27 | frappe.throw(_("Shopify store URL is not configured on Shopify Settings"), ShopifyError) 28 | 29 | def get_request(path, settings=None): 30 | if not settings: 31 | settings = get_shopify_settings() 32 | 33 | s = get_request_session() 34 | url = get_shopify_url(path, settings) 35 | r = s.get(url, headers=get_header(settings)) 36 | check_api_call_limit(r) 37 | r.raise_for_status() 38 | return r.json() 39 | 40 | def post_request(path, data): 41 | settings = get_shopify_settings() 42 | s = get_request_session() 43 | url = get_shopify_url(path, settings) 44 | r = s.post(url, data=json.dumps(data), headers=get_header(settings)) 45 | check_api_call_limit(r) 46 | r.raise_for_status() 47 | return r.json() 48 | 49 | def put_request(path, data): 50 | settings = get_shopify_settings() 51 | s = get_request_session() 52 | url = get_shopify_url(path, settings) 53 | r = s.put(url, data=json.dumps(data), headers=get_header(settings)) 54 | check_api_call_limit(r) 55 | r.raise_for_status() 56 | return r.json() 57 | 58 | def delete_request(path): 59 | s = get_request_session() 60 | url = get_shopify_url(path) 61 | r = s.delete(url) 62 | r.raise_for_status() 63 | 64 | def get_shopify_url(path, settings): 65 | if settings['app_type'] == "Private": 66 | return 'https://{}:{}@{}/{}'.format(settings['api_key'], settings['password'], settings['shopify_url'], path) 67 | else: 68 | return 'https://{}/{}'.format(settings['shopify_url'], path) 69 | 70 | def get_header(settings): 71 | header = {'Content-Type': 'application/json'} 72 | 73 | if settings['app_type'] == "Private": 74 | return header 75 | else: 76 | header["X-Shopify-Access-Token"] = settings['access_token'] 77 | return header 78 | 79 | def get_filtering_condition(): 80 | shopify_settings = get_shopify_settings() 81 | if shopify_settings.last_sync_datetime: 82 | 83 | last_sync_datetime = get_datetime(shopify_settings.last_sync_datetime) 84 | timezone = pytz.timezone(get_time_zone()) 85 | timezone_abbr = timezone.localize(last_sync_datetime, is_dst=False) 86 | 87 | return 'updated_at_min="{0} {1}"'.format(last_sync_datetime.strftime("%Y-%m-%d %H:%M:%S"), timezone_abbr.tzname()) 88 | return '' 89 | 90 | def get_total_pages(resource, ignore_filter_conditions=False): 91 | filter_condition = "" 92 | 93 | if not ignore_filter_conditions: 94 | filter_condition = get_filtering_condition() 95 | 96 | count = get_request('/admin/{0}&{1}'.format(resource, filter_condition)) 97 | return int(math.ceil(count.get('count', 0) / 250)) 98 | 99 | def get_country(): 100 | return get_request('/admin/countries.json')['countries'] 101 | 102 | def get_shopify_items(ignore_filter_conditions=False): 103 | shopify_products = [] 104 | 105 | filter_condition = '' 106 | if not ignore_filter_conditions: 107 | filter_condition = get_filtering_condition() 108 | 109 | for page_idx in xrange(0, get_total_pages("products/count.json?", ignore_filter_conditions) or 1): 110 | shopify_products.extend(get_request('/admin/products.json?limit=250&page={0}&{1}'.format(page_idx+1, 111 | filter_condition))['products']) 112 | 113 | return shopify_products 114 | 115 | def get_shopify_item_image(shopify_product_id): 116 | return get_request("/admin/products/{0}/images.json".format(shopify_product_id))["images"] 117 | 118 | def get_shopify_orders(ignore_filter_conditions=False): 119 | shopify_orders = [] 120 | 121 | filter_condition = '' 122 | 123 | if not ignore_filter_conditions: 124 | filter_condition = get_filtering_condition() 125 | 126 | for page_idx in xrange(0, get_total_pages("orders/count.json?status=any", ignore_filter_conditions) or 1): 127 | shopify_orders.extend(get_request('/admin/orders.json?status=any&limit=250&page={0}&{1}'.format(page_idx+1, 128 | filter_condition))['orders']) 129 | return shopify_orders 130 | 131 | def get_shopify_customers(ignore_filter_conditions=False): 132 | shopify_customers = [] 133 | 134 | filter_condition = '' 135 | 136 | if not ignore_filter_conditions: 137 | filter_condition = get_filtering_condition() 138 | 139 | for page_idx in xrange(0, get_total_pages("customers/count.json?", ignore_filter_conditions) or 1): 140 | shopify_customers.extend(get_request('/admin/customers.json?limit=250&page={0}&{1}'.format(page_idx+1, 141 | filter_condition))['customers']) 142 | return shopify_customers 143 | -------------------------------------------------------------------------------- /erpnext_shopify/sync_customers.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | from frappe import _ 4 | import requests.exceptions 5 | from .shopify_requests import get_shopify_customers, post_request, put_request 6 | from .utils import make_shopify_log 7 | 8 | def sync_customers(): 9 | shopify_customer_list = [] 10 | sync_shopify_customers(shopify_customer_list) 11 | frappe.local.form_dict.count_dict["customers"] = len(shopify_customer_list) 12 | 13 | sync_erpnext_customers(shopify_customer_list) 14 | 15 | def sync_shopify_customers(shopify_customer_list): 16 | for shopify_customer in get_shopify_customers(): 17 | if not frappe.db.get_value("Customer", {"shopify_customer_id": shopify_customer.get('id')}, "name"): 18 | create_customer(shopify_customer, shopify_customer_list) 19 | 20 | def create_customer(shopify_customer, shopify_customer_list): 21 | import frappe.utils.nestedset 22 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 23 | 24 | cust_name = (shopify_customer.get("first_name") + " " + (shopify_customer.get("last_name") \ 25 | and shopify_customer.get("last_name") or "")) if shopify_customer.get("first_name")\ 26 | else shopify_customer.get("email") 27 | 28 | try: 29 | customer = frappe.get_doc({ 30 | "doctype": "Customer", 31 | "name": shopify_customer.get("id"), 32 | "customer_name" : cust_name, 33 | "shopify_customer_id": shopify_customer.get("id"), 34 | "sync_with_shopify": 1, 35 | "customer_group": shopify_settings.customer_group, 36 | "territory": frappe.utils.nestedset.get_root_of("Territory"), 37 | "customer_type": _("Individual") 38 | }) 39 | customer.flags.ignore_mandatory = True 40 | customer.insert() 41 | 42 | if customer: 43 | create_customer_address(customer, shopify_customer) 44 | 45 | shopify_customer_list.append(shopify_customer.get("id")) 46 | frappe.db.commit() 47 | 48 | except Exception as e: 49 | if e.args[0] and e.args[0].startswith("402"): 50 | raise e 51 | else: 52 | make_shopify_log(title=e.message, status="Error", method="create_customer", message=frappe.get_traceback(), 53 | request_data=shopify_customer, exception=True) 54 | 55 | def create_customer_address(customer, shopify_customer): 56 | if not shopify_customer.get("addresses"): 57 | return 58 | 59 | for i, address in enumerate(shopify_customer.get("addresses")): 60 | address_title, address_type = get_address_title_and_type(customer.customer_name, i) 61 | try : 62 | frappe.get_doc({ 63 | "doctype": "Address", 64 | "shopify_address_id": address.get("id"), 65 | "address_title": address_title, 66 | "address_type": address_type, 67 | "address_line1": address.get("address1") or "Address 1", 68 | "address_line2": address.get("address2"), 69 | "city": address.get("city") or "City", 70 | "state": address.get("province"), 71 | "pincode": address.get("zip"), 72 | "country": address.get("country"), 73 | "phone": address.get("phone"), 74 | "email_id": shopify_customer.get("email"), 75 | "links": [{ 76 | "link_doctype": "Customer", 77 | "link_name": customer.name 78 | }] 79 | }).insert(ignore_mandatory=True) 80 | 81 | except Exception as e: 82 | make_shopify_log(title=e.message, status="Error", method="create_customer_address", message=frappe.get_traceback(), 83 | request_data=shopify_customer, exception=True) 84 | 85 | def get_address_title_and_type(customer_name, index): 86 | address_type = _("Billing") 87 | address_title = customer_name 88 | if frappe.db.get_value("Address", "{0}-{1}".format(customer_name.strip(), address_type)): 89 | address_title = "{0}-{1}".format(customer_name.strip(), index) 90 | 91 | return address_title, address_type 92 | 93 | def sync_erpnext_customers(shopify_customer_list): 94 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 95 | 96 | condition = ["sync_with_shopify = 1"] 97 | 98 | last_sync_condition = "" 99 | if shopify_settings.last_sync_datetime: 100 | last_sync_condition = "modified >= '{0}' ".format(shopify_settings.last_sync_datetime) 101 | condition.append(last_sync_condition) 102 | 103 | customer_query = """select name, customer_name, shopify_customer_id from tabCustomer 104 | where {0}""".format(" and ".join(condition)) 105 | 106 | for customer in frappe.db.sql(customer_query, as_dict=1): 107 | try: 108 | if not customer.shopify_customer_id: 109 | create_customer_to_shopify(customer) 110 | 111 | else: 112 | if customer.shopify_customer_id not in shopify_customer_list: 113 | update_customer_to_shopify(customer, shopify_settings.last_sync_datetime) 114 | 115 | frappe.local.form_dict.count_dict["customers"] += 1 116 | frappe.db.commit() 117 | except Exception as e: 118 | make_shopify_log(title=e.message, status="Error", method="sync_erpnext_customers", message=frappe.get_traceback(), 119 | request_data=customer, exception=True) 120 | 121 | def create_customer_to_shopify(customer): 122 | shopify_customer = { 123 | "first_name": customer['customer_name'], 124 | } 125 | 126 | shopify_customer = post_request("/admin/customers.json", { "customer": shopify_customer}) 127 | 128 | customer = frappe.get_doc("Customer", customer['name']) 129 | customer.shopify_customer_id = shopify_customer['customer'].get("id") 130 | 131 | customer.flags.ignore_mandatory = True 132 | customer.save() 133 | 134 | addresses = get_customer_addresses(customer.as_dict()) 135 | for address in addresses: 136 | sync_customer_address(customer, address) 137 | 138 | def sync_customer_address(customer, address): 139 | address_name = address.pop("name") 140 | 141 | shopify_address = post_request("/admin/customers/{0}/addresses.json".format(customer.shopify_customer_id), 142 | {"address": address}) 143 | 144 | address = frappe.get_doc("Address", address_name) 145 | address.shopify_address_id = shopify_address['customer_address'].get("id") 146 | address.save() 147 | 148 | def update_customer_to_shopify(customer, last_sync_datetime): 149 | shopify_customer = { 150 | "first_name": customer['customer_name'], 151 | "last_name": "" 152 | } 153 | 154 | try: 155 | put_request("/admin/customers/{0}.json".format(customer.shopify_customer_id),\ 156 | { "customer": shopify_customer}) 157 | update_address_details(customer, last_sync_datetime) 158 | 159 | except requests.exceptions.HTTPError as e: 160 | if e.args[0] and e.args[0].startswith("404"): 161 | customer = frappe.get_doc("Customer", customer.name) 162 | customer.shopify_customer_id = "" 163 | customer.sync_with_shopify = 0 164 | customer.flags.ignore_mandatory = True 165 | customer.save() 166 | else: 167 | raise 168 | 169 | def update_address_details(customer, last_sync_datetime): 170 | customer_addresses = get_customer_addresses(customer, last_sync_datetime) 171 | for address in customer_addresses: 172 | if address.shopify_address_id: 173 | url = "/admin/customers/{0}/addresses/{1}.json".format(customer.shopify_customer_id,\ 174 | address.shopify_address_id) 175 | 176 | address["id"] = address["shopify_address_id"] 177 | 178 | del address["shopify_address_id"] 179 | 180 | put_request(url, { "address": address}) 181 | 182 | else: 183 | sync_customer_address(customer, address) 184 | 185 | def get_customer_addresses(customer, last_sync_datetime=None): 186 | conditions = ["dl.parent = addr.name", "dl.link_doctype = 'Customer'", 187 | "dl.link_name = '{0}'".format(frappe.db.escape(customer['name']))] 188 | 189 | if last_sync_datetime: 190 | last_sync_condition = "addr.modified >= '{0}'".format(last_sync_datetime) 191 | conditions.append(last_sync_condition) 192 | 193 | address_query = """select addr.name, addr.address_line1 as address1, addr.address_line2 as address2, 194 | addr.city as city, addr.state as province, addr.country as country, addr.pincode as zip, 195 | addr.shopify_address_id from tabAddress addr, `tabDynamic Link` dl 196 | where {0}""".format(' and '.join(conditions)) 197 | 198 | return frappe.db.sql(address_query, as_dict=1) -------------------------------------------------------------------------------- /erpnext_shopify/sync_orders.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | from frappe import _ 4 | from .exceptions import ShopifyError 5 | from .utils import make_shopify_log 6 | from .sync_products import make_item 7 | from .sync_customers import create_customer 8 | from frappe.utils import flt, nowdate, cint 9 | from .shopify_requests import get_request, get_shopify_orders 10 | from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice 11 | product_not_exists = [] 12 | 13 | def sync_orders(): 14 | sync_shopify_orders() 15 | 16 | def sync_shopify_orders(): 17 | frappe.local.form_dict.count_dict["orders"] = 0 18 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 19 | 20 | for shopify_order in get_shopify_orders(): 21 | if valid_customer_and_product(shopify_order): 22 | try: 23 | create_order(shopify_order, shopify_settings) 24 | frappe.local.form_dict.count_dict["orders"] += 1 25 | 26 | except ShopifyError as e: 27 | make_shopify_log(status="Error", method="sync_shopify_orders", message=frappe.get_traceback(), 28 | request_data=shopify_order, exception=True) 29 | except Exception as e: 30 | if e.args and e.args[0] and e.args[0].decode("utf-8").startswith("402"): 31 | raise e 32 | else: 33 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_orders", message=frappe.get_traceback(), 34 | request_data=shopify_order, exception=True) 35 | 36 | def valid_customer_and_product(shopify_order): 37 | customer_id = shopify_order.get("customer", {}).get("id") 38 | if customer_id: 39 | if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"): 40 | create_customer(shopify_order.get("customer"), shopify_customer_list=[]) 41 | 42 | warehouse = frappe.get_doc("Shopify Settings", "Shopify Settings").warehouse 43 | for item in shopify_order.get("line_items"): 44 | if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"): 45 | item = get_request("/admin/products/{}.json".format(item.get("product_id")))["product"] 46 | make_item(warehouse, item, shopify_item_list=[]) 47 | 48 | return True 49 | 50 | def create_order(shopify_order, shopify_settings, company=None): 51 | so = create_sales_order(shopify_order, shopify_settings, company) 52 | if so: 53 | if shopify_order.get("financial_status") == "paid" and cint(shopify_settings.sync_sales_invoice): 54 | create_sales_invoice(shopify_order, shopify_settings, so) 55 | 56 | if shopify_order.get("fulfillments") and cint(shopify_settings.sync_delivery_note): 57 | create_delivery_note(shopify_order, shopify_settings, so) 58 | 59 | def create_sales_order(shopify_order, shopify_settings, company=None): 60 | customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer").get("id")}, "name") 61 | so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name") 62 | 63 | if not so: 64 | items = get_order_items(shopify_order.get("line_items"), shopify_settings) 65 | 66 | if not items: 67 | title = 'Some items not found for Order {}'.format(shopify_order.get('id')) 68 | message = 'Following items are exists in order but relevant record not found in Product master' 69 | 70 | make_shopify_log(title=title, status="Error", method="sync_shopify_orders", 71 | message=message, request_data=product_not_exists, exception=True) 72 | 73 | return '' 74 | 75 | so = frappe.get_doc({ 76 | "doctype": "Sales Order", 77 | "naming_series": shopify_settings.sales_order_series or "SO-Shopify-", 78 | "shopify_order_id": shopify_order.get("id"), 79 | "customer": customer or shopify_settings.default_customer, 80 | "delivery_date": nowdate(), 81 | "company": shopify_settings.company, 82 | "selling_price_list": shopify_settings.price_list, 83 | "ignore_pricing_rule": 1, 84 | "items": items, 85 | "taxes": get_order_taxes(shopify_order, shopify_settings), 86 | "apply_discount_on": "Grand Total", 87 | "discount_amount": get_discounted_amount(shopify_order), 88 | }) 89 | 90 | if company: 91 | so.update({ 92 | "company": company, 93 | "status": "Draft" 94 | }) 95 | so.flags.ignore_mandatory = True 96 | so.save(ignore_permissions=True) 97 | so.submit() 98 | 99 | else: 100 | so = frappe.get_doc("Sales Order", so) 101 | 102 | frappe.db.commit() 103 | return so 104 | 105 | def create_sales_invoice(shopify_order, shopify_settings, so): 106 | if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\ 107 | and so.docstatus==1 and not so.per_billed: 108 | si = make_sales_invoice(so.name) 109 | si.shopify_order_id = shopify_order.get("id") 110 | si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-" 111 | si.flags.ignore_mandatory = True 112 | set_cost_center(si.items, shopify_settings.cost_center) 113 | si.submit() 114 | make_payament_entry_against_sales_invoice(si, shopify_settings) 115 | frappe.db.commit() 116 | 117 | def set_cost_center(items, cost_center): 118 | for item in items: 119 | item.cost_center = cost_center 120 | 121 | def make_payament_entry_against_sales_invoice(doc, shopify_settings): 122 | from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry 123 | payemnt_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account) 124 | payemnt_entry.flags.ignore_mandatory = True 125 | payemnt_entry.reference_no = doc.name 126 | payemnt_entry.reference_date = nowdate() 127 | payemnt_entry.submit() 128 | 129 | def create_delivery_note(shopify_order, shopify_settings, so): 130 | for fulfillment in shopify_order.get("fulfillments"): 131 | if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\ 132 | and so.docstatus==1: 133 | dn = make_delivery_note(so.name) 134 | dn.shopify_order_id = fulfillment.get("order_id") 135 | dn.shopify_fulfillment_id = fulfillment.get("id") 136 | dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-" 137 | dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings) 138 | dn.flags.ignore_mandatory = True 139 | dn.save() 140 | frappe.db.commit() 141 | 142 | def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings): 143 | return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\ 144 | if get_item_code(item) == dn_item.item_code] 145 | 146 | def get_discounted_amount(order): 147 | discounted_amount = 0.0 148 | for discount in order.get("discount_codes"): 149 | discounted_amount += flt(discount.get("amount")) 150 | return discounted_amount 151 | 152 | def get_order_items(order_items, shopify_settings): 153 | items = [] 154 | all_product_exists = True 155 | product_not_exists = [] 156 | 157 | for shopify_item in order_items: 158 | if not shopify_item.get('product_exists'): 159 | all_product_exists = False 160 | product_not_exists.append({'title':shopify_item.get('title'), 161 | 'shopify_order_id': shopify_item.get('id')}) 162 | continue 163 | 164 | if all_product_exists: 165 | item_code = get_item_code(shopify_item) 166 | items.append({ 167 | "item_code": item_code, 168 | "item_name": shopify_item.get("name"), 169 | "rate": shopify_item.get("price"), 170 | "delivery_date": nowdate(), 171 | "qty": shopify_item.get("quantity"), 172 | "stock_uom": shopify_item.get("sku"), 173 | "warehouse": shopify_settings.warehouse 174 | }) 175 | else: 176 | items = [] 177 | 178 | return items 179 | 180 | def get_item_code(shopify_item): 181 | item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code") 182 | if not item_code: 183 | item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code") 184 | if not item_code: 185 | item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code") 186 | 187 | return item_code 188 | 189 | def get_order_taxes(shopify_order, shopify_settings): 190 | taxes = [] 191 | for tax in shopify_order.get("tax_lines"): 192 | taxes.append({ 193 | "charge_type": _("On Net Total"), 194 | "account_head": get_tax_account_head(tax), 195 | "description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0), 196 | "rate": tax.get("rate") * 100.00, 197 | "included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0, 198 | "cost_center": shopify_settings.cost_center 199 | }) 200 | 201 | taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings) 202 | 203 | return taxes 204 | 205 | def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings): 206 | for shipping_charge in shipping_lines: 207 | taxes.append({ 208 | "charge_type": _("Actual"), 209 | "account_head": get_tax_account_head(shipping_charge), 210 | "description": shipping_charge["title"], 211 | "tax_amount": shipping_charge["price"], 212 | "cost_center": shopify_settings.cost_center 213 | }) 214 | 215 | return taxes 216 | 217 | def get_tax_account_head(tax): 218 | tax_title = tax.get("title").encode("utf-8") 219 | 220 | tax_account = frappe.db.get_value("Shopify Tax Account", \ 221 | {"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account") 222 | 223 | if not tax_account: 224 | frappe.throw("Tax Account not specified for Shopify Tax {0}".format(tax.get("title"))) 225 | 226 | return tax_account 227 | -------------------------------------------------------------------------------- /erpnext_shopify/sync_products.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | from frappe import _ 4 | import requests.exceptions 5 | from .exceptions import ShopifyError 6 | from .utils import make_shopify_log, disable_shopify_sync_for_item 7 | from erpnext.stock.utils import get_bin 8 | from frappe.utils import cstr, flt, cint, get_files_path 9 | from .shopify_requests import post_request, get_shopify_items, put_request, get_shopify_item_image 10 | import base64, requests, datetime, os 11 | 12 | shopify_variants_attr_list = ["option1", "option2", "option3"] 13 | 14 | def sync_products(price_list, warehouse): 15 | shopify_item_list = [] 16 | sync_shopify_items(warehouse, shopify_item_list) 17 | frappe.local.form_dict.count_dict["products"] = len(shopify_item_list) 18 | sync_erpnext_items(price_list, warehouse, shopify_item_list) 19 | 20 | def sync_shopify_items(warehouse, shopify_item_list): 21 | for shopify_item in get_shopify_items(): 22 | try: 23 | make_item(warehouse, shopify_item, shopify_item_list) 24 | 25 | except ShopifyError as e: 26 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 27 | request_data=shopify_item, exception=True) 28 | 29 | except Exception as e: 30 | if e.args[0] and e.args[0].startswith("402"): 31 | raise e 32 | else: 33 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 34 | request_data=shopify_item, exception=True) 35 | 36 | def make_item(warehouse, shopify_item, shopify_item_list): 37 | add_item_weight(shopify_item) 38 | if has_variants(shopify_item): 39 | attributes = create_attribute(shopify_item) 40 | create_item(shopify_item, warehouse, 1, attributes, shopify_item_list=shopify_item_list) 41 | create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list, shopify_item_list=shopify_item_list) 42 | 43 | else: 44 | shopify_item["variant_id"] = shopify_item['variants'][0]["id"] 45 | create_item(shopify_item, warehouse, shopify_item_list=shopify_item_list) 46 | 47 | def add_item_weight(shopify_item): 48 | shopify_item["weight"] = shopify_item['variants'][0]["weight"] 49 | shopify_item["weight_unit"] = shopify_item['variants'][0]["weight_unit"] 50 | 51 | def has_variants(shopify_item): 52 | if len(shopify_item.get("options")) >= 1 and "Default Title" not in shopify_item.get("options")[0]["values"]: 53 | return True 54 | return False 55 | 56 | def create_attribute(shopify_item): 57 | attribute = [] 58 | # shopify item dict 59 | for attr in shopify_item.get('options'): 60 | if not frappe.db.get_value("Item Attribute", attr.get("name"), "name"): 61 | frappe.get_doc({ 62 | "doctype": "Item Attribute", 63 | "attribute_name": attr.get("name"), 64 | "item_attribute_values": [ 65 | { 66 | "attribute_value": attr_value, 67 | "abbr":attr_value 68 | } 69 | for attr_value in attr.get("values") 70 | ] 71 | }).insert() 72 | attribute.append({"attribute": attr.get("name")}) 73 | 74 | else: 75 | # check for attribute values 76 | item_attr = frappe.get_doc("Item Attribute", attr.get("name")) 77 | if not item_attr.numeric_values: 78 | set_new_attribute_values(item_attr, attr.get("values")) 79 | item_attr.save() 80 | attribute.append({"attribute": attr.get("name")}) 81 | 82 | else: 83 | attribute.append({ 84 | "attribute": attr.get("name"), 85 | "from_range": item_attr.get("from_range"), 86 | "to_range": item_attr.get("to_range"), 87 | "increment": item_attr.get("increment"), 88 | "numeric_values": item_attr.get("numeric_values") 89 | }) 90 | 91 | return attribute 92 | 93 | def set_new_attribute_values(item_attr, values): 94 | for attr_value in values: 95 | if not any((d.abbr.lower() == attr_value.lower() or d.attribute_value.lower() == attr_value.lower())\ 96 | for d in item_attr.item_attribute_values): 97 | item_attr.append("item_attribute_values", { 98 | "attribute_value": attr_value, 99 | "abbr": attr_value 100 | }) 101 | 102 | def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_of=None, shopify_item_list=[]): 103 | item_dict = { 104 | "doctype": "Item", 105 | "shopify_product_id": shopify_item.get("id"), 106 | "shopify_variant_id": shopify_item.get("variant_id"), 107 | "variant_of": variant_of, 108 | "sync_with_shopify": 1, 109 | "is_stock_item": 1, 110 | "item_code": cstr(shopify_item.get("item_code")) or cstr(shopify_item.get("id")), 111 | "item_name": shopify_item.get("title", '').strip(), 112 | "description": shopify_item.get("body_html") or shopify_item.get("title"), 113 | "shopify_description": shopify_item.get("body_html") or shopify_item.get("title"), 114 | "item_group": get_item_group(shopify_item.get("product_type")), 115 | "has_variants": has_variant, 116 | "attributes":attributes or [], 117 | "stock_uom": shopify_item.get("uom") or _("Nos"), 118 | "stock_keeping_unit": shopify_item.get("sku") or get_sku(shopify_item), 119 | "default_warehouse": warehouse, 120 | "image": get_item_image(shopify_item), 121 | "weight_uom": shopify_item.get("weight_unit"), 122 | "weight_per_unit": shopify_item.get("weight"), 123 | "default_supplier": get_supplier(shopify_item) 124 | } 125 | 126 | if not is_item_exists(item_dict, attributes, variant_of=variant_of, shopify_item_list=shopify_item_list): 127 | item_details = get_item_details(shopify_item) 128 | 129 | if not item_details: 130 | new_item = frappe.get_doc(item_dict) 131 | new_item.insert() 132 | name = new_item.name 133 | 134 | else: 135 | update_item(item_details, item_dict) 136 | name = item_details.name 137 | 138 | if not has_variant: 139 | add_to_price_list(shopify_item, name) 140 | 141 | frappe.db.commit() 142 | 143 | def create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list, shopify_item_list): 144 | template_item = frappe.db.get_value("Item", filters={"shopify_product_id": shopify_item.get("id")}, 145 | fieldname=["name", "stock_uom"], as_dict=True) 146 | 147 | if template_item: 148 | for variant in shopify_item.get("variants"): 149 | shopify_item_variant = { 150 | "id" : variant.get("id"), 151 | "item_code": variant.get("id"), 152 | "title": variant.get("title"), 153 | "product_type": shopify_item.get("product_type"), 154 | "sku": variant.get("sku"), 155 | "uom": template_item.stock_uom or _("Nos"), 156 | "item_price": variant.get("price"), 157 | "variant_id": variant.get("id"), 158 | "weight_unit": variant.get("weight_unit"), 159 | "weight": variant.get("weight") 160 | } 161 | 162 | for i, variant_attr in enumerate(shopify_variants_attr_list): 163 | if variant.get(variant_attr): 164 | attributes[i].update({"attribute_value": get_attribute_value(variant.get(variant_attr), attributes[i])}) 165 | create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name, shopify_item_list=shopify_item_list) 166 | 167 | def get_attribute_value(variant_attr_val, attribute): 168 | attribute_value = frappe.db.sql("""select attribute_value from `tabItem Attribute Value` 169 | where parent = %s and (abbr = %s or attribute_value = %s)""", (attribute["attribute"], variant_attr_val, 170 | variant_attr_val), as_list=1) 171 | return attribute_value[0][0] if len(attribute_value)>0 else cint(variant_attr_val) 172 | 173 | def get_item_group(product_type=None): 174 | import frappe.utils.nestedset 175 | parent_item_group = frappe.utils.nestedset.get_root_of("Item Group") 176 | 177 | if product_type: 178 | if not frappe.db.get_value("Item Group", product_type, "name"): 179 | item_group = frappe.get_doc({ 180 | "doctype": "Item Group", 181 | "item_group_name": product_type, 182 | "parent_item_group": parent_item_group, 183 | "is_group": "No" 184 | }).insert() 185 | return item_group.name 186 | else: 187 | return product_type 188 | else: 189 | return parent_item_group 190 | 191 | 192 | def get_sku(item): 193 | if item.get("variants"): 194 | return item.get("variants")[0].get("sku") 195 | return "" 196 | 197 | def add_to_price_list(item, name): 198 | shopify_settings = frappe.db.get_value("Shopify Settings", None, ["price_list", "push_prices_to_shopify"], as_dict=1) 199 | if shopify_settings.push_prices_to_shopify: 200 | return 201 | 202 | item_price_name = frappe.db.get_value("Item Price", 203 | {"item_code": name, "price_list": shopify_settings.price_list}, "name") 204 | 205 | if not item_price_name: 206 | frappe.get_doc({ 207 | "doctype": "Item Price", 208 | "price_list": shopify_settings.price_list, 209 | "item_code": name, 210 | "price_list_rate": item.get("item_price") or item.get("variants")[0].get("price") 211 | }).insert() 212 | else: 213 | item_rate = frappe.get_doc("Item Price", item_price_name) 214 | item_rate.price_list_rate = item.get("item_price") or item.get("variants")[0].get("price") 215 | item_rate.save() 216 | 217 | def get_item_image(shopify_item): 218 | if shopify_item.get("image"): 219 | return shopify_item.get("image").get("src") 220 | return None 221 | 222 | def get_supplier(shopify_item): 223 | if shopify_item.get("vendor"): 224 | supplier = frappe.db.sql("""select name from tabSupplier 225 | where name = %s or shopify_supplier_id = %s """, (shopify_item.get("vendor"), 226 | shopify_item.get("vendor").lower()), as_list=1) 227 | 228 | if not supplier: 229 | supplier = frappe.get_doc({ 230 | "doctype": "Supplier", 231 | "supplier_name": shopify_item.get("vendor"), 232 | "shopify_supplier_id": shopify_item.get("vendor").lower(), 233 | "supplier_type": get_supplier_type() 234 | }).insert() 235 | return supplier.name 236 | else: 237 | return shopify_item.get("vendor") 238 | else: 239 | return "" 240 | 241 | def get_supplier_type(): 242 | supplier_type = frappe.db.get_value("Supplier Type", _("Shopify Supplier")) 243 | if not supplier_type: 244 | supplier_type = frappe.get_doc({ 245 | "doctype": "Supplier Type", 246 | "supplier_type": _("Shopify Supplier") 247 | }).insert() 248 | return supplier_type.name 249 | return supplier_type 250 | 251 | def get_item_details(shopify_item): 252 | item_details = {} 253 | 254 | item_details = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")}, 255 | ["name", "stock_uom", "item_name"], as_dict=1) 256 | 257 | if item_details: 258 | return item_details 259 | 260 | else: 261 | item_details = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("id")}, 262 | ["name", "stock_uom", "item_name"], as_dict=1) 263 | return item_details 264 | 265 | def is_item_exists(shopify_item, attributes=None, variant_of=None, shopify_item_list=[]): 266 | if variant_of: 267 | name = variant_of 268 | else: 269 | name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")}) 270 | 271 | shopify_item_list.append(cstr(shopify_item.get("shopify_product_id"))) 272 | 273 | if name: 274 | item = frappe.get_doc("Item", name) 275 | item.flags.ignore_mandatory=True 276 | 277 | if not variant_of and not item.shopify_product_id: 278 | item.shopify_product_id = shopify_item.get("shopify_product_id") 279 | item.shopify_variant_id = shopify_item.get("shopify_variant_id") 280 | item.save() 281 | return True 282 | 283 | if item.shopify_product_id and attributes and attributes[0].get("attribute_value"): 284 | if not variant_of: 285 | variant_of = frappe.db.get_value("Item", 286 | {"shopify_product_id": item.shopify_product_id}, "variant_of") 287 | 288 | # create conditions for all item attributes, 289 | # as we are putting condition basis on OR it will fetch all items matching either of conditions 290 | # thus comparing matching conditions with len(attributes) 291 | # which will give exact matching variant item. 292 | 293 | conditions = ["(iv.attribute='{0}' and iv.attribute_value = '{1}')"\ 294 | .format(attr.get("attribute"), attr.get("attribute_value")) for attr in attributes] 295 | 296 | conditions = "( {0} ) and iv.parent = it.name ) = {1}".format(" or ".join(conditions), len(attributes)) 297 | 298 | parent = frappe.db.sql(""" select * from tabItem it where 299 | ( select count(*) from `tabItem Variant Attribute` iv 300 | where {conditions} and it.variant_of = %s """.format(conditions=conditions) , 301 | variant_of, as_list=1) 302 | 303 | if parent: 304 | variant = frappe.get_doc("Item", parent[0][0]) 305 | variant.flags.ignore_mandatory = True 306 | 307 | variant.shopify_product_id = shopify_item.get("shopify_product_id") 308 | variant.shopify_variant_id = shopify_item.get("shopify_variant_id") 309 | variant.save() 310 | return False 311 | 312 | if item.shopify_product_id and item.shopify_product_id != shopify_item.get("shopify_product_id"): 313 | return False 314 | 315 | return True 316 | 317 | else: 318 | return False 319 | 320 | def update_item(item_details, item_dict): 321 | item = frappe.get_doc("Item", item_details.name) 322 | item_dict["stock_uom"] = item_details.stock_uom 323 | 324 | if item_dict.get("default_warehouse"): 325 | del item_dict["default_warehouse"] 326 | 327 | del item_dict["description"] 328 | del item_dict["item_code"] 329 | del item_dict["variant_of"] 330 | del item_dict["item_name"] 331 | del item_dict["image"] 332 | 333 | item.update(item_dict) 334 | item.flags.ignore_mandatory = True 335 | item.save() 336 | 337 | def sync_erpnext_items(price_list, warehouse, shopify_item_list): 338 | for item in get_erpnext_items(price_list): 339 | if item.shopify_product_id not in shopify_item_list: 340 | try: 341 | sync_item_with_shopify(item, price_list, warehouse) 342 | frappe.local.form_dict.count_dict["products"] += 1 343 | 344 | except ShopifyError as e: 345 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 346 | request_data=item, exception=True) 347 | except Exception as e: 348 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 349 | request_data=item, exception=True) 350 | 351 | def get_erpnext_items(price_list): 352 | erpnext_items = [] 353 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 354 | 355 | last_sync_condition, item_price_condition = "", "" 356 | if shopify_settings.last_sync_datetime: 357 | last_sync_condition = "and modified >= '{0}' ".format(shopify_settings.last_sync_datetime) 358 | item_price_condition = "and ip.modified >= '{0}' ".format(shopify_settings.last_sync_datetime) 359 | 360 | item_from_master = """select name, item_code, item_name, item_group, 361 | description, shopify_description, has_variants, variant_of, stock_uom, image, shopify_product_id, 362 | shopify_variant_id, sync_qty_with_shopify, weight_per_unit, weight_uom, default_supplier from tabItem 363 | where sync_with_shopify=1 and (variant_of is null or variant_of = '') 364 | and (disabled is null or disabled = 0) %s """ % last_sync_condition 365 | 366 | erpnext_items.extend(frappe.db.sql(item_from_master, as_dict=1)) 367 | 368 | template_items = [item.name for item in erpnext_items if item.has_variants] 369 | 370 | if len(template_items) > 0: 371 | item_price_condition += ' and i.variant_of not in (%s)'%(' ,'.join(["'%s'"]*len(template_items)))%tuple(template_items) 372 | 373 | item_from_item_price = """select i.name, i.item_code, i.item_name, i.item_group, i.description, 374 | i.shopify_description, i.has_variants, i.variant_of, i.stock_uom, i.image, i.shopify_product_id, 375 | i.shopify_variant_id, i.sync_qty_with_shopify, i.weight_per_unit, i.weight_uom, 376 | i.default_supplier from `tabItem` i, `tabItem Price` ip 377 | where price_list = '%s' and i.name = ip.item_code 378 | and sync_with_shopify=1 and (disabled is null or disabled = 0) %s""" %(price_list, item_price_condition) 379 | 380 | updated_price_item_list = frappe.db.sql(item_from_item_price, as_dict=1) 381 | 382 | # to avoid item duplication 383 | return [frappe._dict(tupleized) for tupleized in set(tuple(item.items()) 384 | for item in erpnext_items + updated_price_item_list)] 385 | 386 | def sync_item_with_shopify(item, price_list, warehouse): 387 | variant_item_name_list = [] 388 | 389 | item_data = { "product": 390 | { 391 | "title": item.get("item_name"), 392 | "body_html": item.get("shopify_description") or item.get("web_long_description") or item.get("description"), 393 | "product_type": item.get("item_group"), 394 | "vendor": item.get("default_supplier"), 395 | "published_scope": "global", 396 | "published_status": "published", 397 | "published_at": datetime.datetime.now().isoformat() 398 | } 399 | } 400 | 401 | if item.get("has_variants") or item.get("variant_of"): 402 | 403 | if item.get("variant_of"): 404 | item = frappe.get_doc("Item", item.get("variant_of")) 405 | 406 | variant_list, options, variant_item_name = get_variant_attributes(item, price_list, warehouse) 407 | 408 | item_data["product"]["title"] = item.get("item_name") 409 | item_data["product"]["body_html"] = item.get("shopify_description") or item.get("web_long_description") or item.get("description") 410 | item_data["product"]["variants"] = variant_list 411 | item_data["product"]["options"] = options 412 | 413 | variant_item_name_list.extend(variant_item_name) 414 | 415 | else: 416 | item_data["product"]["variants"] = [get_price_and_stock_details(item, warehouse, price_list)] 417 | 418 | erp_item = frappe.get_doc("Item", item.get("name")) 419 | erp_item.flags.ignore_mandatory = True 420 | 421 | if not item.get("shopify_product_id"): 422 | create_new_item_to_shopify(item, item_data, erp_item, variant_item_name_list) 423 | sync_item_image(erp_item) 424 | 425 | else: 426 | item_data["product"]["id"] = item.get("shopify_product_id") 427 | try: 428 | put_request("/admin/products/{}.json".format(item.get("shopify_product_id")), item_data) 429 | 430 | except requests.exceptions.HTTPError as e: 431 | if e.args[0] and e.args[0].startswith("404"): 432 | if frappe.db.get_value("Shopify Settings", "Shopify Settings", "if_not_exists_create_item_to_shopify"): 433 | item_data["product"]["id"] = '' 434 | create_new_item_to_shopify(item, item_data, erp_item, variant_item_name_list) 435 | else: 436 | disable_shopify_sync_for_item(erp_item) 437 | else: 438 | raise e 439 | 440 | frappe.db.commit() 441 | 442 | def create_new_item_to_shopify(item, item_data, erp_item, variant_item_name_list): 443 | new_item = post_request("/admin/products.json", item_data) 444 | erp_item.shopify_product_id = new_item['product'].get("id") 445 | 446 | if not item.get("has_variants"): 447 | erp_item.shopify_variant_id = new_item['product']["variants"][0].get("id") 448 | 449 | erp_item.save() 450 | update_variant_item(new_item, variant_item_name_list) 451 | 452 | def sync_item_image(item): 453 | image_info = { 454 | "image": {} 455 | } 456 | 457 | if item.image: 458 | img_details = frappe.db.get_value("File", {"file_url": item.image}, ["file_name", "content_hash"]) 459 | 460 | if img_details and img_details[0] and img_details[1]: 461 | is_private = item.image.startswith("/private/files/") 462 | 463 | with open(get_files_path(img_details[0].strip("/"), is_private=is_private), "rb") as image_file: 464 | image_info["image"]["attachment"] = base64.b64encode(image_file.read()) 465 | image_info["image"]["filename"] = img_details[0] 466 | 467 | #to avoid 422 : Unprocessable Entity 468 | if not image_info["image"]["attachment"] or not image_info["image"]["filename"]: 469 | return False 470 | 471 | elif item.image.startswith("http") or item.image.startswith("ftp"): 472 | if validate_image_url(item.image): 473 | #to avoid 422 : Unprocessable Entity 474 | image_info["image"]["src"] = item.image 475 | 476 | if image_info["image"]: 477 | if not item_image_exists(item.shopify_product_id, image_info): 478 | # to avoid image duplication 479 | post_request("/admin/products/{0}/images.json".format(item.shopify_product_id), image_info) 480 | 481 | 482 | def validate_image_url(url): 483 | """ check on given url image exists or not""" 484 | res = requests.get(url) 485 | if res.headers.get("content-type") in ('image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/tiff'): 486 | return True 487 | return False 488 | 489 | def item_image_exists(shopify_product_id, image_info): 490 | """check same image exist or not""" 491 | for image in get_shopify_item_image(shopify_product_id): 492 | if image_info.get("image").get("filename"): 493 | if os.path.splitext(image.get("src"))[0].split("/")[-1] == os.path.splitext(image_info.get("image").get("filename"))[0]: 494 | return True 495 | elif image_info.get("image").get("src"): 496 | if os.path.splitext(image.get("src"))[0].split("/")[-1] == os.path.splitext(image_info.get("image").get("src"))[0].split("/")[-1]: 497 | return True 498 | else: 499 | return False 500 | 501 | def update_variant_item(new_item, item_code_list): 502 | for i, name in enumerate(item_code_list): 503 | erp_item = frappe.get_doc("Item", name) 504 | erp_item.flags.ignore_mandatory = True 505 | erp_item.shopify_product_id = new_item['product']["variants"][i].get("id") 506 | erp_item.shopify_variant_id = new_item['product']["variants"][i].get("id") 507 | erp_item.save() 508 | 509 | def get_variant_attributes(item, price_list, warehouse): 510 | options, variant_list, variant_item_name, attr_sequence = [], [], [], [] 511 | attr_dict = {} 512 | 513 | for i, variant in enumerate(frappe.get_all("Item", filters={"variant_of": item.get("name")}, 514 | fields=['name'])): 515 | 516 | item_variant = frappe.get_doc("Item", variant.get("name")) 517 | variant_list.append(get_price_and_stock_details(item_variant, warehouse, price_list)) 518 | 519 | for attr in item_variant.get('attributes'): 520 | if attr.attribute not in attr_sequence: 521 | attr_sequence.append(attr.attribute) 522 | 523 | if not attr_dict.get(attr.attribute): 524 | attr_dict.setdefault(attr.attribute, []) 525 | 526 | attr_dict[attr.attribute].append(attr.attribute_value) 527 | 528 | if attr.idx <= 3: 529 | variant_list[i]["option"+cstr(attr.idx)] = attr.attribute_value 530 | 531 | variant_item_name.append(item_variant.name) 532 | 533 | for i, attr in enumerate(attr_sequence): 534 | options.append({ 535 | "name": attr, 536 | "position": i+1, 537 | "values": list(set(attr_dict[attr])) 538 | }) 539 | 540 | return variant_list, options, variant_item_name 541 | 542 | def get_price_and_stock_details(item, warehouse, price_list): 543 | qty = frappe.db.get_value("Bin", {"item_code":item.get("item_code"), "warehouse": warehouse}, "actual_qty") 544 | price = frappe.db.get_value("Item Price", \ 545 | {"price_list": price_list, "item_code":item.get("item_code")}, "price_list_rate") 546 | 547 | item_price_and_quantity = { 548 | "price": flt(price) 549 | } 550 | 551 | if item.weight_per_unit: 552 | if item.weight_uom and item.weight_uom.lower() in ["kg", "g", "oz", "lb"]: 553 | item_price_and_quantity.update({ 554 | "weight_unit": item.weight_uom.lower(), 555 | "weight": item.weight_per_unit, 556 | "grams": get_weight_in_grams(item.weight_per_unit, item.weight_uom) 557 | }) 558 | 559 | 560 | if item.get("sync_qty_with_shopify"): 561 | item_price_and_quantity.update({ 562 | "inventory_quantity": cint(qty) if qty else 0, 563 | "inventory_management": "shopify" 564 | }) 565 | 566 | if item.shopify_variant_id: 567 | item_price_and_quantity["id"] = item.shopify_variant_id 568 | 569 | return item_price_and_quantity 570 | 571 | def get_weight_in_grams(weight, weight_uom): 572 | convert_to_gram = { 573 | "kg": 1000, 574 | "lb": 453.592, 575 | "oz": 28.3495, 576 | "g": 1 577 | } 578 | 579 | return weight * convert_to_gram[weight_uom.lower()] 580 | 581 | def trigger_update_item_stock(doc, method): 582 | if doc.flags.via_stock_ledger_entry: 583 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 584 | if shopify_settings.shopify_url and shopify_settings.enable_shopify: 585 | update_item_stock(doc.item_code, shopify_settings, doc) 586 | 587 | def update_item_stock_qty(): 588 | shopify_settings = frappe.get_doc("Shopify Settings", "Shopify Settings") 589 | for item in frappe.get_all("Item", fields=['name', "item_code"], 590 | filters={"sync_with_shopify": 1, "disabled": ("!=", 1), 'shopify_variant_id': ('!=', '')}): 591 | try: 592 | update_item_stock(item.item_code, shopify_settings) 593 | except ShopifyError as e: 594 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 595 | request_data=item, exception=True) 596 | 597 | except Exception as e: 598 | if e.args[0] and e.args[0].startswith("402"): 599 | raise e 600 | else: 601 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 602 | request_data=item, exception=True) 603 | 604 | def update_item_stock(item_code, shopify_settings, bin=None): 605 | item = frappe.get_doc("Item", item_code) 606 | if item.sync_qty_with_shopify: 607 | if not bin: 608 | bin = get_bin(item_code, shopify_settings.warehouse) 609 | 610 | if not item.shopify_product_id and not item.variant_of: 611 | sync_item_with_shopify(item, shopify_settings.price_list, shopify_settings.warehouse) 612 | 613 | if item.sync_with_shopify and item.shopify_product_id and shopify_settings.warehouse == bin.warehouse: 614 | if item.variant_of: 615 | item_data, resource = get_product_update_dict_and_resource(frappe.get_value("Item", 616 | item.variant_of, "shopify_product_id"), item.shopify_variant_id, is_variant=True, 617 | actual_qty=bin.actual_qty) 618 | else: 619 | item_data, resource = get_product_update_dict_and_resource(item.shopify_product_id, 620 | item.shopify_variant_id, actual_qty=bin.actual_qty) 621 | 622 | try: 623 | put_request(resource, item_data) 624 | except requests.exceptions.HTTPError as e: 625 | if e.args[0] and e.args[0].startswith("404"): 626 | make_shopify_log(title=e.message, status="Error", method="sync_shopify_items", message=frappe.get_traceback(), 627 | request_data=item_data, exception=True) 628 | disable_shopify_sync_for_item(item) 629 | else: 630 | raise e 631 | 632 | def get_product_update_dict_and_resource(shopify_product_id, shopify_variant_id, is_variant=False, actual_qty=0): 633 | """ 634 | JSON required to update product 635 | 636 | item_data = { 637 | "product": { 638 | "id": 3649706435 (shopify_product_id), 639 | "variants": [ 640 | { 641 | "id": 10577917379 (shopify_variant_id), 642 | "inventory_management": "shopify", 643 | "inventory_quantity": 10 644 | } 645 | ] 646 | } 647 | } 648 | """ 649 | 650 | item_data = { 651 | "product": { 652 | "variants": [] 653 | } 654 | } 655 | 656 | varient_data = { 657 | "id": shopify_variant_id, 658 | "inventory_quantity": cint(actual_qty), 659 | "inventory_management": "shopify" 660 | } 661 | 662 | if is_variant: 663 | item_data = { 664 | "variant": varient_data 665 | } 666 | resource = "admin/variants/{}.json".format(shopify_variant_id) 667 | else: 668 | item_data["product"]["id"] = shopify_product_id 669 | item_data["product"]["variants"].append(varient_data) 670 | resource = "admin/products/{}.json".format(shopify_product_id) 671 | 672 | return item_data, resource 673 | -------------------------------------------------------------------------------- /erpnext_shopify/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/templates/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/templates/emails/billing.md: -------------------------------------------------------------------------------- 1 | Dear {{ full_name }}, 2 | 3 | Greetings from ERPNext. 4 | 5 | This is a reminder that your ERPNext account: [{{ site }}](https://{{ site }}) is about to expire on **{{ expires_on }}**. This will also disable Shopify ERPNext connector. We request you to renew your account as soon as possible for uninterrupted access. 6 | 7 | You can renew your subscription through shopify billing service [shopify subscription invoice]({{ confirmation_url }}) or using within the app subscription method if PayPal / RazorPay is supported in your country. Click on your name in the top bar, navigate to Usage Info and click on **Renew / Upgrade**. 8 | 9 | If neither are supported in your country, you can find further instructions at our [payment options page](https://erpnext.com/pricing/payment). 10 | 11 | Feel free to reach us in case of any queries at **{{ support_email }}**. 12 | 13 | Best regards, 14 | 15 | [Frappé Team](https://frappe.io/about) -------------------------------------------------------------------------------- /erpnext_shopify/templates/generators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/templates/generators/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/templates/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frappe/erpnext_shopify/7510dcda90d958d4f10ade15df512d80331ab29a/erpnext_shopify/templates/pages/__init__.py -------------------------------------------------------------------------------- /erpnext_shopify/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors 3 | # For license information, please see license.txt 4 | 5 | from __future__ import unicode_literals 6 | import frappe 7 | import json 8 | from .exceptions import ShopifySetupError 9 | 10 | def disable_shopify_sync_for_item(item, rollback=False): 11 | """Disable Item if not exist on shopify""" 12 | if rollback: 13 | frappe.db.rollback() 14 | 15 | item.sync_with_shopify = 0 16 | item.sync_qty_with_shopify = 0 17 | item.save(ignore_permissions=True) 18 | frappe.db.commit() 19 | 20 | def disable_shopify_sync_on_exception(): 21 | frappe.db.rollback() 22 | frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0) 23 | frappe.db.commit() 24 | 25 | def is_shopify_enabled(): 26 | shopify_settings = frappe.get_doc("Shopify Settings") 27 | if not shopify_settings.enable_shopify: 28 | return False 29 | try: 30 | shopify_settings.validate() 31 | except ShopifySetupError: 32 | return False 33 | 34 | return True 35 | 36 | def make_shopify_log(title="Sync Log", status="Queued", method="sync_shopify", message=None, exception=False, 37 | name=None, request_data={}): 38 | if not name: 39 | name = frappe.db.get_value("Shopify Log", {"status": "Queued"}) 40 | 41 | if name: 42 | """ if name not provided by log calling method then fetch existing queued state log""" 43 | log = frappe.get_doc("Shopify Log", name) 44 | 45 | else: 46 | """ if queued job is not found create a new one.""" 47 | log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True) 48 | 49 | if exception: 50 | frappe.db.rollback() 51 | log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True) 52 | 53 | log.message = message if message else frappe.get_traceback() 54 | log.title = title[0:140] 55 | log.method = method 56 | log.status = status 57 | log.request_data= json.dumps(request_data) 58 | 59 | log.save(ignore_permissions=True) 60 | frappe.db.commit() -------------------------------------------------------------------------------- /erpnext_shopify/webhooks.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | from frappe import _ 4 | from functools import wraps 5 | import hashlib, base64, hmac, json 6 | from frappe.exceptions import AuthenticationError, ValidationError 7 | from .shopify_requests import get_request, get_shopify_settings, post_request, delete_request 8 | 9 | 10 | def shopify_webhook(f): 11 | """ 12 | A decorator thats checks and validates a Shopify Webhook request. 13 | """ 14 | 15 | def _hmac_is_valid(body, secret, hmac_to_verify): 16 | secret = str(secret) 17 | hash = hmac.new(secret, body, hashlib.sha256) 18 | hmac_calculated = base64.b64encode(hash.digest()) 19 | return hmac_calculated == hmac_to_verify 20 | 21 | @wraps(f) 22 | def wrapper(*args, **kwargs): 23 | # Try to get required headers and decode the body of the request. 24 | try: 25 | webhook_topic = frappe.local.request.headers.get('X-Shopify-Topic') 26 | webhook_hmac = frappe.local.request.headers.get('X-Shopify-Hmac-Sha256') 27 | webhook_data = frappe._dict(json.loads(frappe.local.request.get_data())) 28 | except: 29 | raise ValidationError() 30 | 31 | # Verify the HMAC. 32 | if not _hmac_is_valid(frappe.local.request.get_data(), get_shopify_settings().password, webhook_hmac): 33 | raise AuthenticationError() 34 | 35 | # Otherwise, set properties on the request object and return. 36 | frappe.local.request.webhook_topic = webhook_topic 37 | frappe.local.request.webhook_data = webhook_data 38 | kwargs.pop('cmd') 39 | 40 | return f(*args, **kwargs) 41 | return wrapper 42 | 43 | 44 | @frappe.whitelist(allow_guest=True) 45 | @shopify_webhook 46 | def webhook_handler(): 47 | from webhooks import handler_map 48 | topic = frappe.local.request.webhook_topic 49 | data = frappe.local.request.webhook_data 50 | handler = handler_map.get(topic) 51 | if handler: 52 | handler(data) 53 | 54 | def create_webhooks(): 55 | settings = get_shopify_settings() 56 | for event in ["orders/create", "orders/delete", "orders/updated", "orders/paid", "orders/cancelled", "orders/fulfilled", 57 | "orders/partially_fulfilled", "order_transactions/create", "carts/create", "carts/update", 58 | "checkouts/create", "checkouts/update", "checkouts/delete", "refunds/create", "products/create", 59 | "products/update", "products/delete", "collections/create", "collections/update", "collections/delete", 60 | "customer_groups/create", "customer_groups/update", "customer_groups/delete", "customers/create", 61 | "customers/enable", "customers/disable", "customers/update", "customers/delete", "fulfillments/create", 62 | "fulfillments/update", "shop/update", "disputes/create", "disputes/update", "app/uninstalled", 63 | "channels/delete", "product_publications/create", "product_publications/update", 64 | "product_publications/delete", "collection_publications/create", "collection_publications/update", 65 | "collection_publications/delete", "variants/in_stock", "variants/out_of_stock"]: 66 | 67 | create_webhook(event, settings.webhook_address) 68 | 69 | def create_webhook(topic, address): 70 | post_request('admin/webhooks.json', json.dumps({ 71 | "webhook": { 72 | "topic": topic, 73 | "address": address, 74 | "format": "json" 75 | } 76 | })) 77 | 78 | def get_webhooks(): 79 | webhooks = get_request("/admin/webhooks.json") 80 | return webhooks["webhooks"] 81 | 82 | def delete_webhooks(): 83 | webhooks = get_webhooks() 84 | for webhook in webhooks: 85 | delete_request("/admin/webhooks/{}.json".format(webhook['id'])) 86 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License: GNU GPL v3.0 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | frappe 2 | erpnext -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages 3 | import re, ast 4 | 5 | 6 | with open('requirements.txt') as f: 7 | install_requires = f.read().strip().split('\n') 8 | 9 | # get version from __version__ variable in erpnext_shopify/__init__.py 10 | _version_re = re.compile(r'__version__\s+=\s+(.*)') 11 | 12 | with open('erpnext_shopify/__init__.py', 'rb') as f: 13 | version = str(ast.literal_eval(_version_re.search( 14 | f.read().decode('utf-8')).group(1))) 15 | 16 | setup( 17 | name='erpnext_shopify', 18 | version=version, 19 | description='Shopify connector for ERPNext', 20 | author='Frappe Technologies Pvt. Ltd.', 21 | author_email='info@frappe.io', 22 | packages=find_packages(), 23 | zip_safe=False, 24 | include_package_data=True, 25 | install_requires=install_requires 26 | ) 27 | -------------------------------------------------------------------------------- /test_sites/apps.txt: -------------------------------------------------------------------------------- 1 | frappe 2 | erpnext 3 | erpnext_shopify 4 | -------------------------------------------------------------------------------- /test_sites/current_site.txt: -------------------------------------------------------------------------------- 1 | test_site -------------------------------------------------------------------------------- /test_sites/languages.txt: -------------------------------------------------------------------------------- 1 | en english -------------------------------------------------------------------------------- /test_sites/test_site/site_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_name": "test_frappe", 3 | "db_password": "test_frappe", 4 | "auto_email_id": "test@example.com", 5 | "mail_server": "smtp.example.com", 6 | "mail_login": "test@example.com", 7 | "mail_password": "test", 8 | "admin_password": "admin", 9 | "run_selenium_tests": 1, 10 | "host_name": "http://localhost:8000", 11 | "install_apps": ["erpnext", "erpnext_shopify"] 12 | } 13 | --------------------------------------------------------------------------------