diff --git a/.cookiecutter_version b/.cookiecutter_version
new file mode 100644
index 0000000..1cadcf7
--- /dev/null
+++ b/.cookiecutter_version
@@ -0,0 +1,6 @@
+Updated with template from Starktail/frappe_app_cookiecutter
+
+Link to check if this is outdated: https://github.com/Starktail/frappe_app_cookiecutter/compare/80911ee6689096630098ed15f1449e76b7f9914b..main
+
+cookiecutter_template_commit_id: 80911ee6689096630098ed15f1449e76b7f9914b
+cookiecutter_template_commit_date: 2026-02-05T12:54:43+00:00
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..63410ff
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,124 @@
+{
+ "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,
+ "__": 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,
+ "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,
+ "validate_name": true,
+ "validate_phone": true,
+ "validate_url": true,
+ "get_number_format": true,
+ "format_number": true,
+ "format_currency": true,
+ "comment_when": true,
+ "open_url_post": true,
+ "toTitle": true,
+ "lstrip": true,
+ "rstrip": true,
+ "strip": true,
+ "strip_html": true,
+ "replace_all": true,
+ "flt": true,
+ "precision": true,
+ "CREATE": true,
+ "AMEND": true,
+ "CANCEL": true,
+ "copy_dict": true,
+ "get_number_format_info": true,
+ "strip_number_groups": true,
+ "print_table": true,
+ "Layout": true,
+ "web_form_settings": true,
+ "$c": true,
+ "$a": true,
+ "$i": true,
+ "$bg": true,
+ "$y": true,
+ "$c_obj": 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,
+ "md5": true,
+ "$": true,
+ "jQuery": true,
+ "moment": true,
+ "hljs": true,
+ "Awesomplete": true,
+ "Sortable": true,
+ "Showdown": true,
+ "Taggle": true,
+ "Gantt": true,
+ "Slick": true,
+ "Webcam": true,
+ "PhotoSwipe": true,
+ "PhotoSwipeUI_Default": true,
+ "io": true,
+ "JsBarcode": true,
+ "L": true,
+ "Chart": true,
+ "DataTable": true,
+ "Cypress": true,
+ "cy": true,
+ "it": true,
+ "describe": true,
+ "expect": true,
+ "context": true,
+ "before": true,
+ "beforeEach": true,
+ "after": true,
+ "qz": true,
+ "localforage": true,
+ "extend_cscript": true
+ }
+}
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index e9b4c40..576b892 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -40,27 +40,27 @@ body:
label: Environment and Versions
description: |
examples:
- - **Frappe Version**: v15.6.9
- - **ERPNext Version**: v15.6.9
- - **South Africa Customisations Version**: v0.6.2
+ - **Frappe Version**: v15.88.0
+ - **ERPNext Version**: v15.88.0
+ - **South Africa Customisations** (csf_za): v0.6.2
value: |
- Frappe Version:
- ERPNext Version:
- - South Africa Customisations Version:
+ - South Africa Customisations:
render: markdown
validations:
required: false
- type: dropdown
id: version
attributes:
- label: Operating System
- description: On what OS are you seeing the problem on?
+ label: Hosting Method
+ description: How is frappe installed/used?
multiple: true
options:
- - Windows
- - MacOS
- - Android
- - iOS
+ - Frappe Cloud
+ - Custom Frappe Press instance
+ - Remote or local Docker Instance
+ - Barebones
- Other
validations:
required: true
@@ -74,4 +74,5 @@ body:
- Microsoft Edge
- Firefox
- Safari
- - Other
\ No newline at end of file
+ - Other
+ - N/A
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 4927312..eb6e443 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- - name: Finfoot Tech
- url: https://finfoot.tech
- about: More information on Finfoot Tech.
+ - name: Starktail (Pty) Ltd
+ url: https://starktail.com
+ about: More information on Starktail.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 23ba67e..eeef4e0 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -6,9 +6,9 @@
## Type of change
-- āŖ Bug fix (change which fixes an issue)
-- š¢ New feature (change which adds functionality)
-- āŖ Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] Bug fix (change which fixes an issue)
+- [ ] New feature (change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
## Tests
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index a94e483..10c05e5 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -6,7 +6,9 @@ cd ~ || exit
sudo apt update
sudo apt remove mysql-server mysql-client
-sudo apt install libcups2-dev redis-server mariadb-client-10.6
+sudo apt install libcups2-dev redis-server mariadb-client libmariadb-dev
+# Dependencies for cypress: https://docs.cypress.io/guides/continuous-integration/introduction#UbuntuDebian
+sudo apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
pip install frappe-bench
@@ -25,10 +27,9 @@ mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON
mariadb --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
+ wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
+ sudo apt install /tmp/wkhtmltox.deb
+
}
install_whktml &
@@ -39,7 +40,11 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
+# Get dependent apps
+
bench get-app https://github.com/frappe/erpnext --branch $TEST_AGAINST_ERPNEXT_VERSION --resolve-deps
+
+
bench get-app --overwrite csf_za "${GITHUB_WORKSPACE}"
bench --verbose setup env --python python3.10
bench --verbose setup requirements --dev
@@ -48,5 +53,9 @@ bench start &>> ~/frappe-bench/bench_start.log &
CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes
+# Install dependent apps
+
bench --verbose --site test_site install-app erpnext
+
+
bench --verbose --site test_site install-app csf_za
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 600be14..870889f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,8 +12,10 @@ concurrency:
cancel-in-progress: true
env:
- TEST_AGAINST_FRAPPE_VERSION: v15.57.2
- TEST_AGAINST_ERPNEXT_VERSION: v15.54.1
+ TEST_AGAINST_FRAPPE_VERSION: v15.96.0
+
+ TEST_AGAINST_ERPNEXT_VERSION: v15.94.3
+
jobs:
tests:
@@ -29,17 +31,21 @@ jobs:
MYSQL_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
uses: actions/checkout@v4
+ - name: Find tests
+ run: |
+ echo "Finding tests"
+ grep -rn "def test" > /dev/null
+
- name: Setup Python
uses: actions/setup-python@v4
with:
- python-version: |
- 3.10
+ python-version: '3.10'
- name: Setup Node
uses: actions/setup-node@v3
@@ -52,7 +58,7 @@ jobs:
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }}
@@ -62,7 +68,7 @@ jobs:
- name: Get yarn cache directory path
id: yarn-cache-dir-path
- run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
+ run: 'echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT'
- name: Cache yarn
uses: actions/cache@v3
@@ -108,6 +114,8 @@ jobs:
export FRAPPE_TUNE_GC=True
bench start &> bench_start.log &
bench --site test_site run-ui-tests csf_za --headless -- --record
+
+
env:
TYPE: server
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
@@ -133,3 +141,4 @@ jobs:
if: ${{ failure() }}
run: |
cat ~/frappe-bench/bench_start.log
+
diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index 78d31cb..fd760c3 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -1,3 +1,4 @@
+
# Workflow for building and deploying a VitePress site to GitHub Pages
#
name: Deploy VitePress site to Pages
@@ -57,4 +58,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
\ No newline at end of file
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index 80a3ab2..d4533c7 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -1,29 +1,99 @@
+
name: Linters
on:
- pull_request: { }
+ pull_request:
+ workflow_dispatch:
-jobs:
+permissions:
+ contents: read
- linters:
- name: linters
+concurrency:
+ group: commitcheck-frappe-${{ github.event_name }}-${{ github.event.number }}
+ cancel-in-progress: true
+
+jobs:
+ commit-lint:
+ name: 'Semantic Commits'
runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v5
+ with:
+ fetch-depth: 200
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ check-latest: true
+
+ - name: Check commit titles
+ env:
+ npm_config_ignore_scripts: "true"
+ run: |
+ npm i -D --no-save \
+ @commitlint/cli @commitlint/config-conventional \
+ conventional-changelog-conventionalcommits
+ npx commitlint --verbose \
+ --from ${{ github.event.pull_request.base.sha }} \
+ --to ${{ github.event.pull_request.head.sha }}
- - name: Set up Python 3.10
- uses: actions/setup-python@v4
+
+ linter:
+ name: 'Semgrep Rules'
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-python@v6
with:
python-version: '3.10'
-
- - name: Install and Run Pre-commit
- uses: pre-commit/action@v3.0.0
+ cache: pip
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- - name: Download semgrep
- run: pip install semgrep==1.31.1
-
- name: Run Semgrep rules
- run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
\ No newline at end of file
+ run: |
+ pip install semgrep
+ semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
+
+ deps-vulnerable-check:
+ name: 'Vulnerable Dependency Check'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/setup-python@v6
+ with:
+ python-version: '3.13'
+
+ - uses: actions/checkout@v5
+
+ - name: Cache pip
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+ ${{ runner.os }}-
+
+ - name: Install and run pip-audit
+ run: |
+ pip install pip-audit
+ cd ${GITHUB_WORKSPACE}
+ pip-audit --desc on .
+
+ precommit:
+ name: 'Pre-Commit'
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-python@v6
+ with:
+ python-version: '3.10'
+ cache: pip
+ - uses: pre-commit/action@v3.0.1
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8221c48..a4d487d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,43 +1,69 @@
exclude: 'node_modules|.git'
-default_stages: [commit]
+default_stages: [pre-commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.6.0
+ rev: v6.0.0
hooks:
- id: trailing-whitespace
- files: "serco_custom.*"
- exclude: ".*json$|.*txt$|.*csv|.*md"
- - id: check-yaml
- # - id: no-commit-to-branch
- # args: ['--branch', 'version-15']
+ files: "csf_za.*"
+ exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
+ - id: no-commit-to-branch
+ args: ['--branch', 'version-15']
- id: check-merge-conflict
- id: check-ast
+ - id: check-json
+ - id: check-toml
+ - id: check-yaml
+ - id: debug-statements
- - repo: https://github.com/PyCQA/flake8
- rev: 6.1.0
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.14.4
hooks:
- - id: flake8
- additional_dependencies: [
- 'flake8-bugbear',
- ]
- args: ['--config', '.flake8_strict']
- exclude: ".*setup.py$"
-
- - repo: https://github.com/adityahase/black
- rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
- hooks:
- - id: black
- additional_dependencies: ['click==8.0.4']
+ - id: ruff
+ name: "Run ruff import sorter"
+ args: ["--select=I", "--fix"]
+
+ - id: ruff
+ name: "Run ruff linter"
- - repo: https://github.com/PyCQA/isort
- rev: 5.13.2
+ - id: ruff-format
+ name: "Run ruff formatter"
+
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v3.1.0
hooks:
- - id: isort
- exclude: ".*setup.py$"
+ - id: prettier
+ types_or: [javascript, vue, scss]
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ csf_za/public/dist/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ csf_za/templates/includes/.*|
+ csf_za/public/js/lib/.*
+ )$
+
+ - repo: https://github.com/pre-commit/mirrors-eslint
+ rev: v8.56.0
+ hooks:
+ - id: eslint
+ types_or: [javascript]
+ args: ['--quiet']
+ # Ignore any files that might contain jinja / bundles
+ exclude: |
+ (?x)^(
+ csf_za/public/dist/.*|
+ cypress/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ csf_za/templates/includes/.*|
+ csf_za/public/js/lib/.*
+ )$
ci:
autoupdate_schedule: weekly
diff --git a/README.md b/README.md
index 487d9d2..b573a4b 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,23 @@
-## South Africa Customisations
+
-
-[](https://codecov.io/gh/dvdl16/csf_za)
+

-Country Specific Functionality for South Africa
-This is a Frappe app, intended to be used with ERPNext (version 15).
+# South Africa Customisations
-#### License
+**South Africa Customisations**
+
+
-MIT
+
+### South Africa Customisations
+
+
+[](https://codecov.io/github/Starktail/csf_za)
+
+Country Specific Functionality for South Africa
+
+This is a Frappe app, intended to be used with ERPNext (version 15).
### Features
@@ -17,10 +25,23 @@ MIT
2. Support for Bank Statements from South African Banks: Native import of .csv formats from FNB, ABSA and Bank Zero
3. Easy-add of VAT in Bank Reconciliation Tool: No need to Edit in Full Page for those Journal Entries that require a VAT leg
+### License
+
+MIT
### User documentation
-š [South Africa Customisations Documentation](https://csf-za-docs.starktail.com)
+š [South Africa Customisations Documentation](https://csf-za-docs.starktail.com/csf_za_introduction)
+
+### Installation
+
+You can install this app using the [bench](https://github.com/frappe/bench) CLI:
+
+```bash
+cd $PATH_TO_YOUR_BENCH
+bench get-app $URL_OF_THIS_REPO --branch develop
+bench install-app csf_za
+```
### Development
@@ -49,18 +70,24 @@ bench --site test_site run-ui-tests csf_za --headless --browser chromium
#### Contributing
-We use [pre-commit](https://pre-commit.com/) for linting. First time setup may be required:
-```shell
-# Install pre-commit
-pip install pre-commit
+This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:
-# Install the git hook scripts
+```bash
+cd apps/csf_za
pre-commit install
#(optional) Run against all the files
pre-commit run --all-files
```
+Pre-commit is configured to use the following tools for checking and formatting your code:
+
+- ruff
+- eslint
+- prettier
+- pyupgrade
+
+
We use [Semgrep](https://semgrep.dev/docs/getting-started/) rules specific to [Frappe Framework](https://github.com/frappe/frappe)
```shell
# Install semgrep
@@ -76,3 +103,11 @@ semgrep --config=/workspace/development/frappe-semgrep-rules/rules apps/csf_za
#### Updating Documentation
For documentation, we use [vitepress](https://vitepress.dev/). You can run `yarn docs:dev` to preview the docs when applying changes
+
+#### CI
+
+This app can use GitHub Actions for CI. The following workflows are configured:
+
+- CI: Installs this app and runs unit tests on every push to `develop` branch.
+- Linters: Runs [Frappe Semgrep Rules](https://github.com/frappe/semgrep-rules) and [pip-audit](https://pypi.org/project/pip-audit/) on every pull request, as well as [Semgrep](https://semgrep.dev/docs/getting-started/)
+
diff --git a/commitlint.config.js b/commitlint.config.js
index 8847564..bd5cebc 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -1,25 +1,25 @@
module.exports = {
- parserPreset: 'conventional-changelog-conventionalcommits',
- rules: {
- 'subject-empty': [2, 'never'],
- 'type-case': [2, 'always', 'lower-case'],
- 'type-empty': [2, 'never'],
- 'type-enum': [
- 2,
- 'always',
- [
- 'build',
- 'chore',
- 'ci',
- 'docs',
- 'feat',
- 'fix',
- 'perf',
- 'refactor',
- 'revert',
- 'style',
- 'test',
- ],
- ],
- },
+ parserPreset: "conventional-changelog-conventionalcommits",
+ rules: {
+ "subject-empty": [2, "never"],
+ "type-case": [2, "always", "lower-case"],
+ "type-empty": [2, "never"],
+ "type-enum": [
+ 2,
+ "always",
+ [
+ "build",
+ "chore",
+ "ci",
+ "docs",
+ "feat",
+ "fix",
+ "perf",
+ "refactor",
+ "revert",
+ "style",
+ "test",
+ ],
+ ],
+ },
};
diff --git a/csf_za/__init__.py b/csf_za/__init__.py
index 01ef120..6cd38b7 100644
--- a/csf_za/__init__.py
+++ b/csf_za/__init__.py
@@ -1 +1 @@
-__version__ = "0.2.6"
+__version__ = "0.2.7"
diff --git a/csf_za/overrides/accounts/bank_reconciliation_tool.py b/csf_za/overrides/accounts/bank_reconciliation_tool.py
index 2877d87..b0ab690 100644
--- a/csf_za/overrides/accounts/bank_reconciliation_tool.py
+++ b/csf_za/overrides/accounts/bank_reconciliation_tool.py
@@ -12,23 +12,23 @@
@frappe.whitelist()
def custom_create_journal_entry_bts(
- bank_transaction_name,
- reference_number=None,
- reference_date=None,
- posting_date=None,
- entry_type=None,
- second_account=None,
- mode_of_payment=None,
- party_type=None,
- party=None,
- allow_edit=None,
+ bank_transaction_name: str,
+ reference_number: str | None = None,
+ reference_date: str | None = None,
+ posting_date: str | None = None,
+ entry_type: str | None = None,
+ second_account: str | None = None,
+ mode_of_payment: str | None = None,
+ party_type: str | None = None,
+ party: str | None = None,
+ allow_edit: str | None = None,
# ================================================================================================= #
# ==================================== Custom code starts here ==================================== #
# ================================================================================================= #
- cost_center=None,
- custom_tax_account=None,
- custom_tax_rate_for_bank_recon=None,
- custom_cost_center_for_tax_account=None,
+ cost_center: str | None = None,
+ custom_tax_account: str | None = None,
+ custom_tax_rate_for_bank_recon: str | None = None,
+ custom_cost_center_for_tax_account: str | None = None,
):
"""
Override erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts
diff --git a/csf_za/overrides/accounts/bank_statement_import.py b/csf_za/overrides/accounts/bank_statement_import.py
index c90e99a..de71be7 100644
--- a/csf_za/overrides/accounts/bank_statement_import.py
+++ b/csf_za/overrides/accounts/bank_statement_import.py
@@ -211,7 +211,7 @@ def parse_csv_file_capitec(self, file_doc):
date_str = self._parse_date(row[1], formats=["%d/%m/%Y"])
# If amount is None, but there is a value in "Fees", we can safely skip
- if row[4] == None and row[5] != None:
+ if row[4] is None and row[5] is not None:
continue
try:
amount_value = float(row[4])
@@ -223,7 +223,7 @@ def parse_csv_file_capitec(self, file_doc):
# Create a new row if this row has a fee
print(f"\n\n{row[5]}\n\n")
- if row[5] != None and float(row[5]) != 0:
+ if row[5] is not None and float(row[5]) != 0:
try:
fee_value = float(row[5])
except ValueError:
@@ -258,7 +258,7 @@ def parse_csv_file_nedbank(self, file_doc):
if len(row) < 4:
frappe.throw(_("Row {0} has insufficient columns.").format(row_num))
# Skip rows with blank Amounts that do not change the running balance
- if row[2] == None and row[3] == running_balance:
+ if row[2] is None and row[3] == running_balance:
continue
# Skip unwanted rows
@@ -357,7 +357,7 @@ def remove_null_bytes(self):
file_content = file_content.replace("\x00", "")
- file_name, extension = file_doc.get_extension()
+ file_name, _extension = file_doc.get_extension()
_file = frappe.get_doc(
{
"doctype": "File",
@@ -374,7 +374,9 @@ def remove_null_bytes(self):
@frappe.whitelist()
-def custom_get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
+def custom_get_preview_from_template(
+ data_import: str, import_file: str | None = None, google_sheets_url: str | None = None
+):
"""
Override get_preview_from_template to only generate a preview of the bank statement import data
if there are no nulls in the content.
diff --git a/csf_za/overrides/accounts/test_bank_statement_import.py b/csf_za/overrides/accounts/test_bank_statement_import.py
index 1546c78..e5e0c98 100644
--- a/csf_za/overrides/accounts/test_bank_statement_import.py
+++ b/csf_za/overrides/accounts/test_bank_statement_import.py
@@ -21,18 +21,13 @@ def setUp(self):
create_bank_account()
def test_modify_uploaded_bank_statement_function_runs_on_validate(self):
- bank_statement_import = make_bank_statement_import_test_record(
- do_not_save=True, do_not_submit=True
- )
+ bank_statement_import = make_bank_statement_import_test_record(do_not_save=True, do_not_submit=True)
bank_statement_import.modify_uploaded_bank_statement = MagicMock()
bank_statement_import.save()
assert bank_statement_import.modify_uploaded_bank_statement.called
-def create_bank_account(
- bank_name=default_bank, account_name="_Test Bank", company=default_company
-):
-
+def create_bank_account(bank_name=default_bank, account_name="_Test Bank", company=default_company):
try:
gl_account = frappe.get_doc(
{
diff --git a/csf_za/public/js/accounts/bank_reconciliation_tool.js b/csf_za/public/js/accounts/bank_reconciliation_tool.js
index 78b95cd..19c0d33 100644
--- a/csf_za/public/js/accounts/bank_reconciliation_tool.js
+++ b/csf_za/public/js/accounts/bank_reconciliation_tool.js
@@ -1,297 +1,331 @@
/*
Override Bank Reconciliation Tool to change the "Cost Center" field on the "Actions" Dialog
-to be Mandatory when "Action" is set to "Create Voucher", and to have a default value as
+to be Mandatory when "Action" is set to "Create Voucher", and to have a default value as
set on the "Bank Account".
*/
// Turn off the default "render" function on "Bank Reconciliation Tool"
-frappe.ui.form.off('Bank Reconciliation Tool', 'render');
+frappe.ui.form.off("Bank Reconciliation Tool", "render");
// Override the default "render" function on "Bank Reconciliation Tool" to our "custom_render" function
-frappe.ui.form.on(
- 'Bank Reconciliation Tool',
- 'render',
- custom_render
-);
+frappe.ui.form.on("Bank Reconciliation Tool", "render", custom_render);
function custom_render(frm) {
- // This is the custom "render" function for the "Bank Reconciliation Tool" doctype.
- // Modified from the original erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
- //
- // Extend the erpnext.accounts.bank_reconciliation.DataTableManager class and extend the
- // erpnext.accounts.bank_reconciliation.DialogManager class
+ // This is the custom "render" function for the "Bank Reconciliation Tool" doctype.
+ // Modified from the original erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+ //
+ // Extend the erpnext.accounts.bank_reconciliation.DataTableManager class and extend the
+ // erpnext.accounts.bank_reconciliation.DialogManager class
- // ================================================================================================= //
- // ==================================== Custom code starts here ==================================== //
- // ================================================================================================= //
- console.log("csf_za: Overriding 'render' function on 'Bank Reconciliation Tool'");
+ // ================================================================================================= //
+ // ==================================== Custom code starts here ==================================== //
+ // ================================================================================================= //
+ console.log(
+ "csf_za: Overriding 'render' function on 'Bank Reconciliation Tool'",
+ );
- // Extend the DialogManager class
- erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager extends erpnext.accounts.bank_reconciliation.DialogManager {
- // Modified from the original erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
- //
- // Override the get_voucher_fields method to return a list of the original voucher fields,
- // as well as our own custom fields
- get_voucher_fields() {
- // Call the original get_voucher_fields method to get the voucher fields
- let voucherFields = super.get_voucher_fields();
+ // Extend the DialogManager class
+ // eslint-disable-next-line no-undef
+ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager
+ // eslint-disable-next-line no-undef
+ extends erpnext.accounts.bank_reconciliation.DialogManager
+ {
+ // Modified from the original erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+ //
+ // Override the get_voucher_fields method to return a list of the original voucher fields,
+ // as well as our own custom fields
+ get_voucher_fields() {
+ // Call the original get_voucher_fields method to get the voucher fields
+ let voucherFields = super.get_voucher_fields();
- // Now we add our own fields to the voucherFields list
- if (!voucherFields.find(obj => obj.fieldname === "custom_tax_section")){
- const newFields = [
- // A section break to for improved layout
- {
- fieldtype: "Section Break",
- fieldname: "custom_tax_section",
- label: "Tax Details",
- depends_on:
- "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'"
- },
-
- // The rate that should be used to calculate the amount for the third account/tax account
- // in the Journal Entry
- {
- fieldtype: "Float",
- fieldname: "custom_tax_rate_for_bank_recon",
- label: "Rate (%)",
- depends_on:
- "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'"
- },
-
- // The Account that should be used as the third account/tax account in the Journal Entry
- {
- fieldname: "custom_tax_account",
- fieldtype: "Link",
- label: "Tax Account",
- options: "Account",
- get_query: () => {
- return {
- filters: {
- is_group: 0,
- company: this.company,
- },
- };
- }
- },
-
- // The Cost Center that should be used for the tax account in the Journal Entry
- {
- fieldname: "custom_cost_center_for_tax_account",
- fieldtype: "Link",
- label: "Cost Center for Tax Account",
- options: "Cost Center"
- },
-
- // HTML field to preview the Journal Entry
- {
- fieldname: "custom_tax_preview",
- fieldtype: "HTML",
- label: "Journal Entry Preview",
- options: ""
- }]
-
- // Inject these new fields into the existing fields list
- const costCenterFieldIndex = voucherFields.findIndex(field => field.fieldname === "cost_center");
- newFields.slice().reverse().forEach(newField => {
- if (costCenterFieldIndex !== -1) {
- voucherFields.splice(costCenterFieldIndex + 1, 0, newField);
- }
- })
+ // Now we add our own fields to the voucherFields list
+ if (
+ !voucherFields.find((obj) => obj.fieldname === "custom_tax_section")
+ ) {
+ const newFields = [
+ // A section break to for improved layout
+ {
+ fieldtype: "Section Break",
+ fieldname: "custom_tax_section",
+ label: "Tax Details",
+ depends_on:
+ "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'",
+ },
- }
+ // The rate that should be used to calculate the amount for the third account/tax account
+ // in the Journal Entry
+ {
+ fieldtype: "Float",
+ fieldname: "custom_tax_rate_for_bank_recon",
+ label: "Rate (%)",
+ depends_on:
+ "eval:doc.action=='Create Voucher' && doc.document_type=='Journal Entry'",
+ },
- return voucherFields;
- }
- // ================================================================================================= //
- // ===================================== Custom code ends here ===================================== //
- // ================================================================================================= //
+ // The Account that should be used as the third account/tax account in the Journal Entry
+ {
+ fieldname: "custom_tax_account",
+ fieldtype: "Link",
+ label: "Tax Account",
+ options: "Account",
+ get_query: () => {
+ return {
+ filters: {
+ is_group: 0,
+ company: this.company,
+ },
+ };
+ },
+ },
+ // The Cost Center that should be used for the tax account in the Journal Entry
+ {
+ fieldname: "custom_cost_center_for_tax_account",
+ fieldtype: "Link",
+ label: "Cost Center for Tax Account",
+ options: "Cost Center",
+ },
- // Override the add_journal_entry method to pass our custom fields as additional paramters
- add_journal_entry(values) {
- frappe.call({
- method:
- "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts",
- args: {
- bank_transaction_name: this.bank_transaction.name,
- reference_number: values.reference_number,
- reference_date: values.reference_date,
- party_type: values.party_type,
- party: values.party,
- posting_date: values.posting_date,
- mode_of_payment: values.mode_of_payment,
- entry_type: values.journal_entry_type,
- second_account: values.second_account,
- // ================================================================================================= //
- // ==================================== Custom code starts here ==================================== //
- // ================================================================================================= //
- cost_center: values.cost_center,
- custom_tax_account: values.custom_tax_account,
- custom_tax_rate_for_bank_recon: values.custom_tax_rate_for_bank_recon,
- custom_cost_center_for_tax_account: values.custom_cost_center_for_tax_account
- // ================================================================================================= //
- // ===================================== Custom code ends here ===================================== //
- // ================================================================================================= //
- },
- callback: (response) => {
- const alert_string = __("Bank Transaction {0} added as Journal Entry", [this.bank_transaction.name]);
- frappe.show_alert(alert_string);
- this.update_dt_cards(response.message);
- this.dialog.hide();
- },
- });
- }
+ // HTML field to preview the Journal Entry
+ {
+ fieldname: "custom_tax_preview",
+ fieldtype: "HTML",
+ label: "Journal Entry Preview",
+ options: "",
+ },
+ ];
- // Override the edit_in_full_page method to pass our custom fields as additional paramters
- edit_in_full_page() {
- const values = this.dialog.get_values(true);
- if (values.document_type == "Payment Entry") {
- frappe.call({
- method:
- "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_payment_entry_bts",
- args: {
- bank_transaction_name: this.bank_transaction.name,
- reference_number: values.reference_number,
- reference_date: values.reference_date,
- party_type: values.party_type,
- party: values.party,
- posting_date: values.posting_date,
- mode_of_payment: values.mode_of_payment,
- project: values.project,
- cost_center: values.cost_center,
- allow_edit: true
- },
- callback: (r) => {
- const doc = frappe.model.sync(r.message);
- frappe.set_route("Form", doc[0].doctype, doc[0].name);
- },
- });
- } else {
- frappe.call({
- method:
- "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts",
- args: {
- bank_transaction_name: this.bank_transaction.name,
- reference_number: values.reference_number,
- reference_date: values.reference_date,
- party_type: values.party_type,
- party: values.party,
- posting_date: values.posting_date,
- mode_of_payment: values.mode_of_payment,
- entry_type: values.journal_entry_type,
- second_account: values.second_account,
- allow_edit: true,
- // ================================================================================================= //
- // ==================================== Custom code starts here ==================================== //
- // ================================================================================================= //
- cost_center: values.cost_center,
- custom_tax_account: values.custom_tax_account,
- custom_tax_rate_for_bank_recon: values.custom_tax_rate_for_bank_recon,
- custom_cost_center_for_tax_account: values.custom_cost_center_for_tax_account
- // ================================================================================================= //
- // ===================================== Custom code ends here ===================================== //
- // ================================================================================================= //
- },
- callback: (r) => {
- var doc = frappe.model.sync(r.message);
- frappe.set_route("Form", doc[0].doctype, doc[0].name);
- },
- });
+ // Inject these new fields into the existing fields list
+ const costCenterFieldIndex = voucherFields.findIndex(
+ (field) => field.fieldname === "cost_center",
+ );
+ newFields
+ .slice()
+ .reverse()
+ .forEach((newField) => {
+ if (costCenterFieldIndex !== -1) {
+ voucherFields.splice(costCenterFieldIndex + 1, 0, newField);
}
- }
+ });
+ }
+ return voucherFields;
+ }
+ // ================================================================================================= //
+ // ===================================== Custom code ends here ===================================== //
+ // ================================================================================================= //
+ // Override the add_journal_entry method to pass our custom fields as additional paramters
+ add_journal_entry(values) {
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts",
+ args: {
+ bank_transaction_name: this.bank_transaction.name,
+ reference_number: values.reference_number,
+ reference_date: values.reference_date,
+ party_type: values.party_type,
+ party: values.party,
+ posting_date: values.posting_date,
+ mode_of_payment: values.mode_of_payment,
+ entry_type: values.journal_entry_type,
+ second_account: values.second_account,
+ // ================================================================================================= //
+ // ==================================== Custom code starts here ==================================== //
+ // ================================================================================================= //
+ cost_center: values.cost_center,
+ custom_tax_account: values.custom_tax_account,
+ custom_tax_rate_for_bank_recon: values.custom_tax_rate_for_bank_recon,
+ custom_cost_center_for_tax_account:
+ values.custom_cost_center_for_tax_account,
+ // ================================================================================================= //
+ // ===================================== Custom code ends here ===================================== //
+ // ================================================================================================= //
+ },
+ callback: (response) => {
+ const alert_string = __(
+ "Bank Transaction {0} added as Journal Entry",
+ [this.bank_transaction.name],
+ );
+ frappe.show_alert(alert_string);
+ this.update_dt_cards(response.message);
+ this.dialog.hide();
+ },
+ });
}
- erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager extends erpnext.accounts.bank_reconciliation.DataTableManager {
- // Modified from the original erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
- //
- // Override the "set_listeners" function to change meta properties on some of the fields
- set_listeners() {
- var me = this;
- $(`.${this.datatable.style.scopeClass} .dt-scrollable`).on(
- "click",
- `.btn`,
- function () {
- // ================================================================================================= //
- // ==================================== Custom code starts here ==================================== //
- // ================================================================================================= //
- // Get the 'Cost Center' field
- let cost_center_field = me.dialog_manager.dialog.get_field("cost_center");
+ // Override the edit_in_full_page method to pass our custom fields as additional paramters
+ edit_in_full_page() {
+ const values = this.dialog.get_values(true);
+ if (values.document_type == "Payment Entry") {
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_payment_entry_bts",
+ args: {
+ bank_transaction_name: this.bank_transaction.name,
+ reference_number: values.reference_number,
+ reference_date: values.reference_date,
+ party_type: values.party_type,
+ party: values.party,
+ posting_date: values.posting_date,
+ mode_of_payment: values.mode_of_payment,
+ project: values.project,
+ cost_center: values.cost_center,
+ allow_edit: true,
+ },
+ callback: (r) => {
+ const doc = frappe.model.sync(r.message);
+ frappe.set_route("Form", doc[0].doctype, doc[0].name);
+ },
+ });
+ } else {
+ frappe.call({
+ method:
+ "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.create_journal_entry_bts",
+ args: {
+ bank_transaction_name: this.bank_transaction.name,
+ reference_number: values.reference_number,
+ reference_date: values.reference_date,
+ party_type: values.party_type,
+ party: values.party,
+ posting_date: values.posting_date,
+ mode_of_payment: values.mode_of_payment,
+ entry_type: values.journal_entry_type,
+ second_account: values.second_account,
+ allow_edit: true,
+ // ================================================================================================= //
+ // ==================================== Custom code starts here ==================================== //
+ // ================================================================================================= //
+ cost_center: values.cost_center,
+ custom_tax_account: values.custom_tax_account,
+ custom_tax_rate_for_bank_recon:
+ values.custom_tax_rate_for_bank_recon,
+ custom_cost_center_for_tax_account:
+ values.custom_cost_center_for_tax_account,
+ // ================================================================================================= //
+ // ===================================== Custom code ends here ===================================== //
+ // ================================================================================================= //
+ },
+ callback: (r) => {
+ var doc = frappe.model.sync(r.message);
+ frappe.set_route("Form", doc[0].doctype, doc[0].name);
+ },
+ });
+ }
+ }
+ };
- // Set the value of the 'Cost Center' field
- if (frm.doc.custom_bank_reconciliation_default_cost_center) {
- console.log("Overriding default value for 'Cost Center'. Setting to " + frm.doc.custom_bank_reconciliation_default_cost_center)
- cost_center_field.set_value(frm.doc.custom_bank_reconciliation_default_cost_center)
- }
+ // eslint-disable-next-line no-undef
+ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager
+ // eslint-disable-next-line no-undef
+ extends erpnext.accounts.bank_reconciliation.DataTableManager
+ {
+ // Modified from the original erpnext/public/js/bank_reconciliation_tool/data_table_manager.js
+ //
+ // Override the "set_listeners" function to change meta properties on some of the fields
+ set_listeners() {
+ var me = this;
+ $(`.${this.datatable.style.scopeClass} .dt-scrollable`).on(
+ "click",
+ `.btn`,
+ function () {
+ // ================================================================================================= //
+ // ==================================== Custom code starts here ==================================== //
+ // ================================================================================================= //
+ // Get the 'Cost Center' field
+ let cost_center_field =
+ me.dialog_manager.dialog.get_field("cost_center");
- // Change the 'Cost Center' field to show as Mandatory
- cost_center_field.df.mandatory_depends_on = "eval:doc.action=='Create Voucher'";
+ // Set the value of the 'Cost Center' field
+ if (frm.doc.custom_bank_reconciliation_default_cost_center) {
+ console.log(
+ "Overriding default value for 'Cost Center'. Setting to " +
+ frm.doc.custom_bank_reconciliation_default_cost_center,
+ );
+ cost_center_field.set_value(
+ frm.doc.custom_bank_reconciliation_default_cost_center,
+ );
+ }
- // Change the 'Cost Center' field to also show for Journal Entries, not only Payment Entries
- cost_center_field.df.depends_on =
- "eval:doc.action=='Create Voucher' && (doc.document_type=='Payment Entry' || doc.document_type=='Journal Entry')",
- cost_center_field.refresh();
+ // Change the 'Cost Center' field to show as Mandatory
+ cost_center_field.df.mandatory_depends_on =
+ "eval:doc.action=='Create Voucher'";
- // Get the 'Second Account' field, and set an onchange function to fetch the default
- // custom_tax_account, custom_tax_rate_for_bank_recon and custom_cost_center_for_tax_account set on the Account
- let second_account_field = me.dialog_manager.dialog.get_field("second_account");
- second_account_field.df.onchange = () => {
- let secondAccount = me.dialog_manager.dialog.fields_dict.second_account.input.value;
- frappe.db.get_value('Account', secondAccount, ['custom_tax_account', 'custom_tax_rate_for_bank_recon', 'custom_cost_center_for_tax_account'])
- .then(r => {
- let values = r.message;
- let taxAccountField = me.dialog_manager.dialog.get_field("custom_tax_account");
- taxAccountField.set_value(values.custom_tax_account)
- taxAccountField.refresh();
- let rateField = me.dialog_manager.dialog.get_field("custom_tax_rate_for_bank_recon");
- rateField.set_value(values.custom_tax_rate_for_bank_recon)
- rateField.refresh();
- let costCenterForTaxAcctField = me.dialog_manager.dialog.get_field("custom_cost_center_for_tax_account");
- costCenterForTaxAcctField.set_value(values.custom_cost_center_for_tax_account || frm.doc.custom_bank_reconciliation_default_cost_center);
- costCenterForTaxAcctField.refresh();
- })
+ // Change the 'Cost Center' field to also show for Journal Entries, not only Payment Entries
+ (cost_center_field.df.depends_on =
+ "eval:doc.action=='Create Voucher' && (doc.document_type=='Payment Entry' || doc.document_type=='Journal Entry')"),
+ cost_center_field.refresh();
- }
- second_account_field.refresh();
- // ================================================================================================= //
- // ===================================== Custom code ends here ===================================== //
- // ================================================================================================= //
+ // Get the 'Second Account' field, and set an onchange function to fetch the default
+ // custom_tax_account, custom_tax_rate_for_bank_recon and custom_cost_center_for_tax_account set on the Account
+ let second_account_field =
+ me.dialog_manager.dialog.get_field("second_account");
+ second_account_field.df.onchange = () => {
+ let secondAccount =
+ me.dialog_manager.dialog.fields_dict.second_account.input.value;
+ frappe.db
+ .get_value("Account", secondAccount, [
+ "custom_tax_account",
+ "custom_tax_rate_for_bank_recon",
+ "custom_cost_center_for_tax_account",
+ ])
+ .then((r) => {
+ let values = r.message;
+ let taxAccountField =
+ me.dialog_manager.dialog.get_field("custom_tax_account");
+ taxAccountField.set_value(values.custom_tax_account);
+ taxAccountField.refresh();
+ let rateField = me.dialog_manager.dialog.get_field(
+ "custom_tax_rate_for_bank_recon",
+ );
+ rateField.set_value(values.custom_tax_rate_for_bank_recon);
+ rateField.refresh();
+ let costCenterForTaxAcctField =
+ me.dialog_manager.dialog.get_field(
+ "custom_cost_center_for_tax_account",
+ );
+ costCenterForTaxAcctField.set_value(
+ values.custom_cost_center_for_tax_account ||
+ frm.doc.custom_bank_reconciliation_default_cost_center,
+ );
+ costCenterForTaxAcctField.refresh();
+ });
+ };
+ second_account_field.refresh();
+ // ================================================================================================= //
+ // ===================================== Custom code ends here ===================================== //
+ // ================================================================================================= //
- me.dialog_manager.show_dialog(
- $(this).attr("data-name"),
- (bank_transaction) => me.update_dt_cards(bank_transaction)
- );
- return true;
- }
- );
- }
+ me.dialog_manager.show_dialog(
+ $(this).attr("data-name"),
+ (bank_transaction) => me.update_dt_cards(bank_transaction),
+ );
+ return true;
+ },
+ );
}
+ };
- // Same code as "render" function in "Bank Reconciliation Tool", but now the
- // erpnext.accounts.bank_reconciliation.DataTableManager class has been monkeypatched.
- if (frm.doc.bank_account) {
- frm.bank_reconciliation_data_table_manager = new erpnext.accounts.bank_reconciliation.DataTableManager(
- {
- company: frm.doc.company,
- bank_account: frm.doc.bank_account,
- $reconciliation_tool_dt: frm.get_field(
- "reconciliation_tool_dt"
- ).$wrapper,
- $no_bank_transactions: frm.get_field(
- "no_bank_transactions"
- ).$wrapper,
- bank_statement_from_date: frm.doc.bank_statement_from_date,
- bank_statement_to_date: frm.doc.bank_statement_to_date,
- filter_by_reference_date: frm.doc.filter_by_reference_date,
- from_reference_date: frm.doc.from_reference_date,
- to_reference_date: frm.doc.to_reference_date,
- bank_statement_closing_balance:
- frm.doc.bank_statement_closing_balance,
- cards_manager: frm.cards_manager,
- }
- );
- }
+ // Same code as "render" function in "Bank Reconciliation Tool", but now the
+ // erpnext.accounts.bank_reconciliation.DataTableManager class has been monkeypatched.
+ if (frm.doc.bank_account) {
+ frm.bank_reconciliation_data_table_manager =
+ // eslint-disable-next-line no-undef
+ new erpnext.accounts.bank_reconciliation.DataTableManager({
+ company: frm.doc.company,
+ bank_account: frm.doc.bank_account,
+ $reconciliation_tool_dt: frm.get_field("reconciliation_tool_dt")
+ .$wrapper,
+ $no_bank_transactions: frm.get_field("no_bank_transactions").$wrapper,
+ bank_statement_from_date: frm.doc.bank_statement_from_date,
+ bank_statement_to_date: frm.doc.bank_statement_to_date,
+ filter_by_reference_date: frm.doc.filter_by_reference_date,
+ from_reference_date: frm.doc.from_reference_date,
+ to_reference_date: frm.doc.to_reference_date,
+ bank_statement_closing_balance: frm.doc.bank_statement_closing_balance,
+ cards_manager: frm.cards_manager,
+ });
+ }
}
// ================================================================================================= //
@@ -300,36 +334,33 @@ function custom_render(frm) {
// Extend the 'make_reconciliation_tool' function
frappe.ui.form.on(
- 'Bank Reconciliation Tool',
- 'make_reconciliation_tool',
- function (frm) {
- if (frm.doc.bank_account) {
- fetch_default_cost_center_for_bank_account(frm);
- }
+ "Bank Reconciliation Tool",
+ "make_reconciliation_tool",
+ function (frm) {
+ if (frm.doc.bank_account) {
+ fetch_default_cost_center_for_bank_account(frm);
}
+ },
);
// Extend the 'bank_account' function
-frappe.ui.form.on(
- 'Bank Reconciliation Tool',
- 'bank_account',
- function (frm) {
- if (frm.doc.bank_account) {
- fetch_default_cost_center_for_bank_account(frm);
- }
- }
-);
+frappe.ui.form.on("Bank Reconciliation Tool", "bank_account", function (frm) {
+ if (frm.doc.bank_account) {
+ fetch_default_cost_center_for_bank_account(frm);
+ }
+});
// Fetch the custom field for "Bank Reconciliation Default Cost Center" from "Bank Account"
function fetch_default_cost_center_for_bank_account(frm) {
- frappe.db.get_value(
- "Bank Account",
- frm.doc.bank_account,
- "custom_bank_reconciliation_default_cost_center",
- (r) => {
- frm.doc.custom_bank_reconciliation_default_cost_center = r.custom_bank_reconciliation_default_cost_center;
- }
- );
+ frappe.db.get_value(
+ "Bank Account",
+ frm.doc.bank_account,
+ "custom_bank_reconciliation_default_cost_center",
+ (r) => {
+ frm.doc.custom_bank_reconciliation_default_cost_center =
+ r.custom_bank_reconciliation_default_cost_center;
+ },
+ );
}
// ================================================================================================= //
diff --git a/csf_za/tax_compliance/doctype/value_added_tax_return/test_value_added_tax_return.py b/csf_za/tax_compliance/doctype/value_added_tax_return/test_value_added_tax_return.py
index 757f7f9..c622b65 100644
--- a/csf_za/tax_compliance/doctype/value_added_tax_return/test_value_added_tax_return.py
+++ b/csf_za/tax_compliance/doctype/value_added_tax_return/test_value_added_tax_return.py
@@ -41,9 +41,7 @@ def setUp(self):
self.customer = "_Test Customer"
# Create accounts
- self.vat_account = create_account(
- "VAT Test", "Tax Assets - _TC", self.company, account_type="Tax"
- )
+ self.vat_account = create_account("VAT Test", "Tax Assets - _TC", self.company, account_type="Tax")
self.bad_debts_account = create_account(
"Bad Debts Test", "Direct Expenses - _TC", self.company, account_type="Expense Account"
)
@@ -154,9 +152,7 @@ def test_refresh_input_tax_fields(self):
@patch(
"csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.frappe.get_cached_doc"
)
- @patch(
- "csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.transform_gl_entries"
- )
+ @patch("csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.transform_gl_entries")
@patch(
"csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.frappe.get_cached_value"
)
@@ -345,9 +341,7 @@ def test_process_gl_entries(self, mock_cached_value, mock_transform, mock_get_ca
self.assertEqual(results[0].incl_tax_amount, 115)
# PI-001: Purchase Invoice with no VAT Template
- self.assertEqual(
- results[1].classification, None
- ) # Assuming no classification for missing template
+ self.assertEqual(results[1].classification, None) # Assuming no classification for missing template
self.assertEqual(results[1].tax_amount, 15)
self.assertEqual(results[1].incl_tax_amount, 115)
@@ -379,9 +373,7 @@ def test_process_gl_entries(self, mock_cached_value, mock_transform, mock_get_ca
@patch(
"csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.frappe.get_cached_doc"
)
- @patch(
- "csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.transform_gl_entries"
- )
+ @patch("csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.transform_gl_entries")
@patch(
"csf_za.tax_compliance.doctype.value_added_tax_return.value_added_tax_return.frappe.get_cached_value"
)
diff --git a/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.js b/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.js
index 544ac48..416a4bb 100644
--- a/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.js
+++ b/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.js
@@ -2,117 +2,151 @@
// For license information, please see license.txt
frappe.ui.form.on("Value-added Tax Return", {
- refresh(frm) {
- frm.trigger("set_intro");
-
- // Add custom button
- if (!frm.doc.__islocal) {
- frm.add_custom_button(__("Get transactions for period"), function() {
- if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."))
- frappe.call({
- method: "get_gl_entries",
- doc: frm.doc,
- freeze: true,
- freeze_message: __("Retrieving GL Entries..."),
- callback: function(r){
- if (r.message) {
- let response = r.message;
- if(response) {
- // Populate table with new results
- frm.clear_table("gl_entries");
- response.forEach((entry) => {
- let row = frm.add_child("gl_entries");
- row.gl_entry = entry.name;
- row.posting_date = entry.posting_date
- row.voucher_type = entry.voucher_type
- row.voucher_no = entry.voucher_no
- row.taxes_and_charges = entry.taxes_and_charges_template
- row.tax_account_debit = entry.general_ledger_debit
- row.tax_account_credit = entry.general_ledger_credit
- row.classification = entry.classification
- row.classification_debugging = entry.classification_debugging
- row.tax_amount = entry.tax_amount
- row.incl_tax_amount = entry.incl_tax_amount
- row.is_cancelled = entry.is_cancelled
- });
- frm.refresh_field("gl_entries");
- frm.get_field("gl_entries").tab.set_active();
- frm.trigger("set_intro");
- frappe.show_alert({ message: __('GL Entries have been retrieved'), indicator: 'green' });
- frm.save();
- }
- }
- }
- });
- });
- }
- },
- set_intro(frm) {
- // Set the intro message on the form to show unclassified transactions
- const unclassified = frm.doc.gl_entries.filter((entry) => entry.classification.length === 0 && entry.is_cancelled === 0);
- if (unclassified.length > 0) {
- frm.set_intro(`${unclassified.length} ` + __("unclassified transactions"), 'orange');
- }
- else {
- frm.set_intro("");
- }
- },
- date_from(frm) {
- frm.trigger("clear_gl_entries_after_date_change");
- },
- date_to(frm) {
- frm.trigger("clear_gl_entries_after_date_change");
- },
- clear_gl_entries_after_date_change(frm) {
- // Clear the Journal Entries table
- if (frm.doc.gl_entries.length !== 0) {
- frappe.confirm(__('Changing dates will clear the Transactions table. Do you want to proceed?'),
- () => {
- // action to perform if Yes is selected
- frm.clear_table("gl_entries");
- frm.refresh_field("gl_entries");
- }, () => {
- // action to perform if No is selected
- frm.reload_doc();
- })
- }
- },
- button_report_output_a(frm) {
- route_to_report(frm.doc.name, "Output - A Standard rate (excl capital goods)");
- },
- button_report_output_b(frm) {
- route_to_report(frm.doc.name, "Output - B Standard rate (only capital goods)");
- },
- button_report_output_c(frm) {
- route_to_report(frm.doc.name, "Output - C Zero Rated (excl goods exported)");
- },
- button_report_output_d(frm) {
- route_to_report(frm.doc.name, "Output - D Zero Rated (only goods exported)");
- },
- button_report_output_e(frm) {
- route_to_report(frm.doc.name, "Output - E Exempt");
- },
- button_report_input_a(frm) {
- route_to_report(frm.doc.name, "Input - A Capital goods and/or services supplied to you (local)");
- },
- button_report_input_b(frm) {
- route_to_report(frm.doc.name, "Input - B Capital goods imported");
- },
- button_report_input_c(frm) {
- route_to_report(frm.doc.name, "Input - C Other goods supplied to you (excl capital goods)");
- },
- button_report_input_d(frm) {
- route_to_report(frm.doc.name, "Input - D Other goods imported (excl capital goods)");
- },
+ refresh(frm) {
+ frm.trigger("set_intro");
+ // Add custom button
+ if (!frm.doc.__islocal) {
+ frm.add_custom_button(__("Get transactions for period"), function () {
+ if (frm.is_dirty()) frappe.throw(__("Please save before proceeding."));
+ frappe.call({
+ method: "get_gl_entries",
+ doc: frm.doc,
+ freeze: true,
+ freeze_message: __("Retrieving GL Entries..."),
+ callback: function (r) {
+ if (r.message) {
+ let response = r.message;
+ if (response) {
+ // Populate table with new results
+ frm.clear_table("gl_entries");
+ response.forEach((entry) => {
+ let row = frm.add_child("gl_entries");
+ row.gl_entry = entry.name;
+ row.posting_date = entry.posting_date;
+ row.voucher_type = entry.voucher_type;
+ row.voucher_no = entry.voucher_no;
+ row.taxes_and_charges = entry.taxes_and_charges_template;
+ row.tax_account_debit = entry.general_ledger_debit;
+ row.tax_account_credit = entry.general_ledger_credit;
+ row.classification = entry.classification;
+ row.classification_debugging = entry.classification_debugging;
+ row.tax_amount = entry.tax_amount;
+ row.incl_tax_amount = entry.incl_tax_amount;
+ row.is_cancelled = entry.is_cancelled;
+ });
+ frm.refresh_field("gl_entries");
+ frm.get_field("gl_entries").tab.set_active();
+ frm.trigger("set_intro");
+ frappe.show_alert({
+ message: __("GL Entries have been retrieved"),
+ indicator: "green",
+ });
+ frm.save();
+ }
+ }
+ },
+ });
+ });
+ }
+ },
+ set_intro(frm) {
+ // Set the intro message on the form to show unclassified transactions
+ const unclassified = frm.doc.gl_entries.filter(
+ (entry) => entry.classification.length === 0 && entry.is_cancelled === 0,
+ );
+ if (unclassified.length > 0) {
+ frm.set_intro(
+ `${unclassified.length} ` + __("unclassified transactions"),
+ "orange",
+ );
+ } else {
+ frm.set_intro("");
+ }
+ },
+ date_from(frm) {
+ frm.trigger("clear_gl_entries_after_date_change");
+ },
+ date_to(frm) {
+ frm.trigger("clear_gl_entries_after_date_change");
+ },
+ clear_gl_entries_after_date_change(frm) {
+ // Clear the Journal Entries table
+ if (frm.doc.gl_entries.length !== 0) {
+ frappe.confirm(
+ __(
+ "Changing dates will clear the Transactions table. Do you want to proceed?",
+ ),
+ () => {
+ // action to perform if Yes is selected
+ frm.clear_table("gl_entries");
+ frm.refresh_field("gl_entries");
+ },
+ () => {
+ // action to perform if No is selected
+ frm.reload_doc();
+ },
+ );
+ }
+ },
+ button_report_output_a(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Output - A Standard rate (excl capital goods)",
+ );
+ },
+ button_report_output_b(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Output - B Standard rate (only capital goods)",
+ );
+ },
+ button_report_output_c(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Output - C Zero Rated (excl goods exported)",
+ );
+ },
+ button_report_output_d(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Output - D Zero Rated (only goods exported)",
+ );
+ },
+ button_report_output_e(frm) {
+ route_to_report(frm.doc.name, "Output - E Exempt");
+ },
+ button_report_input_a(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Input - A Capital goods and/or services supplied to you (local)",
+ );
+ },
+ button_report_input_b(frm) {
+ route_to_report(frm.doc.name, "Input - B Capital goods imported");
+ },
+ button_report_input_c(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Input - C Other goods supplied to you (excl capital goods)",
+ );
+ },
+ button_report_input_d(frm) {
+ route_to_report(
+ frm.doc.name,
+ "Input - D Other goods imported (excl capital goods)",
+ );
+ },
});
function route_to_report(docname, classification) {
- // Open the 'Value-added Tax Return Linked Transactions' Report
- frappe.route_options = {
- "vat_return": docname,
- "classification": classification
- };
- frappe.set_route("query-report", "Value-added Tax Return Linked Transactions");
-
-}
\ No newline at end of file
+ // Open the 'Value-added Tax Return Linked Transactions' Report
+ frappe.route_options = {
+ vat_return: docname,
+ classification: classification,
+ };
+ frappe.set_route(
+ "query-report",
+ "Value-added Tax Return Linked Transactions",
+ );
+}
diff --git a/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.py b/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.py
index 9cfbfd3..40f8857 100644
--- a/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.py
+++ b/csf_za/tax_compliance/doctype/value_added_tax_return/value_added_tax_return.py
@@ -12,7 +12,6 @@
class ValueaddedTaxReturn(Document):
-
TAX_RATE = 15
def validate(self):
@@ -86,9 +85,7 @@ def refresh_output_tax_fields(self):
)
# Calculate field 6
- self.acc_exceed_28_days_total = (
- self.acc_exceed_28_days * float(self.acc_exceed_28_days_percent) / 100
- )
+ self.acc_exceed_28_days_total = self.acc_exceed_28_days * float(self.acc_exceed_28_days_percent) / 100
# Calculate field 8
self.acc_total_excl = self.acc_exceed_28_days_total + self.acc_not_exceed_28_days
@@ -166,9 +163,7 @@ def on_submit(self):
"""
Validate when document is submitted
"""
- unclassified = [
- row for row in self.gl_entries if not row.classification and not row.is_cancelled
- ]
+ unclassified = [row for row in self.gl_entries if not row.classification and not row.is_cancelled]
if len(unclassified) > 0:
frappe.throw(
_("Please classify the {0} remaining unclassified transactions before submitting").format(
@@ -279,7 +274,7 @@ def process_gl_entries(self, gl_entries):
vouchers = transform_gl_entries(gl_entries, tax_accounts)
- for voucher_no, item in vouchers.items():
+ for _voucher_no, item in vouchers.items():
voucher = item.voucher
# Skip Cancelled GL Entries
@@ -318,7 +313,8 @@ def process_gl_entries(self, gl_entries):
field
for field in taxes_and_charges_map
if field["reference_doctype"] == voucher.voucher_type
- and vat_return_settings.get(field["field_name"]) == voucher.taxes_and_charges_template
+ and vat_return_settings.get(field["field_name"])
+ == voucher.taxes_and_charges_template
),
None,
)
@@ -403,22 +399,28 @@ def process_gl_entries(self, gl_entries):
if tax_leg.journal_entry_account_debit != 0:
# Tax leg is a debit (input tax or reduction of output tax)
other_debits = [je for je in remaining_entries if je.journal_entry_account_debit != 0]
- other_credits = [je for je in remaining_entries if je.journal_entry_account_credit != 0]
+ other_credits = [
+ je for je in remaining_entries if je.journal_entry_account_credit != 0
+ ]
# This handles write-offs where there is one other debit (e.g. bad debts)
# and multiple credit entries (to customer account)
if len(other_debits) == 1 and other_credits:
excl_tax_leg = other_debits[0]
- voucher.incl_tax_amount = sum(c.journal_entry_account_credit for c in other_credits)
+ voucher.incl_tax_amount = sum(
+ c.journal_entry_account_credit for c in other_credits
+ )
+ voucher.classification_debugging += f"\nš tax_leg = '{tax_leg.journal_entry_account}': '{tax_leg.journal_entry_account_debit}'"
voucher.classification_debugging += (
- f"\nš tax_leg = '{tax_leg.journal_entry_account}': '{tax_leg.journal_entry_account_debit}'"
+ f"\nš incl_tax_amount = '{voucher.incl_tax_amount}'"
)
- voucher.classification_debugging += f"\nš incl_tax_amount = '{voucher.incl_tax_amount}'"
voucher.classification_debugging += f"\nš excl_tax_leg = '{excl_tax_leg.journal_entry_account}': '{excl_tax_leg.journal_entry_account_debit}'"
voucher.classification = frappe.get_cached_value(
- "Account", excl_tax_leg.journal_entry_account, "custom_vat_return_debit_classification"
+ "Account",
+ excl_tax_leg.journal_entry_account,
+ "custom_vat_return_debit_classification",
)
voucher.classification_debugging += f"\nš 'Classify Debit entries...' setting for Account '{excl_tax_leg.journal_entry_account}' = '{voucher.classification}'"
continue
@@ -426,7 +428,9 @@ def process_gl_entries(self, gl_entries):
elif tax_leg.journal_entry_account_credit != 0:
# Tax leg is a credit (output tax)
other_debits = [je for je in remaining_entries if je.journal_entry_account_debit != 0]
- other_credits = [je for je in remaining_entries if je.journal_entry_account_credit != 0]
+ other_credits = [
+ je for je in remaining_entries if je.journal_entry_account_credit != 0
+ ]
# This handles cases with one other credit and multiple debit entries
if len(other_credits) == 1 and other_debits:
@@ -434,11 +438,15 @@ def process_gl_entries(self, gl_entries):
voucher.incl_tax_amount = sum(d.journal_entry_account_debit for d in other_debits)
voucher.classification_debugging += f"\nš tax_leg = '{tax_leg.journal_entry_account}': '{tax_leg.journal_entry_account_credit}'"
- voucher.classification_debugging += f"\nš incl_tax_amount = '{voucher.incl_tax_amount}'"
+ voucher.classification_debugging += (
+ f"\nš incl_tax_amount = '{voucher.incl_tax_amount}'"
+ )
voucher.classification_debugging += f"\nš excl_tax_leg = '{excl_tax_leg.journal_entry_account}': '{excl_tax_leg.journal_entry_account_credit}'"
voucher.classification = frappe.get_cached_value(
- "Account", excl_tax_leg.journal_entry_account, "custom_vat_return_credit_classification"
+ "Account",
+ excl_tax_leg.journal_entry_account,
+ "custom_vat_return_credit_classification",
)
voucher.classification_debugging += f"\nš 'Classify Credit entries...' setting for Account '{excl_tax_leg.journal_entry_account}' = '{voucher.classification}'"
continue
@@ -465,19 +473,25 @@ def process_gl_entries(self, gl_entries):
if excl_tax_leg.journal_entry_account_debit != 0:
voucher.classification = frappe.get_cached_value(
- "Account", excl_tax_leg.journal_entry_account, "custom_vat_return_debit_classification"
+ "Account",
+ excl_tax_leg.journal_entry_account,
+ "custom_vat_return_debit_classification",
)
voucher.incl_tax_amount = (
- incl_tax_leg.journal_entry_account_credit or incl_tax_leg.journal_entry_account_debit
+ incl_tax_leg.journal_entry_account_credit
+ or incl_tax_leg.journal_entry_account_debit
)
voucher.classification_debugging += f"\nš 'Classify Debit entries...' setting for Account '{excl_tax_leg.journal_entry_account}' = '{voucher.classification}'"
continue
elif excl_tax_leg.journal_entry_account_credit != 0:
voucher.classification = frappe.get_cached_value(
- "Account", excl_tax_leg.journal_entry_account, "custom_vat_return_credit_classification"
+ "Account",
+ excl_tax_leg.journal_entry_account,
+ "custom_vat_return_credit_classification",
)
voucher.incl_tax_amount = (
- incl_tax_leg.journal_entry_account_credit or incl_tax_leg.journal_entry_account_debit
+ incl_tax_leg.journal_entry_account_credit
+ or incl_tax_leg.journal_entry_account_debit
)
voucher.classification_debugging += f"\nš 'Classify Credit entries..' for Account '{excl_tax_leg.journal_entry_account}' = '{voucher.classification}'"
continue
diff --git a/csf_za/tax_compliance/doctype/value_added_tax_return_settings/value_added_tax_return_settings.js b/csf_za/tax_compliance/doctype/value_added_tax_return_settings/value_added_tax_return_settings.js
index ef010ca..f076cbf 100644
--- a/csf_za/tax_compliance/doctype/value_added_tax_return_settings/value_added_tax_return_settings.js
+++ b/csf_za/tax_compliance/doctype/value_added_tax_return_settings/value_added_tax_return_settings.js
@@ -2,19 +2,20 @@
// For license information, please see license.txt
frappe.ui.form.on("Value-added Tax Return Settings", {
- refresh(frm) {
- frm.trigger('set_intro_text');
- frm.trigger('set_queries');
+ refresh(frm) {
+ frm.trigger("set_intro_text");
+ frm.trigger("set_queries");
- if (frm.doc.company && frm.doc.tax_accounts.length === 0) {
- frm.trigger("auto_set_tax_accounts");
- }
-
- },
- set_intro_text(frm){
- intro_text = `
+ if (frm.doc.company && frm.doc.tax_accounts.length === 0) {
+ frm.trigger("auto_set_tax_accounts");
+ }
+ },
+ set_intro_text(frm) {
+ let intro_text = `
- ${__("When submitting your VAT201 return, your VAT-related transactions need to be classified")}:
+ ${__(
+ "When submitting your VAT201 return, your VAT-related transactions need to be classified",
+ )}:
${__("Output Tax")}
-
@@ -45,20 +46,26 @@ frappe.ui.form.on("Value-added Tax Return Settings", {
V) ${__("Washing powder")};
W) ${__("White bread")}.
-
+
-
${__("Standard rate (only capital goods/services)")}
- ${__("The VAT inclusive amount of goods and/or services supplied by you at the standard rate, only capital goods and/or services This reflects the consideration received (VAT included) in respect of")}:
- A) ${__("Sale of capital goods and/or services (e.g. Sale of land and buildings, plant and machinery, intellectual property)")}.
+ ${__(
+ "The VAT inclusive amount of goods and/or services supplied by you at the standard rate, only capital goods and/or services This reflects the consideration received (VAT included) in respect of",
+ )}:
+ A) ${__(
+ "Sale of capital goods and/or services (e.g. Sale of land and buildings, plant and machinery, intellectual property)",
+ )}.
B) ${__("VAT on assets upon termination of registration")}.
-
${__("Zero Rated (excluding goods exported)")}
- ${__("Goods and/or services supplied by you at zero rate, excluding exported goods. Zero rated supplies are taxable supplies, taxed at a rate of 0%.
Examples of zero-rated supplies are")}:
+ ${__(
+ "Goods and/or services supplied by you at zero rate, excluding exported goods. Zero rated supplies are taxable supplies, taxed at a rate of 0%.
Examples of zero-rated supplies are",
+ )}:
A) ${__("Brown bread")};
B) ${__("Eggs of domesticated chickens")};
C) ${__("Edible legumes and pulses of leguminous plants")};
@@ -72,20 +79,26 @@ frappe.ui.form.on("Value-added Tax Return Settings", {
K) ${__("Vegetable oil excluding olive oil")};
L) ${__("Fuel levy goods (e.g. petrol and diesel)")};
M) ${__("The sale of a business or part of a business as a going concern")};
- N) ${__("Services supplied in respect of goods temporarily admitted into the RSA from an export country for the purposes of being repaired or serviced")}.
+ N) ${__(
+ "Services supplied in respect of goods temporarily admitted into the RSA from an export country for the purposes of being repaired or serviced",
+ )}.
O) ${__("International travel")}.
-
${__("Zero Rated (only exported goods)")}
- ${__("Goods supplied by you at the zero rate which has been exported from the RSA")}.
+ ${__(
+ "Goods supplied by you at the zero rate which has been exported from the RSA",
+ )}.
-
${__("Exempt and non-supplies")}
- ${__("Exempt supplies or non-supplies supplied by you. No output tax is levied in respect of exempt supplies and no input tax relating to the expenditure on these supplies may be deducted. The following are examples of exempt supplies")}:
+ ${__(
+ "Exempt supplies or non-supplies supplied by you. No output tax is levied in respect of exempt supplies and no input tax relating to the expenditure on these supplies may be deducted. The following are examples of exempt supplies",
+ )}:
A) ${__("Financial services")};
B) ${__("Donated goods or services by an association not for gain")};
C) ${__("Residential accommodation")};
@@ -93,7 +106,9 @@ frappe.ui.form.on("Value-added Tax Return Settings", {
E) ${__("The sale or letting of land situated outside the Republic")};
F) ${__("Transport of fare-paying passengers by road or railway")};
G) ${__("The supply of educational services")};
- H) ${__("Membership contributions to employee organisations, such as trade unions")};
+ H) ${__(
+ "Membership contributions to employee organisations, such as trade unions",
+ )};
I) ${__("The supply of childcare services")}.
@@ -122,7 +137,9 @@ frappe.ui.form.on("Value-added Tax Return Settings", {
-
${__("Capital goods and/or services supplied to you (local)")}
- ${__("The permissible VAT amount of capital goods and/or services supplied to you. The prescribed document, for example a valid tax invoice must be held by you before you complete any amount in this field. Examples of such acquisitions are")}:
+ ${__(
+ "The permissible VAT amount of capital goods and/or services supplied to you. The prescribed document, for example a valid tax invoice must be held by you before you complete any amount in this field. Examples of such acquisitions are",
+ )}:
A) ${__("Office equipment")};
B) ${__("Furniture")};
C) ${__("Trucks")};
@@ -132,76 +149,88 @@ frappe.ui.form.on("Value-added Tax Return Settings", {
-
${__("Capital goods imported by you")}
- ${__("The permissible VAT amount of capital goods imported by you. The Customs Code field is mandatory. This field applies to capital goods imported in respect of which a bill of entry valid release document and receipt for the payment of the VAT issued by Customs, is held")}.
+ ${__(
+ "The permissible VAT amount of capital goods imported by you. The Customs Code field is mandatory. This field applies to capital goods imported in respect of which a bill of entry valid release document and receipt for the payment of the VAT issued by Customs, is held",
+ )}.
-
${__("Other goods imported by you (not capital goods)")}
- ${__("The permissible VAT amount of other goods imported by you (not capital goods). This applies to non-capital goods imported in respect of which a bill of entry ,valid release document and receipt for the payment of the VAT issued by Customs, is held. An example of such acquisition is the importation of trading stock")}.
+ ${__(
+ "The permissible VAT amount of other goods imported by you (not capital goods). This applies to non-capital goods imported in respect of which a bill of entry ,valid release document and receipt for the payment of the VAT issued by Customs, is held. An example of such acquisition is the importation of trading stock",
+ )}.
- `
- frm.set_intro(intro_text, 'blue');
- },
- set_queries(frm) {
- fields = [
- "standard_rate_non_capital",
- "standard_rate_capital",
- "zero_rate_non_exported",
- "zero_rate_exported",
- "exempt",
- "goods_supplied",
- "goods_non_import",
- "goods_import",
- "other_goods_import",
- ]
- fields.forEach(field => {
- frm.set_query(field, () => {
- return {
- filters: {
- company: ["=", frm.doc.name],
- },
- };
- });
- });
- },
- transaction_classification(frm) {
- if (frm.doc.transaction_classification) {
- if (frm.doc.transaction_classification === "General Ledger Accounts") {
- frm.set_value("output_tax_doctype", "Account");
- frm.set_value("input_tax_doctype", "Account");
- }
- else if (frm.doc.transaction_classification === "Taxes and Charges Templates") {
- frm.set_value("output_tax_doctype", "Sales Taxes and Charges Template");
- frm.set_value("input_tax_doctype", "Purchase Taxes and Charges Template");
- }
- }
- },
- button_classifications_report(frm) {
- // Open the 'Account Classifications for VAT Return' Report
- frappe.route_options = {
- "company": frm.doc.company
- };
- frappe.set_route("query-report", "Account Classifications for VAT Return");
- },
- company(frm) {
- // Trigger when company is changed
- frm.trigger("auto_set_tax_accounts");
- },
- auto_set_tax_accounts(frm) {
- // Auto-populate the Tax Accounts field based on existing Accounts with Account Type set to Tax
- frappe.db.get_list('Account',
- {fields: ['name'], filters:{"account_type": "Tax", "company": frm.doc.company}}).then((res) => {
- frm.clear_table("tax_accounts");
- res.forEach((account) => {
- let row = frm.add_child("tax_accounts");
- row.account = account.name
- });
- frm.refresh_field("tax_accounts");
- });
- }
+ `;
+ frm.set_intro(intro_text, "blue");
+ },
+ set_queries(frm) {
+ let fields = [
+ "standard_rate_non_capital",
+ "standard_rate_capital",
+ "zero_rate_non_exported",
+ "zero_rate_exported",
+ "exempt",
+ "goods_supplied",
+ "goods_non_import",
+ "goods_import",
+ "other_goods_import",
+ ];
+ fields.forEach((field) => {
+ frm.set_query(field, () => {
+ return {
+ filters: {
+ company: ["=", frm.doc.name],
+ },
+ };
+ });
+ });
+ },
+ transaction_classification(frm) {
+ if (frm.doc.transaction_classification) {
+ if (frm.doc.transaction_classification === "General Ledger Accounts") {
+ frm.set_value("output_tax_doctype", "Account");
+ frm.set_value("input_tax_doctype", "Account");
+ } else if (
+ frm.doc.transaction_classification === "Taxes and Charges Templates"
+ ) {
+ frm.set_value("output_tax_doctype", "Sales Taxes and Charges Template");
+ frm.set_value(
+ "input_tax_doctype",
+ "Purchase Taxes and Charges Template",
+ );
+ }
+ }
+ },
+ button_classifications_report(frm) {
+ // Open the 'Account Classifications for VAT Return' Report
+ frappe.route_options = {
+ company: frm.doc.company,
+ };
+ frappe.set_route("query-report", "Account Classifications for VAT Return");
+ },
+ company(frm) {
+ // Trigger when company is changed
+ frm.trigger("auto_set_tax_accounts");
+ },
+ auto_set_tax_accounts(frm) {
+ // Auto-populate the Tax Accounts field based on existing Accounts with Account Type set to Tax
+ frappe.db
+ .get_list("Account", {
+ fields: ["name"],
+ filters: { account_type: "Tax", company: frm.doc.company },
+ })
+ .then((res) => {
+ frm.clear_table("tax_accounts");
+ res.forEach((account) => {
+ let row = frm.add_child("tax_accounts");
+ row.account = account.name;
+ });
+ frm.refresh_field("tax_accounts");
+ });
+ },
});
diff --git a/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.js b/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.js
index 6dc7191..f23a113 100644
--- a/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.js
+++ b/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.js
@@ -2,33 +2,34 @@
// For license information, please see license.txt
frappe.query_reports["Value-added Tax Return Linked Transactions"] = {
- "filters": [
- {
- "fieldname": "vat_return",
- "label": __("Value-added Tax Return"),
- "fieldtype": "Link",
- "options": "Value-added Tax Return",
- "reqd": 1
- },
- {
- "fieldname": "classification",
- "label": __("Classification"),
- "fieldtype": "Select",
- "options": "Output - A Standard rate (excl capital goods)\nOutput - B Standard rate (only capital goods)\nOutput - C Zero Rated (excl goods exported)\nOutput - D Zero Rated (only goods exported)\nOutput - E Exempt\nInput - A Capital goods and/or services supplied to you (local)\nInput - B Capital goods imported\nInput - C Other goods supplied to you (excl capital goods)\nInput - D Other goods imported (excl capital goods)",
- },
- {
- "fieldname": "show_all_classifications",
- "label": __("Show all Classifications"),
- "fieldtype": "Check",
- on_change: function() {
- frappe.query_report.set_filter_value('classification', "");
- }
- },
- {
- "fieldname": "include_cancelled",
- "label": __("Include Cancelled Vouchers"),
- "default": 0,
- "fieldtype": "Check"
- },
- ],
+ filters: [
+ {
+ fieldname: "vat_return",
+ label: __("Value-added Tax Return"),
+ fieldtype: "Link",
+ options: "Value-added Tax Return",
+ reqd: 1,
+ },
+ {
+ fieldname: "classification",
+ label: __("Classification"),
+ fieldtype: "Select",
+ options:
+ "Output - A Standard rate (excl capital goods)\nOutput - B Standard rate (only capital goods)\nOutput - C Zero Rated (excl goods exported)\nOutput - D Zero Rated (only goods exported)\nOutput - E Exempt\nInput - A Capital goods and/or services supplied to you (local)\nInput - B Capital goods imported\nInput - C Other goods supplied to you (excl capital goods)\nInput - D Other goods imported (excl capital goods)",
+ },
+ {
+ fieldname: "show_all_classifications",
+ label: __("Show all Classifications"),
+ fieldtype: "Check",
+ on_change: function () {
+ frappe.query_report.set_filter_value("classification", "");
+ },
+ },
+ {
+ fieldname: "include_cancelled",
+ label: __("Include Cancelled Vouchers"),
+ default: 0,
+ fieldtype: "Check",
+ },
+ ],
};
diff --git a/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.py b/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.py
index 28dd8af..e0ce43f 100644
--- a/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.py
+++ b/csf_za/tax_compliance/report/value_added_tax_return_linked_transactions/value_added_tax_return_linked_transactions.py
@@ -135,9 +135,7 @@ def get_data(filters):
def group_by_classification(data):
""" """
meta = frappe.get_meta("Value-added Tax Return GL Entry", cached=False)
- classification_field = next(
- (field for field in meta.fields if field.fieldname == "classification")
- )
+ classification_field = next(field for field in meta.fields if field.fieldname == "classification")
classifications = classification_field.options.split("\n")
grouped_data = {classification: [] for classification in classifications}
diff --git a/csf_za/tests/ui_test_helpers.py b/csf_za/tests/ui_test_helpers.py
index 99342cd..d08b7a5 100644
--- a/csf_za/tests/ui_test_helpers.py
+++ b/csf_za/tests/ui_test_helpers.py
@@ -76,7 +76,7 @@ def setup_data_for_payment_entry_customisation_tests(mode_of_payment_name, accou
def create_gl_account_for_bank(account_name):
try:
- gl_account = frappe.get_doc(
+ frappe.get_doc(
{
"doctype": "Account",
"company": get_default_company(),
diff --git a/csf_za/utils.py b/csf_za/utils.py
index 3424dd1..0e0f42d 100644
--- a/csf_za/utils.py
+++ b/csf_za/utils.py
@@ -9,8 +9,8 @@ def before_tests():
from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
print("Running before_tests")
- if not frappe.db.a_row_exists("Company"):
+ if not frappe.db.a_row_exists("Company"):
print("Running setup_complete because company does not exist")
current_year = now_datetime().year
setup_complete(
@@ -31,6 +31,5 @@ def before_tests():
"chart_of_accounts": "Standard",
}
)
-
enable_all_roles_and_domains()
frappe.db.commit() # nosemgrep
diff --git a/cypress.config.js b/cypress.config.js
index b0c6dd3..a1dbb6a 100644
--- a/cypress.config.js
+++ b/cypress.config.js
@@ -1,23 +1,25 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
- projectId: "ve3gnj",
- adminPassword: "admin",
- testUser: "frappe@example.com",
- defaultCommandTimeout: 20000,
- pageLoadTimeout: 15000,
- video: true,
- retries: {
- runMode: 2,
- openMode: 2,
- },
- e2e: {
- // We've imported your old cypress plugins here.
- // You may want to clean this up later by importing these.
- setupNodeEvents(on, config) {
- return require("./cypress/plugins/index.js")(on, config);
- },
- baseUrl: "http://test_site_ui:8000",
- specPattern: ["./cypress/integration/*.js", "**/ui_test_*.js"],
- },
+ projectId: "ve3gnj",
+ adminPassword: "admin",
+ testUser: "frappe@example.com",
+ defaultCommandTimeout: 20000,
+ pageLoadTimeout: 15000,
+ video: true,
+ viewportHeight: 960,
+ viewportWidth: 1400,
+ retries: {
+ runMode: 2,
+ openMode: 2,
+ },
+ e2e: {
+ // We've imported your old cypress plugins here.
+ // You may want to clean this up later by importing these.
+ setupNodeEvents(on, config) {
+ return require("./cypress/plugins/index.js")(on, config);
+ },
+ baseUrl: "http://test_site_ui:8000",
+ specPattern: ["./cypress/integration/*.js", "**/ui_test_*.js"],
+ },
});
diff --git a/cypress/integration/bank_reconciliation_tool.js b/cypress/integration/bank_reconciliation_tool.js
index e0c28d9..ad09c66 100644
--- a/cypress/integration/bank_reconciliation_tool.js
+++ b/cypress/integration/bank_reconciliation_tool.js
@@ -1,81 +1,105 @@
-context('Bank Reconciliation Tool', () => {
- before(() => {
- cy.login('Administrator', 'admin');
- cy.visit('/desk');
- });
-
- it('fetches defaults from account master when creating a journal entry', () => {
- const expense_account_name = "Telephone Expenses - SP";
- const custom_tax_account = "VAT - SP";
- const custom_tax_rate_for_bank_recon = 15;
- const custom_cost_center_for_tax_account = "Main - SP"
-
- // Prepare the data
- cy.call("csf_za.tests.ui_test_helpers.setup_data_for_bank_reconciliation_tool_customisation_tests", {
- expense_account_name: expense_account_name,
- custom_tax_account: custom_tax_account,
- custom_tax_rate_for_bank_recon: custom_tax_rate_for_bank_recon,
- custom_cost_center_for_tax_account: custom_cost_center_for_tax_account
- }).then(() => {
-
- // Navigate to the bank reconciliation tool page
- cy.visit("/app/bank-reconciliation-tool/Bank Reconciliation Tool");
-
- // Select the company filter field and fill in the company name
- cy.get_field("company", "Link").clear();
- cy.fill_field("company", "Sun Power Pty Ltd", "Link").focus().blur();
-
- // Select the bank account filter field and fill in the bank account name
- cy.get_field("bank_account", "Link");
- cy.fill_field("bank_account", "Checking Account - Test Bank", "Link").focus().blur();
-
- // Select the bank statement start date and set the date value
- cy.get_field("bank_statement_from_date", "Date");
- cy.fill_field("bank_statement_from_date", "01-01-2020", "Date").blur();
-
- // Select the bank statement end date and set the date value
- cy.get_field("bank_statement_to_date", "Date");
- cy.fill_field("bank_statement_to_date", "01-01-2040", "Date").blur();
-
- // Click the "Get Unreconciled Entries" button
- cy.get(`.btn-primary[data-label="${encodeURIComponent("Get Unreconciled Entries")}"]`).click();
-
- // Click on the first row's primary button in the datatable
- cy.get('.dt-row-0 .btn-primary').click();
-
- // Select the action field and set it to "Create Voucher"
- cy.get_field("action", "Select");
- cy.fill_field("action", "Create Voucher", "Select")
-
- // Select the document type field and set it to "Payment Entry"
- cy.get_field("document_type", "Select");
- cy.fill_field("document_type", "Payment Entry", "Select")
-
- // Check if the Tax Details section is hidden when the document type is "Payment Entry"
- cy.get(".section-head").contains("Tax Details").parent().should("have.class", "hide-control");
-
- // Change the document type to "Journal Entry"
- cy.get_field("document_type", "Select");
- cy.fill_field("document_type", "Journal Entry", "Select")
-
- // Check if the Tax Details section is visible when the document type is "Journal Entry"
- cy.get(".section-head").contains("Tax Details").parent().should("not.have.class", "hidden");
-
- // Select the second account field and fill it with the expense_account_name value
- cy.get_field("second_account", "Link");
- cy.fill_field("second_account", expense_account_name, "Link").focus().blur().wait(500);
-
- cy.get(`[data-fieldname="custom_tax_rate_for_bank_recon"]`).first().scrollIntoView();
-
- // Check if the value of the 'custom_tax_account' field in the current dialog is equal to the expected 'custom_tax_account' value
- cy.window().its("cur_dialog.fields_dict.custom_tax_account.value").should("be.equal", custom_tax_account)
-
- // Check if the value of the 'custom_tax_rate_for_bank_recon' field in the current dialog is equal to the expected 'custom_tax_rate_for_bank_recon' value
- cy.window().its("cur_dialog.fields_dict.custom_tax_rate_for_bank_recon.value").should("be.equal", custom_tax_rate_for_bank_recon)
-
- // Check if the value of the 'custom_cost_center_for_tax_account' field in the current dialog is equal to the expected 'custom_cost_center_for_tax_account' value
- cy.window().its("cur_dialog.fields_dict.custom_cost_center_for_tax_account.value").should("be.equal", custom_cost_center_for_tax_account)
- });
+context("Bank Reconciliation Tool", () => {
+ before(() => {
+ cy.login("Administrator", "admin");
+ cy.visit("/desk");
+ });
+
+ it("fetches defaults from account master when creating a journal entry", () => {
+ const expense_account_name = "Telephone Expenses - SP";
+ const custom_tax_account = "VAT - SP";
+ const custom_tax_rate_for_bank_recon = 15;
+ const custom_cost_center_for_tax_account = "Main - SP";
+
+ // Prepare the data
+ cy.call(
+ "csf_za.tests.ui_test_helpers.setup_data_for_bank_reconciliation_tool_customisation_tests",
+ {
+ expense_account_name: expense_account_name,
+ custom_tax_account: custom_tax_account,
+ custom_tax_rate_for_bank_recon: custom_tax_rate_for_bank_recon,
+ custom_cost_center_for_tax_account: custom_cost_center_for_tax_account,
+ },
+ ).then(() => {
+ // Navigate to the bank reconciliation tool page
+ cy.visit("/app/bank-reconciliation-tool/Bank Reconciliation Tool");
+
+ // Select the company filter field and fill in the company name
+ cy.get_field("company", "Link").clear();
+ cy.fill_field("company", "Sun Power Pty Ltd", "Link").focus().blur();
+
+ // Select the bank account filter field and fill in the bank account name
+ cy.get_field("bank_account", "Link");
+ cy.fill_field("bank_account", "Checking Account - Test Bank", "Link")
+ .focus()
+ .blur();
+
+ // Select the bank statement start date and set the date value
+ cy.get_field("bank_statement_from_date", "Date");
+ cy.fill_field("bank_statement_from_date", "01-01-2020", "Date").blur();
+
+ // Select the bank statement end date and set the date value
+ cy.get_field("bank_statement_to_date", "Date");
+ cy.fill_field("bank_statement_to_date", "01-01-2040", "Date").blur();
+
+ // Click the "Get Unreconciled Entries" button
+ cy.get(
+ `.btn-primary[data-label="${encodeURIComponent(
+ "Get Unreconciled Entries",
+ )}"]`,
+ ).click();
+
+ // Click on the first row's primary button in the datatable
+ cy.get(".dt-row-0 .btn-primary").click();
+
+ // Select the action field and set it to "Create Voucher"
+ cy.get_field("action", "Select");
+ cy.fill_field("action", "Create Voucher", "Select");
+
+ // Select the document type field and set it to "Payment Entry"
+ cy.get_field("document_type", "Select");
+ cy.fill_field("document_type", "Payment Entry", "Select");
+
+ // Check if the Tax Details section is hidden when the document type is "Payment Entry"
+ cy.get(".section-head")
+ .contains("Tax Details")
+ .parent()
+ .should("have.class", "hide-control");
+
+ // Change the document type to "Journal Entry"
+ cy.get_field("document_type", "Select");
+ cy.fill_field("document_type", "Journal Entry", "Select");
+
+ // Check if the Tax Details section is visible when the document type is "Journal Entry"
+ cy.get(".section-head")
+ .contains("Tax Details")
+ .parent()
+ .should("not.have.class", "hidden");
+
+ // Select the second account field and fill it with the expense_account_name value
+ cy.get_field("second_account", "Link");
+ cy.fill_field("second_account", expense_account_name, "Link")
+ .focus()
+ .blur()
+ .wait(500);
+
+ cy.get(`[data-fieldname="custom_tax_rate_for_bank_recon"]`)
+ .first()
+ .scrollIntoView();
+
+ // Check if the value of the 'custom_tax_account' field in the current dialog is equal to the expected 'custom_tax_account' value
+ cy.window()
+ .its("cur_dialog.fields_dict.custom_tax_account.value")
+ .should("be.equal", custom_tax_account);
+
+ // Check if the value of the 'custom_tax_rate_for_bank_recon' field in the current dialog is equal to the expected 'custom_tax_rate_for_bank_recon' value
+ cy.window()
+ .its("cur_dialog.fields_dict.custom_tax_rate_for_bank_recon.value")
+ .should("be.equal", custom_tax_rate_for_bank_recon);
+
+ // Check if the value of the 'custom_cost_center_for_tax_account' field in the current dialog is equal to the expected 'custom_cost_center_for_tax_account' value
+ cy.window()
+ .its("cur_dialog.fields_dict.custom_cost_center_for_tax_account.value")
+ .should("be.equal", custom_cost_center_for_tax_account);
});
+ });
});
-
diff --git a/cypress/integration/todo.js b/cypress/integration/todo.js
index fbbf32a..0cf096d 100644
--- a/cypress/integration/todo.js
+++ b/cypress/integration/todo.js
@@ -1,26 +1,25 @@
-context('ToDo', () => {
- before(() => {
- cy.login('Administrator', 'admin');
- cy.visit('/desk');
- });
+context("ToDo", () => {
+ before(() => {
+ cy.login("Administrator", "admin");
+ cy.visit("/desk");
+ });
- it('creates a new todo', () => {
- cy.visit("/app/todo/new");
- cy.get_field("description", "Text Editor")
- .type("this is a test todo", { force: true })
- .wait(400);
- cy.get(".page-title").should("contain", "Not Saved");
- cy.intercept({
- method: "POST",
- url: "api/method/frappe.desk.form.save.savedocs",
- }).as("form_save");
- cy.get(".primary-action").click();
- cy.wait("@form_save").its("response.statusCode").should("eq", 200);
+ it("creates a new todo", () => {
+ cy.visit("/app/todo/new");
+ cy.get_field("description", "Text Editor")
+ .type("this is a test todo", { force: true })
+ .wait(400);
+ cy.get(".page-title").should("contain", "Not Saved");
+ cy.intercept({
+ method: "POST",
+ url: "api/method/frappe.desk.form.save.savedocs",
+ }).as("form_save");
+ cy.get(".primary-action").click();
+ cy.wait("@form_save").its("response.statusCode").should("eq", 200);
- cy.go_to_list("ToDo");
- cy.clear_filters();
- cy.get(".page-head").findByTitle("To Do").should("exist");
- cy.get(".list-row").should("contain", "this is a test todo");
-
- });
-});
\ No newline at end of file
+ cy.go_to_list("ToDo");
+ cy.clear_filters();
+ cy.get(".page-head").findByTitle("To Do").should("exist");
+ cy.get(".list-row").should("contain", "this is a test todo");
+ });
+});
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
index b132753..7f662a1 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -12,6 +12,6 @@
// the project's config changing)
module.exports = (on, config) => {
- require("@cypress/code-coverage/task")(on, config);
- return config;
+ require("@cypress/code-coverage/task")(on, config);
+ return config;
};
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 5404cbc..1675ba1 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -28,526 +28,549 @@ import "cypress-real-events/support";
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
Cypress.Commands.add("login", (email, password) => {
- if (!email) {
- email = Cypress.config("testUser") || "Administrator";
- }
- if (!password) {
- password = Cypress.env("adminPassword");
- }
- // cy.session clears all localStorage on new login, so we need to retain the last route
- const session_last_route = window.localStorage.getItem("session_last_route");
- return cy
- .session(
- [email, password] || "",
- () => {
- return cy.request({
- url: "/api/method/login",
- method: "POST",
- body: {
- usr: email,
- pwd: password,
- },
- });
- },
- {
- cacheAcrossSpecs: true,
- }
- )
- .then(() => {
- if (session_last_route) {
- window.localStorage.setItem("session_last_route", session_last_route);
- }
- });
+ if (!email) {
+ email = Cypress.config("testUser") || "Administrator";
+ }
+ if (!password) {
+ password = Cypress.env("adminPassword");
+ }
+ // cy.session clears all localStorage on new login, so we need to retain the last route
+ const session_last_route = window.localStorage.getItem("session_last_route");
+ return cy
+ .session(
+ [email, password] || "",
+ () => {
+ return cy.request({
+ url: "/api/method/login",
+ method: "POST",
+ body: {
+ usr: email,
+ pwd: password,
+ },
+ });
+ },
+ {
+ cacheAcrossSpecs: true,
+ },
+ )
+ .then(() => {
+ if (session_last_route) {
+ window.localStorage.setItem("session_last_route", session_last_route);
+ }
+ });
});
Cypress.Commands.add("call", (method, args) => {
- return cy
- .window()
- .its("frappe.csrf_token")
- .then((csrf_token) => {
- return cy
- .request({
- url: `/api/method/${method}`,
- method: "POST",
- body: args,
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- "X-Frappe-CSRF-Token": csrf_token,
- },
- })
- .then((res) => {
- expect(res.status).eq(200);
- if (method === "logout") {
- Cypress.session.clearAllSavedSessions();
- }
- return res.body;
- });
- });
+ return cy
+ .window()
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
+ return cy
+ .request({
+ url: `/api/method/${method}`,
+ method: "POST",
+ body: args,
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
+ })
+ .then((res) => {
+ expect(res.status).eq(200);
+ if (method === "logout") {
+ Cypress.session.clearAllSavedSessions();
+ }
+ return res.body;
+ });
+ });
});
Cypress.Commands.add("get_list", (doctype, fields = [], filters = []) => {
- filters = JSON.stringify(filters);
- fields = JSON.stringify(fields);
- let url = `/api/resource/${doctype}?fields=${fields}&filters=${filters}`;
- return cy
- .window()
- .its("frappe.csrf_token")
- .then((csrf_token) => {
- return cy
- .request({
- method: "GET",
- url,
- headers: {
- Accept: "application/json",
- "X-Frappe-CSRF-Token": csrf_token,
- },
- })
- .then((res) => {
- expect(res.status).eq(200);
- return res.body;
- });
- });
+ filters = JSON.stringify(filters);
+ fields = JSON.stringify(fields);
+ let url = `/api/resource/${doctype}?fields=${fields}&filters=${filters}`;
+ return cy
+ .window()
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
+ return cy
+ .request({
+ method: "GET",
+ url,
+ headers: {
+ Accept: "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
+ })
+ .then((res) => {
+ expect(res.status).eq(200);
+ return res.body;
+ });
+ });
});
Cypress.Commands.add("get_doc", (doctype, name) => {
- return cy
- .window()
- .its("frappe.csrf_token")
- .then((csrf_token) => {
- return cy
- .request({
- method: "GET",
- url: `/api/resource/${doctype}/${name}`,
- headers: {
- Accept: "application/json",
- "X-Frappe-CSRF-Token": csrf_token,
- },
- })
- .then((res) => {
- expect(res.status).eq(200);
- return res.body;
- });
- });
+ return cy
+ .window()
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
+ return cy
+ .request({
+ method: "GET",
+ url: `/api/resource/${doctype}/${name}`,
+ headers: {
+ Accept: "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
+ })
+ .then((res) => {
+ expect(res.status).eq(200);
+ return res.body;
+ });
+ });
});
Cypress.Commands.add("remove_doc", (doctype, name) => {
- return cy
- .window()
- .its("frappe.csrf_token")
- .then((csrf_token) => {
- return cy
- .request({
- method: "DELETE",
- url: `/api/resource/${doctype}/${name}`,
- headers: {
- Accept: "application/json",
- "X-Frappe-CSRF-Token": csrf_token,
- },
- })
- .then((res) => {
- expect(res.status).eq(202);
- return res.body;
- });
- });
+ return cy
+ .window()
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
+ return cy
+ .request({
+ method: "DELETE",
+ url: `/api/resource/${doctype}/${name}`,
+ headers: {
+ Accept: "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
+ })
+ .then((res) => {
+ expect(res.status).eq(202);
+ return res.body;
+ });
+ });
});
Cypress.Commands.add("create_records", (doc) => {
- return cy
- .call("frappe.tests.ui_test_helpers.create_if_not_exists", { doc: JSON.stringify(doc) })
- .then((r) => r.message);
+ return cy
+ .call("frappe.tests.ui_test_helpers.create_if_not_exists", {
+ doc: JSON.stringify(doc),
+ })
+ .then((r) => r.message);
});
Cypress.Commands.add("set_value", (doctype, name, obj) => {
- return cy.call("frappe.client.set_value", {
- doctype,
- name,
- fieldname: obj,
- });
+ return cy.call("frappe.client.set_value", {
+ doctype,
+ name,
+ fieldname: obj,
+ });
});
Cypress.Commands.add("fill_field", (fieldname, value, fieldtype = "Data") => {
- cy.get_field(fieldname, fieldtype).as("input");
-
- if (["Date", "Time", "Datetime"].includes(fieldtype)) {
- cy.get("@input").clear().wait(200);
- cy.get("@input").click().wait(200);
- cy.get(".datepickers-container .datepicker.active").should("exist");
- }
- if (fieldtype === "Time") {
- cy.get("@input").clear().wait(200);
- }
-
- if (fieldtype === "Select") {
- cy.get("@input").select(value);
- } else {
- cy.get("@input").type(value, {
- waitForAnimations: false,
- parseSpecialCharSequences: false,
- force: true,
- delay: 100,
- });
- }
- return cy.get("@input");
+ cy.get_field(fieldname, fieldtype).as("input");
+
+ if (["Date", "Time", "Datetime"].includes(fieldtype)) {
+ cy.get("@input").clear().wait(200);
+ cy.get("@input").click().wait(200);
+ cy.get(".datepickers-container .datepicker.active").should("exist");
+ }
+ if (fieldtype === "Time") {
+ cy.get("@input").clear().wait(200);
+ }
+
+ if (fieldtype === "Select") {
+ cy.get("@input").select(value);
+ } else {
+ cy.get("@input").type(value, {
+ waitForAnimations: false,
+ parseSpecialCharSequences: false,
+ force: true,
+ delay: 100,
+ });
+ }
+ return cy.get("@input");
});
Cypress.Commands.add("get_field", (fieldname, fieldtype = "Data") => {
- let field_element = fieldtype === "Select" ? "select" : "input";
- let selector = `[data-fieldname="${fieldname}"] ${field_element}:visible`;
+ let field_element = fieldtype === "Select" ? "select" : "input";
+ let selector = `[data-fieldname="${fieldname}"] ${field_element}:visible`;
- selector = fieldtype !== "Select" ? selector : `[data-fieldname="${fieldname}"] ${field_element}`
+ selector =
+ fieldtype !== "Select"
+ ? selector
+ : `[data-fieldname="${fieldname}"] ${field_element}`;
- if (fieldtype === "Text Editor") {
- selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`;
- }
- if (fieldtype === "Code") {
- selector = `[data-fieldname="${fieldname}"] .ace_text-input`;
- }
- if (fieldtype === "Markdown Editor") {
- selector = `[data-fieldname="${fieldname}"] .ace-editor-target`;
- }
+ if (fieldtype === "Text Editor") {
+ selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`;
+ }
+ if (fieldtype === "Code") {
+ selector = `[data-fieldname="${fieldname}"] .ace_text-input`;
+ }
+ if (fieldtype === "Markdown Editor") {
+ selector = `[data-fieldname="${fieldname}"] .ace-editor-target`;
+ }
- return cy.get(selector).first();
+ return cy.get(selector).first();
});
Cypress.Commands.add(
- "fill_table_field",
- (tablefieldname, row_idx, fieldname, value, fieldtype = "Data") => {
- cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as("input");
-
- if (["Date", "Time", "Datetime"].includes(fieldtype)) {
- cy.get("@input").click().wait(200);
- cy.get(".datepickers-container .datepicker.active").should("exist");
- }
- if (fieldtype === "Time") {
- cy.get("@input").clear().wait(200);
- }
-
- if (fieldtype === "Select") {
- cy.get("@input").select(value);
- } else {
- cy.get("@input").type(value, { waitForAnimations: false, force: true });
- }
- return cy.get("@input");
- }
+ "fill_table_field",
+ (tablefieldname, row_idx, fieldname, value, fieldtype = "Data") => {
+ cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as(
+ "input",
+ );
+
+ if (["Date", "Time", "Datetime"].includes(fieldtype)) {
+ cy.get("@input").click().wait(200);
+ cy.get(".datepickers-container .datepicker.active").should("exist");
+ }
+ if (fieldtype === "Time") {
+ cy.get("@input").clear().wait(200);
+ }
+
+ if (fieldtype === "Select") {
+ cy.get("@input").select(value);
+ } else {
+ cy.get("@input").type(value, { waitForAnimations: false, force: true });
+ }
+ return cy.get("@input");
+ },
);
Cypress.Commands.add(
- "get_table_field",
- (tablefieldname, row_idx, fieldname, fieldtype = "Data") => {
- let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
- selector += ` [data-idx="${row_idx}"]`;
-
- if (fieldtype === "Text Editor") {
- selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
- } else if (fieldtype === "Code") {
- selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
- } else {
- selector += ` [data-fieldname="${fieldname}"]`;
- return cy.get(selector).find(".form-control:visible, .static-area:visible").first();
- }
- return cy.get(selector);
- }
+ "get_table_field",
+ (tablefieldname, row_idx, fieldname, fieldtype = "Data") => {
+ let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
+ selector += ` [data-idx="${row_idx}"]`;
+
+ if (fieldtype === "Text Editor") {
+ selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
+ } else if (fieldtype === "Code") {
+ selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
+ } else {
+ selector += ` [data-fieldname="${fieldname}"]`;
+ return cy
+ .get(selector)
+ .find(".form-control:visible, .static-area:visible")
+ .first();
+ }
+ return cy.get(selector);
+ },
);
Cypress.Commands.add("awesomebar", (text) => {
- cy.get("#navbar-search").type(`${text}{downarrow}{enter}`, { delay: 700 });
+ cy.get("#navbar-search").type(`${text}{downarrow}{enter}`, { delay: 700 });
});
Cypress.Commands.add("new_form", (doctype) => {
- let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
- cy.visit(`/app/${dt_in_route}/new`);
- cy.get("body").should(($body) => {
- const dataRoute = $body.attr("data-route");
- expect(dataRoute).to.match(new RegExp(`^Form/${doctype}/new-${dt_in_route}-`));
- });
- cy.get("body").should("have.attr", "data-ajax-state", "complete");
+ let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
+ cy.visit(`/app/${dt_in_route}/new`);
+ cy.get("body").should(($body) => {
+ const dataRoute = $body.attr("data-route");
+ expect(dataRoute).to.match(
+ new RegExp(`^Form/${doctype}/new-${dt_in_route}-`),
+ );
+ });
+ cy.get("body").should("have.attr", "data-ajax-state", "complete");
});
Cypress.Commands.add("select_form_tab", (label) => {
- cy.get(".form-tabs-list [data-toggle='tab']").contains(label).click().wait(500);
+ cy.get(".form-tabs-list [data-toggle='tab']")
+ .contains(label)
+ .click()
+ .wait(500);
});
Cypress.Commands.add("go_to_list", (doctype) => {
- let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
- cy.visit(`/app/${dt_in_route}`);
+ let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
+ cy.visit(`/app/${dt_in_route}`);
});
Cypress.Commands.add("clear_cache", () => {
- cy.window()
- .its("frappe")
- .then((frappe) => {
- frappe.ui.toolbar.clear_cache();
- });
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ frappe.ui.toolbar.clear_cache();
+ });
});
Cypress.Commands.add("dialog", (opts) => {
- return cy
- .window({ log: false })
- .its("frappe", { log: false })
- .then((frappe) => {
- Cypress.log({
- name: "dialog",
- displayName: "dialog",
- message: "frappe.ui.Dialog",
- consoleProps: () => {
- return {
- options: opts,
- dialog: d,
- };
- },
- });
-
- var d = new frappe.ui.Dialog(opts);
- d.show();
- return d;
- });
+ return cy
+ .window({ log: false })
+ .its("frappe", { log: false })
+ .then((frappe) => {
+ Cypress.log({
+ name: "dialog",
+ displayName: "dialog",
+ message: "frappe.ui.Dialog",
+ consoleProps: () => {
+ return {
+ options: opts,
+ dialog: d,
+ };
+ },
+ });
+
+ var d = new frappe.ui.Dialog(opts);
+ d.show();
+ return d;
+ });
});
Cypress.Commands.add("get_open_dialog", () => {
- return cy.get(".modal:visible").last();
+ return cy.get(".modal:visible").last();
});
Cypress.Commands.add("save", () => {
- cy.intercept("/api/method/frappe.desk.form.save.savedocs").as("save_call");
- cy.get(`.page-container:visible button[data-label="Save"]`).click({ force: true });
- cy.wait("@save_call");
+ cy.intercept("/api/method/frappe.desk.form.save.savedocs").as("save_call");
+ cy.get(`.page-container:visible button[data-label="Save"]`).click({
+ force: true,
+ });
+ cy.wait("@save_call");
});
Cypress.Commands.add("hide_dialog", () => {
- cy.wait(500);
- cy.get_open_dialog().focus().find(".btn-modal-close").click();
- cy.get(".modal:visible").should("not.exist");
+ cy.wait(500);
+ cy.get_open_dialog().focus().find(".btn-modal-close").click();
+ cy.get(".modal:visible").should("not.exist");
});
Cypress.Commands.add("clear_dialogs", () => {
- cy.window().then((win) => {
- win.$(".modal, .modal-backdrop").remove();
- });
- cy.get(".modal").should("not.exist");
+ cy.window().then((win) => {
+ win.$(".modal, .modal-backdrop").remove();
+ });
+ cy.get(".modal").should("not.exist");
});
Cypress.Commands.add("clear_datepickers", () => {
- cy.window().then((win) => {
- win.$(".datepicker").remove();
- });
- cy.get(".datepicker").should("not.exist");
+ cy.window().then((win) => {
+ win.$(".datepicker").remove();
+ });
+ cy.get(".datepicker").should("not.exist");
});
Cypress.Commands.add("insert_doc", (doctype, args, ignore_duplicate) => {
- if (!args.doctype) {
- args.doctype = doctype;
- }
- return cy
- .window()
- .its("frappe.csrf_token")
- .then((csrf_token) => {
- return cy
- .request({
- method: "POST",
- url: `/api/resource/${doctype}`,
- body: args,
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- "X-Frappe-CSRF-Token": csrf_token,
- },
- failOnStatusCode: !ignore_duplicate,
- })
- .then((res) => {
- let status_codes = [200];
- if (ignore_duplicate) {
- status_codes.push(409);
- }
-
- let message = null;
- if (ignore_duplicate && !status_codes.includes(res.status)) {
- message = `Document insert failed, response: ${JSON.stringify(
- res,
- null,
- "\t"
- )}`;
- }
- expect(res.status).to.be.oneOf(status_codes, message);
- return res.body.data;
- });
- });
+ if (!args.doctype) {
+ args.doctype = doctype;
+ }
+ return cy
+ .window()
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
+ return cy
+ .request({
+ method: "POST",
+ url: `/api/resource/${doctype}`,
+ body: args,
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
+ failOnStatusCode: !ignore_duplicate,
+ })
+ .then((res) => {
+ let status_codes = [200];
+ if (ignore_duplicate) {
+ status_codes.push(409);
+ }
+
+ let message = null;
+ if (ignore_duplicate && !status_codes.includes(res.status)) {
+ message = `Document insert failed, response: ${JSON.stringify(
+ res,
+ null,
+ "\t",
+ )}`;
+ }
+ expect(res.status).to.be.oneOf(status_codes, message);
+ return res.body.data;
+ });
+ });
});
Cypress.Commands.add("update_doc", (doctype, docname, args) => {
- return cy
- .window()
- .its("frappe.csrf_token")
- .then((csrf_token) => {
- return cy
- .request({
- method: "PUT",
- url: `/api/resource/${doctype}/${docname}`,
- body: args,
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- "X-Frappe-CSRF-Token": csrf_token,
- },
- })
- .then((res) => {
- expect(res.status).to.eq(200);
- return res.body.data;
- });
- });
+ return cy
+ .window()
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
+ return cy
+ .request({
+ method: "PUT",
+ url: `/api/resource/${doctype}/${docname}`,
+ body: args,
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
+ })
+ .then((res) => {
+ expect(res.status).to.eq(200);
+ return res.body.data;
+ });
+ });
});
Cypress.Commands.add("switch_to_user", (user) => {
- cy.call("logout");
- cy.wait(200);
- cy.login(user);
- cy.reload();
+ cy.call("logout");
+ cy.wait(200);
+ cy.login(user);
+ cy.reload();
});
Cypress.Commands.add("add_role", (user, role) => {
- cy.window()
- .its("frappe")
- .then((frappe) => {
- const session_user = frappe.session.user;
- add_remove_role("add", user, role, session_user);
- });
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ const session_user = frappe.session.user;
+ add_remove_role("add", user, role, session_user);
+ });
});
Cypress.Commands.add("remove_role", (user, role) => {
- cy.window()
- .its("frappe")
- .then((frappe) => {
- const session_user = frappe.session.user;
- add_remove_role("remove", user, role, session_user);
- });
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ const session_user = frappe.session.user;
+ add_remove_role("remove", user, role, session_user);
+ });
});
const add_remove_role = (action, user, role, session_user) => {
- if (session_user !== "Administrator") {
- cy.switch_to_user("Administrator");
- }
-
- cy.call("frappe.tests.ui_test_helpers.add_remove_role", {
- action: action,
- user: user,
- role: role,
- });
-
- if (session_user !== "Administrator") {
- cy.switch_to_user(session_user);
- }
+ if (session_user !== "Administrator") {
+ cy.switch_to_user("Administrator");
+ }
+
+ cy.call("frappe.tests.ui_test_helpers.add_remove_role", {
+ action: action,
+ user: user,
+ role: role,
+ });
+
+ if (session_user !== "Administrator") {
+ cy.switch_to_user(session_user);
+ }
};
Cypress.Commands.add("open_list_filter", () => {
- cy.get(".filter-section .filter-button").click();
- cy.wait(300);
- cy.get(".filter-popover").should("exist");
+ cy.get(".filter-section .filter-button").click();
+ cy.wait(300);
+ cy.get(".filter-popover").should("exist");
});
Cypress.Commands.add("click_custom_action_button", (name) => {
- cy.get(`.custom-actions [data-label="${encodeURIComponent(name)}"]`).click();
+ cy.get(`.custom-actions [data-label="${encodeURIComponent(name)}"]`).click();
});
Cypress.Commands.add("click_action_button", (name) => {
- cy.findByRole("button", { name: "Actions" }).click();
- cy.get(`.actions-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
+ cy.findByRole("button", { name: "Actions" }).click();
+ cy.get(
+ `.actions-btn-group [data-label="${encodeURIComponent(name)}"]`,
+ ).click();
});
Cypress.Commands.add("click_menu_button", (name) => {
- cy.get(".standard-actions .menu-btn-group > .btn").click();
- cy.get(`.menu-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
+ cy.get(".standard-actions .menu-btn-group > .btn").click();
+ cy.get(`.menu-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
});
Cypress.Commands.add("clear_filters", () => {
- let has_filter = false;
- cy.intercept({
- method: "POST",
- url: "api/method/frappe.model.utils.user_settings.save",
- }).as("filter-saved");
- cy.get(".filter-section .filter-button").click({ force: true });
- cy.wait(300);
- cy.get(".filter-popover").should("exist");
- cy.get(".filter-popover").then((popover) => {
- if (popover.find("input.input-with-feedback")[0].value != "") {
- has_filter = true;
- }
- });
- cy.get(".filter-popover").find(".clear-filters").click();
- cy.get(".filter-section .filter-button").click();
- cy.window()
- .its("cur_list")
- .then((cur_list) => {
- cur_list && cur_list.filter_area && cur_list.filter_area.clear();
- has_filter && cy.wait("@filter-saved");
- });
+ let has_filter = false;
+ cy.intercept({
+ method: "POST",
+ url: "api/method/frappe.model.utils.user_settings.save",
+ }).as("filter-saved");
+ cy.get(".filter-section .filter-button").click({ force: true });
+ cy.wait(300);
+ cy.get(".filter-popover").should("exist");
+ cy.get(".filter-popover").then((popover) => {
+ if (popover.find("input.input-with-feedback")[0].value != "") {
+ has_filter = true;
+ }
+ });
+ cy.get(".filter-popover").find(".clear-filters").click();
+ cy.get(".filter-section .filter-button").click();
+ cy.window()
+ .its("cur_list")
+ .then((cur_list) => {
+ cur_list && cur_list.filter_area && cur_list.filter_area.clear();
+ has_filter && cy.wait("@filter-saved");
+ });
});
Cypress.Commands.add("click_modal_primary_button", (btn_name) => {
- cy.wait(400);
- cy.get(".modal-footer > .standard-actions > .btn-primary")
- .contains(btn_name)
- .click({ force: true });
+ cy.wait(400);
+ cy.get(".modal-footer > .standard-actions > .btn-primary")
+ .contains(btn_name)
+ .click({ force: true });
});
Cypress.Commands.add("click_sidebar_button", (btn_name) => {
- cy.get(".list-group-by-fields .list-link > a").contains(btn_name).click({ force: true });
+ cy.get(".list-group-by-fields .list-link > a")
+ .contains(btn_name)
+ .click({ force: true });
});
Cypress.Commands.add("click_listview_row_item", (row_no) => {
- cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
- .eq(row_no)
- .click({ force: true });
+ cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
+ .eq(row_no)
+ .click({ force: true });
});
Cypress.Commands.add("click_listview_row_item_with_text", (text) => {
- cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
- .contains(text)
- .first()
- .click({ force: true });
+ cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
+ .contains(text)
+ .first()
+ .click({ force: true });
});
Cypress.Commands.add("click_filter_button", () => {
- cy.get(".filter-button").click();
+ cy.get(".filter-button").click();
});
Cypress.Commands.add("click_listview_primary_button", (btn_name) => {
- cy.get(".primary-action").contains(btn_name).click({ force: true });
+ cy.get(".primary-action").contains(btn_name).click({ force: true });
});
Cypress.Commands.add("click_doc_primary_button", (btn_name) => {
- cy.get(".primary-action").contains(btn_name).click({ force: true });
+ cy.get(".primary-action").contains(btn_name).click({ force: true });
});
Cypress.Commands.add("click_timeline_action_btn", (btn_name) => {
- cy.get(".timeline-message-box .actions .action-btn").contains(btn_name).click();
+ cy.get(".timeline-message-box .actions .action-btn")
+ .contains(btn_name)
+ .click();
});
Cypress.Commands.add("select_listview_row_checkbox", (row_no) => {
- cy.get(".frappe-list .select-like > .list-row-checkbox").eq(row_no).click();
+ cy.get(".frappe-list .select-like > .list-row-checkbox").eq(row_no).click();
});
Cypress.Commands.add("click_form_section", (section_name) => {
- cy.get(".section-head").contains(section_name).click();
+ cy.get(".section-head").contains(section_name).click();
});
const compare_document = (expected, actual) => {
- for (const prop in expected) {
- if (expected[prop] instanceof Array) {
- // recursively compare child documents.
- expected[prop].forEach((item, idx) => {
- compare_document(item, actual[prop][idx]);
- });
- } else {
- assert.equal(expected[prop], actual[prop], `${prop} should be equal.`);
- }
- }
+ for (const prop in expected) {
+ if (expected[prop] instanceof Array) {
+ // recursively compare child documents.
+ expected[prop].forEach((item, idx) => {
+ compare_document(item, actual[prop][idx]);
+ });
+ } else {
+ assert.equal(expected[prop], actual[prop], `${prop} should be equal.`);
+ }
+ }
};
Cypress.Commands.add("compare_document", (expected_document) => {
- cy.window()
- .its("cur_frm")
- .then((frm) => {
- // Don't remove this, cypress can't magically wait for events it has no control over.
- cy.wait(1000);
- compare_document(expected_document, frm.doc);
- });
+ cy.window()
+ .its("cur_frm")
+ .then((frm) => {
+ // Don't remove this, cypress can't magically wait for events it has no control over.
+ cy.wait(1000);
+ compare_document(expected_document, frm.doc);
+ });
});
diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js
index 10fb377..5bf61ea 100644
--- a/cypress/support/e2e.js
+++ b/cypress/support/e2e.js
@@ -18,7 +18,7 @@ import "./commands";
import "@cypress/code-coverage/support";
Cypress.on("uncaught:exception", (err, runnable) => {
- return false;
+ return false;
});
// Alternatively you can use CommonJS syntax:
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 18bcd5c..9d1efea 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -2,8 +2,8 @@ import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
- title: "South Africa Customisations",
- description: "Country Specific Functionality for South Africa",
+ title: "South Africa Customisations Documentation",
+ description: "South Africa Customisations Documentation",
outDir: '../csf_za/www',
assetsDir: 'assets/csf_za',
themeConfig: {
@@ -22,12 +22,13 @@ export default defineConfig({
],
socialLinks: [
+ { icon: 'whatsapp', link: 'https://wa.me/27686318877?text=Hi%2C%20I%20have%20a%20question%20on%20South%20Africa%20Customisations'},
{ icon: 'mailgun', link: 'mailto:support@starktail.com'},
- { icon: 'github', link: 'https://github.com/dvdl16/csf_za' }
+ { icon: 'github', link: 'https://github.com/Starktail/csf_za' }
],
editLink: {
- pattern: 'https://github.com/dvdl16/csf_za/edit/version-15/docs/:path'
+ pattern: 'https://github.com/Starktail/csf_za/edit/version-15/docs/:path'
}
},
// Set metaChunk to avoid having window.__VP_HASH_MAP__ in the generated HTML,
diff --git a/docs/csf_za_introduction.md b/docs/csf_za_introduction.md
index 76690ad..b032e65 100644
--- a/docs/csf_za_introduction.md
+++ b/docs/csf_za_introduction.md
@@ -17,10 +17,10 @@ This is an [Frappe](https://frappeframework.com/) custom app, intended to add So
## Installation
-Go [here](https://github.com/dvdl16/second-ride-custom) for installation.
+Go [here](https://github.com/Starktail/csf_za) for installation.
## Support
- [Starktail Website](https://starktail.com)
- [Starktail Email Support](mailto:support@starktail.com)
-
+- [Starktail WhatsApp Support](https://wa.me/27686318877?text=Hi%2C%20I%20have%20a%20question%20on%20South%20Africa%20Customisations)
diff --git a/docs/images/.gitkeep b/docs/images/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/docs/images/logo.png b/docs/images/logo.png
new file mode 100644
index 0000000..5f3a231
Binary files /dev/null and b/docs/images/logo.png differ
diff --git a/docs/images/screenshot.png b/docs/images/screenshot.png
new file mode 100644
index 0000000..a2a6f9b
Binary files /dev/null and b/docs/images/screenshot.png differ
diff --git a/package.json b/package.json
index d4e6564..737b697 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,11 @@
{
- "dependencies": {
+ "name": "csf_za",
+ "version": "0.2.7",
+ "author": "Starktail (Pty) Ltd ",
+ "main": "index.js",
+ "devDependencies": {
+ "vitepress": "^2.0.0-alpha.12",
+ "vue": "^3.5.24",
"@4tw/cypress-drag-drop": "^2.2.5",
"@cypress/code-coverage": "^3",
"@testing-library/cypress": "^10",
@@ -7,15 +13,6 @@
"cypress": "^13.1.0",
"cypress-real-events": "^1.7.6"
},
- "name": "csf_za",
- "version": "0.2.5",
- "main": "index.js",
- "author": "Dirk van der Laarse ",
- "license": "MIT",
- "devDependencies": {
- "vitepress": "^2.0.0-alpha.12",
- "vue": "^3.5.22"
- },
"scripts": {
"docs:dev": "vitepress dev docs --host",
"build": "vitepress build docs && ./prepare_help_files.sh",
diff --git a/pyproject.toml b/pyproject.toml
index 187073e..4010427 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,9 +1,9 @@
[project]
name = "csf_za"
authors = [
- { name = "Dirk van der Laarse", email = "dirk@finfoot.tech"}
+ { name = "Starktail (Pty) Ltd", email = "support@starktail.com"}
]
-description = "Country Specific Functionality for South Africa"
+description = "South Africa Customisations"
requires-python = ">=3.10"
readme = "README.md"
dynamic = ["version"]
@@ -15,22 +15,46 @@ dependencies = [
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"
+[tool.bench.frappe-dependencies]
+frappe = ">=15.0.0,<16.0.0"
+
# These dependencies are only installed when developer mode is enabled
[tool.bench.dev-dependencies]
# package_name = "~=1.1.0"
-[tool.black]
-line-length = 99
-[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 = "\t"
+[tool.ruff]
+line-length = 110
+target-version = "py310"
-[tool.bench.frappe-dependencies]
-frappe = ">=15.0.0,<16.0.0"
-erpnext = ">=15.0.0,<16.0.0"
+[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
+]
+typing-modules = ["frappe.types.DF"]
+
+[tool.ruff.format]
+quote-style = "double"
+indent-style = "tab"
+docstring-code-format = true