commit 7449337c3a6921f6e149d4326421baa57d10b6ef Author: mb Date: Tue Mar 19 11:28:00 2024 +0300 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba04025 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +*.pyc +*.egg-info +*.swp +tags +node_modules +__pycache__ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/invoice_sync.iml b/.idea/invoice_sync.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/invoice_sync.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8fc842c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2df083 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Invoice Sync diff --git a/invoice_sync/__init__.py b/invoice_sync/__init__.py new file mode 100644 index 0000000..7a0660b --- /dev/null +++ b/invoice_sync/__init__.py @@ -0,0 +1,3 @@ + +__version__ = '0.0.1' + diff --git a/invoice_sync/config/__init__.py b/invoice_sync/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/invoice_sync/hooks.py b/invoice_sync/hooks.py new file mode 100644 index 0000000..70c983b --- /dev/null +++ b/invoice_sync/hooks.py @@ -0,0 +1,221 @@ +app_name = "invoice_sync" +app_title = "Invoice Sync" +app_publisher = "SolutionERP" +app_description = "Syncing invoice with mobile app" +app_email = "support@solutionerp.com" +app_license = "mit" +# required_apps = [] + +# Includes in +# ------------------ + +# include js, css files in header of desk.html +# app_include_css = "/assets/invoice_sync/css/invoice_sync.css" +# app_include_js = "/assets/invoice_sync/js/invoice_sync.js" + +# include js, css files in header of web template +# web_include_css = "/assets/invoice_sync/css/invoice_sync.css" +# web_include_js = "/assets/invoice_sync/js/invoice_sync.js" + +# include custom scss in every website theme (without file extension ".scss") +# website_theme_scss = "invoice_sync/public/scss/website" + +# include js, css files in header of web form +# webform_include_js = {"doctype": "public/js/doctype.js"} +# webform_include_css = {"doctype": "public/css/doctype.css"} + +# include js in page +# page_js = {"page" : "public/js/file.js"} + +# include js in doctype views +# doctype_js = {"doctype" : "public/js/doctype.js"} +# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} +# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} +# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} + +# Svg Icons +# ------------------ +# include app icons in desk +# app_include_icons = "invoice_sync/public/icons.svg" + +# Home Pages +# ---------- + +# application home page (will override Website Settings) +# home_page = "login" + +# website user home page (by Role) +# role_home_page = { +# "Role": "home_page" +# } + +# Generators +# ---------- + +# automatically create page for each record of this doctype +# website_generators = ["Web Page"] + +# Jinja +# ---------- + +# add methods and filters to jinja environment +# jinja = { +# "methods": "invoice_sync.utils.jinja_methods", +# "filters": "invoice_sync.utils.jinja_filters" +# } + +# Installation +# ------------ + +# before_install = "invoice_sync.install.before_install" +# after_install = "invoice_sync.install.after_install" + +# Uninstallation +# ------------ + +# before_uninstall = "invoice_sync.uninstall.before_uninstall" +# after_uninstall = "invoice_sync.uninstall.after_uninstall" + +# Integration Setup +# ------------------ +# To set up dependencies/integrations with other apps +# Name of the app being installed is passed as an argument + +# before_app_install = "invoice_sync.utils.before_app_install" +# after_app_install = "invoice_sync.utils.after_app_install" + +# Integration Cleanup +# ------------------- +# To clean up dependencies/integrations with other apps +# Name of the app being uninstalled is passed as an argument + +# before_app_uninstall = "invoice_sync.utils.before_app_uninstall" +# after_app_uninstall = "invoice_sync.utils.after_app_uninstall" + +# Desk Notifications +# ------------------ +# See xhiveframework.core.notifications.get_notification_config + +# notification_config = "invoice_sync.notifications.get_notification_config" + +# Permissions +# ----------- +# Permissions evaluated in scripted ways + +# permission_query_conditions = { +# "Event": "xhiveframework.desk.doctype.event.event.get_permission_query_conditions", +# } +# +# has_permission = { +# "Event": "xhiveframework.desk.doctype.event.event.has_permission", +# } + +# DocType Class +# --------------- +# Override standard doctype classes + +# override_doctype_class = { +# "ToDo": "custom_app.overrides.CustomToDo" +# } + +# Document Events +# --------------- +# Hook on document methods and events + +# doc_events = { +# "*": { +# "on_update": "method", +# "on_cancel": "method", +# "on_trash": "method" +# } +# } + +# Scheduled Tasks +# --------------- + +# scheduler_events = { +# "all": [ +# "invoice_sync.tasks.all" +# ], +# "daily": [ +# "invoice_sync.tasks.daily" +# ], +# "hourly": [ +# "invoice_sync.tasks.hourly" +# ], +# "weekly": [ +# "invoice_sync.tasks.weekly" +# ], +# "monthly": [ +# "invoice_sync.tasks.monthly" +# ], +# } + +# Testing +# ------- + +# before_tests = "invoice_sync.install.before_tests" + +# Overriding Methods +# ------------------------------ +# +# override_whitelisted_methods = { +# "xhiveframework.desk.doctype.event.event.get_events": "invoice_sync.event.get_events" +# } +# +# each overriding function accepts a `data` argument; +# generated from the base implementation of the doctype dashboard, +# along with any modifications made in other Xhive apps +# override_doctype_dashboards = { +# "Task": "invoice_sync.task.get_dashboard_data" +# } + +# exempt linked doctypes from being automatically cancelled +# +# auto_cancel_exempted_doctypes = ["Auto Repeat"] + +# Ignore links to specified DocTypes when deleting documents +# ----------------------------------------------------------- + +# ignore_links_on_delete = ["Communication", "ToDo"] + +# Request Events +# ---------------- +# before_request = ["invoice_sync.utils.before_request"] +# after_request = ["invoice_sync.utils.after_request"] + +# Job Events +# ---------- +# before_job = ["invoice_sync.utils.before_job"] +# after_job = ["invoice_sync.utils.after_job"] + +# User Data Protection +# -------------------- + +# user_data_fields = [ +# { +# "doctype": "{doctype_1}", +# "filter_by": "{filter_by}", +# "redact_fields": ["{field_1}", "{field_2}"], +# "partial": 1, +# }, +# { +# "doctype": "{doctype_2}", +# "filter_by": "{filter_by}", +# "partial": 1, +# }, +# { +# "doctype": "{doctype_3}", +# "strict": False, +# }, +# { +# "doctype": "{doctype_4}" +# } +# ] + +# Authentication and authorization +# -------------------------------- + +# auth_hooks = [ +# "invoice_sync.auth.validate" +# ] diff --git a/invoice_sync/invoice_sync/__init__.py b/invoice_sync/invoice_sync/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/invoice_sync/invoice_sync/invoice.py b/invoice_sync/invoice_sync/invoice.py new file mode 100644 index 0000000..295f82c --- /dev/null +++ b/invoice_sync/invoice_sync/invoice.py @@ -0,0 +1,253 @@ +import requests +import json +import xhiveframework +import json +import urllib.parse; +import base64 +from werkzeug.wrappers import Response + + +@xhiveframework.whitelist(allow_guest=True) +def generate_token_secure( api_key, api_secret, app_key): + + + try: + try: + app_key = base64.b64decode(app_key).decode("utf-8") + except Exception as e: + return Response(json.dumps({"message": "Security Parameters are not valid" , "user_count": 0}), status=401, mimetype='application/json') + clientID, clientSecret, clientUser = xhiveframework.db.get_value('OAuth Client', {'app_name': app_key}, ['client_id', 'client_secret','user']) + + if clientID is None: + # return app_key + return Response(json.dumps({"message": "Security Parameters are not valid" , "user_count": 0}), status=401, mimetype='application/json') + + client_id = clientID # Replace with your OAuth client ID + client_secret = clientSecret # Replace with your OAuth client secret + url = xhiveframework.local.conf.host_name + "/api/method/xhiveframework.integrations.oauth2.get_token" + payload = { + "username": api_key, + "password": api_secret, + "grant_type": "password", + "client_id": client_id, + "client_secret": client_secret, + + } + files = [] + headers = {"Content-Type": "application/json"} + response = requests.request("POST", url, data=payload, files=files) + if response.status_code == 200: + result_data = json.loads(response.text) + return Response(json.dumps({"data":result_data}), status=200, mimetype='application/json') + + + else: + + xhiveframework.local.response.http_status_code = 401 + return json.loads(response.text) + + + except Exception as e: + + + return Response(json.dumps({"message": e , "user_count": 0}), status=500, mimetype='application/json') + + + + + + +@xhiveframework.whitelist(allow_guest=True) +def create_refresh_token(refresh_token): + url = xhiveframework.local.conf.host_name + "/api/method/xhiveframework.integrations.oauth2.get_token" + payload = f'grant_type=refresh_token&refresh_token={refresh_token}' + headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + } + files = [] + + response = requests.post(url, headers=headers, data=payload, files=files) + + + if response.status_code == 200: + try: + + message_json = json.loads(response.text) + + + new_message = { + "access_token": message_json["access_token"], + "expires_in": message_json["expires_in"], + "token_type": message_json["token_type"], + "scope": message_json["scope"], + "refresh_token": message_json["refresh_token"] + } + + + return Response(json.dumps({"data": new_message}), status=200, mimetype='application/json') + except json.JSONDecodeError as e: + return Response(json.dumps({"data": f"Error decoding JSON: {e}"}), status=401, mimetype='application/json') + else: + + return Response(json.dumps({"data": response.text}), status=401, mimetype='application/json') + + + + + +@xhiveframework.whitelist() +def customer(customer, customer_type, phone, email,is_supplier=False): + response_content =xhiveframework.session.user + is_supplier = is_supplier.lower() in ['true', '1', 'yes'] if isinstance(is_supplier, str) else bool(is_supplier) + + if not is_supplier: + + email_result = [] + + + if email is not None: + email_result = xhiveframework.get_all("Customer", fields=["name as id", "customer_name", "customer_type", "mobile_no", "custom_email as email"], + filters={'custom_email': ['like', email]}) + + if not email_result: + customer_doc = xhiveframework.get_doc({ + "doctype": "Customer", + "customer_name": customer, + "customer_type": customer_type, + "mobile_no": phone, + "custom_email": email, + + }) + + customer_doc.insert(ignore_permissions=True) + customer_doc.save() + + details = xhiveframework.get_all("Customer", fields=["name as id", "customer_name", "customer_type", "mobile_no", "custom_email as email",], + filters={'name': ['like', customer_doc.name]}) + + data = { + "message": "Customer created successfully.", + "Details": details, + } + + + return Response(json.dumps({"data":data}), status=200, mimetype='application/json') + + + else: + data = { + "message": "Customer already exists", + "Details": email_result + } + + + return Response(json.dumps({"data":data}), status=200, mimetype='application/json') + + else: + suplr_already_exist=xhiveframework.get_all("Supplier", fields=["name as id", "supplier_name", "custom_email as email", "custom_mobileno as phone"], + filters={'name': ['like',customer]}) + details={ + "message":"Already exists", + "Details":suplr_already_exist + } + if suplr_already_exist: + return Response(json.dumps({"data":details }), status=409, mimetype='application/json') + supplier_doc = xhiveframework.get_doc({ + "doctype": "Supplier", + "supplier_name": customer, + "custom_mobileno": phone, + "custom_email": email, + }) + supplier_doc.insert(ignore_permissions=True) + + supplier_details = xhiveframework.get_all("Supplier", fields=["name as id", "supplier_name", "custom_email as email", "custom_mobileno as phone"], + filters={'name': ['like', supplier_doc.name]}) + suplr={ + "Details":supplier_details + } + return Response(json.dumps({"data":suplr}), status=200, mimetype='application/json') + + + + + + +@xhiveframework.whitelist() +def create_invoice(customer_id, supplier_id, payment_method, items): + invoice_items = [] + + for item in items: + item_code = item["item_name"] + + item_exists = xhiveframework.get_value("Item", {"name": item_code}, "name") + + if not item_exists: + invoice_item = { + "item_name": item_code, + "qty": item.get("quantity", 0), + "rate": item.get("rate", 0), + "uom": item.get("uom", "Nos"), + "income_account": item.get("income_account", "Sales - erp") + } + else: + invoice_item = { + "item_code": item_code, + "qty": item.get("quantity", 0), + "rate": item.get("rate", 0), + + } + + invoice_items.append(invoice_item) + + new_invoice = xhiveframework.get_doc({ + "doctype": "Sales Invoice", + "customer": customer_id, + "custom_supplier_id": supplier_id, + "custom_payment_method": payment_method, + "items": invoice_items + }) + + new_invoice.insert(ignore_permissions=True) + new_invoice.save() + iitem = xhiveframework.get_doc("Sales Invoice", new_invoice.name) + + attribute_dict = [] + for attribute in iitem.items: + attribute_data = { + "item_name": attribute.item_name, + "item_code": attribute.item_code, + "quantity": attribute.qty, + "rate": attribute.rate, + "uom": attribute.uom, + "income_account": attribute.income_account + } + attribute_dict.append(attribute_data) + + customer_info = { + "id": new_invoice.name, + "customer_id": new_invoice.customer, + "customer_name": new_invoice.customer_name, + "supplier_id": new_invoice.custom_supplier_id, + "payment_method": new_invoice.custom_payment_method, + "total_quantity": new_invoice.total_qty, + "total": new_invoice.total, + "grand_total": new_invoice.grand_total, + "items": attribute_dict + } + + return Response(json.dumps({"data":customer_info}), status=200, mimetype='application/json') + + + + + +@xhiveframework.whitelist() +def customer1(customer, phone, email, is_supplier=False, user_id=None): + try: + return "hello" + + except xhiveframework.exceptions.PermissionError as e: + return Response(json.dumps({"message": "Permission error"}), status=401, mimetype='application/json') + except Exception as e: + # Handle other exceptions if needed + return Response(json.dumps({"message": str(e)}), status=500, mimetype='application/json') diff --git a/invoice_sync/modules.txt b/invoice_sync/modules.txt new file mode 100644 index 0000000..2f78020 --- /dev/null +++ b/invoice_sync/modules.txt @@ -0,0 +1 @@ +Invoice Sync \ No newline at end of file diff --git a/invoice_sync/patches.txt b/invoice_sync/patches.txt new file mode 100644 index 0000000..aa09008 --- /dev/null +++ b/invoice_sync/patches.txt @@ -0,0 +1,6 @@ +[pre_model_sync] +# Patches added in this section will be executed before doctypes are migrated +# Read docs to understand patches: https://xhiveframework.com/docs/v14/user/en/database-migrations + +[post_model_sync] +# Patches added in this section will be executed after doctypes are migrated diff --git a/invoice_sync/public/.gitkeep b/invoice_sync/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/invoice_sync/templates/__init__.py b/invoice_sync/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/invoice_sync/templates/pages/__init__.py b/invoice_sync/templates/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/invoice_sync/www/__init__.py b/invoice_sync/www/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..97bd8f2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "invoice_sync" +authors = [ + { name = "SolutionERP", email = "support@solutionerp.com"} +] +description = "Syncing invoice with mobile app" +requires-python = ">=3.10" +readme = "README.md" +dynamic = ["version"] +dependencies = [ + # "xhiveframework~=15.0.0" # Installed and managed by bench. +] + +[build-system] +requires = ["flit_core >=3.4,<4"] +build-backend = "flit_core.buildapi" + +# These dependencies are only installed when developer mode is enabled +[tool.bench.dev-dependencies] +# package_name = "~=1.1.0"