diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..1f2255279
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,130 @@
+{
+ "env": {
+ "browser": true,
+ "node": true,
+ "es2022": true
+ },
+ "parserOptions": {
+ "sourceType": "module"
+ },
+ "extends": "eslint:recommended",
+ "rules": {
+ "indent": "off",
+ "brace-style": "off",
+ "no-mixed-spaces-and-tabs": "off",
+ "no-useless-escape": "off",
+ "space-unary-ops": ["error", { "words": true }],
+ "linebreak-style": "off",
+ "quotes": ["off"],
+ "semi": "off",
+ "camelcase": "off",
+ "no-unused-vars": "off",
+ "no-console": ["warn"],
+ "no-extra-boolean-cast": ["off"],
+ "no-control-regex": ["off"]
+ },
+ "root": true,
+ "globals": {
+ "frappe": true,
+ "Vue": true,
+ "SetVueGlobals": true,
+ "erpnext": true,
+ "hub": true,
+ "$": true,
+ "jQuery": true,
+ "moment": true,
+ "hljs": true,
+ "Awesomplete": true,
+ "CalHeatMap": true,
+ "Sortable": true,
+ "Showdown": true,
+ "Taggle": true,
+ "Gantt": true,
+ "Slick": true,
+ "PhotoSwipe": true,
+ "PhotoSwipeUI_Default": true,
+ "fluxify": true,
+ "io": true,
+ "c3": true,
+ "__": true,
+ "_p": true,
+ "_f": true,
+ "repl": true,
+ "Class": true,
+ "locals": true,
+ "cint": true,
+ "cstr": true,
+ "cur_frm": true,
+ "cur_dialog": true,
+ "cur_page": true,
+ "cur_list": true,
+ "cur_tree": true,
+ "cur_pos": true,
+ "msg_dialog": true,
+ "is_null": true,
+ "in_list": true,
+ "has_common": true,
+ "posthog": true,
+ "has_words": true,
+ "validate_email": true,
+ "open_web_template_values_editor": true,
+ "get_number_format": true,
+ "format_number": true,
+ "format_currency": true,
+ "round_based_on_smallest_currency_fraction": true,
+ "roundNumber": true,
+ "comment_when": true,
+ "replace_newlines": true,
+ "open_url_post": true,
+ "toTitle": true,
+ "lstrip": true,
+ "strip": true,
+ "strip_html": true,
+ "replace_all": true,
+ "flt": true,
+ "precision": true,
+ "md5": true,
+ "CREATE": true,
+ "AMEND": true,
+ "CANCEL": true,
+ "copy_dict": true,
+ "get_number_format_info": true,
+ "print_table": true,
+ "Layout": true,
+ "web_form_settings": true,
+ "$c": true,
+ "$a": true,
+ "$i": true,
+ "$bg": true,
+ "$y": true,
+ "$c_obj": true,
+ "$c_obj_csv": true,
+ "refresh_many": true,
+ "refresh_field": true,
+ "toggle_field": true,
+ "get_field_obj": true,
+ "get_query_params": true,
+ "unhide_field": true,
+ "hide_field": true,
+ "set_field_options": true,
+ "getCookie": true,
+ "getCookies": true,
+ "get_url_arg": true,
+ "get_server_fields": true,
+ "set_multiple": true,
+ "QUnit": true,
+ "Chart": true,
+ "Cypress": true,
+ "cy": true,
+ "describe": true,
+ "expect": true,
+ "it": true,
+ "context": true,
+ "before": true,
+ "beforeEach": true,
+ "onScan": true,
+ "extend_cscript": true,
+ "localforage": true,
+ "Plaid": true
+ }
+}
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index ce3401804..406052d80 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -4,26 +4,40 @@ set -e
cd ~ || exit
-sudo apt-get update
-sudo apt-get -y remove mysql-server mysql-client
-sudo apt-get -y install redis-server libcups2-dev mariadb-client-10.6 -qq
+sudo apt update
+sudo apt remove mysql-server mysql-client
+sudo apt install libcups2-dev redis-server mariadb-client-10.6
pip install frappe-bench
-git clone https://github.com/frappe/frappe --branch develop --depth 1
+githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
+frappeuser=${FRAPPE_USER:-"frappe"}
+frappebranch=${FRAPPE_BRANCH:-$githubbranch}
+erpnextbranch=${ERPNEXT_BRANCH:-$githubbranch}
+paymentsbranch=${PAYMENTS_BRANCH:-${githubbranch%"-hotfix"}}
+
+git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
-mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
-mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
+
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
-mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
-mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
-mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
-mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
+install_whktml() {
+ wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
+ tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
+ sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
+ sudo chmod o+x /usr/local/bin/wkhtmltopdf
+}
+install_whktml &
cd ~/frappe-bench || exit
@@ -32,12 +46,13 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
-bench get-app payments --branch develop
-bench get-app erpnext --branch develop
+bench get-app "https://github.com/${frappeuser}/payments" --branch "$paymentsbranch"
+bench get-app "https://github.com/${frappeuser}/erpnext" --branch "$erpnextbranch" --resolve-deps
+bench get-app ecommerce_integrations "${GITHUB_WORKSPACE}"
+bench setup requirements --dev
-bench start &
+bench start &>> ~/frappe-bench/bench_start.log &
+CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes
-bench get-app ecommerce_integrations "${GITHUB_WORKSPACE}"
-bench --site test_site install-app ecommerce_integrations
-bench setup requirements --dev
+bench --verbose --site test_site install-app ecommerce_integrations
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000..080e248c8
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,4 @@
+# Any python files modifed but no test files modified
+needs-tests:
+- any: ['hrms/**/*.py']
+ all: ['!hrms/**/test*.py']
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 366751e31..1732f42fa 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,13 +1,16 @@
name: CI
on:
- push:
- branches:
- - develop
pull_request:
- branches:
- - develop
- - main
+ paths-ignore:
+ - "**.css"
+ - "**.js"
+ - "**.md"
+ - "**.html"
+ - "**.csv"
+ schedule:
+ # Run everday at midnight UTC / 5:30 IST
+ - cron: "0 0 * * *"
concurrency:
group: develop-${{ github.event.number }}
@@ -16,12 +19,18 @@ concurrency:
jobs:
tests:
runs-on: ubuntu-latest
- timeout-minutes: 20
+ timeout-minutes: 60
+ env:
+ NODE_ENV: "production"
+ WITH_COVERAGE: ${{ github.event_name != 'pull_request' }}
strategy:
fail-fast: false
- name: Server
+ matrix:
+ container: [1]
+
+ name: Python Unit Tests
services:
mysql:
@@ -30,7 +39,7 @@ jobs:
MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
+ options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
@@ -41,6 +50,14 @@ jobs:
with:
python-version: '3.10'
+ - name: Check for valid Python & Merge Conflicts
+ run: |
+ python -m compileall -f "${GITHUB_WORKSPACE}"
+ if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
+ then echo "Found merge conflicts"
+ exit 1
+ fi
+
- name: Setup Node
uses: actions/setup-node@v2
with:
@@ -54,10 +71,11 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt') }}
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
+
- name: Cache node modules
uses: actions/cache@v2
env:
@@ -85,16 +103,40 @@ jobs:
- name: Install
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
+ env:
+ FRAPPE_USER: blaggacao
+ FRAPPE_BRANCH: feat/add-read-only-document-context-to-mapper
+ ERPNEXT_BRANCH: feat/incorporate-read-only-mode-mapper
- name: Run Tests
- run: cd ~/frappe-bench/ && bench --site test_site run-tests --app ecommerce_integrations --coverage
+ run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app ecommerce_integrations --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }}
env:
TYPE: server
+ CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}
+
+ - name: Upload coverage data
+ uses: actions/upload-artifact@v3
+ if: github.event_name != 'pull_request'
+ with:
+ name: coverage-${{ matrix.container }}
+ path: /home/runner/frappe-bench/sites/coverage.xml
+
+ coverage:
+ name: Coverage Wrap Up
+ needs: tests
+ runs-on: ubuntu-latest
+ if: ${{ github.event_name != 'pull_request' }}
+ steps:
+ - name: Clone
+ uses: actions/checkout@v4
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v3
- name: Upload coverage data
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v4
with:
+ token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
- files: /home/runner/frappe-bench/sites/coverage.xml
verbose: true
diff --git a/.github/workflows/labeller.yml b/.github/workflows/labeller.yml
new file mode 100644
index 000000000..97fa4a1a2
--- /dev/null
+++ b/.github/workflows/labeller.yml
@@ -0,0 +1,12 @@
+name: "Pull Request Labeler"
+on:
+ pull_request_target:
+ types: [opened, reopened]
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@v4
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.gitignore b/.gitignore
index 9faed4da5..7ba46dbf9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,7 @@
*.egg-info
*.swp
tags
-ecommerce_integrations/docs/current
\ No newline at end of file
+ecommerce_integrations/docs/current
+
+.aider*
+.helix
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6c5ded60d..0697268c2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,37 +1,68 @@
exclude: 'node_modules|.git'
default_stages: [commit]
-fail_fast: true
+fail_fast: false
+
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.0.1
+ rev: v4.3.0
hooks:
- id: trailing-whitespace
files: "ecommerce_integrations.*"
- - id: end-of-file-fixer
- files: "ecommerce_integrations.*"
- exclude: ".*json$|.*txt$"
+ exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
+ - id: check-yaml
+ - id: no-commit-to-branch
+ args: ['--branch', 'develop']
+ - id: check-merge-conflict
+ - id: check-ast
+ - id: check-json
+ - id: check-toml
- id: check-yaml
+ - id: debug-statements
- - repo: https://github.com/adityahase/black
- rev: 364d1ddcf58eb6bad2e0b757329f06f40ea83044
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v2.7.1
hooks:
- - id: black
- exclude: ".*setup.py$"
- additional_dependencies: ['click==8.0.4']
+ - id: prettier
+ types_or: [javascript, vue, scss]
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ ecommerce_integrations/public/dist/.*|
+ cypress/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ ecommerce_integrations/templates/includes/.*
+ )$
- - repo: https://github.com/PyCQA/isort
- rev: 5.12.0
+ - repo: https://github.com/pre-commit/mirrors-eslint
+ rev: v8.44.0
hooks:
- - id: isort
- exclude: ".*setup.py$"
+ - id: eslint
+ types_or: [javascript]
+ args: ['--quiet']
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ ecommerce_integrations/public/dist/.*|
+ cypress/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ ecommerce_integrations/templates/includes/.*
+ )$
- - repo: https://github.com/PyCQA/flake8
- rev: 5.0.4
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.2.0
hooks:
- - id: flake8
- additional_dependencies: [flake8-isort]
- exclude: ".*setup.py$"
+ - id: ruff
+ name: "Run ruff import sorter"
+ args: ["--select=I", "--fix"]
+
+ - id: ruff
+ name: "Run ruff linter"
+
+ - id: ruff-format
+ name: "Run ruff formatter"
ci:
autoupdate_schedule: weekly
diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py
index e5456d927..1672801d4 100644
--- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py
+++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_repository.py
@@ -47,7 +47,7 @@ def call_sp_api_method(self, sp_api_method, **kwargs) -> dict:
errors = {}
max_retries = self.amz_setting.max_retry_limit
- for x in range(max_retries):
+ for _x in range(max_retries):
try:
result = sp_api_method(**kwargs)
return result.get("payload")
@@ -62,7 +62,8 @@ def call_sp_api_method(self, sp_api_method, **kwargs) -> dict:
msg = f"Error: {error}
Error Description: {errors.get(error)}"
frappe.msgprint(msg, alert=True, indicator="red")
frappe.log_error(
- message=f"{error}: {errors.get(error)}", title=f'Method "{sp_api_method.__name__}" failed',
+ message=f"{error}: {errors.get(error)}",
+ title=f'Method "{sp_api_method.__name__}" failed',
)
self.amz_setting.enable_sync = 0
@@ -76,11 +77,11 @@ def get_finances_instance(self) -> Finances:
return Finances(**self.instance_params)
def get_account(self, name) -> str:
- account_name = frappe.db.get_value("Account", {"account_name": "Amazon {0}".format(name)})
+ account_name = frappe.db.get_value("Account", {"account_name": f"Amazon {name}"})
if not account_name:
new_account = frappe.new_doc("Account")
- new_account.account_name = "Amazon {0}".format(name)
+ new_account.account_name = f"Amazon {name}"
new_account.company = self.amz_setting.company
new_account.parent_account = self.amz_setting.market_place_account_group
new_account.insert(ignore_permissions=True)
@@ -271,9 +272,7 @@ def get_item_code(self, order_item) -> str:
def get_order_items(self, order_id) -> list:
orders = self.get_orders_instance()
- order_items_payload = self.call_sp_api_method(
- sp_api_method=orders.get_order_items, order_id=order_id
- )
+ order_items_payload = self.call_sp_api_method(sp_api_method=orders.get_order_items, order_id=order_id)
final_order_items = []
warehouse = self.amz_setting.warehouse
@@ -301,7 +300,9 @@ def get_order_items(self, order_id) -> list:
break
order_items_payload = self.call_sp_api_method(
- sp_api_method=orders.get_order_items, order_id=order_id, next_token=next_token,
+ sp_api_method=orders.get_order_items,
+ order_id=order_id,
+ next_token=next_token,
)
return final_order_items
@@ -333,7 +334,8 @@ def create_customer(order) -> str:
new_contact = frappe.new_doc("Contact")
new_contact.first_name = order_customer_name
new_contact.append(
- "links", {"link_doctype": "Customer", "link_name": existing_customer_name},
+ "links",
+ {"link_doctype": "Customer", "link_name": existing_customer_name},
)
new_contact.insert()
@@ -469,7 +471,9 @@ def get_orders(self, created_after) -> list:
break
orders_payload = self.call_sp_api_method(
- sp_api_method=orders.get_orders, created_after=created_after, next_token=next_token,
+ sp_api_method=orders.get_orders,
+ created_after=created_after,
+ next_token=next_token,
)
return sales_orders
diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py
index c2abd0de2..d9872c59d 100644
--- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py
+++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py
@@ -136,7 +136,7 @@ def __call__(self, request):
# Create payload hash (hash of the request body content).
if request.method == "GET":
- payload_hash = hashlib.sha256(("").encode("utf-8")).hexdigest()
+ payload_hash = hashlib.sha256(b"").hexdigest()
else:
if request.body:
if isinstance(request.body, bytes):
@@ -156,9 +156,7 @@ def __call__(self, request):
map(lambda H: H.lower(), request.headers.keys()),
)
)
- canonical_headers = "".join(
- map(lambda h: ":".join((h, request.headers[h])) + "\n", headers_to_sign)
- )
+ canonical_headers = "".join([f"{h}:{request.headers[h]}\n" for h in headers_to_sign])
signed_headers = ";".join(headers_to_sign)
# Combine elements to create canonical request.
@@ -207,8 +205,8 @@ def __init__(self, *args, **kwargs) -> None:
super().__init__(*args)
-class SPAPI(object):
- """ Base Amazon SP-API class """
+class SPAPI:
+ """Base Amazon SP-API class"""
# https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md#connecting-to-the-selling-partner-api
AUTH_URL = "https://api.amazon.com/auth/o2/token"
@@ -246,9 +244,7 @@ def get_access_token(self) -> str:
result = response.json()
if response.status_code == 200:
return result.get("access_token")
- exception = SPAPIError(
- error=result.get("error"), error_description=result.get("error_description")
- )
+ exception = SPAPIError(error=result.get("error"), error_description=result.get("error_description"))
raise exception
def get_auth(self) -> AWSSigV4:
@@ -281,7 +277,11 @@ def get_headers(self) -> dict:
return {"x-amz-access-token": self.get_access_token()}
def make_request(
- self, method: str = "GET", append_to_base_uri: str = "", params: dict = None, data: dict = None,
+ self,
+ method: str = "GET",
+ append_to_base_uri: str = "",
+ params: dict | None = None,
+ data: dict | None = None,
) -> dict:
if isinstance(params, dict):
params = Util.remove_empty(params)
@@ -307,45 +307,45 @@ def list_to_dict(self, key: str, values: list, data: dict) -> None:
class Finances(SPAPI):
- """ Amazon Finances API """
+ """Amazon Finances API"""
BASE_URI = "/finances/v0/"
def list_financial_events_by_order_id(
- self, order_id: str, max_results: int = None, next_token: str = None
+ self, order_id: str, max_results: int | None = None, next_token: str | None = None
) -> dict:
- """ Returns all financial events for the specified order. """
+ """Returns all financial events for the specified order."""
append_to_base_uri = f"orders/{order_id}/financialEvents"
data = dict(MaxResultsPerPage=max_results, NextToken=next_token)
return self.make_request(append_to_base_uri=append_to_base_uri, params=data)
class Orders(SPAPI):
- """ Amazon Orders API """
+ """Amazon Orders API"""
BASE_URI = "/orders/v0/orders"
def get_orders(
self,
created_after: str,
- created_before: str = None,
- last_updated_after: str = None,
- last_updated_before: str = None,
- order_statuses: list = None,
- marketplace_ids: list = None,
- fulfillment_channels: list = None,
- payment_methods: list = None,
- buyer_email: str = None,
- seller_order_id: str = None,
+ created_before: str | None = None,
+ last_updated_after: str | None = None,
+ last_updated_before: str | None = None,
+ order_statuses: list | None = None,
+ marketplace_ids: list | None = None,
+ fulfillment_channels: list | None = None,
+ payment_methods: list | None = None,
+ buyer_email: str | None = None,
+ seller_order_id: str | None = None,
max_results: int = 100,
- easyship_shipment_statuses: list = None,
- next_token: str = None,
- amazon_order_ids: list = None,
- actual_fulfillment_supply_source_id: str = None,
+ easyship_shipment_statuses: list | None = None,
+ next_token: str | None = None,
+ amazon_order_ids: list | None = None,
+ actual_fulfillment_supply_source_id: str | None = None,
is_ispu: bool = False,
- store_chain_store_id: str = None,
+ store_chain_store_id: str | None = None,
) -> dict:
- """ Returns orders created or updated during the time frame indicated by the specified parameters. You can also apply a range of filtering criteria to narrow the list of orders returned. If NextToken is present, that will be used to retrieve the orders instead of other criteria. """
+ """Returns orders created or updated during the time frame indicated by the specified parameters. You can also apply a range of filtering criteria to narrow the list of orders returned. If NextToken is present, that will be used to retrieve the orders instead of other criteria."""
data = dict(
CreatedAfter=created_after,
CreatedBefore=created_before,
@@ -373,20 +373,24 @@ def get_orders(
return self.make_request(params=data)
- def get_order_items(self, order_id: str, next_token: str = None) -> dict:
- """ Returns detailed order item information for the order indicated by the specified order ID. If NextToken is provided, it's used to retrieve the next page of order items. """
+ def get_order_items(self, order_id: str, next_token: str | None = None) -> dict:
+ """Returns detailed order item information for the order indicated by the specified order ID. If NextToken is provided, it's used to retrieve the next page of order items."""
append_to_base_uri = f"/{order_id}/orderItems"
data = dict(NextToken=next_token)
return self.make_request(append_to_base_uri=append_to_base_uri, params=data)
class CatalogItems(SPAPI):
- """ Amazon Catalog Items API """
+ """Amazon Catalog Items API"""
BASE_URI = "/catalog/v0"
- def get_catalog_item(self, asin: str, marketplace_id: str = None,) -> dict:
- """ Returns a specified item and its attributes. """
+ def get_catalog_item(
+ self,
+ asin: str,
+ marketplace_id: str | None = None,
+ ) -> dict:
+ """Returns a specified item and its attributes."""
if not marketplace_id:
marketplace_id = self.marketplace_id
diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.js b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.js
index 22b4a243c..d6cf3d5aa 100644
--- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.js
+++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.js
@@ -1,7 +1,7 @@
// Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Amazon SP API Settings', {
+frappe.ui.form.on("Amazon SP API Settings", {
refresh(frm) {
if (frm.doc.__islocal && !frm.doc.amazon_fields_map) {
frm.trigger("set_default_fields_map");
@@ -25,19 +25,19 @@ frappe.ui.form.on('Amazon SP API Settings', {
frm.set_query("warehouse", () => {
return {
filters: {
- "is_group": 0,
- "company": frm.doc.company,
- }
+ is_group: 0,
+ company: frm.doc.company,
+ },
};
});
frm.set_query("market_place_account_group", () => {
return {
filters: {
- "is_group": 1,
- "company": frm.doc.company,
- }
+ is_group: 1,
+ company: frm.doc.company,
+ },
};
});
- }
+ },
});
diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py
index 42ed32252..bcafd5ac7 100644
--- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py
+++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/amazon_sp_api_settings.py
@@ -32,7 +32,7 @@ def validate(self):
frappe.throw(frappe._("Value for Max Retry Limit must be less than or equal to 5."))
def save(self):
- super(AmazonSPAPISettings, self).save()
+ super().save()
if not self.is_old_data_migrated:
migrate_old_data()
@@ -96,8 +96,16 @@ def validate_credentials(self):
def set_default_fields_map(self):
for field_map in [
{"amazon_field": "ASIN", "item_field": "item_code", "use_to_find_item_code": 1},
- {"amazon_field": "SellerSKU", "item_field": None, "use_to_find_item_code": 0,},
- {"amazon_field": "Title", "item_field": None, "use_to_find_item_code": 0,},
+ {
+ "amazon_field": "SellerSKU",
+ "item_field": None,
+ "use_to_find_item_code": 0,
+ },
+ {
+ "amazon_field": "Title",
+ "item_field": None,
+ "use_to_find_item_code": 0,
+ },
]:
self.append("amazon_fields_map", field_map)
@@ -124,9 +132,7 @@ def get_order_details(self):
frappe.msgprint(_("Order details will be fetched in the background."))
else:
- frappe.msgprint(
- _("Please enable the Amazon SP API Settings {0}.").format(frappe.bold(self.name))
- )
+ frappe.msgprint(_("Please enable the Amazon SP API Settings {0}.").format(frappe.bold(self.name)))
# Called via a hook in every hour.
@@ -167,9 +173,7 @@ def migrate_old_data():
if column_exists:
item = frappe.qb.DocType("Item")
- items = (frappe.qb.from_(item).select("*").where(item.amazon_item_code.notnull())).run(
- as_dict=True
- )
+ items = (frappe.qb.from_(item).select("*").where(item.amazon_item_code.notnull())).run(as_dict=True)
for item in items:
if not frappe.db.exists("Ecommerce Item", {"erpnext_item_code": item.name}):
diff --git a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py
index 9f72487dc..9c0847831 100644
--- a/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py
+++ b/ecommerce_integrations/amazon/doctype/amazon_sp_api_settings/test_amazon_sp_api_settings.py
@@ -6,6 +6,7 @@
import os
import time
import unittest
+from typing import ClassVar
import frappe
import responses
@@ -30,7 +31,7 @@
)
file_path = os.path.join(os.path.dirname(__file__), "test_data.json")
-with open(file_path, "r") as json_file:
+with open(file_path) as json_file:
try:
DATA = json.load(json_file)
except json.decoder.JSONDecodeError as e:
@@ -38,13 +39,16 @@
class TestSPAPI(SPAPI):
-
# Expected response after hitting the URL.
- expected_response = {}
+ expected_response: ClassVar = {}
@responses.activate
def make_request(
- self, method: str = "GET", append_to_base_uri: str = "", params: dict = None, data: dict = None,
+ self,
+ method: str = "GET",
+ append_to_base_uri: str = "",
+ params: dict | None = None,
+ data: dict | None = None,
) -> object:
if isinstance(params, dict):
params = Util.remove_empty(params)
@@ -78,7 +82,7 @@ def make_request(
class TestFinances(Finances, TestSPAPI):
def list_financial_events_by_order_id(
- self, order_id: str, max_results: int = None, next_token: str = None
+ self, order_id: str, max_results: int | None = None, next_token: str | None = None
) -> object:
self.expected_response = DATA.get("list_financial_events_by_order_id_200")
return super().list_financial_events_by_order_id(order_id, max_results, next_token)
@@ -88,22 +92,22 @@ class TestOrders(Orders, TestSPAPI):
def get_orders(
self,
created_after: str,
- created_before: str = None,
- last_updated_after: str = None,
- last_updated_before: str = None,
- order_statuses: list = None,
- marketplace_ids: list = None,
- fulfillment_channels: list = None,
- payment_methods: list = None,
- buyer_email: str = None,
- seller_order_id: str = None,
+ created_before: str | None = None,
+ last_updated_after: str | None = None,
+ last_updated_before: str | None = None,
+ order_statuses: list | None = None,
+ marketplace_ids: list | None = None,
+ fulfillment_channels: list | None = None,
+ payment_methods: list | None = None,
+ buyer_email: str | None = None,
+ seller_order_id: str | None = None,
max_results: int = 100,
- easyship_shipment_statuses: list = None,
- next_token: str = None,
- amazon_order_ids: list = None,
- actual_fulfillment_supply_source_id: str = None,
+ easyship_shipment_statuses: list | None = None,
+ next_token: str | None = None,
+ amazon_order_ids: list | None = None,
+ actual_fulfillment_supply_source_id: str | None = None,
is_ispu: bool = False,
- store_chain_store_id: str = None,
+ store_chain_store_id: str | None = None,
) -> object:
self.expected_response = DATA.get("get_orders_200")
return super().get_orders(
@@ -126,13 +130,17 @@ def get_orders(
store_chain_store_id,
)
- def get_order_items(self, order_id: str, next_token: str = None) -> object:
+ def get_order_items(self, order_id: str, next_token: str | None = None) -> object:
self.expected_response = DATA.get("get_order_items_200")
return super().get_order_items(order_id, next_token)
class TestCatalogItems(CatalogItems, TestSPAPI):
- def get_catalog_item(self, asin: str, marketplace_id: str = None,) -> object:
+ def get_catalog_item(
+ self,
+ asin: str,
+ marketplace_id: str | None = None,
+ ) -> object:
self.expected_response = DATA.get("get_catalog_item_200")
return super().get_catalog_item(asin, marketplace_id)
@@ -163,7 +171,11 @@ def get_company():
def get_warehouse():
warehouse_name = frappe.db.get_value(
- "Warehouse", {"warehouse_name": "Amazon Test Warehouse",}, "warehouse_name"
+ "Warehouse",
+ {
+ "warehouse_name": "Amazon Test Warehouse",
+ },
+ "warehouse_name",
)
if not warehouse_name:
@@ -181,12 +193,19 @@ def get_warehouse():
def get_item_group():
item_group_name = frappe.db.get_value(
- "Item Group", {"item_group_name": "Amazon Test Warehouse",}, "item_group_name"
+ "Item Group",
+ {
+ "item_group_name": "Amazon Test Warehouse",
+ },
+ "item_group_name",
)
if not item_group_name:
item_group = frappe.get_doc(
- {"doctype": "Item Group", "item_group_name": "Amazon Test Warehouse",}
+ {
+ "doctype": "Item Group",
+ "item_group_name": "Amazon Test Warehouse",
+ }
)
item_group.insert(ignore_permissions=True)
item_group_name = item_group.item_group_name
@@ -235,7 +254,7 @@ def __init__(self) -> None:
def call_sp_api_method(self, sp_api_method, **kwargs):
max_retries = self.amz_setting.max_retry_limit
- for x in range(max_retries):
+ for _x in range(max_retries):
try:
result = sp_api_method(**kwargs)
return result.get("payload")
diff --git a/ecommerce_integrations/controllers/customer.py b/ecommerce_integrations/controllers/customer.py
index b6b231c4d..4c543b7ac 100644
--- a/ecommerce_integrations/controllers/customer.py
+++ b/ecommerce_integrations/controllers/customer.py
@@ -1,5 +1,3 @@
-from typing import Dict
-
import frappe
from frappe import _
from frappe.utils.nestedset import get_root_of
@@ -50,7 +48,7 @@ def get_customer_address_doc(self, address_type: str):
except frappe.DoesNotExistError:
return None
- def create_customer_address(self, address: Dict[str, str]) -> None:
+ def create_customer_address(self, address: dict[str, str]) -> None:
"""Create address from dictionary containing fields used in Address doctype of ERPNext."""
customer_doc = self.get_customer_doc()
@@ -63,7 +61,7 @@ def create_customer_address(self, address: Dict[str, str]) -> None:
}
).insert(ignore_mandatory=True)
- def create_customer_contact(self, contact: Dict[str, str]) -> None:
+ def create_customer_contact(self, contact: dict[str, str]) -> None:
"""Create contact from dictionary containing fields used in Address doctype of ERPNext."""
customer_doc = self.get_customer_doc()
diff --git a/ecommerce_integrations/controllers/inventory.py b/ecommerce_integrations/controllers/inventory.py
index 3438086f7..d75d32b5e 100644
--- a/ecommerce_integrations/controllers/inventory.py
+++ b/ecommerce_integrations/controllers/inventory.py
@@ -1,12 +1,12 @@
-from typing import List, Tuple
-
import frappe
from frappe import _dict
+from frappe.query_builder import DocType
+from frappe.query_builder.functions import Max, Sum
from frappe.utils import now
from frappe.utils.nestedset import get_descendants_of
-def get_inventory_levels(warehouses: Tuple[str], integration: str) -> List[_dict]:
+def get_inventory_levels(warehouses: tuple[str], integration: str) -> list[_dict]:
"""
Get list of dict containing items for which the inventory needs to be updated on Integeration.
@@ -16,21 +16,30 @@ def get_inventory_levels(warehouses: Tuple[str], integration: str) -> List[_dict
returns: list of _dict containing ecom_item, item_code, integration_item_code, variant_id, actual_qty, warehouse, reserved_qty
"""
- data = frappe.db.sql(
- f"""
- SELECT ei.name as ecom_item, bin.item_code as item_code, integration_item_code, variant_id, actual_qty, warehouse, reserved_qty
- FROM `tabEcommerce Item` ei
- JOIN tabBin bin
- ON ei.erpnext_item_code = bin.item_code
- WHERE bin.warehouse in ({', '.join('%s' for _ in warehouses)})
- AND bin.modified > ei.inventory_synced_on
- AND ei.integration = %s
- """,
- values=warehouses + (integration,),
- as_dict=1,
+ EcommerceItem = DocType("Ecommerce Item")
+ Bin = DocType("Bin")
+
+ query = (
+ frappe.qb.from_(EcommerceItem)
+ .join(Bin)
+ .on(EcommerceItem.erpnext_item_code == Bin.item_code)
+ .select(
+ EcommerceItem.name.as_("ecom_item"),
+ Bin.item_code.as_("item_code"),
+ EcommerceItem.integration_item_code,
+ EcommerceItem.variant_id,
+ Bin.actual_qty,
+ Bin.warehouse,
+ Bin.reserved_qty,
+ )
+ .where(
+ (Bin.warehouse.isin(warehouses))
+ & (Bin.modified > EcommerceItem.inventory_synced_on)
+ & (EcommerceItem.integration == integration)
+ )
)
- return data
+ return query.run(as_dict=1)
def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str):
@@ -40,31 +49,32 @@ def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str):
leaf warehouses is required"""
child_warehouse = get_descendants_of("Warehouse", warehouse)
- all_warehouses = tuple(child_warehouse) + (warehouse,)
-
- data = frappe.db.sql(
- f"""
- SELECT ei.name as ecom_item, bin.item_code as item_code,
- integration_item_code,
- variant_id,
- sum(actual_qty) as actual_qty,
- sum(reserved_qty) as reserved_qty,
- max(bin.modified) as last_updated,
- max(ei.inventory_synced_on) as last_synced
- FROM `tabEcommerce Item` ei
- JOIN tabBin bin
- ON ei.erpnext_item_code = bin.item_code
- WHERE bin.warehouse in ({', '.join(['%s'] * len(all_warehouses))})
- AND integration = %s
- GROUP BY
- ei.erpnext_item_code
- HAVING
- last_updated > last_synced
- """,
- values=all_warehouses + (integration,),
- as_dict=1,
+ all_warehouses = (*tuple(child_warehouse), warehouse)
+
+ EcommerceItem = DocType("Ecommerce Item")
+ Bin = DocType("Bin")
+
+ query = (
+ frappe.qb.from_(EcommerceItem)
+ .join(Bin)
+ .on(EcommerceItem.erpnext_item_code == Bin.item_code)
+ .select(
+ EcommerceItem.name.as_("ecom_item"),
+ Bin.item_code.as_("item_code"),
+ EcommerceItem.integration_item_code,
+ EcommerceItem.variant_id,
+ Sum(Bin.actual_qty).as_("actual_qty"),
+ Sum(Bin.reserved_qty).as_("reserved_qty"),
+ Max(Bin.modified).as_("last_updated"),
+ Max(EcommerceItem.inventory_synced_on).as_("last_synced"),
+ )
+ .where((Bin.warehouse.isin(all_warehouses)) & (EcommerceItem.integration == integration))
+ .groupby(EcommerceItem.erpnext_item_code)
+ .having(Max(Bin.modified) > Max(EcommerceItem.inventory_synced_on))
)
+ data = query.run(as_dict=1)
+
# add warehouse as group warehouse for sending to integrations
for item in data:
item.warehouse = warehouse
diff --git a/ecommerce_integrations/controllers/scheduling.py b/ecommerce_integrations/controllers/scheduling.py
index b97b4f81e..9b59e30ae 100644
--- a/ecommerce_integrations/controllers/scheduling.py
+++ b/ecommerce_integrations/controllers/scheduling.py
@@ -16,9 +16,7 @@ def need_to_run(setting, interval_field, timestamp_field) -> bool:
interval = frappe.db.get_single_value(setting, interval_field, cache=True)
last_run = frappe.db.get_single_value(setting, timestamp_field)
- if last_run and get_datetime() < get_datetime(
- add_to_date(last_run, minutes=cint(interval, default=10))
- ):
+ if last_run and get_datetime() < get_datetime(add_to_date(last_run, minutes=cint(interval, default=10))):
return False
frappe.db.set_value(setting, None, timestamp_field, now(), update_modified=False)
diff --git a/ecommerce_integrations/controllers/setting.py b/ecommerce_integrations/controllers/setting.py
index 7b48c66b7..8f6e0da29 100644
--- a/ecommerce_integrations/controllers/setting.py
+++ b/ecommerce_integrations/controllers/setting.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, NewType
+from typing import NewType
from frappe.model.document import Document
@@ -11,11 +11,11 @@ def is_enabled(self) -> bool:
"""Check if integration is enabled or not."""
raise NotImplementedError()
- def get_erpnext_warehouses(self) -> List[ERPNextWarehouse]:
+ def get_erpnext_warehouses(self) -> list[ERPNextWarehouse]:
raise NotImplementedError()
- def get_erpnext_to_integration_wh_mapping(self) -> Dict[ERPNextWarehouse, IntegrationWarehouse]:
+ def get_erpnext_to_integration_wh_mapping(self) -> dict[ERPNextWarehouse, IntegrationWarehouse]:
raise NotImplementedError()
- def get_integration_to_erpnext_wh_mapping(self) -> Dict[IntegrationWarehouse, ERPNextWarehouse]:
+ def get_integration_to_erpnext_wh_mapping(self) -> dict[IntegrationWarehouse, ERPNextWarehouse]:
raise NotImplementedError()
diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.js b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.js
index c77422b94..5e55650ce 100644
--- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.js
+++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.js
@@ -1,22 +1,22 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see LICENSE
-frappe.ui.form.on('Ecommerce Integration Log', {
- refresh: function(frm) {
- if (frm.doc.request_data && frm.doc.status=='Error'){
- frm.add_custom_button('Retry', function() {
+frappe.ui.form.on("Ecommerce Integration Log", {
+ refresh: function (frm) {
+ if (frm.doc.request_data && frm.doc.status == "Error") {
+ frm.add_custom_button(__("Retry"), function () {
frappe.call({
- method:"ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_integration_log.ecommerce_integration_log.resync",
- args:{
- method:frm.doc.method,
+ method: "ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_integration_log.ecommerce_integration_log.resync",
+ args: {
+ method: frm.doc.method,
name: frm.doc.name,
- request_data: frm.doc.request_data
+ request_data: frm.doc.request_data,
},
- callback: function(r){
- frappe.msgprint(__("Reattempting to sync"))
- }
- })
- }).addClass('btn-primary');
+ callback: function (r) {
+ frappe.msgprint(__("Reattempting to sync"));
+ },
+ });
+ }).addClass("btn-primary");
}
- }
+ },
});
diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py
index 4a9f67231..c5ed9f380 100644
--- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py
+++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_integration_log/ecommerce_integration_log.py
@@ -33,7 +33,7 @@ def _set_title(self):
def clear_old_logs(days=90):
table = frappe.qb.DocType("Ecommerce Integration Log")
frappe.db.delete(
- table, filters=((table.modified < (Now() - Interval(days=days)))) & (table.status == "Success")
+ table, filters=(table.modified < (Now() - Interval(days=days))) & (table.status == "Success")
)
diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.js b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.js
index e4774864d..bedbf38f8 100644
--- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.js
+++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see LICENSE
-frappe.ui.form.on('Ecommerce Item', {
+frappe.ui.form.on("Ecommerce Item", {
// refresh: function(frm) {
-
// }
});
diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py
index 423a26829..81985d80c 100644
--- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py
+++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py
@@ -1,8 +1,6 @@
# Copyright (c) 2021, Frappe and contributors
# For license information, please see LICENSE
-from typing import Dict, Optional
-
import frappe
from erpnext import get_default_company
from frappe import _
@@ -56,8 +54,8 @@ def set_defaults(self):
def is_synced(
integration: str,
integration_item_code: str,
- variant_id: Optional[str] = None,
- sku: Optional[str] = None,
+ variant_id: str | None = None,
+ sku: str | None = None,
) -> bool:
"""Check if item is synced from integration.
@@ -86,9 +84,9 @@ def _is_sku_synced(integration: str, sku: str) -> bool:
def get_erpnext_item_code(
integration: str,
integration_item_code: str,
- variant_id: Optional[str] = None,
- has_variants: Optional[int] = 0,
-) -> Optional[str]:
+ variant_id: str | None = None,
+ has_variants: int | None = 0,
+) -> str | None:
filters = {"integration": integration, "integration_item_code": integration_item_code}
if variant_id:
filters.update({"variant_id": variant_id})
@@ -101,9 +99,9 @@ def get_erpnext_item_code(
def get_erpnext_item(
integration: str,
integration_item_code: str,
- variant_id: Optional[str] = None,
- sku: Optional[str] = None,
- has_variants: Optional[int] = 0,
+ variant_id: str | None = None,
+ sku: str | None = None,
+ has_variants: int | None = 0,
):
"""Get ERPNext item for specified ecommerce_item.
@@ -127,10 +125,10 @@ def get_erpnext_item(
def create_ecommerce_item(
integration: str,
integration_item_code: str,
- item_dict: Dict,
- variant_id: Optional[str] = None,
- sku: Optional[str] = None,
- variant_of: Optional[str] = None,
+ item_dict: dict,
+ variant_id: str | None = None,
+ sku: str | None = None,
+ variant_of: str | None = None,
has_variants=0,
) -> None:
"""Create Item in erpnext and link it with Ecommerce item doctype.
diff --git a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item_list.js b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item_list.js
index 68165a8ae..eef48f22c 100644
--- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item_list.js
+++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item_list.js
@@ -1,3 +1,3 @@
frappe.listview_settings["Ecommerce Item"] = {
hide_name_column: true,
-}
+};
diff --git a/ecommerce_integrations/hooks.py b/ecommerce_integrations/hooks.py
index face21c3f..c2ebc85c2 100644
--- a/ecommerce_integrations/hooks.py
+++ b/ecommerce_integrations/hooks.py
@@ -137,9 +137,7 @@
scheduler_events = {
"all": ["ecommerce_integrations.shopify.inventory.update_inventory_on_shopify"],
"daily": [],
- "daily_long": [
- "ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.sync_stocks"
- ],
+ "daily_long": ["ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.sync_stocks"],
"hourly": [
"ecommerce_integrations.shopify.order.sync_old_orders",
"ecommerce_integrations.amazon.doctype.amazon_sp_api_settings.amazon_sp_api_settings.schedule_get_order_details",
diff --git a/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py b/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py
index 9bf161bcf..5fcebd20d 100644
--- a/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py
+++ b/ecommerce_integrations/patches/set_default_amazon_item_fields_map.py
@@ -6,8 +6,16 @@ def execute():
default_fields_map = [
{"amazon_field": "ASIN", "item_field": "item_code", "use_to_find_item_code": 1},
- {"amazon_field": "SellerSKU", "item_field": None, "use_to_find_item_code": 0,},
- {"amazon_field": "Title", "item_field": None, "use_to_find_item_code": 0,},
+ {
+ "amazon_field": "SellerSKU",
+ "item_field": None,
+ "use_to_find_item_code": 0,
+ },
+ {
+ "amazon_field": "Title",
+ "item_field": None,
+ "use_to_find_item_code": 0,
+ },
]
amz_settings = frappe.db.get_all("Amazon SP API Settings", pluck="name")
diff --git a/ecommerce_integrations/public/js/shopify/old_settings.js b/ecommerce_integrations/public/js/shopify/old_settings.js
index f7b0491cd..dc3c39ba4 100644
--- a/ecommerce_integrations/public/js/shopify/old_settings.js
+++ b/ecommerce_integrations/public/js/shopify/old_settings.js
@@ -1,8 +1,7 @@
-
-frappe.ui.form.on('Shopify Settings', {
- onload_post_render: function(frm) {
- let msg = __("You have Ecommerce Integration app installed.") + " "
- msg += __("This setting page refers to old Shopify connector.")
+frappe.ui.form.on("Shopify Settings", {
+ onload_post_render: function (frm) {
+ let msg = __("You have Ecommerce Integration app installed.") + " ";
+ msg += __("This setting page refers to old Shopify connector.");
frappe.msgprint(msg);
- }
+ },
});
diff --git a/ecommerce_integrations/public/js/unicommerce/item.js b/ecommerce_integrations/public/js/unicommerce/item.js
index e0f049bc2..cef5e9571 100644
--- a/ecommerce_integrations/public/js/unicommerce/item.js
+++ b/ecommerce_integrations/public/js/unicommerce/item.js
@@ -5,8 +5,7 @@ frappe.ui.form.on("Item", {
__("Open Unicommerce Item"),
function () {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
+ method: "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
args: {
code: frm.doc.item_code,
doctype: frm.doc.doctype,
diff --git a/ecommerce_integrations/public/js/unicommerce/pick_list.js b/ecommerce_integrations/public/js/unicommerce/pick_list.js
index 679bace94..5f09cafa8 100644
--- a/ecommerce_integrations/public/js/unicommerce/pick_list.js
+++ b/ecommerce_integrations/public/js/unicommerce/pick_list.js
@@ -1,47 +1,51 @@
-frappe.ui.form.on('Pick List', {
- refresh(frm){
- if (frm.doc.order_details){
- frm.add_custom_button(__('Generate Invoice'), () => frm.trigger('generate_invoice'))
- }
- },
- generate_invoice(frm){
- let selected_so = []
- var tbl = frm.doc.order_details || [];
- for(var i = 0; i < tbl.length; i++) {
- selected_so.push(tbl[i].sales_order)
- }
- let sales_orders = [];
- let so_item_list = [];
- const warehouse_allocation = {};
- selected_so.forEach(function(so) {
- const item_details = frm.doc.locations.map((item) => {
- if (item.sales_order == so && item.picked_qty > 0){
- so_item_list.push({so_item:item.sales_order_item,
- qty:item.qty
- });
- return {
- sales_order_row: item.sales_order_item,
- item_code: item.item_code,
- warehouse: item.warehouse,
- shelf:item.shelf
- }
- }
- else{
- return {}
- }
- });
- sales_orders.push(so);
- warehouse_allocation[so] = item_details.filter(value => Object.keys(value).length !== 0);
- });
- frappe.call({
- method: 'ecommerce_integrations.unicommerce.invoice.generate_unicommerce_invoices',
- args: {
- 'sales_orders': sales_orders,
- 'warehouse_allocation': warehouse_allocation
- },
- freeze: true,
- freeze_message: "Requesting Invoice generation. Once synced, invoice will appear in linked documents.",
- });
-
- },
-})
+frappe.ui.form.on("Pick List", {
+ refresh(frm) {
+ if (frm.doc.order_details) {
+ frm.add_custom_button(__("Generate Invoice"), () =>
+ frm.trigger("generate_invoice")
+ );
+ }
+ },
+ generate_invoice(frm) {
+ let selected_so = [];
+ var tbl = frm.doc.order_details || [];
+ for (var i = 0; i < tbl.length; i++) {
+ selected_so.push(tbl[i].sales_order);
+ }
+ let sales_orders = [];
+ let so_item_list = [];
+ const warehouse_allocation = {};
+ selected_so.forEach(function (so) {
+ const item_details = frm.doc.locations.map((item) => {
+ if (item.sales_order == so && item.picked_qty > 0) {
+ so_item_list.push({
+ so_item: item.sales_order_item,
+ qty: item.qty,
+ });
+ return {
+ sales_order_row: item.sales_order_item,
+ item_code: item.item_code,
+ warehouse: item.warehouse,
+ shelf: item.shelf,
+ };
+ } else {
+ return {};
+ }
+ });
+ sales_orders.push(so);
+ warehouse_allocation[so] = item_details.filter(
+ (value) => Object.keys(value).length !== 0
+ );
+ });
+ frappe.call({
+ method: "ecommerce_integrations.unicommerce.invoice.generate_unicommerce_invoices",
+ args: {
+ sales_orders: sales_orders,
+ warehouse_allocation: warehouse_allocation,
+ },
+ freeze: true,
+ freeze_message:
+ "Requesting Invoice generation. Once synced, invoice will appear in linked documents.",
+ });
+ },
+});
diff --git a/ecommerce_integrations/public/js/unicommerce/sales_invoice.js b/ecommerce_integrations/public/js/unicommerce/sales_invoice.js
index d3b4e96cd..471167f27 100644
--- a/ecommerce_integrations/public/js/unicommerce/sales_invoice.js
+++ b/ecommerce_integrations/public/js/unicommerce/sales_invoice.js
@@ -5,8 +5,7 @@ frappe.ui.form.on("Sales Invoice", {
__("Open Unicommerce Order"),
function () {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
+ method: "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
args: {
code: frm.doc.unicommerce_order_code,
doctype: frm.doc.doctype,
diff --git a/ecommerce_integrations/public/js/unicommerce/sales_order.js b/ecommerce_integrations/public/js/unicommerce/sales_order.js
index 5e9852aac..ce803da3f 100644
--- a/ecommerce_integrations/public/js/unicommerce/sales_order.js
+++ b/ecommerce_integrations/public/js/unicommerce/sales_order.js
@@ -6,8 +6,7 @@ frappe.ui.form.on("Sales Order", {
__("Open Unicommerce Order"),
function () {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
+ method: "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
args: {
code: frm.doc.unicommerce_order_code,
doctype: frm.doc.doctype,
@@ -22,7 +21,11 @@ frappe.ui.form.on("Sales Order", {
__("Unicommerce")
);
}
- if (frm.doc.unicommerce_order_code && frm.doc.docstatus == 1 && flt(frm.doc.per_billed, 6) < 100) {
+ if (
+ frm.doc.unicommerce_order_code &&
+ frm.doc.docstatus == 1 &&
+ flt(frm.doc.per_billed, 6) < 100
+ ) {
// remove default button
frm.remove_custom_button("Sales Invoice", "Create");
const so_code = frm.doc.name;
@@ -33,7 +36,7 @@ frappe.ui.form.on("Sales Order", {
sales_order_row: item.name,
item_code: item.item_code,
warehouse: item.warehouse,
- }
+ };
});
const warehouse_allocation = {};
@@ -43,17 +46,17 @@ frappe.ui.form.on("Sales Order", {
__("Generate Invoice"),
function () {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.invoice.generate_unicommerce_invoices",
+ method: "ecommerce_integrations.unicommerce.invoice.generate_unicommerce_invoices",
args: {
sales_orders: [so_code],
warehouse_allocation: warehouse_allocation,
},
freeze: true,
- freeze_message: "Requesting Invoice generation. Once synced, invoice will appear in linked documents.",
+ freeze_message:
+ "Requesting Invoice generation. Once synced, invoice will appear in linked documents.",
callback: function (r) {
frm.reload_doc();
- }
+ },
});
},
__("Unicommerce")
diff --git a/ecommerce_integrations/public/js/unicommerce/stock_entry.js b/ecommerce_integrations/public/js/unicommerce/stock_entry.js
index acfee9fa4..50ba51663 100644
--- a/ecommerce_integrations/public/js/unicommerce/stock_entry.js
+++ b/ecommerce_integrations/public/js/unicommerce/stock_entry.js
@@ -5,10 +5,9 @@ frappe.ui.form.on("Stock Entry", {
__("Open GRNs"),
function () {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
+ method: "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
args: {
- code: '',
+ code: "",
doctype: frm.doc.doctype,
},
callback: function (r) {
diff --git a/ecommerce_integrations/shopify/connection.py b/ecommerce_integrations/shopify/connection.py
index 003b51ee1..4a4c7c86d 100644
--- a/ecommerce_integrations/shopify/connection.py
+++ b/ecommerce_integrations/shopify/connection.py
@@ -3,7 +3,6 @@
import hashlib
import hmac
import json
-from typing import List
import frappe
from frappe import _
@@ -24,7 +23,6 @@ def temp_shopify_session(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
-
# no auth in testing
if frappe.flags.in_test:
return func(*args, **kwargs)
@@ -39,7 +37,7 @@ def wrapper(*args, **kwargs):
return wrapper
-def register_webhooks(shopify_url: str, password: str) -> List[Webhook]:
+def register_webhooks(shopify_url: str, password: str) -> list[Webhook]:
"""Register required webhooks with shopify and return registered webhooks."""
new_webhooks = []
@@ -54,7 +52,9 @@ def register_webhooks(shopify_url: str, password: str) -> List[Webhook]:
new_webhooks.append(webhook)
else:
create_shopify_log(
- status="Error", response_data=webhook.to_dict(), exception=webhook.errors.full_messages(),
+ status="Error",
+ response_data=webhook.to_dict(),
+ exception=webhook.errors.full_messages(),
)
return new_webhooks
@@ -65,7 +65,6 @@ def unregister_webhooks(shopify_url: str, password: str) -> None:
url = get_current_domain_name()
with Session.temp(shopify_url, API_VERSION, password):
-
for webhook in Webhook.find():
if url in webhook.address:
webhook.destroy()
@@ -106,7 +105,6 @@ def store_request_data() -> None:
def process_request(data, event):
-
# create log
log = create_shopify_log(method=EVENT_MAPPER[event], request_data=data)
diff --git a/ecommerce_integrations/shopify/customer.py b/ecommerce_integrations/shopify/customer.py
index e4b4b9bc2..3a0ee952f 100644
--- a/ecommerce_integrations/shopify/customer.py
+++ b/ecommerce_integrations/shopify/customer.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, Optional
+from typing import Any
import frappe
from frappe import _
@@ -18,7 +18,7 @@ def __init__(self, customer_id: str):
self.setting = frappe.get_doc(SETTING_DOCTYPE)
super().__init__(customer_id, CUSTOMER_ID_FIELD, MODULE_NAME)
- def sync_customer(self, customer: Dict[str, Any]) -> None:
+ def sync_customer(self, customer: dict[str, Any]) -> None:
"""Create Customer in ERPNext using shopify's Customer dict."""
customer_name = cstr(customer.get("first_name")) + " " + cstr(customer.get("last_name"))
@@ -45,9 +45,9 @@ def sync_customer(self, customer: Dict[str, Any]) -> None:
def create_customer_address(
self,
customer_name,
- shopify_address: Dict[str, Any],
+ shopify_address: dict[str, Any],
address_type: str = "Billing",
- email: Optional[str] = None,
+ email: str | None = None,
) -> None:
"""Create customer address(es) using Customer dict provided by shopify."""
address_fields = _map_address_fields(shopify_address, customer_name, address_type, email)
@@ -68,9 +68,9 @@ def update_existing_addresses(self, customer):
def _update_existing_address(
self,
customer_name,
- shopify_address: Dict[str, Any],
+ shopify_address: dict[str, Any],
address_type: str = "Billing",
- email: Optional[str] = None,
+ email: str | None = None,
) -> None:
old_address = self.get_customer_address_doc(address_type)
@@ -84,8 +84,7 @@ def _update_existing_address(
old_address.flags.ignore_mandatory = True
old_address.save()
- def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None:
-
+ def create_customer_contact(self, shopify_customer: dict[str, Any]) -> None:
if not (shopify_customer.get("first_name") and shopify_customer.get("email")):
return
@@ -99,9 +98,7 @@ def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None:
if shopify_customer.get("email"):
contact_fields["email_ids"] = [{"email_id": shopify_customer.get("email"), "is_primary": True}]
- phone_no = shopify_customer.get("phone") or shopify_customer.get("default_address", {}).get(
- "phone"
- )
+ phone_no = shopify_customer.get("phone") or shopify_customer.get("default_address", {}).get("phone")
if validate_phone_number(phone_no, throw=False):
contact_fields["phone_nos"] = [{"phone": phone_no, "is_primary_phone": True}]
@@ -110,7 +107,7 @@ def create_customer_contact(self, shopify_customer: Dict[str, Any]) -> None:
def _map_address_fields(shopify_address, customer_name, address_type, email):
- """ returns dict with shopify address fields mapped to equivalent ERPNext fields"""
+ """returns dict with shopify address fields mapped to equivalent ERPNext fields"""
address_fields = {
"address_title": customer_name,
"address_type": address_type,
diff --git a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py
index 2fd82ba94..dc974e70e 100644
--- a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py
+++ b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py
@@ -1,8 +1,6 @@
# Copyright (c) 2021, Frappe and contributors
# For license information, please see LICENSE
-from typing import Dict, List
-
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
@@ -94,19 +92,17 @@ def update_location_table(self):
{"shopify_location_id": location.id, "shopify_location_name": location.name},
)
- def get_erpnext_warehouses(self) -> List[ERPNextWarehouse]:
+ def get_erpnext_warehouses(self) -> list[ERPNextWarehouse]:
return [wh_map.erpnext_warehouse for wh_map in self.shopify_warehouse_mapping]
- def get_erpnext_to_integration_wh_mapping(self) -> Dict[ERPNextWarehouse, IntegrationWarehouse]:
+ def get_erpnext_to_integration_wh_mapping(self) -> dict[ERPNextWarehouse, IntegrationWarehouse]:
return {
- wh_map.erpnext_warehouse: wh_map.shopify_location_id
- for wh_map in self.shopify_warehouse_mapping
+ wh_map.erpnext_warehouse: wh_map.shopify_location_id for wh_map in self.shopify_warehouse_mapping
}
- def get_integration_to_erpnext_wh_mapping(self) -> Dict[IntegrationWarehouse, ERPNextWarehouse]:
+ def get_integration_to_erpnext_wh_mapping(self) -> dict[IntegrationWarehouse, ERPNextWarehouse]:
return {
- wh_map.shopify_location_id: wh_map.erpnext_warehouse
- for wh_map in self.shopify_warehouse_mapping
+ wh_map.shopify_location_id: wh_map.erpnext_warehouse for wh_map in self.shopify_warehouse_mapping
}
diff --git a/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py b/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py
index 5277352cf..ab05370b9 100644
--- a/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py
+++ b/ecommerce_integrations/shopify/doctype/shopify_setting/test_shopify_setting.py
@@ -29,7 +29,6 @@ def setUpClass(cls):
)
def test_custom_field_creation(self):
-
setup_custom_fields()
created_fields = frappe.get_all(
diff --git a/ecommerce_integrations/shopify/fulfillment.py b/ecommerce_integrations/shopify/fulfillment.py
index 97a6706eb..5ffc0ebae 100644
--- a/ecommerce_integrations/shopify/fulfillment.py
+++ b/ecommerce_integrations/shopify/fulfillment.py
@@ -41,7 +41,6 @@ def create_delivery_note(shopify_order, setting, so):
not frappe.db.get_value("Delivery Note", {FULLFILLMENT_ID_FIELD: fulfillment.get("id")}, "name")
and so.docstatus == 1
):
-
dn = make_delivery_note(so.name)
setattr(dn, ORDER_ID_FIELD, fulfillment.get("order_id"))
setattr(dn, ORDER_NUMBER_FIELD, shopify_order.get("name"))
@@ -82,8 +81,6 @@ def find_matching_fullfilement_item(dn_item):
for dn_item in dn_items:
if shopify_item := find_matching_fullfilement_item(dn_item):
- final_items.append(
- dn_item.update({"qty": shopify_item.get("quantity"), "warehouse": warehouse})
- )
+ final_items.append(dn_item.update({"qty": shopify_item.get("quantity"), "warehouse": warehouse}))
return final_items
diff --git a/ecommerce_integrations/shopify/invoice.py b/ecommerce_integrations/shopify/invoice.py
index 26afb8258..f841cb416 100644
--- a/ecommerce_integrations/shopify/invoice.py
+++ b/ecommerce_integrations/shopify/invoice.py
@@ -37,7 +37,6 @@ def create_sales_invoice(shopify_order, setting, so):
and not so.per_billed
and cint(setting.sync_sales_invoice)
):
-
posting_date = getdate(shopify_order.get("created_at")) or nowdate()
sales_invoice = make_sales_invoice(so.name, ignore_permissions=True)
diff --git a/ecommerce_integrations/shopify/order.py b/ecommerce_integrations/shopify/order.py
index 431c431cf..0570d035b 100644
--- a/ecommerce_integrations/shopify/order.py
+++ b/ecommerce_integrations/shopify/order.py
@@ -172,7 +172,6 @@ def get_order_items(order_items, setting, delivery_date, taxes_inclusive):
def _get_item_price(line_item, taxes_inclusive: bool) -> float:
-
price = flt(line_item.get("price"))
qty = cint(line_item.get("quantity"))
@@ -206,7 +205,8 @@ def get_order_taxes(shopify_order, setting, items):
"charge_type": "Actual",
"account_head": get_tax_account_head(tax, charge_type="sales_tax"),
"description": (
- get_tax_account_description(tax) or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%"
+ get_tax_account_description(tax)
+ or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%"
),
"tax_amount": tax.get("price"),
"included_in_print_rate": 0,
@@ -259,11 +259,13 @@ def consolidate_order_taxes(taxes):
return tax_account_wise_data.values()
-def get_tax_account_head(tax, charge_type: Optional[Literal["shipping", "sales_tax"]] = None):
+def get_tax_account_head(tax, charge_type: Literal["shipping", "sales_tax"] | None = None):
tax_title = str(tax.get("title"))
tax_account = frappe.db.get_value(
- "Shopify Tax Account", {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title}, "tax_account",
+ "Shopify Tax Account",
+ {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title},
+ "tax_account",
)
if not tax_account and charge_type:
@@ -279,7 +281,9 @@ def get_tax_account_description(tax):
tax_title = tax.get("title")
tax_description = frappe.db.get_value(
- "Shopify Tax Account", {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title}, "tax_description",
+ "Shopify Tax Account",
+ {"parent": SETTING_DOCTYPE, "shopify_tax": tax_title},
+ "tax_description",
)
return tax_description
@@ -317,7 +321,8 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, setting, items, taxe
{
"charge_type": "Actual",
"account_head": get_tax_account_head(shipping_charge, charge_type="shipping"),
- "description": get_tax_account_description(shipping_charge) or shipping_charge["title"],
+ "description": get_tax_account_description(shipping_charge)
+ or shipping_charge["title"],
"tax_amount": shipping_charge_amount,
"cost_center": setting.cost_center,
}
@@ -329,7 +334,8 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, setting, items, taxe
"charge_type": "Actual",
"account_head": get_tax_account_head(tax, charge_type="sales_tax"),
"description": (
- get_tax_account_description(tax) or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%"
+ get_tax_account_description(tax)
+ or f"{tax.get('title')} - {tax.get('rate') * 100.0:.2f}%"
),
"tax_amount": tax["price"],
"cost_center": setting.cost_center,
diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js
index ca95228a2..62db3c216 100644
--- a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js
+++ b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js
@@ -1,25 +1,22 @@
-frappe.provide('shopify');
+frappe.provide("shopify");
-frappe.pages['shopify-import-products'].on_page_load = function (wrapper) {
+frappe.pages["shopify-import-products"].on_page_load = function (wrapper) {
let page = frappe.ui.make_app_page({
parent: wrapper,
- title: 'Import Shopify Products',
- single_column: true
+ title: "Import Shopify Products",
+ single_column: true,
});
-
+ // eslint-disable-next-line no-undef
new shopify.ProductImporter(wrapper);
+};
-}
-
+// eslint-disable-next-line no-undef
shopify.ProductImporter = class {
-
constructor(wrapper) {
-
- this.wrapper = $(wrapper).find('.layout-main-section');
+ this.wrapper = $(wrapper).find(".layout-main-section");
this.page = wrapper.page;
this.init();
this.syncRunning = false;
-
}
init() {
@@ -33,18 +30,21 @@ shopify.ProductImporter = class {
}
async checkSyncStatus() {
- const jobs = await frappe.db.get_list("RQ Job", {filters: {"status": ("in", ("queued", "started"))}});
- this.syncRunning = jobs.find(job => job.job_name == 'shopify.job.sync.all.products') !== undefined;
+ const jobs = await frappe.db.get_list("RQ Job", {
+ filters: { status: ("in", ("queued", "started")) },
+ });
+ this.syncRunning =
+ jobs.find(
+ (job) => job.job_name == "shopify.job.sync.all.products"
+ ) !== undefined;
if (this.syncRunning) {
this.toggleSyncAllButton();
this.logSync();
}
-
}
addMarkup() {
-
const _markup = $(`
${message}`;
- _log.append(message);
- _log.scrollTop(_log[0].scrollHeight)
-
- if (synced) this.updateSyncedCount(_syncedCounter, _erpnextCounter);
-
- if (done) {
- frappe.realtime.off('shopify.key.sync.all.products');
- this.toggleSyncAllButton(false);
- this.fetchProductCount();
- this.syncRunning = false;
+ const _syncedCounter = $("#count-products-synced");
+ const _erpnextCounter = $("#count-products-erpnext");
+
+ frappe.realtime.on(
+ "shopify.key.sync.all.products",
+ ({ message, synced, done, error }) => {
+ message = `${message}`;
+ _log.append(message);
+ _log.scrollTop(_log[0].scrollHeight);
+
+ if (synced)
+ this.updateSyncedCount(_syncedCounter, _erpnextCounter);
+
+ if (done) {
+ frappe.realtime.off("shopify.key.sync.all.products");
+ this.toggleSyncAllButton(false);
+ this.fetchProductCount();
+ this.syncRunning = false;
+ }
}
-
- })
-
+ );
}
toggleSyncAllButton(disable = true) {
+ const btn = $("#btn-sync-all");
- const btn = $('#btn-sync-all');
+ const _toggleClass = (d) => (d ? "btn-success" : "btn-primary");
+ const _toggleText = () => (disable ? "Syncing..." : "Sync Products");
- const _toggleClass = d => d ? 'btn-success' : 'btn-primary';
- const _toggleText = () => disable ? 'Syncing...' : 'Sync Products';
-
- btn.prop('disabled', disable)
+ btn.prop("disabled", disable)
.addClass(_toggleClass(disable))
.removeClass(_toggleClass(!disable))
.text(_toggleText());
-
}
updateSyncedCount(_syncedCounter, _erpnextCounter) {
@@ -369,6 +357,5 @@ shopify.ProductImporter = class {
_syncedCounter.text(_synced + 1);
_erpnextCounter.text(_erpnext + 1);
-
}
-}
+};
diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py
index ad2f94f3a..e30a102e9 100644
--- a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py
+++ b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.py
@@ -119,7 +119,10 @@ def is_synced(product):
@frappe.whitelist()
def import_all_products():
frappe.enqueue(
- queue_sync_all_products, queue="long", job_name=SYNC_JOB_NAME, key=REALTIME_KEY,
+ queue_sync_all_products,
+ queue="long",
+ job_name=SYNC_JOB_NAME,
+ key=REALTIME_KEY,
)
@@ -150,12 +153,12 @@ def queue_sync_all_products(*args, **kwargs):
publish(f"✅ Synced Product {product.id}", synced=True)
except UniqueValidationError as e:
- publish(f"❌ Error Syncing Product {product.id} : {str(e)}", error=True)
+ publish(f"❌ Error Syncing Product {product.id} : {e!s}", error=True)
frappe.db.rollback(save_point=savepoint)
continue
except Exception as e:
- publish(f"❌ Error Syncing Product {product.id} : {str(e)}", error=True)
+ publish(f"❌ Error Syncing Product {product.id} : {e!s}", error=True)
frappe.db.rollback(save_point=savepoint)
continue
diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py b/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py
index 0f03997e2..620d93685 100644
--- a/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py
+++ b/ecommerce_integrations/shopify/page/shopify_import_products/test_shopify_import_products.py
@@ -12,16 +12,13 @@
class TestShopifyImportProducts(TestCase):
def __init__(self, obj):
- with open(
- os.path.join(os.path.dirname(__file__), "../../tests/data/bulk_products.json"), "rb"
- ) as f:
+ with open(os.path.join(os.path.dirname(__file__), "../../tests/data/bulk_products.json"), "rb") as f:
products_json = json.loads(f.read())
self._products = products_json["products"]
- super(TestShopifyImportProducts, self).__init__(obj)
+ super().__init__(obj)
def test_import_all_products(self):
-
required_products = {
"6808908169263": [
"40279118250031",
@@ -31,7 +28,12 @@ def test_import_all_products(self):
"40279118381103",
"40279118413871",
],
- "6808928124975": ["40279218028591", "40279218061359", "40279218094127", "40279218126895",],
+ "6808928124975": [
+ "40279218028591",
+ "40279218061359",
+ "40279218094127",
+ "40279218126895",
+ ],
"6808887689263": ["40279042883631", "40279042916399", "40279042949167"],
"6808908955695": ["40279122673711", "40279122706479", "40279122739247"],
"6808917737519": ["40279168221231", "40279168253999", "40279168286767"],
@@ -64,7 +66,6 @@ def test_import_all_products(self):
queue_sync_all_products()
for product, required_variants in required_products.items():
-
# has_variants is needed to avoid get_erpnext_item()
# fetching the variant instead of template because of
# matching integration_item_code
@@ -95,8 +96,8 @@ def test_import_all_products(self):
self.assertEqual(sorted(required_variants), sorted(created_ecom_variants))
def fake_single_product_from_bulk(self, product):
- item = [p for p in self._products if str(p["id"]) == product][0]
+ item = next(p for p in self._products if str(p["id"]) == product)
product_json = json.dumps({"product": item})
- self.fake("products/%s" % product, body=product_json)
+ self.fake(f"products/{product}", body=product_json)
diff --git a/ecommerce_integrations/shopify/product.py b/ecommerce_integrations/shopify/product.py
index ffae32049..92c31f467 100644
--- a/ecommerce_integrations/shopify/product.py
+++ b/ecommerce_integrations/shopify/product.py
@@ -23,9 +23,9 @@ class ShopifyProduct:
def __init__(
self,
product_id: str,
- variant_id: Optional[str] = None,
- sku: Optional[str] = None,
- has_variants: Optional[int] = 0,
+ variant_id: str | None = None,
+ sku: str | None = None,
+ has_variants: int | None = 0,
):
self.product_id = str(product_id)
self.variant_id = str(variant_id) if variant_id else None
@@ -38,7 +38,10 @@ def __init__(
def is_synced(self) -> bool:
return ecommerce_item.is_synced(
- MODULE_NAME, integration_item_code=self.product_id, variant_id=self.variant_id, sku=self.sku,
+ MODULE_NAME,
+ integration_item_code=self.product_id,
+ variant_id=self.variant_id,
+ sku=self.sku,
)
def get_erpnext_item(self):
@@ -81,7 +84,8 @@ def _create_attribute(self, product_dict):
"doctype": "Item Attribute",
"attribute_name": attr.get("name"),
"item_attribute_values": [
- {"attribute_value": attr_value, "abbr": attr_value} for attr_value in attr.get("values")
+ {"attribute_value": attr_value, "abbr": attr_value}
+ for attr_value in attr.get("values")
],
}
).insert()
@@ -175,7 +179,11 @@ def _create_item_variants(self, product_dict, warehouse, attributes):
for i, variant_attr in enumerate(SHOPIFY_VARIANTS_ATTR_LIST):
if variant.get(variant_attr):
attributes[i].update(
- {"attribute_value": self._get_attribute_value(variant.get(variant_attr), attributes[i])}
+ {
+ "attribute_value": self._get_attribute_value(
+ variant.get(variant_attr), attributes[i]
+ )
+ }
)
self._create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name)
@@ -263,9 +271,7 @@ def _get_item_image(product_dict):
return None
-def _match_sku_and_link_item(
- item_dict, product_id, variant_id, variant_of=None, has_variant=False
-) -> bool:
+def _match_sku_and_link_item(item_dict, product_id, variant_id, variant_of=None, has_variant=False) -> bool:
"""Tries to match new item with existing item using Shopify SKU == item_code.
Returns true if matched and linked.
@@ -298,7 +304,6 @@ def _match_sku_and_link_item(
def create_items_if_not_exist(order):
"""Using shopify order, sync all items that are not already synced."""
for item in order.get("line_items", []):
-
product_id = item["product_id"]
variant_id = item.get("variant_id")
sku = item.get("sku")
@@ -401,7 +406,9 @@ def upload_erpnext_item(doc, method=None):
try:
variant_attributes[f"option{i+1}"] = item.attributes[i].attribute_value
except IndexError:
- frappe.throw(_("Shopify Error: Missing value for attribute {}").format(attr.attribute))
+ frappe.throw(
+ _("Shopify Error: Missing value for attribute {}").format(attr.attribute)
+ )
product.variants.append(Variant(variant_attributes))
product.save() # push variant
@@ -429,7 +436,9 @@ def upload_erpnext_item(doc, method=None):
map_erpnext_item_to_shopify(shopify_product=product, erpnext_item=template_item)
if not item.variant_of:
update_default_variant_properties(
- product, is_stock_item=template_item.is_stock_item, price=item.get(ITEM_SELLING_RATE_FIELD)
+ product,
+ is_stock_item=template_item.is_stock_item,
+ price=item.get(ITEM_SELLING_RATE_FIELD),
)
else:
variant_attributes = {"sku": item.item_code, "price": item.get(ITEM_SELLING_RATE_FIELD)}
@@ -448,7 +457,9 @@ def upload_erpnext_item(doc, method=None):
try:
variant_attributes[f"option{i+1}"] = item.attributes[i].attribute_value
except IndexError:
- frappe.throw(_("Shopify Error: Missing value for attribute {}").format(attr.attribute))
+ frappe.throw(
+ _("Shopify Error: Missing value for attribute {}").format(attr.attribute)
+ )
product.variants.append(Variant(variant_attributes))
is_successful = product.save()
@@ -458,9 +469,7 @@ def upload_erpnext_item(doc, method=None):
write_upload_log(status=is_successful, product=product, item=item, action="Updated")
-def map_erpnext_variant_to_shopify_variant(
- shopify_product: Product, erpnext_item, variant_attributes
-):
+def map_erpnext_variant_to_shopify_variant(shopify_product: Product, erpnext_item, variant_attributes):
variant_product_id = frappe.db.get_value(
"Ecommerce Item",
{"erpnext_item_code": erpnext_item.name, "integration": MODULE_NAME},
@@ -520,8 +529,8 @@ def get_shopify_weight_uom(erpnext_weight_uom: str) -> str:
def update_default_variant_properties(
shopify_product: Product,
is_stock_item: bool,
- sku: Optional[str] = None,
- price: Optional[float] = None,
+ sku: str | None = None,
+ price: float | None = None,
):
"""Shopify creates default variant upon saving the product.
@@ -547,7 +556,10 @@ def write_upload_log(status: bool, product: Product, item, action="Created") ->
msgprint(msg, title="Note", indicator="orange")
create_shopify_log(
- status="Error", request_data=product.to_dict(), message=msg, method="upload_erpnext_item",
+ status="Error",
+ request_data=product.to_dict(),
+ message=msg,
+ method="upload_erpnext_item",
)
else:
create_shopify_log(
diff --git a/ecommerce_integrations/shopify/tests/test_connection.py b/ecommerce_integrations/shopify/tests/test_connection.py
index 697b88066..d488885d2 100644
--- a/ecommerce_integrations/shopify/tests/test_connection.py
+++ b/ecommerce_integrations/shopify/tests/test_connection.py
@@ -18,7 +18,6 @@ def setUpClass(cls):
@unittest.skip("Can't run these tests in CI")
def test_register_webhooks(self):
-
webhooks = connection.register_webhooks(
self.setting.shopify_url, self.setting.get_password("password")
)
@@ -30,7 +29,6 @@ def test_register_webhooks(self):
@unittest.skip("Can't run these tests in CI")
def test_unregister_webhooks(self):
-
connection.unregister_webhooks(self.setting.shopify_url, self.setting.get_password("password"))
callback_url = connection.get_callback_url()
diff --git a/ecommerce_integrations/shopify/tests/test_product.py b/ecommerce_integrations/shopify/tests/test_product.py
index 31758ad7a..78d04d240 100644
--- a/ecommerce_integrations/shopify/tests/test_product.py
+++ b/ecommerce_integrations/shopify/tests/test_product.py
@@ -165,7 +165,10 @@ def make_item(item_code=None, properties=None):
"item_name": item_code,
"description": item_code,
"item_group": "Products",
- "attributes": [{"attribute": "Test Sync Size"}, {"attribute": "Test Sync Colour"},],
+ "attributes": [
+ {"attribute": "Test Sync Size"},
+ {"attribute": "Test Sync Colour"},
+ ],
"has_variants": 1,
}
)
diff --git a/ecommerce_integrations/shopify/tests/utils.py b/ecommerce_integrations/shopify/tests/utils.py
index fd4f075ca..8cc246c7d 100644
--- a/ecommerce_integrations/shopify/tests/utils.py
+++ b/ecommerce_integrations/shopify/tests/utils.py
@@ -95,7 +95,7 @@ def setUp(self):
self.http.site = "https://frappetest.myshopify.com"
def load_fixture(self, name, format="json"):
- with open(os.path.dirname(__file__) + "/data/%s.%s" % (name, format), "rb") as f:
+ with open(os.path.dirname(__file__) + f"/data/{name}.{format}", "rb") as f:
return f.read()
def fake(self, endpoint, **kwargs):
@@ -106,9 +106,9 @@ def fake(self, endpoint, **kwargs):
if "extension" in kwargs and not kwargs["extension"]:
extension = ""
else:
- extension = ".%s" % (kwargs.pop("extension", "json"))
+ extension = ".{}".format(kwargs.pop("extension", "json"))
- url = "https://frappetest.myshopify.com%s/%s%s" % (prefix, endpoint, extension)
+ url = f"https://frappetest.myshopify.com{prefix}/{endpoint}{extension}"
try:
url = kwargs["url"]
except KeyError:
@@ -116,7 +116,7 @@ def fake(self, endpoint, **kwargs):
headers = {}
if kwargs.pop("has_user_agent", True):
- userAgent = "ShopifyPythonAPI/%s Python/%s" % (shopify.VERSION, sys.version.split(" ", 1)[0])
+ userAgent = "ShopifyPythonAPI/{} Python/{}".format(shopify.VERSION, sys.version.split(" ", 1)[0])
headers["User-agent"] = userAgent
try:
diff --git a/ecommerce_integrations/shopify/utils.py b/ecommerce_integrations/shopify/utils.py
index d1d55c00f..06bf1f582 100644
--- a/ecommerce_integrations/shopify/utils.py
+++ b/ecommerce_integrations/shopify/utils.py
@@ -1,6 +1,5 @@
# Copyright (c) 2021, Frappe and contributors
# For license information, please see LICENSE
-from typing import List
import frappe
from frappe import _, _dict
@@ -26,11 +25,15 @@ def migrate_from_old_connector(payload=None, request_id=None):
log = frappe.get_doc("Ecommerce Integration Log", request_id)
else:
log = create_shopify_log(
- status="Queued", method="ecommerce_integrations.shopify.utils.migrate_from_old_connector",
+ status="Queued",
+ method="ecommerce_integrations.shopify.utils.migrate_from_old_connector",
)
frappe.enqueue(
- method=_migrate_items_to_ecommerce_item, queue="long", is_async=True, log=log,
+ method=_migrate_items_to_ecommerce_item,
+ queue="long",
+ is_async=True,
+ log=log,
)
@@ -48,7 +51,6 @@ def ensure_old_connector_is_disabled():
def _migrate_items_to_ecommerce_item(log):
-
shopify_fields = ["shopify_product_id", "shopify_variant_id"]
for field in shopify_fields:
@@ -70,7 +72,7 @@ def _migrate_items_to_ecommerce_item(log):
log.save()
-def _get_items_to_migrate() -> List[_dict]:
+def _get_items_to_migrate() -> list[_dict]:
"""get all list of items that have shopify fields but do not have associated ecommerce item."""
old_data = frappe.db.sql(
@@ -84,7 +86,7 @@ def _get_items_to_migrate() -> List[_dict]:
return old_data or []
-def _create_ecommerce_items(items: List[_dict]) -> None:
+def _create_ecommerce_items(items: list[_dict]) -> None:
for item in items:
if not all((item.erpnext_item_code, item.shopify_product_id, item.shopify_variant_id)):
continue
diff --git a/ecommerce_integrations/tests/__init__.py b/ecommerce_integrations/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ecommerce_integrations/unicommerce/api_client.py b/ecommerce_integrations/unicommerce/api_client.py
index 1b3581eb1..770d38672 100644
--- a/ecommerce_integrations/unicommerce/api_client.py
+++ b/ecommerce_integrations/unicommerce/api_client.py
@@ -1,5 +1,5 @@
import base64
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any
import frappe
import requests
@@ -10,7 +10,7 @@
from ecommerce_integrations.unicommerce.constants import SETTINGS_DOCTYPE
from ecommerce_integrations.unicommerce.utils import create_unicommerce_log
-JsonDict = Dict[str, Any]
+JsonDict = dict[str, Any]
class UnicommerceAPIClient:
@@ -20,7 +20,9 @@ class UnicommerceAPIClient:
"""
def __init__(
- self, url: Optional[str] = None, access_token: Optional[str] = None,
+ self,
+ url: str | None = None,
+ access_token: str | None = None,
):
self.settings = frappe.get_doc(SETTINGS_DOCTYPE)
self.base_url = url or f"https://{self.settings.unicommerce_site}"
@@ -39,13 +41,12 @@ def request(
self,
endpoint: str,
method: str = "POST",
- headers: Optional[JsonDict] = None,
- body: Optional[JsonDict] = None,
- params: Optional[JsonDict] = None,
- files: Optional[JsonDict] = None,
+ headers: JsonDict | None = None,
+ body: JsonDict | None = None,
+ params: JsonDict | None = None,
+ files: JsonDict | None = None,
log_error=True,
- ) -> Tuple[JsonDict, bool]:
-
+ ) -> tuple[JsonDict, bool]:
if headers is None:
headers = {}
@@ -83,7 +84,7 @@ def request(
return data, status
- def get_unicommerce_item(self, sku: str, log_error=True) -> Optional[JsonDict]:
+ def get_unicommerce_item(self, sku: str, log_error=True) -> JsonDict | None:
"""Get Unicommerce item data for specified SKU code.
ref: https://documentation.unicommerce.com/docs/itemtype-get.html
@@ -94,7 +95,7 @@ def get_unicommerce_item(self, sku: str, log_error=True) -> Optional[JsonDict]:
if status:
return item
- def create_update_item(self, item_dict: JsonDict, update=False) -> Tuple[JsonDict, bool]:
+ def create_update_item(self, item_dict: JsonDict, update=False) -> tuple[JsonDict, bool]:
"""Create/update item on unicommerce.
ref: https://documentation.unicommerce.com/docs/createoredit-itemtype.html
@@ -106,7 +107,7 @@ def create_update_item(self, item_dict: JsonDict, update=False) -> Tuple[JsonDic
endpoint = "/services/rest/v1/catalog/itemType/edit"
return self.request(endpoint=endpoint, body={"itemType": item_dict})
- def get_sales_order(self, order_code: str) -> Optional[JsonDict]:
+ def get_sales_order(self, order_code: str) -> JsonDict | None:
"""Get details for a sales order.
ref: https://documentation.unicommerce.com/docs/saleorder-get.html
@@ -120,13 +121,13 @@ def get_sales_order(self, order_code: str) -> Optional[JsonDict]:
def search_sales_order(
self,
- from_date: Optional[str] = None,
- to_date: Optional[str] = None,
- status: Optional[str] = None,
- channel: Optional[str] = None,
- facility_codes: Optional[List[str]] = None,
- updated_since: Optional[int] = None,
- ) -> Optional[List[JsonDict]]:
+ from_date: str | None = None,
+ to_date: str | None = None,
+ status: str | None = None,
+ channel: str | None = None,
+ facility_codes: list[str] | None = None,
+ updated_since: int | None = None,
+ ) -> list[JsonDict] | None:
"""Search sales order using specified parameters and return search results.
ref: https://documentation.unicommerce.com/docs/saleorder-search.html
@@ -143,16 +144,14 @@ def search_sales_order(
# remove None values.
body = {k: v for k, v in body.items() if v is not None}
- search_results, status = self.request(
- endpoint="/services/rest/v1/oms/saleOrder/search", body=body
- )
+ search_results, status = self.request(endpoint="/services/rest/v1/oms/saleOrder/search", body=body)
if status and "elements" in search_results:
return search_results["elements"]
def get_inventory_snapshot(
- self, sku_codes: List[str], facility_code: str, updated_since: int = 1430
- ) -> Optional[JsonDict]:
+ self, sku_codes: list[str], facility_code: str, updated_since: int = 1430
+ ) -> JsonDict | None:
"""Get current inventory snapshot.
ref: https://documentation.unicommerce.com/docs/inventory-snapshot.html
@@ -163,13 +162,15 @@ def get_inventory_snapshot(
body = {"itemTypeSKUs": sku_codes, "updatedSinceInMinutes": updated_since}
response, status = self.request(
- endpoint="/services/rest/v1/inventory/inventorySnapshot/get", headers=extra_headers, body=body,
+ endpoint="/services/rest/v1/inventory/inventorySnapshot/get",
+ headers=extra_headers,
+ body=body,
)
if status:
return response
- def bulk_inventory_update(self, facility_code: str, inventory_map: Dict[str, int]):
+ def bulk_inventory_update(self, facility_code: str, inventory_map: dict[str, int]):
"""Bulk update inventory on unicommerce using SKU and qty.
The qty should be "total" quantity.
@@ -219,8 +220,8 @@ def bulk_inventory_update(self, facility_code: str, inventory_map: Dict[str, int
return response, False
def create_sales_invoice(
- self, so_code: str, so_item_codes: List[str], facility_code: str
- ) -> Optional[JsonDict]:
+ self, so_code: str, so_item_codes: list[str], facility_code: str
+ ) -> JsonDict | None:
body = {"saleOrderCode": so_code, "saleOrderItemCodes": so_item_codes}
extra_headers = {"Facility": facility_code}
@@ -280,7 +281,7 @@ def create_invoice_and_label_by_shipping_code(
def get_sales_invoice(
self, shipping_package_code: str, facility_code: str, is_return: bool = False
- ) -> Optional[JsonDict]:
+ ) -> JsonDict | None:
"""Get invoice details
ref: https://documentation.unicommerce.com/docs/invoice-getdetails.html
@@ -329,10 +330,12 @@ def _positive(numbers):
extra_headers = {"Facility": facility_code}
return self.request(
- endpoint="/services/rest/v1/oms/shippingPackage/edit", body=body, headers=extra_headers,
+ endpoint="/services/rest/v1/oms/shippingPackage/edit",
+ body=body,
+ headers=extra_headers,
)
- def get_invoice_label(self, shipping_package_code: str, facility_code: str) -> Optional[str]:
+ def get_invoice_label(self, shipping_package_code: str, facility_code: str) -> str | None:
"""Get the generated label for a given shipping package.
ref: undocumented.
@@ -352,7 +355,7 @@ def create_and_close_shipping_manifest(
channel: str,
shipping_provider_code: str,
shipping_method_code: str,
- shipping_packages: List[str],
+ shipping_packages: list[str],
facility_code: str,
third_party_shipping: bool = True,
):
@@ -371,7 +374,9 @@ def create_and_close_shipping_manifest(
}
response, status = self.request(
- endpoint="/services/rest/v1/oms/shippingManifest/createclose", body=body, headers=extra_headers,
+ endpoint="/services/rest/v1/oms/shippingManifest/createclose",
+ body=body,
+ headers=extra_headers,
)
if status:
@@ -390,9 +395,9 @@ def get_shipping_manifest(self, shipping_manifest_code, facility_code):
def search_shipping_packages(
self,
facility_code: str,
- channel: Optional[str] = None,
- statuses: Optional[List[str]] = None,
- updated_since: Optional[int] = 6 * 60,
+ channel: str | None = None,
+ statuses: list[str] | None = None,
+ updated_since: int | None = 6 * 60,
):
"""Search shipping packages on unicommerce matching specified criterias.
@@ -408,14 +413,20 @@ def search_shipping_packages(
body = {k: v for k, v in body.items() if v is not None}
search_results, statuses = self.request(
- endpoint="/services/rest/v1/oms/shippingPackage/search", body=body, headers=extra_headers,
+ endpoint="/services/rest/v1/oms/shippingPackage/search",
+ body=body,
+ headers=extra_headers,
)
if statuses and "elements" in search_results:
return search_results["elements"]
def create_import_job(
- self, job_name: str, csv_filename: str, facility_code: str, job_type: str = "CREATE_NEW",
+ self,
+ job_name: str,
+ csv_filename: str,
+ facility_code: str,
+ job_type: str = "CREATE_NEW",
):
"""Create import job by specifying job name and CSV file
@@ -448,7 +459,7 @@ def create_import_job(
def _utc_timeformat(datetime) -> str:
- """ Get datetime in UTC/GMT as required by Unicommerce"""
+ """Get datetime in UTC/GMT as required by Unicommerce"""
return get_datetime(datetime).astimezone(timezone("UTC")).strftime("%Y-%m-%dT%H:%M:%SZ")
diff --git a/ecommerce_integrations/unicommerce/cancellation_and_returns.py b/ecommerce_integrations/unicommerce/cancellation_and_returns.py
index 264ea6406..fc2c8968f 100644
--- a/ecommerce_integrations/unicommerce/cancellation_and_returns.py
+++ b/ecommerce_integrations/unicommerce/cancellation_and_returns.py
@@ -1,7 +1,6 @@
import json
from collections import defaultdict
from datetime import date, datetime
-from typing import List
import frappe
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
@@ -22,7 +21,7 @@
)
-def fully_cancel_orders(unicommerce_order_codes: List[str]) -> None:
+def fully_cancel_orders(unicommerce_order_codes: list[str]) -> None:
"""Perform "cancel" action on ERPNext sales orders which are fully cancelled in Unicommerce."""
current_orders_status = frappe.db.get_values(
@@ -90,9 +89,7 @@ def update_erpnext_order_items(so_data, so=None):
def _delete_cancelled_items(erpnext_items, cancelled_items):
- items = [
- d.as_dict() for d in erpnext_items if d.get(ORDER_ITEM_CODE_FIELD) not in cancelled_items
- ]
+ items = [d.as_dict() for d in erpnext_items if d.get(ORDER_ITEM_CODE_FIELD) not in cancelled_items]
# add `docname` same as name, required for Update Items functionality
for item in items:
@@ -104,7 +101,7 @@ def _serialize_items(trans_items) -> str:
# serialie date/datetime objects to string
for item in trans_items:
for k, v in item.items():
- if isinstance(v, (datetime, date)):
+ if isinstance(v, datetime | date):
item[k] = str(v)
return json.dumps(trans_items)
@@ -157,7 +154,7 @@ def create_credit_note(invoice_name):
for tax in credit_note.taxes:
tax.item_wise_tax_detail = json.loads(tax.item_wise_tax_detail)
- for item, tax_distribution in tax.item_wise_tax_detail.items():
+ for _item, tax_distribution in tax.item_wise_tax_detail.items():
tax_distribution[1] *= -1
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail)
@@ -177,7 +174,6 @@ def check_and_update_customer_initiated_returns(orders, client: UnicommerceAPICl
def sync_customer_initiated_returns(so_data):
-
customer_returns = [r for r in so_data.get("returns", []) if r["type"] == "Customer Returned"]
if not customer_returns:
return
@@ -194,9 +190,7 @@ def create_cir_credit_note(so_data, return_data):
# Get items from SO which are returned, map SO item -> SI item with linked rows.
so_item_code_map = {item.get(ORDER_ITEM_CODE_FIELD): item.name for item in so.items}
- invoice_name = frappe.db.get_value(
- "Sales Invoice", {ORDER_CODE_FIELD: so_data["code"], "is_return": 0}
- )
+ invoice_name = frappe.db.get_value("Sales Invoice", {ORDER_CODE_FIELD: so_data["code"], "is_return": 0})
si = frappe.get_doc("Sales Invoice", invoice_name)
so_si_item_map = {item.so_detail: item.name for item in si.items}
@@ -215,7 +209,7 @@ def create_cir_credit_note(so_data, return_data):
credit_note.save()
-def _handle_partial_returns(credit_note, returned_items: List[str]) -> None:
+def _handle_partial_returns(credit_note, returned_items: list[str]) -> None:
"""Remove non-returned item from credit note and update taxes"""
item_code_to_qty_map = defaultdict(float)
@@ -223,9 +217,7 @@ def _handle_partial_returns(credit_note, returned_items: List[str]) -> None:
item_code_to_qty_map[item.item_code] += item.qty
# remove non-returned items
- credit_note.items = [
- item for item in credit_note.items if item.sales_invoice_item in returned_items
- ]
+ credit_note.items = [item for item in credit_note.items if item.sales_invoice_item in returned_items]
returned_qty_map = defaultdict(float)
for item in credit_note.items:
diff --git a/ecommerce_integrations/unicommerce/customer.py b/ecommerce_integrations/unicommerce/customer.py
index c1b5248ae..7cac4a309 100644
--- a/ecommerce_integrations/unicommerce/customer.py
+++ b/ecommerce_integrations/unicommerce/customer.py
@@ -1,5 +1,5 @@
import json
-from typing import Any, Dict, List
+from typing import Any
import frappe
from frappe import _
@@ -78,7 +78,7 @@ def _check_if_customer_exists(address, customer_code):
return frappe.get_doc("Customer", customer_name)
-def _create_customer_addresses(addresses: List[Dict[str, Any]], customer) -> None:
+def _create_customer_addresses(addresses: list[dict[str, Any]], customer) -> None:
"""Create address from dictionary containing fields used in Address doctype of ERPNext.
Unicommerce orders contain address list,
@@ -93,7 +93,6 @@ def _create_customer_addresses(addresses: List[Dict[str, Any]], customer) -> Non
def _create_customer_address(uni_address, address_type, customer, also_shipping=False):
-
country_code = uni_address.get("country")
country = UNICOMMERCE_COUNTRY_MAPPING.get(country_code)
diff --git a/ecommerce_integrations/unicommerce/delivery_note.py b/ecommerce_integrations/unicommerce/delivery_note.py
index c578f6ab7..0af339bd0 100644
--- a/ecommerce_integrations/unicommerce/delivery_note.py
+++ b/ecommerce_integrations/unicommerce/delivery_note.py
@@ -24,9 +24,7 @@ def prepare_delivery_note():
)
for facility in enabled_facilities:
- updated_packages = client.search_shipping_packages(
- updated_since=minutes, facility_code=facility
- )
+ updated_packages = client.search_shipping_packages(updated_since=minutes, facility_code=facility)
valid_packages = [p for p in updated_packages if p.get("channel") in enabled_channels]
if not valid_packages:
continue
@@ -48,52 +46,16 @@ def prepare_delivery_note():
def create_delivery_note(so, sales_invoice):
- try:
- # Create the delivery note
- from frappe.model.mapper import make_mapped_doc
-
- res = make_mapped_doc(
- method="erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", source_name=so.name
- )
- res.update({"items": []})
- for item in sales_invoice.items:
- res.append(
- "items",
- {
- "item_code": item.item_code,
- "item_name": item.item_name,
- "description": item.description,
- "qty": item.qty,
- "uom": item.uom,
- "rate": item.rate,
- "amount": item.amount,
- "warehouse": item.warehouse,
- "against_sales_order": item.sales_order,
- "batch_no": item.batch_no,
- "so_detail": item.so_detail,
- },
- )
- for item in sales_invoice.taxes:
- res.append(
- "taxes",
- {
- "charge_type": item.charge_type,
- "account_head": item.account_head,
- "tax_amount": item.tax_amount,
- "description": item.description,
- "item_wise_tax_detail": item.item_wise_tax_detail,
- "dont_recompute_tax": item.dont_recompute_tax,
- },
- )
- res.unicommerce_order_code = sales_invoice.unicommerce_order_code
- res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code
- res.save()
- res.submit()
- log = create_unicommerce_log(method="create_delevery_note", make_new=True)
- frappe.flags.request_id = log.name
- except Exception as e:
- create_unicommerce_log(status="Error", exception=e, rollback=True)
- else:
- create_unicommerce_log(status="Success")
- frappe.flags.request_id = None
- return res
+ # Create the delivery note
+ from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+
+ res = make_delivery_note(source_name=so.name)
+ res.unicommerce_order_code = sales_invoice.unicommerce_order_code
+ res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code
+ res.save()
+ res.submit()
+ log = create_unicommerce_log(method="create_delevery_note", make_new=True)
+ frappe.flags.request_id = log.name
+ create_unicommerce_log(status="Success")
+ frappe.flags.request_id = None
+ return res
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_channel/unicommerce_channel.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_channel/unicommerce_channel.js
index f2abcb83e..1812a30c7 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_channel/unicommerce_channel.js
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_channel/unicommerce_channel.js
@@ -16,14 +16,15 @@ frappe.ui.form.on("Unicommerce Channel", {
filters: { company: frm.doc.company, is_group: 0 },
}));
- ["warehouse", "return_warehouse"].forEach(wh_field => frm.set_query(wh_field, () => ({
- filters: {
- company: frm.doc.company,
- is_group: 0,
- disabled: 0,
- },
- })));
-
+ ["warehouse", "return_warehouse"].forEach((wh_field) =>
+ frm.set_query(wh_field, () => ({
+ filters: {
+ company: frm.doc.company,
+ is_group: 0,
+ disabled: 0,
+ },
+ }))
+ );
const tax_accounts = [
"igst_account",
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_package_type/unicommerce_package_type.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_package_type/unicommerce_package_type.js
index cdfde5404..38bffe383 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_package_type/unicommerce_package_type.js
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_package_type/unicommerce_package_type.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see LICENSE
-frappe.ui.form.on('Unicommerce Package Type', {
+frappe.ui.form.on("Unicommerce Package Type", {
// refresh: function(frm) {
-
// }
});
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py
index d3fc0fc50..c8f7efe0c 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/test_unicommerce_settings.py
@@ -44,9 +44,7 @@ def test_failed_auth(self):
"""requirement: When improper credentials are provided, system throws error."""
# failure case
- responses.add(
- responses.GET, "https://demostaging.unicommerce.com/oauth/token", json={}, status=401
- )
+ responses.add(responses.GET, "https://demostaging.unicommerce.com/oauth/token", json={}, status=401)
self.assertRaises(frappe.ValidationError, self.settings.update_tokens)
@responses.activate
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.js
index 4f4344c32..09eb66f3b 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.js
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.js
@@ -8,7 +8,9 @@ frappe.ui.form.on("Unicommerce Settings", {
}
frm.add_custom_button(__("View Logs"), () => {
- frappe.set_route("List", "Ecommerce Integration Log", {"integration": "Unicommerce"});
+ frappe.set_route("List", "Ecommerce Integration Log", {
+ integration: "Unicommerce",
+ });
});
let sync_buttons = ["Items", "Orders", "Inventory"];
@@ -18,8 +20,7 @@ frappe.ui.form.on("Unicommerce Settings", {
action,
() => {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.utils.force_sync",
+ method: "ecommerce_integrations.unicommerce.utils.force_sync",
args: {
document: action,
},
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py
index cd5a46da5..02141d260 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py
@@ -1,7 +1,6 @@
# Copyright (c) 2021, Frappe and contributors
# For license information, please see LICENSE
-from typing import Dict, List, Optional, Tuple
import frappe
import requests
@@ -143,18 +142,16 @@ def validate_warehouse_mapping(self):
_("Warehouse Mapping should be unique and one-to-one without repeating same warehouses.")
)
- def get_erpnext_warehouses(self, all_wh=False) -> List[ERPNextWarehouse]:
+ def get_erpnext_warehouses(self, all_wh=False) -> list[ERPNextWarehouse]:
"""Get list of configured ERPNext warehouses.
all_wh flag ignores enabled status.
"""
- return [
- wh_map.erpnext_warehouse for wh_map in self.warehouse_mapping if wh_map.enabled or all_wh
- ]
+ return [wh_map.erpnext_warehouse for wh_map in self.warehouse_mapping if wh_map.enabled or all_wh]
def get_erpnext_to_integration_wh_mapping(
self, all_wh=False
- ) -> Dict[ERPNextWarehouse, IntegrationWarehouse]:
+ ) -> dict[ERPNextWarehouse, IntegrationWarehouse]:
"""Get enabled mapping from ERPNextWarehouse to Unicommerce facility.
all_wh flag ignores enabled status."""
@@ -166,7 +163,7 @@ def get_erpnext_to_integration_wh_mapping(
def get_integration_to_erpnext_wh_mapping(
self, all_wh=False
- ) -> Dict[IntegrationWarehouse, ERPNextWarehouse]:
+ ) -> dict[IntegrationWarehouse, ERPNextWarehouse]:
"""Get enabled mapping from Unicommerce facility to ERPNext warehouse.
all_wh flag ignores enabled status."""
@@ -174,8 +171,8 @@ def get_integration_to_erpnext_wh_mapping(
return {v: k for k, v in reverse_map.items()}
- def get_company_addresses(self, facility_code: str) -> Tuple[Optional[str], Optional[str]]:
- """ Get mapped company billing and shipping addresses."""
+ def get_company_addresses(self, facility_code: str) -> tuple[str | None, str | None]:
+ """Get mapped company billing and shipping addresses."""
for wh_map in self.warehouse_mapping:
if wh_map.unicommerce_facility_code == facility_code:
return wh_map.company_address, wh_map.dispatch_address
@@ -183,7 +180,6 @@ def get_company_addresses(self, facility_code: str) -> Tuple[Optional[str], Opti
def setup_custom_fields(update=True):
-
custom_sections = {
"Sales Order": [
dict(
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.js
index b7b4025e2..276e0f18f 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.js
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.js
@@ -9,8 +9,7 @@ frappe.ui.form.on("Unicommerce Shipment Manifest", {
__("Open on Unicommerce"),
function () {
frappe.call({
- method:
- "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
+ method: "ecommerce_integrations.unicommerce.utils.get_unicommerce_document_url",
args: {
code: frm.doc.unicommerce_manifest_code,
doctype: frm.doc.doctype,
@@ -44,8 +43,7 @@ frappe.ui.form.on("Unicommerce Shipment Manifest", {
return;
}
erpnext.utils.map_current_doc({
- method:
- "ecommerce_integrations.unicommerce.doctype.unicommerce_shipment_manifest.unicommerce_shipment_manifest.get_shipping_package_list",
+ method: "ecommerce_integrations.unicommerce.doctype.unicommerce_shipment_manifest.unicommerce_shipment_manifest.get_shipping_package_list",
source_doctype: "Sales Invoice",
target: frm.doc,
setters: [
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py
index 7dc3e9df6..28df32182 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipment_manifest/unicommerce_shipment_manifest.py
@@ -80,7 +80,7 @@ def get_facility_code(self) -> str:
",".join(facility_codes)
)
)
- return list(facility_codes)[0]
+ return next(iter(facility_codes))
def create_and_close_manifest_on_unicommerce(self):
shipping_packages = [d.shipping_package_code for d in self.manifest_items]
@@ -152,9 +152,7 @@ def get_sales_invoice_details(sales_invoice):
as_dict=True,
)
- items = frappe.db.get_values(
- "Sales Invoice Item", {"parent": sales_invoice}, "item_name", as_dict=True
- )
+ items = frappe.db.get_values("Sales Invoice Item", {"parent": sales_invoice}, "item_name", as_dict=True)
unique_items = {item.item_name for item in items}
si_data["item_list"] = ",".join(unique_items)
@@ -163,9 +161,7 @@ def get_sales_invoice_details(sales_invoice):
@frappe.whitelist()
-def search_packages(
- search_term: str, channel: Optional[str] = None, shipper: Optional[str] = None
-):
+def search_packages(search_term: str, channel: str | None = None, shipper: str | None = None):
filters = {
CHANNEL_ID_FIELD: channel,
SHIPPING_PROVIDER_CODE: shipper,
@@ -181,9 +177,7 @@ def search_packages(
INVOICE_CODE_FIELD: search_term,
}
- packages = frappe.get_list(
- "Sales Invoice", filters=filters, or_filters=or_filters, limit_page_length=1
- )
+ packages = frappe.get_list("Sales Invoice", filters=filters, or_filters=or_filters, limit_page_length=1)
if packages:
return packages[0].name
@@ -191,7 +185,6 @@ def search_packages(
@frappe.whitelist()
def get_shipping_package_list(source_name, target_doc=None):
-
if target_doc and isinstance(target_doc, str):
target_doc = json.loads(target_doc)
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_method/unicommerce_shipping_method.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_method/unicommerce_shipping_method.js
index 1f42ce32a..c34adc310 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_method/unicommerce_shipping_method.js
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_method/unicommerce_shipping_method.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see LICENSE
-frappe.ui.form.on('Unicommerce Shipping Method', {
+frappe.ui.form.on("Unicommerce Shipping Method", {
// refresh: function(frm) {
-
// }
});
diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_provider/unicommerce_shipping_provider.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_provider/unicommerce_shipping_provider.js
index 293c53352..21bb2858e 100644
--- a/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_provider/unicommerce_shipping_provider.js
+++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_shipping_provider/unicommerce_shipping_provider.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe and contributors
// For license information, please see LICENSE
-frappe.ui.form.on('Unicommerce Shipping Provider', {
+frappe.ui.form.on("Unicommerce Shipping Provider", {
// refresh: function(frm) {
-
// }
});
diff --git a/ecommerce_integrations/unicommerce/grn.py b/ecommerce_integrations/unicommerce/grn.py
index a28db7c46..0baa7b0aa 100644
--- a/ecommerce_integrations/unicommerce/grn.py
+++ b/ecommerce_integrations/unicommerce/grn.py
@@ -1,5 +1,4 @@
from dataclasses import dataclass
-from typing import List
import frappe
from erpnext.stock.doctype.batch.batch import Batch
@@ -95,7 +94,7 @@ def get_facility_code(stock_entry, unicommerce_settings) -> str:
_("{} only supports one target warehouse (unicommerce facility)").format(GRN_STOCK_ENTRY_TYPE)
)
- warehouse = list(target_warehouses)[0]
+ warehouse = next(iter(target_warehouses))
warehouse_mapping = unicommerce_settings.get_erpnext_to_integration_wh_mapping(all_wh=True)
facility = warehouse_mapping.get(warehouse)
@@ -130,9 +129,7 @@ def upload_grn(doc, method=None):
msg += _("Confirm the status on Import Log in Uniware.")
frappe.msgprint(msg, title="Success")
elif response.successful and errors:
- frappe.msgprint(
- "Partial success, unicommerce reported errors: