From 17f729690719529898de93f0aefb02013d9fe31d Mon Sep 17 00:00:00 2001 From: David Date: Mon, 16 Sep 2024 21:45:31 +0200 Subject: [PATCH 01/11] chore: add helix and aider to ignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9faed4da..7ba46dbf 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 From f6fb6176b5794548273791049bae7279a32d2779 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:02:31 +0200 Subject: [PATCH 02/11] ci: best practices --- .github/helper/install.sh | 47 ++++++++++------ .github/labeler.yml | 4 ++ .github/workflows/ci.yml | 68 +++++++++++++++++++----- .github/workflows/labeller.yml | 12 +++++ ecommerce_integrations/tests/__init__.py | 0 5 files changed, 101 insertions(+), 30 deletions(-) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeller.yml create mode 100644 ecommerce_integrations/tests/__init__.py diff --git a/.github/helper/install.sh b/.github/helper/install.sh index ce340180..406052d8 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 00000000..080e248c --- /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 366751e3..7ed62bd4 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,38 @@ jobs: - name: Install run: | bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - + env: + FRAPPE_USER: ${{ github.event.inputs.user }} + FRAPPE_BRANCH: ${{ github.event.inputs.branch }} - 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 00000000..97fa4a1a --- /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/ecommerce_integrations/tests/__init__.py b/ecommerce_integrations/tests/__init__.py new file mode 100644 index 00000000..e69de29b From ba5b543d88d35aed1f094a42c1757f9b100aab48 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:30:15 +0200 Subject: [PATCH 03/11] fix(unicommerce): dn creation --- .../unicommerce/delivery_note.py | 62 ++++--------------- 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/ecommerce_integrations/unicommerce/delivery_note.py b/ecommerce_integrations/unicommerce/delivery_note.py index c578f6ab..55560a3e 100644 --- a/ecommerce_integrations/unicommerce/delivery_note.py +++ b/ecommerce_integrations/unicommerce/delivery_note.py @@ -48,52 +48,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 From 9336f30b614ca0782e60b496ec6783af9d3412fc Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:07:09 +0200 Subject: [PATCH 04/11] lint: best practices --- .eslintrc | 130 ++++++++++++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 69 +++++++++++++++------ pyproject.toml | 45 ++++++++++---- 3 files changed, 215 insertions(+), 29 deletions(-) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..1f225527 --- /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/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c5ded60..0697268c 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/pyproject.toml b/pyproject.toml index c11448c3..deac6bb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,14 +20,39 @@ file = "./LICENSE" requires = ["flit_core >=3.4,<4"] build-backend = "flit_core.buildapi" -[tool.black] -line-length = 99 +[tool.ruff] +line-length = 110 +target-version = "py310" -[tool.isort] -line_length = 99 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true -indent = " " +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "I", + "UP", + "B", + "RUF", +] +ignore = [ + "B017", # assertRaises(Exception) - should be more specific + "B018", # useless expression, not assigned to anything + "B023", # function doesn't bind loop variable - will have last iteration's value + "B904", # raise inside except without from + "E101", # indentation contains mixed spaces and tabs + "E402", # module level import not at top of file + "E501", # line too long + "E741", # ambiguous variable name + "F401", # "unused" imports + "F403", # can't detect undefined names from * import + "F405", # can't detect undefined names from * import + "F722", # syntax error in forward type annotation + "W191", # indentation contains tabs + "RUF001", # string contains ambiguous unicode character +] +typing-modules = ["frappe.types.DF"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" +docstring-code-format = true From 609015af733355a4038c24c089d3135462367858 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:22:37 +0200 Subject: [PATCH 05/11] style: autoformat --- .../amazon_repository.py | 22 +- .../amazon_sp_api_settings/amazon_sp_api.py | 40 +-- .../amazon_sp_api_settings.js | 16 +- .../amazon_sp_api_settings.py | 20 +- .../test_amazon_sp_api_settings.py | 32 +- .../controllers/customer.py | 4 +- .../controllers/inventory.py | 2 +- .../controllers/scheduling.py | 4 +- ecommerce_integrations/controllers/setting.py | 6 +- .../ecommerce_integration_log.js | 28 +- .../ecommerce_integration_log.py | 2 +- .../doctype/ecommerce_item/ecommerce_item.js | 3 +- .../doctype/ecommerce_item/ecommerce_item.py | 24 +- .../ecommerce_item/ecommerce_item_list.js | 2 +- ecommerce_integrations/hooks.py | 4 +- .../set_default_amazon_item_fields_map.py | 12 +- .../public/js/shopify/old_settings.js | 11 +- .../public/js/unicommerce/item.js | 3 +- .../public/js/unicommerce/pick_list.js | 98 +++--- .../public/js/unicommerce/sales_invoice.js | 3 +- .../public/js/unicommerce/sales_order.js | 19 +- .../public/js/unicommerce/stock_entry.js | 5 +- ecommerce_integrations/shopify/connection.py | 9 +- ecommerce_integrations/shopify/customer.py | 19 +- .../shopify_setting/shopify_setting.py | 12 +- .../shopify_setting/test_shopify_setting.py | 1 - ecommerce_integrations/shopify/fulfillment.py | 5 +- ecommerce_integrations/shopify/invoice.py | 1 - ecommerce_integrations/shopify/order.py | 20 +- .../shopify_import_products.js | 294 +++++++++--------- .../shopify_import_products.py | 9 +- .../test_shopify_import_products.py | 13 +- ecommerce_integrations/shopify/product.py | 50 +-- .../shopify/tests/test_connection.py | 2 - .../shopify/tests/test_product.py | 5 +- ecommerce_integrations/shopify/utils.py | 13 +- .../unicommerce/api_client.py | 87 +++--- .../unicommerce/cancellation_and_returns.py | 17 +- .../unicommerce/customer.py | 3 +- .../unicommerce/delivery_note.py | 4 +- .../unicommerce_channel.js | 17 +- .../unicommerce_package_type.js | 3 +- .../test_unicommerce_settings.py | 4 +- .../unicommerce_settings.js | 7 +- .../unicommerce_settings.py | 15 +- .../unicommerce_shipment_manifest.js | 6 +- .../unicommerce_shipment_manifest.py | 13 +- .../unicommerce_shipping_method.js | 3 +- .../unicommerce_shipping_provider.js | 3 +- ecommerce_integrations/unicommerce/grn.py | 9 +- .../unicommerce/inventory.py | 8 +- ecommerce_integrations/unicommerce/invoice.py | 59 ++-- ecommerce_integrations/unicommerce/order.py | 33 +- .../unicommerce/pick_list.py | 4 +- ecommerce_integrations/unicommerce/product.py | 15 +- .../unicommerce/status_updater.py | 11 +- .../unicommerce/tests/test_client.py | 9 +- .../unicommerce/tests/test_inventory.py | 1 - .../unicommerce/tests/test_invoice.py | 8 +- .../unicommerce/tests/test_order.py | 12 +- .../unicommerce/tests/test_product.py | 4 +- .../unicommerce/tests/test_status.py | 1 - ecommerce_integrations/unicommerce/utils.py | 2 +- .../zenoti_category/zenoti_category.js | 3 +- .../doctype/zenoti_center/zenoti_center.js | 284 ++++++++++------- .../zenoti_error_logs/zenoti_error_logs.js | 3 +- .../zenoti_settings/zenoti_settings.js | 65 ++-- .../zenoti/purchase_transactions.py | 8 +- .../zenoti/sales_transactions.py | 8 +- .../zenoti/stock_reconciliation.py | 10 +- ecommerce_integrations/zenoti/utils.py | 4 +- 71 files changed, 788 insertions(+), 768 deletions(-) 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 e5456d92..aaf5b7d3 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 @@ -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 c2abd0de..517d7527 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(map(lambda h: ":".join((h, request.headers[h])) + "\n", 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, + data: dict = None, ) -> dict: if isinstance(params, dict): params = Util.remove_empty(params) @@ -307,21 +307,21 @@ 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 ) -> 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" @@ -345,7 +345,7 @@ def get_orders( is_ispu: bool = False, store_chain_store_id: str = 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, @@ -374,19 +374,23 @@ 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. """ + """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, + ) -> 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 22b4a243..d6cf3d5a 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 42ed3225..0765ebf2 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 @@ -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 9f72487d..17f8ffc5 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 @@ -30,7 +30,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 +38,16 @@ class TestSPAPI(SPAPI): - # Expected response after hitting the URL. expected_response = {} @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, + data: dict = None, ) -> object: if isinstance(params, dict): params = Util.remove_empty(params) @@ -132,7 +135,11 @@ def get_order_items(self, order_id: str, next_token: str = None) -> object: 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, + ) -> object: self.expected_response = DATA.get("get_catalog_item_200") return super().get_catalog_item(asin, marketplace_id) @@ -163,7 +170,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 +192,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 diff --git a/ecommerce_integrations/controllers/customer.py b/ecommerce_integrations/controllers/customer.py index b6b231c4..1dcd8379 100644 --- a/ecommerce_integrations/controllers/customer.py +++ b/ecommerce_integrations/controllers/customer.py @@ -50,7 +50,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 +63,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 3438086f..d00545ba 100644 --- a/ecommerce_integrations/controllers/inventory.py +++ b/ecommerce_integrations/controllers/inventory.py @@ -6,7 +6,7 @@ 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. diff --git a/ecommerce_integrations/controllers/scheduling.py b/ecommerce_integrations/controllers/scheduling.py index b97b4f81..9b59e30a 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 7b48c66b..d80d08d2 100644 --- a/ecommerce_integrations/controllers/setting.py +++ b/ecommerce_integrations/controllers/setting.py @@ -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 c77422b9..962ca27b 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 4a9f6723..c5ed9f38 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 e4774864..bedbf38f 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 423a2682..20b3de2f 100644 --- a/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py +++ b/ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_item/ecommerce_item.py @@ -56,8 +56,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 +86,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 +101,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 +127,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 68165a8a..eef48f22 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 face21c3..c2ebc85c 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 9bf161bc..5fcebd20 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 f7b0491c..dc3c39ba 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 e0f049bc..cef5e957 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 679bace9..5f09cafa 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 d3b4e96c..471167f2 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 5e9852aa..ce803da3 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 acfee9fa..50ba5166 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 003b51ee..4a484bef 100644 --- a/ecommerce_integrations/shopify/connection.py +++ b/ecommerce_integrations/shopify/connection.py @@ -24,7 +24,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 +38,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 +53,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 +66,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 +106,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 e4b4b9bc..09250dc8 100644 --- a/ecommerce_integrations/shopify/customer.py +++ b/ecommerce_integrations/shopify/customer.py @@ -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 2fd82ba9..cfd2b6e6 100644 --- a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py +++ b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py @@ -94,19 +94,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 5277352c..ab05370b 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 97a6706e..5ffc0eba 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 26afb825..f841cb41 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 431c431c..0570d035 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 ca95228a..455987a2 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,21 @@ -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, }); new shopify.ProductImporter(wrapper); - -} +}; 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 +29,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 = $(`
@@ -97,27 +96,26 @@ shopify.ProductImporter = class { `); this.wrapper.append(_markup); - } async fetchProductCount() { - try { - const { message: { erpnextCount, shopifyCount, syncedCount } } = await frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_product_count' }); - - this.wrapper.find('#count-products-shopify').text(shopifyCount); - this.wrapper.find('#count-products-erpnext').text(erpnextCount); - this.wrapper.find('#count-products-synced').text(syncedCount); - + const { + message: { erpnextCount, shopifyCount, syncedCount }, + } = await frappe.call({ + method: "ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_product_count", + }); + + this.wrapper.find("#count-products-shopify").text(shopifyCount); + this.wrapper.find("#count-products-erpnext").text(erpnextCount); + this.wrapper.find("#count-products-synced").text(syncedCount); } catch (error) { - frappe.throw(__('Error fetching product count.')); + frappe.throw(__("Error fetching product count.")); } - } async addTable() { - - const listElement = this.wrapper.find('#shopify-product-list')[0]; + const listElement = this.wrapper.find("#shopify-product-list")[0]; this.shopifyProductTable = new frappe.DataTable(listElement, { columns: [ // { @@ -125,242 +123,231 @@ shopify.ProductImporter = class { // align: 'center', // }, { - name: 'ID', - align: 'left', + name: "ID", + align: "left", editable: false, focusable: false, }, { - name: 'Name', + name: "Name", editable: false, focusable: false, }, { - name: 'SKUs', + name: "SKUs", editable: false, focusable: false, }, { - name: 'Status', - align: 'center', + name: "Status", + align: "center", editable: false, focusable: false, }, { - name: 'Action', - align: 'center', + name: "Action", + align: "center", editable: false, focusable: false, }, ], data: await this.fetchShopifyProducts(), - layout: 'fixed', + layout: "fixed", }); - this.wrapper.find('.shopify-datatable-footer').show(); - + this.wrapper.find(".shopify-datatable-footer").show(); } async fetchShopifyProducts(from_ = null) { - try { - const { message: { products, nextUrl, prevUrl } } = await frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_shopify_products', args: { from_ } }); + const { + message: { products, nextUrl, prevUrl }, + } = await frappe.call({ + method: "ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_shopify_products", + args: { from_ }, + }); this.nextUrl = nextUrl; this.prevUrl = prevUrl; const shopifyProducts = products.map((product) => ({ // 'Image': product.image && product.image.src && ``, - 'ID': product.id, - 'Name': product.title, - 'SKUs': product.variants && product.variants.map(a => `${a.sku}`).join(', '), - 'Status': this.getProductSyncStatus(product.synced), - 'Action': !product.synced ? - `` : - ``, + ID: product.id, + Name: product.title, + SKUs: + product.variants && + product.variants.map((a) => `${a.sku}`).join(", "), + Status: this.getProductSyncStatus(product.synced), + Action: !product.synced + ? `` + : ``, })); return shopifyProducts; } catch (error) { - frappe.throw(__('Error fetching products.')); + frappe.throw(__("Error fetching products.")); } - } getProductSyncStatus(status) { - - return status ? - `Synced` : - `Not Synced`; - + return status + ? `Synced` + : `Not Synced`; } listen() { - // sync a product from table - this.wrapper.on('click', '.btn-sync', e => { - + this.wrapper.on("click", ".btn-sync", (e) => { const _this = $(e.currentTarget); - _this.prop('disabled', true).text('Syncing...'); + _this.prop("disabled", true).text("Syncing..."); + + const product = _this.attr("data-product"); + this.syncProduct(product).then((status) => { + if (!status) { + frappe.throw(__("Error syncing product")); + _this.prop("disabled", false).text("Sync"); + return; + } + + _this + .parents(".dt-row") + .find(".indicator-pill") + .replaceWith(this.getProductSyncStatus(true)); + + _this.replaceWith( + `` + ); + }); + }); + + this.wrapper.on("click", ".btn-resync", (e) => { + const _this = $(e.currentTarget); - const product = _this.attr('data-product'); - this.syncProduct(product) - .then(status => { + _this.prop("disabled", true).text("Syncing..."); + const product = _this.attr("data-product"); + this.resyncProduct(product) + .then((status) => { if (!status) { - frappe.throw(__('Error syncing product')); - _this.prop('disabled', false).text('Sync'); + frappe.throw(__("Error syncing product")); return; } - _this.parents('.dt-row') - .find('.indicator-pill') + _this + .parents(".dt-row") + .find(".indicator-pill") .replaceWith(this.getProductSyncStatus(true)); - _this.replaceWith(``); - + _this.prop("disabled", false).text("Re-sync"); + }) + .catch((ex) => { + _this.prop("disabled", false).text("Re-sync"); + frappe.throw(__("Error syncing Product")); }); - }); - this.wrapper.on('click', '.btn-resync', e => { - const _this = $(e.currentTarget); - - _this.prop('disabled', true).text('Syncing...'); - - const product = _this.attr('data-product'); - this.resyncProduct(product) - .then(status => { - - if (!status) { - frappe.throw(__('Error syncing product')); - return; - } - - _this.parents('.dt-row') - .find('.indicator-pill') - .replaceWith(this.getProductSyncStatus(true)); - - _this.prop('disabled', false).text('Re-sync'); - - }) - .catch(ex => { - _this.prop('disabled', false).text('Re-sync'); - frappe.throw(__('Error syncing Product')); - }); - }); - // pagination - this.wrapper.on('click', '.btn-prev,.btn-next', e => this.switchPage(e)); + this.wrapper.on("click", ".btn-prev,.btn-next", (e) => + this.switchPage(e) + ); // sync all products - this.wrapper.on('click', '#btn-sync-all', e => this.syncAll(e)); - + this.wrapper.on("click", "#btn-sync-all", (e) => this.syncAll(e)); } async syncProduct(product) { - const { message: status } = await frappe.call({ - method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.sync_product', + method: "ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.sync_product", args: { product }, }); - if (status) - this.fetchProductCount(); + if (status) this.fetchProductCount(); return status; - } - async resyncProduct(product) { - - const { message: status } = await frappe.call({ - method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.resync_product', - args: { product }, - }); - - if (status) - this.fetchProductCount(); + async resyncProduct(product) { + const { message: status } = await frappe.call({ + method: "ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.resync_product", + args: { product }, + }); - return status; + if (status) this.fetchProductCount(); - } + return status; + } async switchPage({ currentTarget }) { - const _this = $(currentTarget); - $('.btn-paginate').prop('disabled', true); - this.shopifyProductTable.showToastMessage('Loading...'); + $(".btn-paginate").prop("disabled", true); + this.shopifyProductTable.showToastMessage("Loading..."); const newProducts = await this.fetchShopifyProducts( - _this.hasClass('btn-next') ? this.nextUrl : this.prevUrl + _this.hasClass("btn-next") ? this.nextUrl : this.prevUrl ); this.shopifyProductTable.refresh(newProducts); - $('.btn-paginate').prop('disabled', false); + $(".btn-paginate").prop("disabled", false); this.shopifyProductTable.clearToastMessage(); - } syncAll() { - this.checkSyncStatus(); this.toggleSyncAllButton(); if (this.syncRunning) { - frappe.msgprint(__('Sync already in progress')); + frappe.msgprint(__("Sync already in progress")); } else { - frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.import_all_products' }) + frappe.call({ + method: "ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.import_all_products", + }); } // sync progress this.logSync(); - } logSync() { - - const _log = $('#shopify-sync-log'); - _log.parents('.card').show(); - _log.text(''); // clear logs + const _log = $("#shopify-sync-log"); + _log.parents(".card").show(); + _log.text(""); // clear logs // define counters here to prevent calling jquery every time - 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; + 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 +356,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 ad2f94f3..e30a102e 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 0f03997e..ddacf8d2 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) 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 diff --git a/ecommerce_integrations/shopify/product.py b/ecommerce_integrations/shopify/product.py index ffae3204..92c31f46 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 697b8806..d488885d 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 31758ad7..78d04d24 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/utils.py b/ecommerce_integrations/shopify/utils.py index d1d55c00..66f7981f 100644 --- a/ecommerce_integrations/shopify/utils.py +++ b/ecommerce_integrations/shopify/utils.py @@ -26,11 +26,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 +52,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 +73,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 +87,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/unicommerce/api_client.py b/ecommerce_integrations/unicommerce/api_client.py index 1b3581eb..8f252429 100644 --- a/ecommerce_integrations/unicommerce/api_client.py +++ b/ecommerce_integrations/unicommerce/api_client.py @@ -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 264ea640..5cf36c99 100644 --- a/ecommerce_integrations/unicommerce/cancellation_and_returns.py +++ b/ecommerce_integrations/unicommerce/cancellation_and_returns.py @@ -22,7 +22,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 +90,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: @@ -177,7 +175,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 +191,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 +210,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 +218,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 c1b5248a..376ed124 100644 --- a/ecommerce_integrations/unicommerce/customer.py +++ b/ecommerce_integrations/unicommerce/customer.py @@ -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 55560a3e..0af339bd 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 diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_channel/unicommerce_channel.js b/ecommerce_integrations/unicommerce/doctype/unicommerce_channel/unicommerce_channel.js index f2abcb83..1812a30c 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 cdfde540..38bffe38 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 d3fc0fc5..c8f7efe0 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 4f4344c3..09eb66f3 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 cd5a46da..a7b79096 100644 --- a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py +++ b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py @@ -143,18 +143,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 +164,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 +172,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 +181,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 b7b4025e..276e0f18 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 7dc3e9df..2eed1151 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 @@ -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 1f42ce32..c34adc31 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 293c5335..21bb2858 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 a28db7c4..973ed299 100644 --- a/ecommerce_integrations/unicommerce/grn.py +++ b/ecommerce_integrations/unicommerce/grn.py @@ -130,9 +130,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:
{}".format("
".join(errors)) - ) + frappe.msgprint("Partial success, unicommerce reported errors:
{}".format("
".join(errors))) def _prepare_grn_import_csv(stock_entry) -> str: @@ -190,8 +188,7 @@ def _prepare_grn_import_csv(stock_entry) -> str: return file.file_name -def _get_csv_content(rows: List[GRNItemRow]) -> bytes: - +def _get_csv_content(rows: list[GRNItemRow]) -> bytes: writer = UnicodeWriter() for row in rows: @@ -208,7 +205,7 @@ def _get_unicommerce_format_date(date) -> str: def create_auto_grn_import(csv_filename: str, facility_code: str, client=None): - """ Create new import job for Auto GRN items""" + """Create new import job for Auto GRN items""" if client is None: client = UnicommerceAPIClient() resp = client.create_import_job( diff --git a/ecommerce_integrations/unicommerce/inventory.py b/ecommerce_integrations/unicommerce/inventory.py index 704c2c01..67b7744c 100644 --- a/ecommerce_integrations/unicommerce/inventory.py +++ b/ecommerce_integrations/unicommerce/inventory.py @@ -32,9 +32,7 @@ def update_inventory_on_unicommerce(client=None, force=False): return # check if need to run based on configured sync frequency - if not force and not need_to_run( - SETTINGS_DOCTYPE, "inventory_sync_frequency", "last_inventory_sync" - ): + if not force and not need_to_run(SETTINGS_DOCTYPE, "inventory_sync_frequency", "last_inventory_sync"): return # get configured warehouses @@ -45,7 +43,7 @@ def update_inventory_on_unicommerce(client=None, force=False): client = UnicommerceAPIClient() # track which ecommerce item was updated successfully - success_map: Dict[str, bool] = defaultdict(lambda: True) + success_map: dict[str, bool] = defaultdict(lambda: True) inventory_synced_on = now() for warehouse in warehouses: @@ -82,7 +80,7 @@ def update_inventory_on_unicommerce(client=None, force=False): _update_inventory_sync_status(success_map, inventory_synced_on) -def _update_inventory_sync_status(ecom_item_success_map: Dict[str, bool], timestamp: str) -> None: +def _update_inventory_sync_status(ecom_item_success_map: dict[str, bool], timestamp: str) -> None: for ecom_item, status in ecom_item_success_map.items(): if status: update_inventory_sync_status(ecom_item, timestamp) diff --git a/ecommerce_integrations/unicommerce/invoice.py b/ecommerce_integrations/unicommerce/invoice.py index 8f99a520..1361c327 100644 --- a/ecommerce_integrations/unicommerce/invoice.py +++ b/ecommerce_integrations/unicommerce/invoice.py @@ -34,7 +34,7 @@ remove_non_alphanumeric_chars, ) -JsonDict = Dict[str, Any] +JsonDict = dict[str, Any] SOCode = NewType("SOCode", str) # TypedDict @@ -42,17 +42,17 @@ # item_code: str # warehouse: str # batch_no: str -ItemWHAlloc = Dict[str, str] +ItemWHAlloc = dict[str, str] -WHAllocation = Dict[SOCode, List[ItemWHAlloc]] +WHAllocation = dict[SOCode, list[ItemWHAlloc]] INVOICED_STATE = ["PACKED", "READY_TO_SHIP", "DISPATCHED", "MANIFESTED", "SHIPPED", "DELIVERED"] @frappe.whitelist() def generate_unicommerce_invoices( - sales_orders: List[SOCode], warehouse_allocation: Optional[WHAllocation] = None + sales_orders: list[SOCode], warehouse_allocation: WHAllocation | None = None ): """Request generation of invoice to Unicommerce and sync that invoice. @@ -126,8 +126,8 @@ def generate_unicommerce_invoices( def bulk_generate_invoices( - sales_orders: List[SOCode], - warehouse_allocation: Optional[WHAllocation] = None, + sales_orders: list[SOCode], + warehouse_allocation: WHAllocation | None = None, request_id=None, client=None, ): @@ -153,7 +153,6 @@ def bulk_generate_invoices( def _log_invoice_generation(sales_orders, failed_orders): - failed_orders = set(failed_orders) failed_orders.update(_get_orders_with_missing_invoice(sales_orders)) successful_orders = list(set(sales_orders) - set(failed_orders)) @@ -187,7 +186,7 @@ def _get_orders_with_missing_invoice(sales_orders): return missing_invoices -def update_invoicing_status(sales_orders: List[str], status: str) -> None: +def update_invoicing_status(sales_orders: list[str], status: str) -> None: if not sales_orders: return @@ -236,9 +235,7 @@ def _validate_wh_allocation(warehouse_allocation: WHAllocation): frappe.throw(msg) -def _generate_invoice( - client: UnicommerceAPIClient, erpnext_order, channel_config, warehouse_allocation=None -): +def _generate_invoice(client: UnicommerceAPIClient, erpnext_order, channel_config, warehouse_allocation=None): unicommerce_so_code = erpnext_order.get(ORDER_CODE_FIELD) so_data = client.get_sales_order(unicommerce_so_code) @@ -286,16 +283,12 @@ def _fetch_and_sync_invoice( """ so_data = client.get_sales_order(unicommerce_so_code) - shipping_packages = [ - d["code"] for d in so_data["shippingPackages"] if d["status"] in INVOICED_STATE - ] + shipping_packages = [d["code"] for d in so_data["shippingPackages"] if d["status"] in INVOICED_STATE] for package in shipping_packages: invoice_response = invoice_responses.get(package) or {} invoice_data = client.get_sales_invoice(package, facility_code)["invoice"] - label_pdf = fetch_label_pdf( - package, invoice_response, client=client, facility_code=facility_code - ) + label_pdf = fetch_label_pdf(package, invoice_response, client=client, facility_code=facility_code) create_sales_invoice( invoice_data, erpnext_so_code, @@ -315,7 +308,7 @@ def create_sales_invoice( shipping_label=None, warehouse_allocations=None, invoice_response=None, - so_data: Optional[JsonDict] = None, + so_data: JsonDict | None = None, ): """Create ERPNext Sales Invcoice using Unicommerce sales invoice data and related Sales order. @@ -351,9 +344,7 @@ def create_sales_invoice( shipping_package_code = si_data.get("shippingPackageCode") shipping_package_info = _get_shipping_package(so_data, shipping_package_code) or {} - tracking_no = invoice_response.get("trackingNumber") or shipping_package_info.get( - "trackingNumber" - ) + tracking_no = invoice_response.get("trackingNumber") or shipping_package_info.get("trackingNumber") shipping_provider_code = ( invoice_response.get("shippingProviderCode") or shipping_package_info.get("shippingProvider") @@ -412,10 +403,10 @@ def create_sales_invoice( def attach_unicommerce_docs( sales_invoice: str, - invoice: Optional[str], - label: Optional[str], - invoice_code: Optional[str], - package_code: Optional[str], + invoice: str | None, + label: str | None, + invoice_code: str | None, + package_code: str | None, ) -> None: """Attach invoice and label to specified sales invoice. @@ -452,9 +443,9 @@ def _get_line_items( warehouse: str, so_code: str, cost_center: str, - warehouse_allocations: Optional[WHAllocation] = None, -) -> List[Dict[str, Any]]: - """ Invoice items can be different and are consolidated, hence recomputing is required """ + warehouse_allocations: WHAllocation | None = None, +) -> list[dict[str, Any]]: + """Invoice items can be different and are consolidated, hence recomputing is required""" si_items = [] for item in line_items: @@ -481,15 +472,12 @@ def _get_line_items( return si_items -def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], so_code: str): - +def _assign_wh_and_so_row(line_items, warehouse_allocation: list[ItemWHAlloc], so_code: str): so_items = frappe.get_doc("Sales Order", so_code).items so_item_price_map = {d.name: d.rate for d in so_items} # remove cancelled items - warehouse_allocation = [ - d for d in warehouse_allocation if d["sales_order_row"] in so_item_price_map - ] + warehouse_allocation = [d for d in warehouse_allocation if d["sales_order_row"] in so_item_price_map] # update price for item in warehouse_allocation: @@ -501,7 +489,7 @@ def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], s line_items.sort(key=sort_key) # update references - for item, wh_alloc in zip(line_items, warehouse_allocation): + for item, wh_alloc in zip(line_items, warehouse_allocation, strict=False): item["so_detail"] = wh_alloc["sales_order_row"] item["warehouse"] = wh_alloc["warehouse"] item["batch_no"] = wh_alloc.get("batch_no") @@ -510,7 +498,7 @@ def _assign_wh_and_so_row(line_items, warehouse_allocation: List[ItemWHAlloc], s def _verify_total(si, si_data) -> None: - """ Leave a comment if grand total does not match unicommerce total""" + """Leave a comment if grand total does not match unicommerce total""" if abs(si.grand_total - flt(si_data["total"])) > 0.5: si.add_comment(text=f"Invoice totals mismatch: Unicommerce reported total of {si_data['total']}") @@ -541,7 +529,6 @@ def make_payment_entry(invoice, channel_config, invoice_posting_date=None): def fetch_label_pdf(package, invoicing_response, client, facility_code): - if invoicing_response and invoicing_response.get("shippingLabelLink"): link = invoicing_response.get("shippingLabelLink") return fetch_pdf_as_base64(link) diff --git a/ecommerce_integrations/unicommerce/order.py b/ecommerce_integrations/unicommerce/order.py index 6b1ea741..411c4f50 100644 --- a/ecommerce_integrations/unicommerce/order.py +++ b/ecommerce_integrations/unicommerce/order.py @@ -1,6 +1,7 @@ import json from collections import defaultdict, namedtuple -from typing import Any, Dict, Iterator, List, NewType, Optional, Set, Tuple +from collections.abc import Iterator +from typing import Any, Dict, List, NewType, Optional, Set, Tuple import frappe from frappe.utils import add_to_date, flt @@ -29,7 +30,7 @@ from ecommerce_integrations.unicommerce.utils import create_unicommerce_log, get_unicommerce_date from ecommerce_integrations.utils.taxation import get_dummy_tax_category -UnicommerceOrder = NewType("UnicommerceOrder", Dict[str, Any]) +UnicommerceOrder = NewType("UnicommerceOrder", dict[str, Any]) def sync_new_orders(client: UnicommerceAPIClient = None, force=False): @@ -61,10 +62,7 @@ def sync_new_orders(client: UnicommerceAPIClient = None, force=False): _create_sales_invoices(order, sales_order, client) -def _get_new_orders( - client: UnicommerceAPIClient, status: Optional[str] -) -> Optional[Iterator[UnicommerceOrder]]: - +def _get_new_orders(client: UnicommerceAPIClient, status: str | None) -> Iterator[UnicommerceOrder] | None: """Search new sales order from unicommerce.""" updated_since = 24 * 60 # minutes @@ -124,8 +122,7 @@ def _create_sales_invoices(unicommerce_order, sales_order, client: UnicommerceAP frappe.flags.request_id = None -def create_order(payload: UnicommerceOrder, request_id: Optional[str] = None, client=None) -> None: - +def create_order(payload: UnicommerceOrder, request_id: str | None = None, client=None) -> None: order = payload existing_so = frappe.db.get_value("Sales Order", {ORDER_CODE_FIELD: order["code"]}) @@ -158,7 +155,7 @@ def create_order(payload: UnicommerceOrder, request_id: Optional[str] = None, cl return order -def _sync_order_items(order: UnicommerceOrder, client: UnicommerceAPIClient) -> Set[str]: +def _sync_order_items(order: UnicommerceOrder, client: UnicommerceAPIClient) -> set[str]: """Ensure all items are synced before processing order. If not synced then product sync for specific item is initiated""" @@ -174,7 +171,6 @@ def _sync_order_items(order: UnicommerceOrder, client: UnicommerceAPIClient) -> def _create_order(order: UnicommerceOrder, customer) -> None: - channel_config = frappe.get_doc("Unicommerce Channel", order["channel"]) settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) @@ -219,9 +215,8 @@ def _create_order(order: UnicommerceOrder, customer) -> None: def _get_line_items( - line_items, default_warehouse: Optional[str] = None, is_cancelled: bool = False -) -> List[Dict[str, Any]]: - + line_items, default_warehouse: str | None = None, is_cancelled: bool = False +) -> list[dict[str, Any]]: settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) wh_map = settings.get_integration_to_erpnext_wh_mapping(all_wh=True) so_items = [] @@ -249,7 +244,7 @@ def _get_line_items( return so_items -def get_taxes(line_items, channel_config) -> List: +def get_taxes(line_items, channel_config) -> list: taxes = [] # Note: Tax details are NOT available during SO stage. @@ -330,9 +325,7 @@ def _update_package_info_on_unicommerce(so_code): shipping_packages = updated_so_data.get("shippingPackages") if not shipping_packages: - frappe.throw( - frappe._("Shipping package not present on Unicommerce for order {}").format(so.name) - ) + frappe.throw(frappe._("Shipping package not present on Unicommerce for order {}").format(so.name)) shipping_package_code = shipping_packages[0].get("code") @@ -356,7 +349,7 @@ def _update_package_info_on_unicommerce(so_code): raise -def _get_batch_no(so_line_item) -> Optional[str]: +def _get_batch_no(so_line_item) -> str | None: """If specified vendor batch code is valid batch number in ERPNext then get batch no. SO line items contain batch no detail like this: @@ -374,9 +367,7 @@ def _get_batch_no(so_line_item) -> Optional[str]: } }, """ - batch_no = ((so_line_item.get("batchDTO") or {}).get("batchFieldsDTO") or {}).get( - "vendorBatchNumber" - ) + batch_no = ((so_line_item.get("batchDTO") or {}).get("batchFieldsDTO") or {}).get("vendorBatchNumber") if batch_no and frappe.db.exists("Batch", batch_no): return batch_no diff --git a/ecommerce_integrations/unicommerce/pick_list.py b/ecommerce_integrations/unicommerce/pick_list.py index 7a883347..c5334fc7 100644 --- a/ecommerce_integrations/unicommerce/pick_list.py +++ b/ecommerce_integrations/unicommerce/pick_list.py @@ -21,7 +21,9 @@ def validate(self, method=None): if pl.picked_qty > pl.qty: pl.picked_qty = pl.qty - frappe.throw(_("Row {0} Picked Qty cannot be more than Sales Order Qty").format(pl.idx)) + frappe.throw( + _("Row {0} Picked Qty cannot be more than Sales Order Qty").format(pl.idx) + ) if pl.picked_qty == 0 and pl.docstatus == 1: frappe.throw( _("You have not picked {0} in row {1} . Pick the item to proceed!").format( diff --git a/ecommerce_integrations/unicommerce/product.py b/ecommerce_integrations/unicommerce/product.py index 553bc9e3..9d34431a 100644 --- a/ecommerce_integrations/unicommerce/product.py +++ b/ecommerce_integrations/unicommerce/product.py @@ -91,7 +91,6 @@ def _create_item_dict(uni_item): _validate_create_brand(uni_item.get("brand")) for uni_field, erpnext_field in UNI_TO_ERPNEXT_ITEM_MAPPING.items(): - value = uni_item.get(uni_field) if not _validate_field(erpnext_field, value): continue @@ -218,7 +217,7 @@ def upload_new_items(force=False) -> None: log.save() -def _get_new_items() -> List[ItemCode]: +def _get_new_items() -> list[ItemCode]: new_items = frappe.db.sql( f""" SELECT item.item_code @@ -234,8 +233,8 @@ def _get_new_items() -> List[ItemCode]: def upload_items_to_unicommerce( - item_codes: List[ItemCode], client: UnicommerceAPIClient = None -) -> List[ItemCode]: + item_codes: list[ItemCode], client: UnicommerceAPIClient = None +) -> list[ItemCode]: """Upload multiple items to Unicommerce. Return Successfully synced item codes. @@ -284,9 +283,7 @@ def _build_unicommerce_item(item_code: ItemCode) -> JsonDict: elif barcode.barcode_type == "UPC-A": item_json["upc"] = barcode.barcode - item_json["categoryCode"] = frappe.db.get_value( - "Item Group", item.item_group, PRODUCT_CATEGORY_FIELD - ) + item_json["categoryCode"] = frappe.db.get_value("Item Group", item.item_group, PRODUCT_CATEGORY_FIELD) # append site prefix to image url item_json["imageUrl"] = get_url(item.image) item_json["maxRetailPrice"] = item.standard_rate @@ -338,6 +335,4 @@ def validate_item(doc, method=None): item_group = frappe.get_cached_doc("Item Group", item.item_group) if not item_group.get(PRODUCT_CATEGORY_FIELD): - frappe.throw( - _("Unicommerce Product category required in Item Group: {}").format(item_group.name) - ) + frappe.throw(_("Unicommerce Product category required in Item Group: {}").format(item_group.name)) diff --git a/ecommerce_integrations/unicommerce/status_updater.py b/ecommerce_integrations/unicommerce/status_updater.py index fdd85dd6..c81d7966 100644 --- a/ecommerce_integrations/unicommerce/status_updater.py +++ b/ecommerce_integrations/unicommerce/status_updater.py @@ -47,7 +47,6 @@ def update_sales_order_status(): - settings = frappe.get_cached_doc(SETTINGS_DOCTYPE) if not settings.is_enabled(): return @@ -58,9 +57,7 @@ def update_sales_order_status(): minutes = days_to_sync * 24 * 60 updated_orders = client.search_sales_order(updated_since=minutes) - enabled_channels = frappe.db.get_list( - "Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id" - ) + enabled_channels = frappe.db.get_list("Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id") valid_orders = [order for order in updated_orders if order.get("channel") in enabled_channels] if valid_orders: _update_order_status_fields(valid_orders) @@ -79,7 +76,6 @@ def update_sales_order_status(): def _update_order_status_fields(orders): - order_status_map = {d["code"]: d["status"] for d in orders} order_codes = list(order_status_map.keys()) @@ -121,9 +117,7 @@ def update_shipping_package_status(): # find all Facilities enabled_facilities = list(settings.get_integration_to_erpnext_wh_mapping().keys()) - enabled_channels = frappe.db.get_list( - "Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id" - ) + enabled_channels = frappe.db.get_list("Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id") for facility in enabled_facilities: updated_packages = client.search_shipping_packages(updated_since=minutes, facility_code=facility) @@ -140,7 +134,6 @@ def update_shipping_package_status(): def _update_package_status_fields(packages): - package_status_map = {d["code"]: d["status"] for d in packages} package_codes = list(package_status_map.keys()) diff --git a/ecommerce_integrations/unicommerce/tests/test_client.py b/ecommerce_integrations/unicommerce/tests/test_client.py index 50f6c5f3..ee667c2a 100644 --- a/ecommerce_integrations/unicommerce/tests/test_client.py +++ b/ecommerce_integrations/unicommerce/tests/test_client.py @@ -131,7 +131,6 @@ def test_create_update_item(self): self.assertTrue(response["successful"]) def test_bulk_inventory_sync(self): - expected_body = { "inventoryAdjustments": [ { @@ -289,9 +288,7 @@ def test_update_shipping_package(self): ], ) - self.client.update_shipping_package( - "SP_CODE", "TEST", "DEFAULT", length=100, width=200, height=300 - ) + self.client.update_shipping_package("SP_CODE", "TEST", "DEFAULT", length=100, width=200, height=300) self.assert_last_request_headers("Facility", "TEST") def test_get_invoice_label(self): @@ -323,7 +320,9 @@ def test_bulk_import(self): responses.POST, "https://demostaging.unicommerce.com/services/rest/v1/data/import/job/create", status=200, - match=[query_param_matcher({"name": "Auto GRN Items", "importOption": "CREATE_NEW"}),], + match=[ + query_param_matcher({"name": "Auto GRN Items", "importOption": "CREATE_NEW"}), + ], json={"successful": True}, ) diff --git a/ecommerce_integrations/unicommerce/tests/test_inventory.py b/ecommerce_integrations/unicommerce/tests/test_inventory.py index b931c9f3..0dba3751 100644 --- a/ecommerce_integrations/unicommerce/tests/test_inventory.py +++ b/ecommerce_integrations/unicommerce/tests/test_inventory.py @@ -93,7 +93,6 @@ def test_inventory_sync(self): def make_ecommerce_item(item_code): - if ecommerce_item.is_synced(MODULE_NAME, item_code): return diff --git a/ecommerce_integrations/unicommerce/tests/test_invoice.py b/ecommerce_integrations/unicommerce/tests/test_invoice.py index ac80d9fa..b551aec2 100644 --- a/ecommerce_integrations/unicommerce/tests/test_invoice.py +++ b/ecommerce_integrations/unicommerce/tests/test_invoice.py @@ -55,9 +55,7 @@ def test_create_invoice(self): attachments = frappe.get_all( "File", fields=["name", "file_name"], filters={"attached_to_name": si.name} ) - self.assertGreaterEqual( - len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}" - ) + self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {attachments!s}") def test_end_to_end_invoice_generation(self): """Full invoice generation test with mocked responses.""" @@ -113,6 +111,4 @@ def test_end_to_end_invoice_generation(self): attachments = frappe.get_all( "File", fields=["name", "file_name"], filters={"attached_to_name": si.name} ) - self.assertGreaterEqual( - len(attachments), 2, msg=f"Expected 2 attachments, found: {str(attachments)}" - ) + self.assertGreaterEqual(len(attachments), 2, msg=f"Expected 2 attachments, found: {attachments!s}") diff --git a/ecommerce_integrations/unicommerce/tests/test_order.py b/ecommerce_integrations/unicommerce/tests/test_order.py index 20b3071f..aea4c166 100644 --- a/ecommerce_integrations/unicommerce/tests/test_order.py +++ b/ecommerce_integrations/unicommerce/tests/test_order.py @@ -26,9 +26,15 @@ def setUpClass(cls): def test_validate_item_list(self): order_files = ["order-SO5905", "order-SO5906", "order-SO5907"] - items_list = [{"MC-100", "TITANIUM_WATCH"}, {"MC-100",}, {"MC-100", "TITANIUM_WATCH"}] - - for order_file, items in zip(order_files, items_list): + items_list = [ + {"MC-100", "TITANIUM_WATCH"}, + { + "MC-100", + }, + {"MC-100", "TITANIUM_WATCH"}, + ] + + for order_file, items in zip(order_files, items_list, strict=False): order = self.load_fixture(order_file)["saleOrderDTO"] self.assertEqual(items, _sync_order_items(order, client=self.client)) diff --git a/ecommerce_integrations/unicommerce/tests/test_product.py b/ecommerce_integrations/unicommerce/tests/test_product.py index 96c72e5b..2b45839a 100644 --- a/ecommerce_integrations/unicommerce/tests/test_product.py +++ b/ecommerce_integrations/unicommerce/tests/test_product.py @@ -28,9 +28,7 @@ def test_import_missing_item_raises_error(self): json=self.load_fixture("missing_item"), match=[responses.json_params_matcher({"skuCode": "MISSING"})], ) - self.assertRaises( - frappe.ValidationError, import_product_from_unicommerce, "MISSING", self.client - ) + self.assertRaises(frappe.ValidationError, import_product_from_unicommerce, "MISSING", self.client) log = frappe.get_last_doc("Ecommerce Integration Log", filters={"integration": "unicommerce"}) self.assertTrue("Failed to import" in log.message, "Logging for missing item not working") diff --git a/ecommerce_integrations/unicommerce/tests/test_status.py b/ecommerce_integrations/unicommerce/tests/test_status.py index 6d61564c..8cdc6581 100644 --- a/ecommerce_integrations/unicommerce/tests/test_status.py +++ b/ecommerce_integrations/unicommerce/tests/test_status.py @@ -15,7 +15,6 @@ def test_serialization(self): _serialize_items([si_item.as_dict()]) def test_delete_cancelled_items(self): - item1 = frappe.new_doc("Sales Order Item").update({ORDER_ITEM_CODE_FIELD: "cancelled"}) item2 = frappe.new_doc("Sales Order Item").update({ORDER_ITEM_CODE_FIELD: "not cancelled"}) diff --git a/ecommerce_integrations/unicommerce/utils.py b/ecommerce_integrations/unicommerce/utils.py index 966c53ee..cd4d699a 100644 --- a/ecommerce_integrations/unicommerce/utils.py +++ b/ecommerce_integrations/unicommerce/utils.py @@ -48,7 +48,7 @@ def force_sync(document) -> None: def get_unicommerce_date(timestamp: int) -> datetime.date: - """ Convert unicommerce ms timestamp to datetime.""" + """Convert unicommerce ms timestamp to datetime.""" return datetime.date.fromtimestamp(timestamp // 1000) diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_category/zenoti_category.js b/ecommerce_integrations/zenoti/doctype/zenoti_category/zenoti_category.js index 6177800e..10e6614f 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_category/zenoti_category.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_category/zenoti_category.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Zenoti Category', { +frappe.ui.form.on("Zenoti Category", { // refresh: function(frm) { - // } }); diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js b/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js index 09f21a47..f7b5d9eb 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js @@ -1,145 +1,187 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see license.txt -frappe.ui.form.on('Zenoti Center', { +frappe.ui.form.on("Zenoti Center", { refresh(frm) { - frm.add_custom_button('Employees', function() { - frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", - args:{ - center: frm.doc.name, - record_type: "Employees", - }, - callback: function(r){ - frappe.show_alert({message:__("Syncing"), indicator:'orange'}); - } - }) - }, 'Sync'); - - frm.add_custom_button('Customers', function() { - frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", - args:{ - center: frm.doc.name, - record_type: "Customers", - }, - callback: function(r){ - frappe.show_alert({message:__("Syncing"), indicator:'orange'}); - } - }) - }, 'Sync'); + frm.add_custom_button( + "Employees", + function () { + frappe.call({ + method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", + args: { + center: frm.doc.name, + record_type: "Employees", + }, + callback: function (r) { + frappe.show_alert({ + message: __("Syncing"), + indicator: "orange", + }); + }, + }); + }, + "Sync" + ); - frm.add_custom_button('Items', function() { - frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", - args:{ - center: frm.doc.name, - record_type: "Items", - }, - callback: function(r){ - frappe.show_alert({message:__("Syncing"), indicator:'orange'}); - } - }) - }, 'Sync'); + frm.add_custom_button( + "Customers", + function () { + frappe.call({ + method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", + args: { + center: frm.doc.name, + record_type: "Customers", + }, + callback: function (r) { + frappe.show_alert({ + message: __("Syncing"), + indicator: "orange", + }); + }, + }); + }, + "Sync" + ); - frm.add_custom_button('Categories', function() { - frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", - args:{ - center: frm.doc.name, - record_type: "Categories", - }, - callback: function(r){ - frappe.show_alert({message:__("Syncing"), indicator:'orange'}); - } - }) - }, 'Sync'); + frm.add_custom_button( + "Items", + function () { + frappe.call({ + method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", + args: { + center: frm.doc.name, + record_type: "Items", + }, + callback: function (r) { + frappe.show_alert({ + message: __("Syncing"), + indicator: "orange", + }); + }, + }); + }, + "Sync" + ); - frm.add_custom_button('Sales Invoice', function() { - let d = new frappe.ui.Dialog({ - title: __('Sync Sales Invoice'), - fields: [ - { - "label" : "From Date", - "fieldname": "start_date", - "fieldtype": "Date", - "reqd": 1 + frm.add_custom_button( + "Categories", + function () { + frappe.call({ + method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", + args: { + center: frm.doc.name, + record_type: "Categories", }, - { - "label" : "To Date", - "fieldname": "end_date", - "fieldtype": "Date", - "reqd": 1 - } - ], - primary_action: function() { - let data = d.get_values(); - frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", - args:{ - center: frm.doc.name, - record_type: "Sales Invoice", - start_date: data.start_date, - end_date: data.end_date + callback: function (r) { + frappe.show_alert({ + message: __("Syncing"), + indicator: "orange", + }); + }, + }); + }, + "Sync" + ); + + frm.add_custom_button( + "Sales Invoice", + function () { + let d = new frappe.ui.Dialog({ + title: __("Sync Sales Invoice"), + fields: [ + { + label: "From Date", + fieldname: "start_date", + fieldtype: "Date", + reqd: 1, }, - callback: function(r){ - frappe.show_alert({message:__("Syncing"), indicator:'orange'}); - } - }) - d.hide(); - }, - primary_action_label: __('Sync Sales Invoice') - }); - d.show(); - }, 'Sync'); + { + label: "To Date", + fieldname: "end_date", + fieldtype: "Date", + reqd: 1, + }, + ], + primary_action: function () { + let data = d.get_values(); + frappe.call({ + method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", + args: { + center: frm.doc.name, + record_type: "Sales Invoice", + start_date: data.start_date, + end_date: data.end_date, + }, + callback: function (r) { + frappe.show_alert({ + message: __("Syncing"), + indicator: "orange", + }); + }, + }); + d.hide(); + }, + primary_action_label: __("Sync Sales Invoice"), + }); + d.show(); + }, + "Sync" + ); - frm.add_custom_button('Stock Reconciliation', function() { - let d = new frappe.ui.Dialog({ - title: __('Sync Stock Reconciliation'), - fields: [ - { - "label" : "Date", - "fieldname": "date", - "fieldtype": "Date", - "reqd": 1 - } - ], - primary_action: function() { - let data = d.get_values(); - frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", - args:{ - center: frm.doc.name, - record_type: "Stock Reconciliation", - start_date: data.date + frm.add_custom_button( + "Stock Reconciliation", + function () { + let d = new frappe.ui.Dialog({ + title: __("Sync Stock Reconciliation"), + fields: [ + { + label: "Date", + fieldname: "date", + fieldtype: "Date", + reqd: 1, }, - callback: function(r){ - frappe.show_alert({message:__("Syncing"), indicator:'orange'}); - } - }) - d.hide(); - }, - primary_action_label: __('Sync Stock Reconciliation') - }); - d.show(); - }, 'Sync'); + ], + primary_action: function () { + let data = d.get_values(); + frappe.call({ + method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", + args: { + center: frm.doc.name, + record_type: "Stock Reconciliation", + start_date: data.date, + }, + callback: function (r) { + frappe.show_alert({ + message: __("Syncing"), + indicator: "orange", + }); + }, + }); + d.hide(); + }, + primary_action_label: __("Sync Stock Reconciliation"), + }); + d.show(); + }, + "Sync" + ); }, setup(frm) { - frm.set_query("erpnext_cost_center", function() { + frm.set_query("erpnext_cost_center", function () { return { filters: { is_group: 0, - } - } + }, + }; }); - frm.set_query("erpnext_warehouse", function() { + frm.set_query("erpnext_warehouse", function () { return { filters: { is_group: 0, - } - } + }, + }; }); - } + }, }); diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_error_logs/zenoti_error_logs.js b/ecommerce_integrations/zenoti/doctype/zenoti_error_logs/zenoti_error_logs.js index 46634696..931c347a 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_error_logs/zenoti_error_logs.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_error_logs/zenoti_error_logs.js @@ -1,8 +1,7 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see LICENSE -frappe.ui.form.on('Zenoti Error Logs', { +frappe.ui.form.on("Zenoti Error Logs", { // refresh: function(frm) { - // } }); diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js index 1a69aa61..e7178526 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js @@ -1,65 +1,68 @@ // Copyright (c) 2021, Frappe and contributors // For license information, please see LICENSE -frappe.ui.form.on('Zenoti Settings', { - setup: function(frm){ - frm.set_query("liability_income_account_for_gift_and_prepaid_cards", function() { - if (!frm.doc.company) { - frappe.throw(__("Please select company first")) - } - return { - filters: { - root_type: "Liability", - is_group: 0, - account_type: "Income Account", - company: frm.doc.company +frappe.ui.form.on("Zenoti Settings", { + setup: function (frm) { + frm.set_query( + "liability_income_account_for_gift_and_prepaid_cards", + function () { + if (!frm.doc.company) { + frappe.throw(__("Please select company first")); } + return { + filters: { + root_type: "Liability", + is_group: 0, + account_type: "Income Account", + company: frm.doc.company, + }, + }; } - }); + ); - frm.set_query("default_purchase_warehouse", function() { + frm.set_query("default_purchase_warehouse", function () { if (!frm.doc.company) { - frappe.throw(__("Please select company first")) + frappe.throw(__("Please select company first")); } return { filters: { is_group: 0, - company: frm.doc.company - } + company: frm.doc.company, + }, }; }); - frm.set_query("default_buying_price_list", function() { + frm.set_query("default_buying_price_list", function () { return { filters: { buying: 1, - } - } + }, + }; }); - frm.set_query("default_selling_price_list", function() { + frm.set_query("default_selling_price_list", function () { return { filters: { selling: 1, - } - } + }, + }; }); }, refresh(frm) { - if(cint(frm.doc.enable_zenoti)) { - frm.add_custom_button('Update Centers', function() { + if (cint(frm.doc.enable_zenoti)) { + frm.add_custom_button("Update Centers", function () { frappe.call({ - method:"ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.update_centers", + method: "ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.update_centers", freeze: true, freeze_message: "Updating Centers...", - callback: function(r){ + callback: function (r) { if (!r.exc) { - frappe.show_alert(__("Centers Updated")) + frappe.show_alert(__("Centers Updated")); } - } - }) + }, + }); d.hide(); }); } - } + }, }); diff --git a/ecommerce_integrations/zenoti/purchase_transactions.py b/ecommerce_integrations/zenoti/purchase_transactions.py index e49840c8..c4a66e0e 100644 --- a/ecommerce_integrations/zenoti/purchase_transactions.py +++ b/ecommerce_integrations/zenoti/purchase_transactions.py @@ -28,9 +28,7 @@ def get_list_of_purchase_orders_for_center(center, date=None): start_date = add_to_date(end_date, days=-1) route = "inventory/purchase_orders?center_id=" url_end = "&show_delivery_details=true&date_criteria=1&status=-1" - full_url = ( - api_url + route + center + "&start_date=" + start_date + "&end_date=" + end_date + url_end - ) + full_url = api_url + route + center + "&start_date=" + start_date + "&end_date=" + end_date + url_end all_orders = make_api_call(full_url) return all_orders @@ -208,7 +206,9 @@ def add_items(doc, item_data): invoice_item[key] = value if key == "item_code": item_code = frappe.db.get_value( - "Item", {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, "item_code" + "Item", + {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, + "item_code", ) invoice_item["item_code"] = item_code diff --git a/ecommerce_integrations/zenoti/sales_transactions.py b/ecommerce_integrations/zenoti/sales_transactions.py index 5fe131b1..a1ae3cf6 100644 --- a/ecommerce_integrations/zenoti/sales_transactions.py +++ b/ecommerce_integrations/zenoti/sales_transactions.py @@ -250,9 +250,7 @@ def process_sales_line_items(invoice, cost_center, center): if len(item_err_msg_list): item_err_msg = "\n".join(err for err in item_err_msg_list) err_msg_list.append(item_err_msg) - emp_err_msg = check_for_employee( - line_item["employee"]["name"], line_item["employee"]["code"], center - ) + emp_err_msg = check_for_employee(line_item["employee"]["name"], line_item["employee"]["code"], center) if emp_err_msg: err_msg_list.append(emp_err_msg) sold_by = frappe.db.get_value( @@ -449,9 +447,7 @@ def make_invoice(invoice_details): doc.posting_time = invoice_details["posting_time"] doc.due_date = invoice_details["posting_date"] doc.cost_center = invoice_details["cost_center"] - doc.selling_price_list = frappe.db.get_single_value( - "Zenoti Settings", "default_selling_price_list" - ) + doc.selling_price_list = frappe.db.get_single_value("Zenoti Settings", "default_selling_price_list") doc.set_warehouse = invoice_details["set_warehouse"] doc.update_stock = 1 doc.rounding_adjustment = invoice_details["rounding_adjustment"] diff --git a/ecommerce_integrations/zenoti/stock_reconciliation.py b/ecommerce_integrations/zenoti/stock_reconciliation.py index 249374f7..2f9e1dde 100644 --- a/ecommerce_integrations/zenoti/stock_reconciliation.py +++ b/ecommerce_integrations/zenoti/stock_reconciliation.py @@ -10,9 +10,7 @@ def process_stock_reconciliation(center, error_logs, date=None): if not date: date = now() list_for_entry = [] - stock_quantities_of_products_in_a_center = retrieve_stock_quantities_of_products( - center.name, date - ) + stock_quantities_of_products_in_a_center = retrieve_stock_quantities_of_products(center.name, date) if stock_quantities_of_products_in_a_center: cost_center = center.get("erpnext_cost_center") if not cost_center: @@ -31,7 +29,7 @@ def process_stock_reconciliation(center, error_logs, date=None): def retrieve_stock_quantities_of_products(center, date): - url = api_url + "inventory/stock?center_id={0}&inventory_date={1}".format(center, date) + url = api_url + f"inventory/stock?center_id={center}&inventory_date={date}" stock_quantities_of_products = make_api_call(url) return stock_quantities_of_products @@ -81,7 +79,9 @@ def add_items_to_reconcile(doc, list_for_entry): invoice_item[key] = value if key == "item_code": item_code = frappe.db.get_value( - "Item", {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, "item_code" + "Item", + {"zenoti_item_code": item["item_code"], "item_name": item["item_name"]}, + "item_code", ) invoice_item["item_code"] = item_code doc.append("items", invoice_item) diff --git a/ecommerce_integrations/zenoti/utils.py b/ecommerce_integrations/zenoti/utils.py index 16b69692..b16f00da 100644 --- a/ecommerce_integrations/zenoti/utils.py +++ b/ecommerce_integrations/zenoti/utils.py @@ -80,9 +80,7 @@ def check_for_item(list_of_items, item_group, center=None): def make_item(item, item_group, center=None): item_details, center = get_item_details(item, item_group, center) if not item_details: - err_msg = _("Details for Item {0} does not exist in Zenoti").format( - frappe.bold(item["item_name"]) - ) + err_msg = _("Details for Item {0} does not exist in Zenoti").format(frappe.bold(item["item_name"])) return err_msg create_item(item, item_details, item_group, center) From ce75bf284b44a5ff33785649371bccdea0e8d715 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:23:07 +0200 Subject: [PATCH 06/11] style: autoformat (ruff unsafe) --- .../amazon_repository.py | 2 +- .../amazon_sp_api_settings/amazon_sp_api.py | 38 +++++++++--------- .../amazon_sp_api_settings.py | 2 +- .../test_amazon_sp_api_settings.py | 40 +++++++++---------- .../controllers/inventory.py | 6 +-- .../test_shopify_import_products.py | 6 +-- ecommerce_integrations/shopify/tests/utils.py | 8 ++-- .../unicommerce/cancellation_and_returns.py | 2 +- .../unicommerce_shipment_manifest.py | 2 +- ecommerce_integrations/unicommerce/grn.py | 2 +- ecommerce_integrations/unicommerce/order.py | 2 +- 11 files changed, 55 insertions(+), 55 deletions(-) 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 aaf5b7d3..1672801d 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") 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 517d7527..d277389a 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 @@ -280,8 +280,8 @@ def make_request( self, method: str = "GET", append_to_base_uri: str = "", - params: dict = None, - data: dict = None, + params: dict | None = None, + data: dict | None = None, ) -> dict: if isinstance(params, dict): params = Util.remove_empty(params) @@ -312,7 +312,7 @@ class Finances(SPAPI): 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.""" append_to_base_uri = f"orders/{order_id}/financialEvents" @@ -328,22 +328,22 @@ class Orders(SPAPI): 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.""" data = dict( @@ -373,7 +373,7 @@ def get_orders( return self.make_request(params=data) - def get_order_items(self, order_id: str, next_token: str = None) -> dict: + 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) @@ -388,7 +388,7 @@ class CatalogItems(SPAPI): def get_catalog_item( self, asin: str, - marketplace_id: str = None, + marketplace_id: str | None = None, ) -> dict: """Returns a specified item and its attributes.""" if not marketplace_id: 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 0765ebf2..bcafd5ac 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() 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 17f8ffc5..899e77a1 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 @@ -46,8 +46,8 @@ def make_request( self, method: str = "GET", append_to_base_uri: str = "", - params: dict = None, - data: dict = None, + params: dict | None = None, + data: dict | None = None, ) -> object: if isinstance(params, dict): params = Util.remove_empty(params) @@ -81,7 +81,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) @@ -91,22 +91,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( @@ -129,7 +129,7 @@ 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) @@ -138,7 +138,7 @@ class TestCatalogItems(CatalogItems, TestSPAPI): def get_catalog_item( self, asin: str, - marketplace_id: str = None, + marketplace_id: str | None = None, ) -> object: self.expected_response = DATA.get("get_catalog_item_200") return super().get_catalog_item(asin, marketplace_id) @@ -253,7 +253,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/inventory.py b/ecommerce_integrations/controllers/inventory.py index d00545ba..deacd571 100644 --- a/ecommerce_integrations/controllers/inventory.py +++ b/ecommerce_integrations/controllers/inventory.py @@ -26,7 +26,7 @@ def get_inventory_levels(warehouses: tuple[str], integration: str) -> list[_dict AND bin.modified > ei.inventory_synced_on AND ei.integration = %s """, - values=warehouses + (integration,), + values=(*warehouses, integration), as_dict=1, ) @@ -40,7 +40,7 @@ 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,) + all_warehouses = (*tuple(child_warehouse), warehouse) data = frappe.db.sql( f""" @@ -61,7 +61,7 @@ def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str): HAVING last_updated > last_synced """, - values=all_warehouses + (integration,), + values=(*all_warehouses, integration), as_dict=1, ) 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 ddacf8d2..620d9368 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 @@ -16,7 +16,7 @@ def __init__(self, obj): 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 = { @@ -96,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/tests/utils.py b/ecommerce_integrations/shopify/tests/utils.py index fd4f075c..8cc246c7 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/unicommerce/cancellation_and_returns.py b/ecommerce_integrations/unicommerce/cancellation_and_returns.py index 5cf36c99..b87d9270 100644 --- a/ecommerce_integrations/unicommerce/cancellation_and_returns.py +++ b/ecommerce_integrations/unicommerce/cancellation_and_returns.py @@ -102,7 +102,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) 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 2eed1151..28df3218 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] diff --git a/ecommerce_integrations/unicommerce/grn.py b/ecommerce_integrations/unicommerce/grn.py index 973ed299..df25fe3f 100644 --- a/ecommerce_integrations/unicommerce/grn.py +++ b/ecommerce_integrations/unicommerce/grn.py @@ -95,7 +95,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) diff --git a/ecommerce_integrations/unicommerce/order.py b/ecommerce_integrations/unicommerce/order.py index 411c4f50..f4aa296b 100644 --- a/ecommerce_integrations/unicommerce/order.py +++ b/ecommerce_integrations/unicommerce/order.py @@ -296,7 +296,7 @@ def _get_facility_code(line_items) -> str: if len(facility_codes) > 1: frappe.throw("Multiple facility codes found in single order") - return list(facility_codes)[0] + return next(iter(facility_codes)) def update_shipping_info(doc, method=None): From d8b39ecbe5f8602ac7999ab1e25bdf69b1ef0b1c Mon Sep 17 00:00:00 2001 From: David Date: Mon, 16 Sep 2024 21:57:03 +0200 Subject: [PATCH 07/11] style: ruff format (manual) --- .../amazon_sp_api_settings/test_amazon_sp_api_settings.py | 3 ++- ecommerce_integrations/controllers/customer.py | 2 -- ecommerce_integrations/controllers/inventory.py | 2 -- ecommerce_integrations/controllers/setting.py | 2 +- .../doctype/ecommerce_item/ecommerce_item.py | 2 -- ecommerce_integrations/shopify/connection.py | 1 - ecommerce_integrations/shopify/customer.py | 2 +- .../shopify/doctype/shopify_setting/shopify_setting.py | 2 -- ecommerce_integrations/shopify/utils.py | 1 - ecommerce_integrations/unicommerce/api_client.py | 2 +- ecommerce_integrations/unicommerce/cancellation_and_returns.py | 3 +-- ecommerce_integrations/unicommerce/customer.py | 2 +- .../doctype/unicommerce_settings/unicommerce_settings.py | 1 - ecommerce_integrations/unicommerce/grn.py | 1 - ecommerce_integrations/unicommerce/inventory.py | 1 - ecommerce_integrations/unicommerce/invoice.py | 2 +- ecommerce_integrations/unicommerce/order.py | 2 +- ecommerce_integrations/unicommerce/product.py | 2 +- ecommerce_integrations/unicommerce/tests/utils.py | 3 ++- ecommerce_integrations/zenoti/utils.py | 2 +- 20 files changed, 13 insertions(+), 25 deletions(-) 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 899e77a1..9c084783 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 @@ -39,7 +40,7 @@ class TestSPAPI(SPAPI): # Expected response after hitting the URL. - expected_response = {} + expected_response: ClassVar = {} @responses.activate def make_request( diff --git a/ecommerce_integrations/controllers/customer.py b/ecommerce_integrations/controllers/customer.py index 1dcd8379..4c543b7a 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 diff --git a/ecommerce_integrations/controllers/inventory.py b/ecommerce_integrations/controllers/inventory.py index deacd571..59b02f35 100644 --- a/ecommerce_integrations/controllers/inventory.py +++ b/ecommerce_integrations/controllers/inventory.py @@ -1,5 +1,3 @@ -from typing import List, Tuple - import frappe from frappe import _dict from frappe.utils import now diff --git a/ecommerce_integrations/controllers/setting.py b/ecommerce_integrations/controllers/setting.py index d80d08d2..8f6e0da2 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 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 20b3de2f..81985d80 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 _ diff --git a/ecommerce_integrations/shopify/connection.py b/ecommerce_integrations/shopify/connection.py index 4a484bef..4a4c7c86 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 _ diff --git a/ecommerce_integrations/shopify/customer.py b/ecommerce_integrations/shopify/customer.py index 09250dc8..3a0ee952 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 _ diff --git a/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py b/ecommerce_integrations/shopify/doctype/shopify_setting/shopify_setting.py index cfd2b6e6..dc974e70 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 diff --git a/ecommerce_integrations/shopify/utils.py b/ecommerce_integrations/shopify/utils.py index 66f7981f..06bf1f58 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 diff --git a/ecommerce_integrations/unicommerce/api_client.py b/ecommerce_integrations/unicommerce/api_client.py index 8f252429..770d3867 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 diff --git a/ecommerce_integrations/unicommerce/cancellation_and_returns.py b/ecommerce_integrations/unicommerce/cancellation_and_returns.py index b87d9270..fc2c8968 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 @@ -155,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) diff --git a/ecommerce_integrations/unicommerce/customer.py b/ecommerce_integrations/unicommerce/customer.py index 376ed124..7cac4a30 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 _ diff --git a/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py b/ecommerce_integrations/unicommerce/doctype/unicommerce_settings/unicommerce_settings.py index a7b79096..02141d26 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 diff --git a/ecommerce_integrations/unicommerce/grn.py b/ecommerce_integrations/unicommerce/grn.py index df25fe3f..0baa7b0a 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 diff --git a/ecommerce_integrations/unicommerce/inventory.py b/ecommerce_integrations/unicommerce/inventory.py index 67b7744c..d8f6961d 100644 --- a/ecommerce_integrations/unicommerce/inventory.py +++ b/ecommerce_integrations/unicommerce/inventory.py @@ -1,5 +1,4 @@ from collections import defaultdict -from typing import Dict import frappe from frappe.utils import cint, now diff --git a/ecommerce_integrations/unicommerce/invoice.py b/ecommerce_integrations/unicommerce/invoice.py index 1361c327..3ae3d8f3 100644 --- a/ecommerce_integrations/unicommerce/invoice.py +++ b/ecommerce_integrations/unicommerce/invoice.py @@ -1,7 +1,7 @@ import base64 import json from collections import defaultdict -from typing import Any, Dict, List, NewType, Optional +from typing import Any, NewType import frappe import requests diff --git a/ecommerce_integrations/unicommerce/order.py b/ecommerce_integrations/unicommerce/order.py index f4aa296b..7c78608b 100644 --- a/ecommerce_integrations/unicommerce/order.py +++ b/ecommerce_integrations/unicommerce/order.py @@ -1,7 +1,7 @@ import json from collections import defaultdict, namedtuple from collections.abc import Iterator -from typing import Any, Dict, List, NewType, Optional, Set, Tuple +from typing import Any, NewType import frappe from frappe.utils import add_to_date, flt diff --git a/ecommerce_integrations/unicommerce/product.py b/ecommerce_integrations/unicommerce/product.py index 9d34431a..26c62c16 100644 --- a/ecommerce_integrations/unicommerce/product.py +++ b/ecommerce_integrations/unicommerce/product.py @@ -1,4 +1,4 @@ -from typing import List, NewType +from typing import NewType import frappe from frappe import _ diff --git a/ecommerce_integrations/unicommerce/tests/utils.py b/ecommerce_integrations/unicommerce/tests/utils.py index 3c0e0688..d779c536 100644 --- a/ecommerce_integrations/unicommerce/tests/utils.py +++ b/ecommerce_integrations/unicommerce/tests/utils.py @@ -2,6 +2,7 @@ import json import os import unittest +from typing import ClassVar import frappe @@ -12,7 +13,7 @@ class TestCase(unittest.TestCase): - config = { + config: ClassVar = { "is_enabled": 1, "enable_inventory_sync": 1, "use_stock_entry_for_grn": 1, diff --git a/ecommerce_integrations/zenoti/utils.py b/ecommerce_integrations/zenoti/utils.py index b16f00da..3d7682ee 100644 --- a/ecommerce_integrations/zenoti/utils.py +++ b/ecommerce_integrations/zenoti/utils.py @@ -274,7 +274,7 @@ def get_state(country_id, state_id): if list_of_states_of_the_country: for states in list_of_states_of_the_country["states"]: if states["id"] == state_id: - state == states + state = states return state From 3a4ad5a2b45e05b0e4346cad9de72f3cc90e49ba Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:25:21 +0200 Subject: [PATCH 08/11] style: except from eslint --- .../page/shopify_import_products/shopify_import_products.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 455987a2..62db3c21 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 @@ -6,10 +6,11 @@ frappe.pages["shopify-import-products"].on_page_load = function (wrapper) { 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"); From 89a0e3212e2855c21d6b9bed974f6b351346988e Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 04:28:09 +0200 Subject: [PATCH 09/11] fix: code oversight --- .../zenoti/doctype/zenoti_settings/zenoti_settings.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js index e7178526..c7d8c5e1 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js @@ -61,7 +61,6 @@ frappe.ui.form.on("Zenoti Settings", { } }, }); - d.hide(); }); } }, From 6309a7ae52f4d06cc2c55c0ada1b4a1e81814777 Mon Sep 17 00:00:00 2001 From: "David (aider)" Date: Wed, 2 Oct 2024 10:43:57 +0200 Subject: [PATCH 10/11] fix: Replace SQL queries with frappe.qb in ecommerce_integrations/controllers/inventory.py feat: Wrap button texts and messages in __() for translation in ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js and ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js --- .../controllers/inventory.py | 80 +++++++++++-------- .../doctype/zenoti_center/zenoti_center.js | 24 +++--- .../zenoti_settings/zenoti_settings.js | 4 +- 3 files changed, 60 insertions(+), 48 deletions(-) diff --git a/ecommerce_integrations/controllers/inventory.py b/ecommerce_integrations/controllers/inventory.py index 59b02f35..d75d32b5 100644 --- a/ecommerce_integrations/controllers/inventory.py +++ b/ecommerce_integrations/controllers/inventory.py @@ -1,5 +1,7 @@ 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 @@ -14,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,29 +51,30 @@ def get_inventory_levels_of_group_warehouse(warehouse: str, integration: str): 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, + 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/zenoti/doctype/zenoti_center/zenoti_center.js b/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js index f7b5d9eb..e38403f9 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_center/zenoti_center.js @@ -4,7 +4,7 @@ frappe.ui.form.on("Zenoti Center", { refresh(frm) { frm.add_custom_button( - "Employees", + __("Employees"), function () { frappe.call({ method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", @@ -20,11 +20,11 @@ frappe.ui.form.on("Zenoti Center", { }, }); }, - "Sync" + __("Sync") ); frm.add_custom_button( - "Customers", + __("Customers"), function () { frappe.call({ method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", @@ -40,11 +40,11 @@ frappe.ui.form.on("Zenoti Center", { }, }); }, - "Sync" + __("Sync") ); frm.add_custom_button( - "Items", + __("Items"), function () { frappe.call({ method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", @@ -60,11 +60,11 @@ frappe.ui.form.on("Zenoti Center", { }, }); }, - "Sync" + __("Sync") ); frm.add_custom_button( - "Categories", + __("Categories"), function () { frappe.call({ method: "ecommerce_integrations.zenoti.doctype.zenoti_center.zenoti_center.sync", @@ -80,11 +80,11 @@ frappe.ui.form.on("Zenoti Center", { }, }); }, - "Sync" + __("Sync") ); frm.add_custom_button( - "Sales Invoice", + __("Sales Invoice"), function () { let d = new frappe.ui.Dialog({ title: __("Sync Sales Invoice"), @@ -125,11 +125,11 @@ frappe.ui.form.on("Zenoti Center", { }); d.show(); }, - "Sync" + __("Sync") ); frm.add_custom_button( - "Stock Reconciliation", + __("Stock Reconciliation"), function () { let d = new frappe.ui.Dialog({ title: __("Sync Stock Reconciliation"), @@ -163,7 +163,7 @@ frappe.ui.form.on("Zenoti Center", { }); d.show(); }, - "Sync" + __("Sync") ); }, diff --git a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js index c7d8c5e1..0d8c3fc2 100644 --- a/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js +++ b/ecommerce_integrations/zenoti/doctype/zenoti_settings/zenoti_settings.js @@ -50,11 +50,11 @@ frappe.ui.form.on("Zenoti Settings", { }, refresh(frm) { if (cint(frm.doc.enable_zenoti)) { - frm.add_custom_button("Update Centers", function () { + frm.add_custom_button(__("Update Centers"), function () { frappe.call({ method: "ecommerce_integrations.zenoti.doctype.zenoti_settings.zenoti_settings.update_centers", freeze: true, - freeze_message: "Updating Centers...", + freeze_message: __("Updating Centers..."), callback: function (r) { if (!r.exc) { frappe.show_alert(__("Centers Updated")); From 2151bd1f6f1090d8ce5d7b42fa2c0840975bd2e5 Mon Sep 17 00:00:00 2001 From: "David (aider)" Date: Wed, 2 Oct 2024 11:41:48 +0200 Subject: [PATCH 11/11] fix: Replace map() with list comprehension in amazon_sp_api.py and wrap "Retry" button text in __() in ecommerce_integration_log.js --- .../amazon/doctype/amazon_sp_api_settings/amazon_sp_api.py | 2 +- .../ecommerce_integration_log/ecommerce_integration_log.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 d277389a..d9872c59 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 @@ -156,7 +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. 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 962ca27b..5e55650c 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 @@ -4,7 +4,7 @@ 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 () { + frm.add_custom_button(__("Retry"), function () { frappe.call({ method: "ecommerce_integrations.ecommerce_integrations.doctype.ecommerce_integration_log.ecommerce_integration_log.resync", args: {