Merge branch 'develop' into e-commerce-refactor-develop
This commit is contained in:
commit
b5abf68b0d
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,47 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug encountered while using ERPNext
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!--
|
||||
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
|
||||
|
||||
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
|
||||
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
|
||||
- For documentation issues, refer to https://github.com/frappe/erpnext_com
|
||||
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
|
||||
the original discussion.
|
||||
3. When making a bug report, make sure you provide all required information. The easier it is for
|
||||
maintainers to reproduce, the faster it'll be fixed.
|
||||
4. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
|
||||
-->
|
||||
|
||||
## Description of the issue
|
||||
|
||||
## Context information (for bug reports)
|
||||
|
||||
**Output of `bench version`**
|
||||
```
|
||||
(paste here)
|
||||
```
|
||||
|
||||
## Steps to reproduce the issue
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Observed result
|
||||
|
||||
### Expected result
|
||||
|
||||
### Stacktrace / full error message
|
||||
|
||||
```
|
||||
(paste here)
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
OS version / distribution, `ERPNext` install method, etc.
|
89
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
89
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
name: Bug Report
|
||||
description: Report a bug encountered while using ERPNext
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
|
||||
|
||||
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
|
||||
- For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.erpnext.com)
|
||||
- For documentation issues, propose edit on [documentation site](https://docs.erpnext.com/) directly.
|
||||
2. When making a bug report, make sure you provide all required information. The easier it is for
|
||||
maintainers to reproduce, the faster it'll be fixed.
|
||||
3. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
|
||||
|
||||
- type: textarea
|
||||
id: bug-info
|
||||
attributes:
|
||||
label: Information about bug
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Please provide as much information as possible.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: module
|
||||
attributes:
|
||||
label: Module
|
||||
description: Select affected module of ERPNext.
|
||||
multiple: true
|
||||
options:
|
||||
- accounts
|
||||
- stock
|
||||
- buying
|
||||
- selling
|
||||
- ecommerce
|
||||
- manufacturing
|
||||
- HR
|
||||
- projects
|
||||
- support
|
||||
- CRM
|
||||
- assets
|
||||
- integrations
|
||||
- quality
|
||||
- regional
|
||||
- portal
|
||||
- agriculture
|
||||
- education
|
||||
- non-profit
|
||||
- other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: exact-version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Share exact version number of Frappe and ERPNext you are using.
|
||||
placeholder: |
|
||||
Frappe version -
|
||||
ERPNext Verion -
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: install-method
|
||||
attributes:
|
||||
label: Installation method
|
||||
options:
|
||||
- docker
|
||||
- easy-install
|
||||
- manual install
|
||||
- FrappeCloud
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output / Stack trace / Full Error Message.
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted.
|
||||
render: shell
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/erpnext/blob/develop/CODE_OF_CONDUCT.md)
|
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,7 +1,10 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea to improve ERPNext
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Question about using ERPNext
|
||||
about: This is not the appropriate channel
|
||||
labels: invalid
|
||||
---
|
||||
|
||||
Please post on our forums:
|
||||
|
||||
for questions about using `ERPNext`: https://discuss.erpnext.com
|
||||
|
||||
for questions about using the `Frappe Framework`: ~~https://discuss.frappe.io~~ => [stackoverflow](https://stackoverflow.com/questions/tagged/frappe) tagged under `frappe`
|
||||
|
||||
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)
|
||||
|
||||
For documentation issues, use the [ERPNext Documentation](https://erpnext.com/docs/) or [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
|
||||
|
||||
> **Posts that are not bug reports or feature requests will not be addressed on this issue tracker.**
|
29
.github/helper/install.sh
vendored
29
.github/helper/install.sh
vendored
@ -12,17 +12,30 @@ git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB
|
||||
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
||||
|
||||
mkdir ~/frappe-bench/sites/test_site
|
||||
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||
if [ "$DB" == "mariadb" ];then
|
||||
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_mariadb.json" ~/frappe-bench/sites/test_site/site_config.json
|
||||
else
|
||||
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_postgres.json" ~/frappe-bench/sites/test_site/site_config.json
|
||||
fi
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
|
||||
if [ "$DB" == "mariadb" ];then
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
||||
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
|
||||
fi
|
||||
|
||||
if [ "$DB" == "postgres" ];then
|
||||
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe" -U postgres;
|
||||
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres;
|
||||
fi
|
||||
|
||||
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
|
||||
|
@ -13,4 +13,4 @@
|
||||
"host_name": "http://test_site:8000",
|
||||
"install_apps": ["erpnext"],
|
||||
"throttle_user_limit": 100
|
||||
}
|
||||
}
|
18
.github/helper/site_config_postgres.json
vendored
Normal file
18
.github/helper/site_config_postgres.json
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_host": "127.0.0.1",
|
||||
"db_port": 5432,
|
||||
"db_name": "test_frappe",
|
||||
"db_password": "test_frappe",
|
||||
"db_type": "postgres",
|
||||
"allow_tests": true,
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
"mail_login": "test@example.com",
|
||||
"mail_password": "test",
|
||||
"admin_password": "admin",
|
||||
"root_login": "postgres",
|
||||
"root_password": "travis",
|
||||
"host_name": "http://test_site:8000",
|
||||
"install_apps": ["erpnext"],
|
||||
"throttle_user_limit": 100
|
||||
}
|
55
.github/labeler.yml
vendored
Normal file
55
.github/labeler.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
accounts:
|
||||
- 'erpnext/accounts/*'
|
||||
- 'erpnext/controllers/accounts_controller.py'
|
||||
- 'erpnext/controllers/taxes_and_totals.py'
|
||||
|
||||
stock:
|
||||
- 'erpnext/stock/*'
|
||||
- 'erpnext/controllers/stock_controller.py'
|
||||
- 'erpnext/controllers/item_variant.py'
|
||||
|
||||
assets:
|
||||
- 'erpnext/assets/*'
|
||||
|
||||
regional:
|
||||
- 'erpnext/regional/*'
|
||||
|
||||
selling:
|
||||
- 'erpnext/selling/*'
|
||||
- 'erpnext/controllers/selling_controller.py'
|
||||
|
||||
buying:
|
||||
- 'erpnext/buying/*'
|
||||
- 'erpnext/controllers/buying_controller.py'
|
||||
|
||||
support:
|
||||
- 'erpnext/support/*'
|
||||
|
||||
POS:
|
||||
- 'pos*'
|
||||
|
||||
ecommerce:
|
||||
- 'erpnext/e_commerce/*'
|
||||
|
||||
maintenance:
|
||||
- 'erpnext/maintenance/*'
|
||||
|
||||
manufacturing:
|
||||
- 'erpnext/manufacturing/*'
|
||||
|
||||
crm:
|
||||
- 'erpnext/crm/*'
|
||||
|
||||
HR:
|
||||
- 'erpnext/hr/*'
|
||||
|
||||
payroll:
|
||||
- 'erpnext/payroll*'
|
||||
|
||||
projects:
|
||||
- 'erpnext/projects/*'
|
||||
|
||||
# Any python files modifed but no test files modified
|
||||
needs-tests:
|
||||
- any: ['erpnext/**/*.py']
|
||||
all: ['!erpnext/**/test*.py']
|
56
.github/stale.yml
vendored
56
.github/stale.yml
vendored
@ -1,34 +1,36 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 15
|
||||
|
||||
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 3
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- hotfix
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: inactive
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed within a week if no further activity occurs, but it
|
||||
only takes a comment to keep a contribution alive :) Also, even if it is closed,
|
||||
you can always reopen the PR when you're ready. Thank you for contributing.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
limitPerRun: 10
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: pulls
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: true
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
pulls:
|
||||
daysUntilStale: 15
|
||||
daysUntilClose: 3
|
||||
exemptLabels:
|
||||
- hotfix
|
||||
markComment: >
|
||||
This pull request has been automatically marked as inactive because it has
|
||||
not had recent activity. It will be closed within 3 days if no further
|
||||
activity occurs, but it only takes a comment to keep a contribution alive
|
||||
:) Also, even if it is closed, you can always reopen the PR when you're
|
||||
ready. Thank you for contributing.
|
||||
|
||||
issues:
|
||||
daysUntilStale: 60
|
||||
daysUntilClose: 7
|
||||
exemptLabels:
|
||||
- valid
|
||||
- to-validate
|
||||
markComment: >
|
||||
This issue has been automatically marked as inactive because it has not had
|
||||
recent activity and it wasn't validated by maintainer team. It will be
|
||||
closed within a week if no further activity occurs.
|
||||
|
2
.github/workflows/docs-checker.yml
vendored
2
.github/workflows/docs-checker.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
- name: 'Setup Environment'
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
python-version: 3.8
|
||||
|
||||
- name: 'Clone repo'
|
||||
uses: actions/checkout@v2
|
||||
|
12
.github/workflows/labeller.yml
vendored
Normal file
12
.github/workflows/labeller.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
5
.github/workflows/patch.yml
vendored
5
.github/workflows/patch.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
@ -80,6 +80,9 @@ jobs:
|
||||
|
||||
- name: Install
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
DB: mariadb
|
||||
TYPE: server
|
||||
|
||||
- name: Run Patch Tests
|
||||
run: |
|
||||
|
@ -1,10 +1,11 @@
|
||||
name: Server
|
||||
name: Server (Mariadb)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.js'
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
@ -13,7 +14,7 @@ on:
|
||||
- '**.md'
|
||||
|
||||
concurrency:
|
||||
group: server-develop-${{ github.event.number }}
|
||||
group: server-mariadb-develop-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
@ -45,7 +46,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
@ -92,6 +93,7 @@ jobs:
|
||||
- name: Install
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
DB: mariadb
|
||||
TYPE: server
|
||||
|
||||
- name: Run Tests
|
105
.github/workflows/server-tests-postgres.yml
vendored
Normal file
105
.github/workflows/server-tests-postgres.yml
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
name: Server (Postgres)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.js'
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
types: [opened, labelled, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
group: server-postgres-develop-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'postgres') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
container: [1, 2, 3]
|
||||
|
||||
name: Python Unit Tests
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13.3
|
||||
env:
|
||||
POSTGRES_PASSWORD: travis
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
|
||||
- name: Install
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
DB: postgres
|
||||
TYPE: server
|
||||
|
||||
- name: Run Tests
|
||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
|
||||
env:
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
2
.github/workflows/ui-tests.yml
vendored
2
.github/workflows/ui-tests.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
|
@ -23,13 +23,13 @@ erpnext/stock/ @marination @rohitwaghchaure @ankush
|
||||
|
||||
erpnext/crm/ @ruchamahabal @pateljannat
|
||||
erpnext/education/ @ruchamahabal @pateljannat
|
||||
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
|
||||
erpnext/hr/ @ruchamahabal @pateljannat
|
||||
erpnext/non_profit/ @ruchamahabal
|
||||
erpnext/payroll @ruchamahabal @pateljannat
|
||||
erpnext/projects/ @ruchamahabal @pateljannat
|
||||
|
||||
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush
|
||||
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination @ankush
|
||||
erpnext/public/ @nextchamp-saqib @marination
|
||||
|
||||
.github/ @surajshetty3416 @ankush
|
||||
.github/ @ankush
|
||||
requirements.txt @gavindsouza
|
||||
|
10
codecov.yml
10
codecov.yml
@ -8,6 +8,16 @@ coverage:
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
|
||||
patch:
|
||||
default:
|
||||
target: 85%
|
||||
threshold: 0%
|
||||
base: auto
|
||||
branches:
|
||||
- develop
|
||||
if_ci_failed: ignore
|
||||
only_pulls: true
|
||||
|
||||
comment:
|
||||
layout: "diff, files"
|
||||
require_changes: true
|
||||
|
1
dev-requirements.txt
Normal file
1
dev-requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
hypothesis~=6.31.0
|
@ -4,7 +4,7 @@ import frappe
|
||||
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = '13.9.0'
|
||||
__version__ = '14.0.0-dev'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
@ -55,9 +55,9 @@ def set_perpetual_inventory(enable=1, company=None):
|
||||
company.enable_perpetual_inventory = enable
|
||||
company.save()
|
||||
|
||||
def encode_company_abbr(name, company):
|
||||
def encode_company_abbr(name, company=None, abbr=None):
|
||||
'''Returns name encoded with company abbreviation'''
|
||||
company_abbr = frappe.get_cached_value('Company', company, "abbr")
|
||||
company_abbr = abbr or frappe.get_cached_value('Company', company, "abbr")
|
||||
parts = name.rsplit(" - ", 1)
|
||||
|
||||
if parts[-1].lower() != company_abbr.lower():
|
||||
|
@ -374,12 +374,13 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
frappe.db.commit()
|
||||
except Exception as e:
|
||||
if frappe.flags.in_test:
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
|
||||
raise e
|
||||
else:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(message=traceback)
|
||||
|
||||
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
|
||||
def send_mail(deferred_process):
|
||||
@ -446,10 +447,12 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
||||
|
||||
if submit:
|
||||
journal_entry.submit()
|
||||
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(message=traceback)
|
||||
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
|
||||
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
|
||||
|
@ -43,12 +43,12 @@ frappe.ui.form.on('Account', {
|
||||
frm.trigger('add_toolbar_buttons');
|
||||
}
|
||||
if (frm.has_perm('write')) {
|
||||
frm.add_custom_button(__('Update Account Name / Number'), function () {
|
||||
frm.trigger("update_account_number");
|
||||
});
|
||||
frm.add_custom_button(__('Merge Account'), function () {
|
||||
frm.trigger("merge_account");
|
||||
});
|
||||
}, __('Actions'));
|
||||
frm.add_custom_button(__('Update Account Name / Number'), function () {
|
||||
frm.trigger("update_account_number");
|
||||
}, __('Actions'));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -59,11 +59,12 @@ frappe.ui.form.on('Account', {
|
||||
}
|
||||
},
|
||||
add_toolbar_buttons: function(frm) {
|
||||
frm.add_custom_button(__('Chart of Accounts'),
|
||||
function () { frappe.set_route("Tree", "Account"); });
|
||||
frm.add_custom_button(__('Chart of Accounts'), () => {
|
||||
frappe.set_route("Tree", "Account");
|
||||
}, __('View'));
|
||||
|
||||
if (frm.doc.is_group == 1) {
|
||||
frm.add_custom_button(__('Group to Non-Group'), function () {
|
||||
frm.add_custom_button(__('Convert to Non-Group'), function () {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'convert_group_to_ledger',
|
||||
@ -71,10 +72,11 @@ frappe.ui.form.on('Account', {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, __('Actions'));
|
||||
|
||||
} else if (cint(frm.doc.is_group) == 0
|
||||
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
|
||||
frm.add_custom_button(__('Ledger'), function () {
|
||||
frm.add_custom_button(__('General Ledger'), function () {
|
||||
frappe.route_options = {
|
||||
"account": frm.doc.name,
|
||||
"from_date": frappe.sys_defaults.year_start_date,
|
||||
@ -82,9 +84,9 @@ frappe.ui.form.on('Account', {
|
||||
"company": frm.doc.company
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
});
|
||||
}, __('View'));
|
||||
|
||||
frm.add_custom_button(__('Non-Group to Group'), function () {
|
||||
frm.add_custom_button(__('Convert to Group'), function () {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'convert_ledger_to_group',
|
||||
@ -92,7 +94,7 @@ frappe.ui.form.on('Account', {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, __('Actions'));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -78,6 +78,7 @@ frappe.treeview_settings["Account"] = {
|
||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||
|
||||
if (account.balance!==undefined) {
|
||||
node.parent && node.parent.find('.balance-area').remove();
|
||||
$('<span class="balance-area pull-right">'
|
||||
+ (account.balance_in_account_currency ?
|
||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||
@ -175,7 +176,7 @@ frappe.treeview_settings["Account"] = {
|
||||
&& node.expandable && !node.hide_add;
|
||||
},
|
||||
click: function() {
|
||||
var me = frappe.treeview_settings['Account'].treeview;
|
||||
var me = frappe.views.trees['Account'];
|
||||
me.new_node();
|
||||
},
|
||||
btnClass: "hidden-xs"
|
||||
|
@ -1,29 +0,0 @@
|
||||
QUnit.module('accounts');
|
||||
|
||||
QUnit.test("test account", function(assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Tree', 'Account'),
|
||||
() => frappe.timeout(3),
|
||||
() => frappe.click_button('Expand All'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Debtors'),
|
||||
() => frappe.click_button('Edit'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.root_type=='Asset');
|
||||
assert.ok(cur_frm.doc.report_type=='Balance Sheet');
|
||||
assert.ok(cur_frm.doc.account_type=='Receivable');
|
||||
},
|
||||
() => frappe.click_button('Ledger'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
// check if general ledger report shown
|
||||
assert.deepEqual(frappe.get_route(), ['query-report', 'General Ledger']);
|
||||
window.history.back();
|
||||
return frappe.timeout(1);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,69 +0,0 @@
|
||||
QUnit.module('accounts');
|
||||
|
||||
QUnit.test("test account with number", function(assert) {
|
||||
assert.expect(7);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Tree', 'Account'),
|
||||
() => frappe.click_link('Income'),
|
||||
() => frappe.click_button('Add Child'),
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
cur_dialog.fields_dict.account_name.$input.val("Test Income");
|
||||
cur_dialog.fields_dict.account_number.$input.val("4010");
|
||||
},
|
||||
() => frappe.click_button('Create New'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok($('a:contains("4010 - Test Income"):visible').length!=0, "Account created with number");
|
||||
},
|
||||
() => frappe.click_link('4010 - Test Income'),
|
||||
() => frappe.click_button('Edit'),
|
||||
() => frappe.timeout(.5),
|
||||
() => frappe.click_button('Update Account Number'),
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
cur_dialog.fields_dict.account_number.$input.val("4020");
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => cur_dialog.primary_action(),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.refresh_fields(),
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
var abbr = frappe.get_abbr(frappe.defaults.get_default("Company"));
|
||||
var new_account = "4020 - Test Income - " + abbr;
|
||||
assert.ok(cur_frm.doc.name==new_account, "Account renamed");
|
||||
assert.ok(cur_frm.doc.account_name=="Test Income", "account name remained same");
|
||||
assert.ok(cur_frm.doc.account_number=="4020", "Account number updated to 4020");
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Menu'),
|
||||
() => frappe.click_link('Rename'),
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
cur_dialog.fields_dict.new_name.$input.val("4030 - Test Income");
|
||||
},
|
||||
() => frappe.timeout(.5),
|
||||
() => frappe.click_button("Rename"),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.account_name=="Test Income", "account name remained same");
|
||||
assert.ok(cur_frm.doc.account_number=="4030", "Account number updated to 4030");
|
||||
},
|
||||
() => frappe.timeout(.5),
|
||||
() => frappe.click_button('Chart of Accounts'),
|
||||
() => frappe.timeout(.5),
|
||||
() => frappe.click_button('Menu'),
|
||||
() => frappe.click_link('Refresh'),
|
||||
() => frappe.click_button('Expand All'),
|
||||
() => frappe.click_link('4030 - Test Income'),
|
||||
() => frappe.click_button('Delete'),
|
||||
() => frappe.click_button('Yes'),
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
assert.ok($('a:contains("4030 - Test Account"):visible').length==0, "Account deleted");
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,46 +0,0 @@
|
||||
QUnit.module('accounts');
|
||||
QUnit.test("test account", assert => {
|
||||
assert.expect(3);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Tree', 'Account'),
|
||||
() => frappe.click_button('Expand All'),
|
||||
() => frappe.click_link('Duties and Taxes - '+ frappe.get_abbr(frappe.defaults.get_default("Company"))),
|
||||
() => {
|
||||
if($('a:contains("CGST"):visible').length == 0){
|
||||
return frappe.map_tax.make('CGST', 9);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
if($('a:contains("SGST"):visible').length == 0){
|
||||
return frappe.map_tax.make('SGST', 9);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
if($('a:contains("IGST"):visible').length == 0){
|
||||
return frappe.map_tax.make('IGST', 18);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
assert.ok($('a:contains("CGST"):visible').length!=0, "CGST Checked");
|
||||
assert.ok($('a:contains("SGST"):visible').length!=0, "SGST Checked");
|
||||
assert.ok($('a:contains("IGST"):visible').length!=0, "IGST Checked");
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
frappe.map_tax = {
|
||||
make:function(text,rate){
|
||||
return frappe.run_serially([
|
||||
() => frappe.click_button('Add Child'),
|
||||
() => frappe.timeout(0.2),
|
||||
() => cur_dialog.set_value('account_name',text),
|
||||
() => cur_dialog.set_value('account_type','Tax'),
|
||||
() => cur_dialog.set_value('tax_rate',rate),
|
||||
() => cur_dialog.set_value('account_currency','INR'),
|
||||
() => frappe.click_button('Create New'),
|
||||
]);
|
||||
}
|
||||
};
|
@ -10,6 +10,8 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
from erpnext.stock.utils import check_pending_reposting
|
||||
|
||||
|
||||
class AccountsSettings(Document):
|
||||
def on_update(self):
|
||||
@ -19,9 +21,13 @@ class AccountsSettings(Document):
|
||||
frappe.db.set_default("add_taxes_from_item_tax_template",
|
||||
self.get("add_taxes_from_item_tax_template", 0))
|
||||
|
||||
frappe.db.set_default("enable_common_party_accounting",
|
||||
self.get("enable_common_party_accounting", 0))
|
||||
|
||||
self.validate_stale_days()
|
||||
self.enable_payment_schedule_in_print()
|
||||
self.toggle_discount_accounting_fields()
|
||||
self.validate_pending_reposts()
|
||||
|
||||
def validate_stale_days(self):
|
||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||
@ -53,3 +59,8 @@ class AccountsSettings(Document):
|
||||
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
||||
|
||||
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||
|
||||
|
||||
def validate_pending_reposts(self):
|
||||
if self.acc_frozen_upto:
|
||||
check_pending_reposting(self.acc_frozen_upto)
|
||||
|
@ -1,35 +0,0 @@
|
||||
QUnit.module('accounts');
|
||||
|
||||
QUnit.test("test: Accounts Settings doesn't allow negatives", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
assert.expect(2);
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Form', 'Accounts Settings', 'Accounts Settings'),
|
||||
() => frappe.timeout(2),
|
||||
() => unchecked_if_checked(cur_frm, 'Allow Stale Exchange Rates', frappe.click_check),
|
||||
() => cur_frm.set_value('stale_days', 0),
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.ok(cur_dialog);
|
||||
},
|
||||
() => frappe.click_button('Close'),
|
||||
() => cur_frm.set_value('stale_days', -1),
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.ok(cur_dialog);
|
||||
},
|
||||
() => frappe.click_button('Close'),
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
const unchecked_if_checked = function(frm, field_name, fn){
|
||||
if (frm.doc.allow_stale) {
|
||||
return fn(field_name);
|
||||
}
|
||||
};
|
56
erpnext/accounts/doctype/advance_tax/advance_tax.json
Normal file
56
erpnext/accounts/doctype/advance_tax/advance_tax.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-11-25 10:24:39.836195",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"reference_detail",
|
||||
"account_head",
|
||||
"allocated_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Name",
|
||||
"options": "reference_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_detail",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Detail"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_head",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Head",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "party_account_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-25 10:27:51.712286",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Tax",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
9
erpnext/accounts/doctype/advance_tax/advance_tax.py
Normal file
9
erpnext/accounts/doctype/advance_tax/advance_tax.py
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AdvanceTax(Document):
|
||||
pass
|
@ -25,8 +25,7 @@
|
||||
"allocated_amount",
|
||||
"column_break_13",
|
||||
"base_tax_amount",
|
||||
"base_total",
|
||||
"base_allocated_amount"
|
||||
"base_total"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -168,12 +167,6 @@
|
||||
"label": "Allocated Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
@ -186,7 +179,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-09 11:46:58.373170",
|
||||
"modified": "2021-11-25 11:10:10.945027",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Taxes and Charges",
|
||||
|
@ -7,7 +7,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
frm.set_query("bank_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: ["in", frm.doc.company],
|
||||
company: frm.doc.company,
|
||||
'is_company_account': 1
|
||||
},
|
||||
};
|
||||
|
@ -218,6 +218,8 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
# updated clear date of all the vouchers based on the bank transaction
|
||||
vouchers = json.loads(vouchers)
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
company_account = frappe.db.get_value('Bank Account', transaction.bank_account, 'account')
|
||||
|
||||
if transaction.unallocated_amount == 0:
|
||||
frappe.throw(_("This bank transaction is already fully reconciled"))
|
||||
total_amount = 0
|
||||
@ -226,7 +228,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
total_amount += get_paid_amount(frappe._dict({
|
||||
'payment_document': voucher['payment_doctype'],
|
||||
'payment_entry': voucher['payment_name'],
|
||||
}), transaction.currency)
|
||||
}), transaction.currency, company_account)
|
||||
|
||||
if total_amount > transaction.unallocated_amount:
|
||||
frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction"))
|
||||
@ -261,7 +263,7 @@ def get_linked_payments(bank_transaction_name, document_types = None):
|
||||
return matching
|
||||
|
||||
def check_matching(bank_account, company, transaction, document_types):
|
||||
# combine all types of vocuhers
|
||||
# combine all types of vouchers
|
||||
subquery = get_queries(bank_account, company, transaction, document_types)
|
||||
filters = {
|
||||
"amount": transaction.unallocated_amount,
|
||||
@ -343,13 +345,11 @@ def get_pe_matching_query(amount_condition, account_from_to, transaction):
|
||||
def get_je_matching_query(amount_condition, transaction):
|
||||
# get matching journal entry query
|
||||
|
||||
# We have mapping at the bank level
|
||||
# So one bank could have both types of bank accounts like asset and liability
|
||||
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
|
||||
company_account = frappe.get_value("Bank Account", transaction.bank_account, "account")
|
||||
root_type = frappe.get_value("Account", company_account, "root_type")
|
||||
|
||||
if root_type == "Liability":
|
||||
cr_or_dr = "debit" if transaction.withdrawal > 0 else "credit"
|
||||
else:
|
||||
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
|
||||
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
|
||||
|
||||
return f"""
|
||||
|
||||
@ -434,7 +434,7 @@ def get_pi_matching_query(amount_condition):
|
||||
|
||||
def get_ec_matching_query(bank_account, company, amount_condition):
|
||||
# get matching Expense Claim query
|
||||
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
|
||||
mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
|
||||
filters={"default_account": bank_account}, fields=["parent"])]
|
||||
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
|
||||
company_currency = get_company_currency(company)
|
||||
|
@ -239,7 +239,8 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
"withdrawal",
|
||||
"description",
|
||||
"reference_number",
|
||||
"bank_account"
|
||||
"bank_account",
|
||||
"currency"
|
||||
],
|
||||
},
|
||||
});
|
||||
|
@ -102,7 +102,7 @@ def get_total_allocated_amount(payment_entry):
|
||||
AND
|
||||
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
|
||||
|
||||
def get_paid_amount(payment_entry, currency):
|
||||
def get_paid_amount(payment_entry, currency, bank_account):
|
||||
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
|
||||
|
||||
paid_amount_field = "paid_amount"
|
||||
@ -115,7 +115,7 @@ def get_paid_amount(payment_entry, currency):
|
||||
payment_entry.payment_entry, paid_amount_field)
|
||||
|
||||
elif payment_entry.payment_document == "Journal Entry":
|
||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")
|
||||
return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account}, "sum(credit_in_account_currency)")
|
||||
|
||||
elif payment_entry.payment_document == "Expense Claim":
|
||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
|
||||
|
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Currency Exchange Settings', {
|
||||
service_provider: function(frm) {
|
||||
if (frm.doc.service_provider == "exchangerate.host") {
|
||||
let result = ['result'];
|
||||
let params = {
|
||||
date: '{transaction_date}',
|
||||
from: '{from_currency}',
|
||||
to: '{to_currency}'
|
||||
};
|
||||
add_param(frm, "https://api.exchangerate.host/convert", params, result);
|
||||
} else if (frm.doc.service_provider == "frankfurter.app") {
|
||||
let result = ['rates', '{to_currency}'];
|
||||
let params = {
|
||||
base: '{from_currency}',
|
||||
symbols: '{to_currency}'
|
||||
};
|
||||
add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function add_param(frm, api, params, result) {
|
||||
var row;
|
||||
frm.clear_table("req_params");
|
||||
frm.clear_table("result_key");
|
||||
|
||||
frm.doc.api_endpoint = api;
|
||||
|
||||
$.each(params, function(key, value) {
|
||||
row = frm.add_child("req_params");
|
||||
row.key = key;
|
||||
row.value = value;
|
||||
});
|
||||
|
||||
$.each(result, function(key, value) {
|
||||
row = frm.add_child("result_key");
|
||||
row.key = value;
|
||||
});
|
||||
|
||||
frm.refresh_fields();
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2022-01-10 13:03:26.237081",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"api_details_section",
|
||||
"service_provider",
|
||||
"api_endpoint",
|
||||
"url",
|
||||
"column_break_3",
|
||||
"help",
|
||||
"section_break_2",
|
||||
"req_params",
|
||||
"column_break_4",
|
||||
"result_key"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "api_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "API Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "api_endpoint",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "API Endpoint",
|
||||
"read_only_depends_on": "eval: doc.service_provider != \"Custom\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Example URL",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Help",
|
||||
"options": "<h3>Currency Exchange Settings Help</h3>\n<p>There are 3 variables that could be used within the endpoint, result key and in values of the parameter.</p>\n<p>Exchange rate between {from_currency} and {to_currency} on {transaction_date} is fetched by the API.</p>\n<p>Example: If your endpoint is exchange.com/2021-08-01, then, you will have to input exchange.com/{transaction_date}</p>"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Request Parameters"
|
||||
},
|
||||
{
|
||||
"fieldname": "req_params",
|
||||
"fieldtype": "Table",
|
||||
"label": "Parameters",
|
||||
"options": "Currency Exchange Settings Details",
|
||||
"read_only_depends_on": "eval: doc.service_provider != \"Custom\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "result_key",
|
||||
"fieldtype": "Table",
|
||||
"label": "Result Key",
|
||||
"options": "Currency Exchange Settings Result",
|
||||
"read_only_depends_on": "eval: doc.service_provider != \"Custom\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "service_provider",
|
||||
"fieldtype": "Select",
|
||||
"label": "Service Provider",
|
||||
"options": "frankfurter.app\nexchangerate.host\nCustom",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-10 15:51:14.521174",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
import requests
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import nowdate
|
||||
|
||||
|
||||
class CurrencyExchangeSettings(Document):
|
||||
def validate(self):
|
||||
self.set_parameters_and_result()
|
||||
response, value = self.validate_parameters()
|
||||
self.validate_result(response, value)
|
||||
|
||||
def set_parameters_and_result(self):
|
||||
if self.service_provider == 'exchangerate.host':
|
||||
self.set('result_key', [])
|
||||
self.set('req_params', [])
|
||||
|
||||
self.api_endpoint = "https://api.exchangerate.host/convert"
|
||||
self.append('result_key', {'key': 'result'})
|
||||
self.append('req_params', {'key': 'date', 'value': '{transaction_date}'})
|
||||
self.append('req_params', {'key': 'from', 'value': '{from_currency}'})
|
||||
self.append('req_params', {'key': 'to', 'value': '{to_currency}'})
|
||||
elif self.service_provider == 'frankfurter.app':
|
||||
self.set('result_key', [])
|
||||
self.set('req_params', [])
|
||||
|
||||
self.api_endpoint = "https://frankfurter.app/{transaction_date}"
|
||||
self.append('result_key', {'key': 'rates'})
|
||||
self.append('result_key', {'key': '{to_currency}'})
|
||||
self.append('req_params', {'key': 'base', 'value': '{from_currency}'})
|
||||
self.append('req_params', {'key': 'symbols', 'value': '{to_currency}'})
|
||||
|
||||
def validate_parameters(self):
|
||||
if frappe.flags.in_test:
|
||||
return None, None
|
||||
|
||||
params = {}
|
||||
for row in self.req_params:
|
||||
params[row.key] = row.value.format(
|
||||
transaction_date=nowdate(),
|
||||
to_currency='INR',
|
||||
from_currency='USD'
|
||||
)
|
||||
|
||||
api_url = self.api_endpoint.format(
|
||||
transaction_date=nowdate(),
|
||||
to_currency='INR',
|
||||
from_currency='USD'
|
||||
)
|
||||
|
||||
try:
|
||||
response = requests.get(api_url, params=params)
|
||||
except requests.exceptions.RequestException as e:
|
||||
frappe.throw("Error: " + str(e))
|
||||
|
||||
response.raise_for_status()
|
||||
value = response.json()
|
||||
|
||||
return response, value
|
||||
|
||||
def validate_result(self, response, value):
|
||||
if frappe.flags.in_test:
|
||||
return
|
||||
|
||||
try:
|
||||
for key in self.result_key:
|
||||
value = value[str(key.key).format(
|
||||
transaction_date=nowdate(),
|
||||
to_currency='INR',
|
||||
from_currency='USD'
|
||||
)]
|
||||
except Exception:
|
||||
frappe.throw("Invalid result key. Response: " + response.text)
|
||||
if not isinstance(value, (int, float)):
|
||||
frappe.throw(_("Returned exchange rate is neither integer not float."))
|
||||
|
||||
self.url = response.url
|
||||
frappe.msgprint("Exchange rate of USD to INR is " + str(value))
|
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
|
||||
class TestCurrencyExchangeSettings(unittest.TestCase):
|
||||
pass
|
@ -0,0 +1,39 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-09-02 14:54:49.033512",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "key",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Key",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Value",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-03 19:14:55.889037",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings Details",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CurrencyExchangeSettingsDetails(Document):
|
||||
pass
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-09-03 13:17:22.088259",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"key"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "key",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Key",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-03 19:14:40.054245",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings Result",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CurrencyExchangeSettingsResult(Document):
|
||||
pass
|
@ -5,10 +5,10 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import now_datetime
|
||||
|
||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
|
||||
|
||||
test_records = frappe.get_test_records('Fiscal Year')
|
||||
test_ignore = ["Company"]
|
||||
|
||||
class TestFiscalYear(unittest.TestCase):
|
||||
@ -25,3 +25,29 @@ class TestFiscalYear(unittest.TestCase):
|
||||
})
|
||||
|
||||
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
|
||||
|
||||
|
||||
def test_record_generator():
|
||||
test_records = [
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Short Fiscal Year 2011",
|
||||
"is_short_year": 1,
|
||||
"year_end_date": "2011-04-01",
|
||||
"year_start_date": "2011-12-31"
|
||||
}
|
||||
]
|
||||
|
||||
start = 2012
|
||||
end = now_datetime().year + 5
|
||||
for year in range(start, end):
|
||||
test_records.append({
|
||||
"doctype": "Fiscal Year",
|
||||
"year": f"_Test Fiscal Year {year}",
|
||||
"year_start_date": f"{year}-01-01",
|
||||
"year_end_date": f"{year}-12-31"
|
||||
})
|
||||
|
||||
return test_records
|
||||
|
||||
test_records = test_record_generator()
|
||||
|
@ -1,69 +0,0 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Short Fiscal Year 2011",
|
||||
"is_short_year": 1,
|
||||
"year_end_date": "2011-04-01",
|
||||
"year_start_date": "2011-12-31"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2012",
|
||||
"year_end_date": "2012-12-31",
|
||||
"year_start_date": "2012-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2013",
|
||||
"year_end_date": "2013-12-31",
|
||||
"year_start_date": "2013-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2014",
|
||||
"year_end_date": "2014-12-31",
|
||||
"year_start_date": "2014-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2015",
|
||||
"year_end_date": "2015-12-31",
|
||||
"year_start_date": "2015-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2016",
|
||||
"year_end_date": "2016-12-31",
|
||||
"year_start_date": "2016-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2017",
|
||||
"year_end_date": "2017-12-31",
|
||||
"year_start_date": "2017-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2018",
|
||||
"year_end_date": "2018-12-31",
|
||||
"year_start_date": "2018-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2019",
|
||||
"year_end_date": "2019-12-31",
|
||||
"year_start_date": "2019-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2020",
|
||||
"year_end_date": "2020-12-31",
|
||||
"year_start_date": "2020-01-01"
|
||||
},
|
||||
{
|
||||
"doctype": "Fiscal Year",
|
||||
"year": "_Test Fiscal Year 2021",
|
||||
"year_end_date": "2021-12-31",
|
||||
"year_start_date": "2021-01-01"
|
||||
}
|
||||
]
|
@ -31,7 +31,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
if(frm.doc.docstatus==1) {
|
||||
frm.add_custom_button(__('Reverse Journal Entry'), function() {
|
||||
return erpnext.journal_entry.reverse_journal_entry(frm);
|
||||
}, __('Make'));
|
||||
}, __('Actions'));
|
||||
}
|
||||
|
||||
if (frm.doc.__islocal) {
|
||||
|
@ -13,6 +13,7 @@
|
||||
"voucher_type",
|
||||
"naming_series",
|
||||
"finance_book",
|
||||
"reversal_of",
|
||||
"tax_withholding_category",
|
||||
"column_break1",
|
||||
"from_template",
|
||||
@ -515,13 +516,21 @@
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount "
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus",
|
||||
"fieldname": "reversal_of",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reversal Of",
|
||||
"options": "Journal Entry",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-09 15:31:14.484029",
|
||||
"modified": "2022-01-04 13:39:36.485954",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
@ -1157,9 +1157,8 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def update_accounts(source, target, source_parent):
|
||||
target.reference_type = "Journal Entry"
|
||||
target.reference_name = source_parent.name
|
||||
def post_process(source, target):
|
||||
target.reversal_of = source.name
|
||||
|
||||
doclist = get_mapped_doc("Journal Entry", source_name, {
|
||||
"Journal Entry": {
|
||||
@ -1177,9 +1176,8 @@ def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
"debit": "credit",
|
||||
"credit_in_account_currency": "debit_in_account_currency",
|
||||
"credit": "debit",
|
||||
},
|
||||
"postprocess": update_accounts,
|
||||
}
|
||||
},
|
||||
}, target_doc)
|
||||
}, target_doc, post_process)
|
||||
|
||||
return doclist
|
||||
|
@ -1,39 +0,0 @@
|
||||
QUnit.module('Journal Entry');
|
||||
|
||||
QUnit.test("test journal entry", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Journal Entry', [
|
||||
{posting_date:frappe.datetime.add_days(frappe.datetime.nowdate(), 0)},
|
||||
{accounts: [
|
||||
[
|
||||
{'account':'Debtors - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
|
||||
{'party_type':'Customer'},
|
||||
{'party':'Test Customer 1'},
|
||||
{'credit_in_account_currency':1000},
|
||||
{'is_advance':'Yes'},
|
||||
],
|
||||
[
|
||||
{'account':'HDFC - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
|
||||
{'debit_in_account_currency':1000},
|
||||
]
|
||||
]},
|
||||
{cheque_no:1234},
|
||||
{cheque_date: frappe.datetime.add_days(frappe.datetime.nowdate(), -1)},
|
||||
{user_remark: 'Test'},
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.total_debit==1000, "total debit correct");
|
||||
assert.ok(cur_frm.doc.total_credit==1000, "total credit correct");
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
128
erpnext/accounts/doctype/ledger_merge/ledger_merge.js
Normal file
128
erpnext/accounts/doctype/ledger_merge/ledger_merge.js
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Ledger Merge', {
|
||||
setup: function(frm) {
|
||||
frappe.realtime.on('ledger_merge_refresh', ({ ledger_merge }) => {
|
||||
if (ledger_merge !== frm.doc.name) return;
|
||||
frappe.model.clear_doc(frm.doc.doctype, frm.doc.name);
|
||||
frappe.model.with_doc(frm.doc.doctype, frm.doc.name).then(() => {
|
||||
frm.refresh();
|
||||
});
|
||||
});
|
||||
|
||||
frappe.realtime.on('ledger_merge_progress', data => {
|
||||
if (data.ledger_merge !== frm.doc.name) return;
|
||||
let message = __('Merging {0} of {1}', [data.current, data.total]);
|
||||
let percent = Math.floor((data.current * 100) / data.total);
|
||||
frm.dashboard.show_progress(__('Merge Progress'), percent, message);
|
||||
frm.page.set_indicator(__('In Progress'), 'orange');
|
||||
});
|
||||
|
||||
frm.set_query("account", function(doc) {
|
||||
if (!doc.company) frappe.throw(__('Please set Company'));
|
||||
if (!doc.root_type) frappe.throw(__('Please set Root Type'));
|
||||
return {
|
||||
filters: {
|
||||
root_type: doc.root_type,
|
||||
company: doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('account', 'merge_accounts', function(doc) {
|
||||
if (!doc.company) frappe.throw(__('Please set Company'));
|
||||
if (!doc.root_type) frappe.throw(__('Please set Root Type'));
|
||||
if (!doc.account) frappe.throw(__('Please set Account'));
|
||||
let acc = [doc.account];
|
||||
frm.doc.merge_accounts.forEach((row) => {
|
||||
acc.push(row.account);
|
||||
});
|
||||
return {
|
||||
filters: {
|
||||
is_group: doc.is_group,
|
||||
root_type: doc.root_type,
|
||||
name: ["not in", acc],
|
||||
company: doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.page.hide_icon_group();
|
||||
frm.trigger('set_merge_status');
|
||||
frm.trigger('update_primary_action');
|
||||
},
|
||||
|
||||
after_save: function(frm) {
|
||||
setTimeout(() => {
|
||||
frm.trigger('update_primary_action');
|
||||
}, 500);
|
||||
},
|
||||
|
||||
update_primary_action: function(frm) {
|
||||
if (frm.is_dirty()) {
|
||||
frm.enable_save();
|
||||
return;
|
||||
}
|
||||
frm.disable_save();
|
||||
if (frm.doc.status !== 'Success') {
|
||||
if (!frm.is_new()) {
|
||||
let label = frm.doc.status === 'Pending' ? __('Start Merge') : __('Retry');
|
||||
frm.page.set_primary_action(label, () => frm.events.start_merge(frm));
|
||||
} else {
|
||||
frm.page.set_primary_action(__('Save'), () => frm.save());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
start_merge: function(frm) {
|
||||
frm.call({
|
||||
method: 'form_start_merge',
|
||||
args: { docname: frm.doc.name },
|
||||
btn: frm.page.btn_primary
|
||||
}).then(r => {
|
||||
if (r.message === true) {
|
||||
frm.disable_save();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_merge_status: function(frm) {
|
||||
if (frm.doc.status == "Pending") return;
|
||||
let successful_records = 0;
|
||||
frm.doc.merge_accounts.forEach((row) => {
|
||||
if (row.merged) successful_records += 1;
|
||||
});
|
||||
let message_args = [successful_records, frm.doc.merge_accounts.length];
|
||||
frm.dashboard.set_headline(__('Successfully merged {0} out of {1}.', message_args));
|
||||
},
|
||||
|
||||
root_type: function(frm) {
|
||||
frm.set_value('account', '');
|
||||
frm.set_value('merge_accounts', []);
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
frm.set_value('account', '');
|
||||
frm.set_value('merge_accounts', []);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Ledger Merge Accounts', {
|
||||
merge_accounts_add: function(frm) {
|
||||
frm.trigger('update_primary_action');
|
||||
},
|
||||
|
||||
merge_accounts_remove: function(frm) {
|
||||
frm.trigger('update_primary_action');
|
||||
},
|
||||
|
||||
account: function(frm, cdt, cdn) {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
row.account_name = row.account;
|
||||
frm.refresh_field('merge_accounts');
|
||||
frm.trigger('update_primary_action');
|
||||
}
|
||||
});
|
130
erpnext/accounts/doctype/ledger_merge/ledger_merge.json
Normal file
130
erpnext/accounts/doctype/ledger_merge/ledger_merge.json
Normal file
@ -0,0 +1,130 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:{account_name} merger on {creation}",
|
||||
"creation": "2021-12-09 15:38:04.556584",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_1",
|
||||
"root_type",
|
||||
"account",
|
||||
"account_name",
|
||||
"column_break_3",
|
||||
"company",
|
||||
"status",
|
||||
"is_group",
|
||||
"section_break_5",
|
||||
"merge_accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"depends_on": "root_type",
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account",
|
||||
"options": "Account",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "merge_accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "Accounts to Merge",
|
||||
"options": "Ledger Merge Accounts",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "account",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Pending\nSuccess\nPartial Success\nError",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "root_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Root Type",
|
||||
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "account",
|
||||
"fetch_from": "account.account_name",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "account_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Account Name",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "account",
|
||||
"fetch_from": "account.is_group",
|
||||
"fieldname": "is_group",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Group",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2021-12-12 21:34:55.155146",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Ledger Merge",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
76
erpnext/accounts/doctype/ledger_merge/ledger_merge.py
Normal file
76
erpnext/accounts/doctype/ledger_merge/ledger_merge.py
Normal file
@ -0,0 +1,76 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.accounts.doctype.account.account import merge_account
|
||||
|
||||
|
||||
class LedgerMerge(Document):
|
||||
def start_merge(self):
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||
frappe.throw(
|
||||
_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive")
|
||||
)
|
||||
|
||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||
|
||||
if self.name not in enqueued_jobs:
|
||||
enqueue(
|
||||
start_merge,
|
||||
queue="default",
|
||||
timeout=6000,
|
||||
event="ledger_merge",
|
||||
job_name=self.name,
|
||||
docname=self.name,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def form_start_merge(docname):
|
||||
return frappe.get_doc("Ledger Merge", docname).start_merge()
|
||||
|
||||
def start_merge(docname):
|
||||
ledger_merge = frappe.get_doc("Ledger Merge", docname)
|
||||
successful_merges = 0
|
||||
total = len(ledger_merge.merge_accounts)
|
||||
for row in ledger_merge.merge_accounts:
|
||||
if not row.merged:
|
||||
try:
|
||||
merge_account(
|
||||
row.account,
|
||||
ledger_merge.account,
|
||||
ledger_merge.is_group,
|
||||
ledger_merge.root_type,
|
||||
ledger_merge.company
|
||||
)
|
||||
row.db_set('merged', 1)
|
||||
frappe.db.commit()
|
||||
successful_merges += 1
|
||||
frappe.publish_realtime("ledger_merge_progress", {
|
||||
"ledger_merge": ledger_merge.name,
|
||||
"current": successful_merges,
|
||||
"total": total
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(title=ledger_merge.name)
|
||||
finally:
|
||||
if successful_merges == total:
|
||||
ledger_merge.db_set('status', 'Success')
|
||||
elif successful_merges > 0:
|
||||
ledger_merge.db_set('status', 'Partial Success')
|
||||
else:
|
||||
ledger_merge.db_set('status', 'Error')
|
||||
|
||||
frappe.publish_realtime("ledger_merge_refresh", {"ledger_merge": ledger_merge.name})
|
118
erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py
Normal file
118
erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py
Normal file
@ -0,0 +1,118 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.accounts.doctype.ledger_merge.ledger_merge import start_merge
|
||||
|
||||
|
||||
class TestLedgerMerge(unittest.TestCase):
|
||||
def test_merge_success(self):
|
||||
if not frappe.db.exists("Account", "Indirect Expenses - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Indirect Expenses"
|
||||
acc.is_group = 1
|
||||
acc.parent_account = "Expenses - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
if not frappe.db.exists("Account", "Indirect Test Expenses - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Indirect Test Expenses"
|
||||
acc.is_group = 1
|
||||
acc.parent_account = "Expenses - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
if not frappe.db.exists("Account", "Administrative Test Expenses - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Administrative Test Expenses"
|
||||
acc.parent_account = "Indirect Test Expenses - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Ledger Merge",
|
||||
"company": "_Test Company",
|
||||
"root_type": frappe.db.get_value("Account", "Indirect Test Expenses - _TC", "root_type"),
|
||||
"account": "Indirect Expenses - _TC",
|
||||
"merge_accounts": [
|
||||
{
|
||||
"account": "Indirect Test Expenses - _TC",
|
||||
"account_name": "Indirect Expenses"
|
||||
}
|
||||
]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
parent = frappe.db.get_value("Account", "Administrative Test Expenses - _TC", "parent_account")
|
||||
self.assertEqual(parent, "Indirect Test Expenses - _TC")
|
||||
|
||||
start_merge(doc.name)
|
||||
|
||||
parent = frappe.db.get_value("Account", "Administrative Test Expenses - _TC", "parent_account")
|
||||
self.assertEqual(parent, "Indirect Expenses - _TC")
|
||||
|
||||
self.assertFalse(frappe.db.exists("Account", "Indirect Test Expenses - _TC"))
|
||||
|
||||
def test_partial_merge_success(self):
|
||||
if not frappe.db.exists("Account", "Indirect Income - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Indirect Income"
|
||||
acc.is_group = 1
|
||||
acc.parent_account = "Income - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
if not frappe.db.exists("Account", "Indirect Test Income - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Indirect Test Income"
|
||||
acc.is_group = 1
|
||||
acc.parent_account = "Income - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
if not frappe.db.exists("Account", "Administrative Test Income - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Administrative Test Income"
|
||||
acc.parent_account = "Indirect Test Income - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Ledger Merge",
|
||||
"company": "_Test Company",
|
||||
"root_type": frappe.db.get_value("Account", "Indirect Income - _TC", "root_type"),
|
||||
"account": "Indirect Income - _TC",
|
||||
"merge_accounts": [
|
||||
{
|
||||
"account": "Indirect Test Income - _TC",
|
||||
"account_name": "Indirect Test Income"
|
||||
},
|
||||
{
|
||||
"account": "Administrative Test Income - _TC",
|
||||
"account_name": "Administrative Test Income"
|
||||
}
|
||||
]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
parent = frappe.db.get_value("Account", "Administrative Test Income - _TC", "parent_account")
|
||||
self.assertEqual(parent, "Indirect Test Income - _TC")
|
||||
|
||||
start_merge(doc.name)
|
||||
|
||||
parent = frappe.db.get_value("Account", "Administrative Test Income - _TC", "parent_account")
|
||||
self.assertEqual(parent, "Indirect Income - _TC")
|
||||
|
||||
self.assertFalse(frappe.db.exists("Account", "Indirect Test Income - _TC"))
|
||||
self.assertTrue(frappe.db.exists("Account", "Administrative Test Income - _TC"))
|
||||
|
||||
def tearDown(self):
|
||||
for entry in frappe.db.get_all("Ledger Merge"):
|
||||
frappe.delete_doc("Ledger Merge", entry.name)
|
||||
|
||||
test_accounts = [
|
||||
"Indirect Test Expenses - _TC",
|
||||
"Administrative Test Expenses - _TC",
|
||||
"Indirect Test Income - _TC",
|
||||
"Administrative Test Income - _TC"
|
||||
]
|
||||
for account in test_accounts:
|
||||
frappe.delete_doc_if_exists("Account", account)
|
@ -0,0 +1,52 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-12-09 15:44:58.033398",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account",
|
||||
"account_name",
|
||||
"merged"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 4,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"fieldname": "merged",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Merged",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"fieldname": "account_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Account Name",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-12-10 15:27:24.477139",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Ledger Merge Accounts",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LedgerMergeAccounts(Document):
|
||||
pass
|
@ -75,7 +75,7 @@
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2019-07-25 14:57:33.187689",
|
||||
"modified": "2022-01-04 15:25:06.053187",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Opening Invoice Creation Tool",
|
||||
|
@ -159,7 +159,8 @@ class OpeningInvoiceCreationTool(Document):
|
||||
frappe.scrub(row.party_type): row.party,
|
||||
"is_pos": 0,
|
||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||
"update_stock": 0
|
||||
"update_stock": 0,
|
||||
"invoice_number": row.invoice_number
|
||||
})
|
||||
|
||||
accounting_dimension = get_accounting_dimensions()
|
||||
@ -200,10 +201,13 @@ def start_import(invoices):
|
||||
names = []
|
||||
for idx, d in enumerate(invoices):
|
||||
try:
|
||||
invoice_number = None
|
||||
if d.invoice_number:
|
||||
invoice_number = d.invoice_number
|
||||
publish(idx, len(invoices), d.doctype)
|
||||
doc = frappe.get_doc(d)
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.insert()
|
||||
doc.insert(set_name=invoice_number)
|
||||
doc.submit()
|
||||
frappe.db.commit()
|
||||
names.append(doc.name)
|
||||
|
@ -18,10 +18,10 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||
make_company()
|
||||
|
||||
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
|
||||
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
|
||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
||||
party_1=party_1, party_2=party_2)
|
||||
party_1=party_1, party_2=party_2, invoice_number=invoice_number)
|
||||
doc.update(args)
|
||||
return doc.make_invoices()
|
||||
|
||||
@ -92,6 +92,20 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
# teardown
|
||||
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
||||
|
||||
def test_renaming_of_invoice_using_invoice_number_field(self):
|
||||
company = "_Test Opening Invoice Company"
|
||||
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
|
||||
self.make_invoices(company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11")
|
||||
|
||||
sales_inv1 = frappe.get_all('Sales Invoice', filters={'customer':'Customer A'})[0].get("name")
|
||||
sales_inv2 = frappe.get_all('Sales Invoice', filters={'customer':'Customer B'})[0].get("name")
|
||||
self.assertEqual(sales_inv1, "TEST-NEW-INV-11")
|
||||
|
||||
#teardown
|
||||
for inv in [sales_inv1, sales_inv2]:
|
||||
doc = frappe.get_doc('Sales Invoice', inv)
|
||||
doc.cancel()
|
||||
|
||||
def get_opening_invoice_creation_dict(**args):
|
||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||
company = args.get("company", "_Test Company")
|
||||
@ -107,7 +121,8 @@ def get_opening_invoice_creation_dict(**args):
|
||||
"item_name": "Opening Item",
|
||||
"due_date": "2016-09-10",
|
||||
"posting_date": "2016-09-05",
|
||||
"temporary_opening_account": get_temporary_opening_account(company)
|
||||
"temporary_opening_account": get_temporary_opening_account(company),
|
||||
"invoice_number": args.get("invoice_number")
|
||||
},
|
||||
{
|
||||
"qty": 2.0,
|
||||
@ -116,7 +131,8 @@ def get_opening_invoice_creation_dict(**args):
|
||||
"item_name": "Opening Item",
|
||||
"due_date": "2016-09-10",
|
||||
"posting_date": "2016-09-05",
|
||||
"temporary_opening_account": get_temporary_opening_account(company)
|
||||
"temporary_opening_account": get_temporary_opening_account(company),
|
||||
"invoice_number": None
|
||||
}
|
||||
]
|
||||
})
|
||||
@ -132,7 +148,7 @@ def make_company():
|
||||
company.company_name = "_Test Opening Invoice Company"
|
||||
company.abbr = "_TOIC"
|
||||
company.default_currency = "INR"
|
||||
company.country = "India"
|
||||
company.country = "Pakistan"
|
||||
company.insert()
|
||||
return company
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2017-08-29 04:26:36.159247",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"invoice_number",
|
||||
"party_type",
|
||||
"party",
|
||||
"temporary_opening_account",
|
||||
@ -103,10 +105,18 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Reference number of the invoice from the previous system",
|
||||
"fieldname": "invoice_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice Number"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-07-25 15:00:00.460695",
|
||||
"links": [],
|
||||
"modified": "2021-12-17 19:25:06.053187",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Opening Invoice Creation Tool Item",
|
||||
|
@ -2,7 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
@ -12,6 +12,17 @@ class PartyLink(Document):
|
||||
frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."),
|
||||
title=_("Invalid Primary Role"))
|
||||
|
||||
existing_party_link = frappe.get_all('Party Link', {
|
||||
'primary_party': self.primary_party,
|
||||
'secondary_party': self.secondary_party
|
||||
}, pluck="primary_role")
|
||||
if existing_party_link:
|
||||
frappe.throw(_('{} {} is already linked with {} {}')
|
||||
.format(
|
||||
self.primary_role, bold(self.primary_party),
|
||||
self.secondary_role, bold(self.secondary_party)
|
||||
))
|
||||
|
||||
existing_party_link = frappe.get_all('Party Link', {
|
||||
'primary_party': self.secondary_party
|
||||
}, pluck="primary_role")
|
||||
@ -25,3 +36,17 @@ class PartyLink(Document):
|
||||
if existing_party_link:
|
||||
frappe.throw(_('{} {} is already linked with another {}')
|
||||
.format(self.primary_role, self.primary_party, existing_party_link[0]))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_party_link(primary_role, primary_party, secondary_party):
|
||||
party_link = frappe.new_doc('Party Link')
|
||||
party_link.primary_role = primary_role
|
||||
party_link.primary_party = primary_party
|
||||
party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
|
||||
party_link.secondary_party = secondary_party
|
||||
|
||||
party_link.save(ignore_permissions=True)
|
||||
|
||||
return party_link
|
||||
|
||||
|
@ -61,7 +61,6 @@
|
||||
"taxes_and_charges_section",
|
||||
"purchase_taxes_and_charges_template",
|
||||
"sales_taxes_and_charges_template",
|
||||
"advance_tax_account",
|
||||
"column_break_55",
|
||||
"apply_tax_withholding_amount",
|
||||
"tax_withholding_category",
|
||||
@ -685,15 +684,6 @@
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
|
||||
"fieldname": "advance_tax_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Advance Tax Account",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||
"fieldname": "received_amount_after_tax",
|
||||
@ -730,7 +720,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-10-22 17:50:24.632806",
|
||||
"modified": "2021-11-24 18:58:24.919764",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
@ -20,7 +20,7 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
|
||||
from erpnext.controllers.accounts_controller import (
|
||||
@ -339,7 +339,7 @@ class PaymentEntry(AccountsController):
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(
|
||||
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
|
||||
.format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount")))
|
||||
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||
title=_("Warning"), indicator="orange")
|
||||
|
||||
@ -433,23 +433,12 @@ class PaymentEntry(AccountsController):
|
||||
if not self.apply_tax_withholding_amount:
|
||||
return
|
||||
|
||||
if not self.advance_tax_account:
|
||||
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
|
||||
|
||||
net_total = self.paid_amount
|
||||
|
||||
for reference in self.get("references"):
|
||||
net_total_for_tds = 0
|
||||
if reference.reference_doctype == 'Purchase Order':
|
||||
net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
|
||||
|
||||
if net_total_for_tds:
|
||||
net_total = net_total_for_tds
|
||||
|
||||
# Adding args as purchase invoice to get TDS amount
|
||||
args = frappe._dict({
|
||||
'company': self.company,
|
||||
'doctype': 'Purchase Invoice',
|
||||
'doctype': 'Payment Entry',
|
||||
'supplier': self.party,
|
||||
'posting_date': self.posting_date,
|
||||
'net_total': net_total
|
||||
@ -461,7 +450,6 @@ class PaymentEntry(AccountsController):
|
||||
return
|
||||
|
||||
tax_withholding_details.update({
|
||||
'add_deduct_tax': 'Add',
|
||||
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||
})
|
||||
|
||||
@ -623,7 +611,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
if not total_negative_outstanding:
|
||||
frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
|
||||
.format(self.payment_type, ("to" if self.party_type=="Customer" else "from"),
|
||||
.format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")),
|
||||
self.party_type), InvalidPaymentEntry)
|
||||
|
||||
elif paid_amount - additional_charges > total_negative_outstanding:
|
||||
@ -689,6 +677,7 @@ class PaymentEntry(AccountsController):
|
||||
self.add_deductions_gl_entries(gl_entries)
|
||||
self.add_tax_gl_entries(gl_entries)
|
||||
|
||||
gl_entries = process_gl_map(gl_entries)
|
||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
||||
|
||||
def add_party_gl_entries(self, gl_entries):
|
||||
@ -752,7 +741,8 @@ class PaymentEntry(AccountsController):
|
||||
"against": self.party if self.payment_type=="Pay" else self.paid_to,
|
||||
"credit_in_account_currency": self.paid_amount,
|
||||
"credit": self.base_paid_amount,
|
||||
"cost_center": self.cost_center
|
||||
"cost_center": self.cost_center,
|
||||
"post_net_value": True
|
||||
}, item=self)
|
||||
)
|
||||
if self.payment_type in ("Receive", "Internal Transfer"):
|
||||
@ -782,14 +772,10 @@ class PaymentEntry(AccountsController):
|
||||
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
|
||||
against = self.party or self.paid_to
|
||||
|
||||
payment_or_advance_account = self.get_party_account_for_taxes()
|
||||
payment_account = self.get_party_account_for_taxes()
|
||||
tax_amount = d.tax_amount
|
||||
base_tax_amount = d.base_tax_amount
|
||||
|
||||
if self.advance_tax_account:
|
||||
tax_amount = -1 * tax_amount
|
||||
base_tax_amount = -1 * base_tax_amount
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": d.account_head,
|
||||
@ -798,19 +784,21 @@ class PaymentEntry(AccountsController):
|
||||
dr_or_cr + "_in_account_currency": base_tax_amount
|
||||
if account_currency==self.company_currency
|
||||
else d.tax_amount,
|
||||
"cost_center": d.cost_center
|
||||
"cost_center": d.cost_center,
|
||||
"post_net_value": True,
|
||||
}, account_currency, item=d))
|
||||
|
||||
if not d.included_in_paid_amount or self.advance_tax_account:
|
||||
if not d.included_in_paid_amount:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": payment_or_advance_account,
|
||||
"account": payment_account,
|
||||
"against": against,
|
||||
rev_dr_or_cr: tax_amount,
|
||||
rev_dr_or_cr + "_in_account_currency": base_tax_amount
|
||||
if account_currency==self.company_currency
|
||||
else d.tax_amount,
|
||||
"cost_center": self.cost_center,
|
||||
"post_net_value": True,
|
||||
}, account_currency, item=d))
|
||||
|
||||
def add_deductions_gl_entries(self, gl_entries):
|
||||
@ -832,9 +820,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
def get_party_account_for_taxes(self):
|
||||
if self.advance_tax_account:
|
||||
return self.advance_tax_account
|
||||
elif self.payment_type == 'Receive':
|
||||
if self.payment_type == 'Receive':
|
||||
return self.paid_to
|
||||
elif self.payment_type in ('Pay', 'Internal Transfer'):
|
||||
return self.paid_from
|
||||
@ -1106,7 +1092,7 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
if not data:
|
||||
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
|
||||
.format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
|
||||
.format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))))
|
||||
|
||||
return data
|
||||
|
||||
@ -1599,13 +1585,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
})
|
||||
pe.set_difference_amount()
|
||||
|
||||
if doc.doctype == 'Purchase Order' and doc.apply_tds:
|
||||
pe.apply_tax_withholding_amount = 1
|
||||
pe.tax_withholding_category = doc.tax_withholding_category
|
||||
|
||||
if not pe.advance_tax_account:
|
||||
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
|
||||
|
||||
return pe
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
@ -1729,7 +1708,10 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
||||
|
||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||
total_discount = 0
|
||||
if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
|
||||
eligible_for_payments = ['Sales Order', 'Sales Invoice', 'Purchase Order', 'Purchase Invoice']
|
||||
has_payment_schedule = hasattr(doc, 'payment_schedule') and doc.payment_schedule
|
||||
|
||||
if doc.doctype in eligible_for_payments and has_payment_schedule:
|
||||
for term in doc.payment_schedule:
|
||||
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
|
||||
if term.discount_type == 'Percentage':
|
||||
|
@ -1,55 +0,0 @@
|
||||
QUnit.module('Payment Entry');
|
||||
|
||||
QUnit.test("test payment entry", function(assert) {
|
||||
assert.expect(6);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'item_code': 'Test Product 1'},
|
||||
{'qty': 1},
|
||||
{'rate': 101},
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Payment'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
||||
'made payment entry');
|
||||
assert.equal(cur_frm.doc.party, 'Test Customer 1',
|
||||
'customer set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_amount, 101,
|
||||
'paid amount set in payment entry');
|
||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 101,
|
||||
'amount allocated against sales invoice');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value('paid_amount', 100),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.model.set_value("Payment Entry Reference", cur_frm.doc.references[0].name,
|
||||
"allocated_amount", 101);
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Write Off Difference Amount'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
||||
assert.equal(cur_frm.doc.deductions[0].amount, 1, 'Write off amount = 1');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
QUnit.module('Payment Entry');
|
||||
|
||||
QUnit.test("test payment entry", function(assert) {
|
||||
assert.expect(7 );
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Purchase Invoice', [
|
||||
{supplier: 'Test Supplier'},
|
||||
{bill_no: 'in1234'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 2},
|
||||
{'item_code': 'Test Product 1'},
|
||||
{'rate':1000},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{supplier_address: 'Test1-Billing'},
|
||||
{contact_person: 'Contact 3-Test Supplier'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is just a Test'}
|
||||
]);
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.click_link('Payment'),
|
||||
() => frappe.timeout(3),
|
||||
() => cur_frm.set_value('mode_of_payment','Cash'),
|
||||
() => frappe.timeout(3),
|
||||
() => {
|
||||
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
||||
'made payment entry');
|
||||
assert.equal(cur_frm.doc.party, 'Test Supplier',
|
||||
'supplier set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_amount, 2000,
|
||||
'paid amount set in payment entry');
|
||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 2000,
|
||||
'amount allocated against purchase invoice');
|
||||
assert.equal(cur_frm.doc.references[0].bill_no, 'in1234',
|
||||
'invoice number allocated against purchase invoice');
|
||||
assert.equal(cur_frm.get_field('total_allocated_amount').value, 2000,
|
||||
'correct amount allocated in Write Off');
|
||||
assert.equal(cur_frm.get_field('unallocated_amount').value, 0,
|
||||
'correct amount unallocated in Write Off');
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
QUnit.module('Accounts');
|
||||
|
||||
QUnit.test("test payment entry", function(assert) {
|
||||
assert.expect(1);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Payment Entry', [
|
||||
{payment_type:'Receive'},
|
||||
{mode_of_payment:'Cash'},
|
||||
{party_type:'Customer'},
|
||||
{party:'Test Customer 3'},
|
||||
{paid_amount:675},
|
||||
{reference_no:123},
|
||||
{reference_date: frappe.datetime.add_days(frappe.datetime.nowdate(), 0)},
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.total_allocated_amount==675, "Allocated AmountCorrect");
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
QUnit.module('Payment Entry');
|
||||
|
||||
QUnit.test("test payment entry", function(assert) {
|
||||
assert.expect(8);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{company: 'For Testing'},
|
||||
{currency: 'INR'},
|
||||
{selling_price_list: '_Test Price List'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 1},
|
||||
{'item_code': 'Test Product 1'},
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1.5),
|
||||
() => frappe.click_button('Close'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Payment'),
|
||||
() => frappe.timeout(2),
|
||||
() => cur_frm.set_value("paid_to", "_Test Cash - FT"),
|
||||
() => frappe.timeout(0.5),
|
||||
() => {
|
||||
assert.equal(frappe.get_route()[1], 'Payment Entry', 'made payment entry');
|
||||
assert.equal(cur_frm.doc.party, 'Test Customer 1', 'customer set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_from, 'Debtors - FT', 'customer account set in payment entry');
|
||||
assert.equal(cur_frm.doc.paid_amount, 100, 'paid amount set in payment entry');
|
||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 100,
|
||||
'amount allocated against sales invoice');
|
||||
},
|
||||
() => cur_frm.set_value('paid_amount', 95),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.model.set_value("Payment Entry Reference",
|
||||
cur_frm.doc.references[0].name, "allocated_amount", 100);
|
||||
},
|
||||
() => frappe.timeout(.5),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.difference_amount, 5, 'difference amount is 5');
|
||||
},
|
||||
() => {
|
||||
frappe.db.set_value("Company", "For Testing", "write_off_account", "_Test Write Off - FT");
|
||||
frappe.timeout(1);
|
||||
frappe.db.set_value("Company", "For Testing",
|
||||
"exchange_gain_loss_account", "_Test Exchange Gain/Loss - FT");
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Write Off Difference Amount'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
||||
assert.equal(cur_frm.doc.deductions[0].amount, 5, 'Write off amount = 5');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -548,10 +548,14 @@ def make_payment_order(source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
def validate_payment(doc, method=""):
|
||||
if not frappe.db.has_column(doc.reference_doctype, 'status'):
|
||||
def validate_payment(doc, method=None):
|
||||
if doc.reference_doctype != "Payment Request" or (
|
||||
frappe.db.get_value(doc.reference_doctype, doc.reference_docname, 'status')
|
||||
!= "Paid"
|
||||
):
|
||||
return
|
||||
|
||||
status = frappe.db.get_value(doc.reference_doctype, doc.reference_docname, 'status')
|
||||
if status == 'Paid':
|
||||
frappe.throw(_("The Payment Request {0} is already paid, cannot process payment twice").format(doc.reference_docname))
|
||||
frappe.throw(
|
||||
_("The Payment Request {0} is already paid, cannot process payment twice")
|
||||
.format(doc.reference_docname)
|
||||
)
|
||||
|
@ -88,9 +88,10 @@ class PeriodClosingVoucher(AccountsController):
|
||||
|
||||
for acc in pl_accounts:
|
||||
if flt(acc.bal_in_company_currency):
|
||||
cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"cost_center": acc.cost_center or company_cost_center,
|
||||
"cost_center": cost_center,
|
||||
"finance_book": acc.finance_book,
|
||||
"account_currency": acc.account_currency,
|
||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||
|
@ -66,8 +66,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
company = create_company()
|
||||
surplus_account = create_account()
|
||||
|
||||
cost_center1 = create_cost_center("Test Cost Center 1")
|
||||
cost_center2 = create_cost_center("Test Cost Center 2")
|
||||
cost_center1 = create_cost_center("Main")
|
||||
cost_center2 = create_cost_center("Western Branch")
|
||||
|
||||
create_sales_invoice(
|
||||
company=company,
|
||||
@ -86,7 +86,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
debit_to="Debtors - TPC"
|
||||
)
|
||||
|
||||
pcv = self.make_period_closing_voucher()
|
||||
pcv = self.make_period_closing_voucher(submit=False)
|
||||
pcv.cost_center_wise_pnl = 1
|
||||
pcv.save()
|
||||
pcv.submit()
|
||||
surplus_account = pcv.closing_account_head
|
||||
|
||||
expected_gle = (
|
||||
@ -149,7 +152,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
|
||||
self.assertEqual(pcv_gle, expected_gle)
|
||||
|
||||
def make_period_closing_voucher(self):
|
||||
def make_period_closing_voucher(self, submit=True):
|
||||
surplus_account = create_account()
|
||||
cost_center = create_cost_center("Test Cost Center 1")
|
||||
pcv = frappe.get_doc({
|
||||
@ -163,7 +166,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
"remarks": "test"
|
||||
})
|
||||
pcv.insert()
|
||||
pcv.submit()
|
||||
if submit:
|
||||
pcv.submit()
|
||||
|
||||
return pcv
|
||||
|
||||
|
@ -171,6 +171,7 @@
|
||||
"sales_team_section_break",
|
||||
"sales_partner",
|
||||
"column_break10",
|
||||
"amount_eligible_for_commission",
|
||||
"commission_rate",
|
||||
"total_commission",
|
||||
"section_break2",
|
||||
@ -1561,16 +1562,23 @@
|
||||
"label": "Coupon Code",
|
||||
"options": "Coupon Code",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-27 20:12:57.306772",
|
||||
"modified": "2021-10-05 12:11:53.871828",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
update_multi_mode_option,
|
||||
)
|
||||
from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty, get_pos_reserved_batch_qty
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||
|
||||
|
||||
@ -124,9 +125,26 @@ class POSInvoice(SalesInvoice):
|
||||
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
|
||||
elif invalid_serial_nos:
|
||||
frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
frappe.throw(_("Row #{}: Serial Nos. {} have already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
|
||||
|
||||
def validate_pos_reserved_batch_qty(self, item):
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no":item.batch_no}
|
||||
|
||||
available_batch_qty = get_batch_qty(item.batch_no, item.warehouse, item.item_code)
|
||||
reserved_batch_qty = get_pos_reserved_batch_qty(filters)
|
||||
|
||||
bold_item_name = frappe.bold(item.item_name)
|
||||
bold_extra_batch_qty_needed = frappe.bold(abs(available_batch_qty - reserved_batch_qty - item.qty))
|
||||
bold_invalid_batch_no = frappe.bold(item.batch_no)
|
||||
|
||||
if (available_batch_qty - reserved_batch_qty) == 0:
|
||||
frappe.throw(_("Row #{}: Batch No. {} of item {} has no stock available. Please select valid batch no.")
|
||||
.format(item.idx, bold_invalid_batch_no, bold_item_name), title=_("Item Unavailable"))
|
||||
elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
|
||||
frappe.throw(_("Row #{}: Batch No. {} of item {} has less than required stock available, {} more required")
|
||||
.format(item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed), title=_("Item Unavailable"))
|
||||
|
||||
def validate_delivered_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
delivered_serial_nos = frappe.db.get_list('Serial No', {
|
||||
@ -149,6 +167,8 @@ class POSInvoice(SalesInvoice):
|
||||
if d.serial_no:
|
||||
self.validate_pos_reserved_serial_nos(d)
|
||||
self.validate_delivered_serial_nos(d)
|
||||
elif d.batch_no:
|
||||
self.validate_pos_reserved_batch_qty(d)
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
return
|
||||
@ -333,7 +353,6 @@ class POSInvoice(SalesInvoice):
|
||||
if not for_validate and not self.customer:
|
||||
self.customer = profile.customer
|
||||
|
||||
self.ignore_pricing_rule = profile.ignore_pricing_rule
|
||||
self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
|
||||
self.set_warehouse = profile.get('warehouse') or self.set_warehouse
|
||||
|
||||
|
@ -521,6 +521,72 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 400)
|
||||
|
||||
def test_pos_batch_item_qty_validation(self):
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_batch_item_with_batch,
|
||||
)
|
||||
create_batch_item_with_batch('_BATCH ITEM', 'TestBatch 01')
|
||||
item = frappe.get_doc('Item', '_BATCH ITEM')
|
||||
batch = frappe.get_doc('Batch', 'TestBatch 01')
|
||||
batch.submit()
|
||||
item.batch_no = 'TestBatch 01'
|
||||
item.save()
|
||||
|
||||
se = make_stock_entry(target="_Test Warehouse - _TC", item_code="_BATCH ITEM", qty=2, basic_rate=100, batch_no='TestBatch 01')
|
||||
|
||||
pos_inv1 = create_pos_invoice(item=item.name, rate=300, qty=1, do_not_submit=1)
|
||||
pos_inv1.items[0].batch_no = 'TestBatch 01'
|
||||
pos_inv1.save()
|
||||
pos_inv1.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(item=item.name, rate=300, qty=2, do_not_submit=1)
|
||||
pos_inv2.items[0].batch_no = 'TestBatch 01'
|
||||
pos_inv2.save()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos_inv2.submit)
|
||||
|
||||
#teardown
|
||||
pos_inv1.reload()
|
||||
pos_inv1.cancel()
|
||||
pos_inv1.delete()
|
||||
pos_inv2.reload()
|
||||
pos_inv2.delete()
|
||||
se.cancel()
|
||||
batch.reload()
|
||||
batch.cancel()
|
||||
batch.delete()
|
||||
|
||||
def test_ignore_pricing_rule(self):
|
||||
from erpnext.accounts.doctype.pricing_rule.test_pricing_rule import make_pricing_rule
|
||||
|
||||
item_price = frappe.get_doc({
|
||||
'doctype': 'Item Price',
|
||||
'item_code': '_Test Item',
|
||||
'price_list': '_Test Price List',
|
||||
'price_list_rate': '450',
|
||||
})
|
||||
item_price.insert()
|
||||
pr = make_pricing_rule(selling=1, priority=5, discount_percentage=10)
|
||||
pr.save()
|
||||
pos_inv = create_pos_invoice(qty=1, do_not_submit=1)
|
||||
pos_inv.items[0].rate = 300
|
||||
pos_inv.save()
|
||||
self.assertEquals(pos_inv.items[0].discount_percentage, 10)
|
||||
# rate shouldn't change
|
||||
self.assertEquals(pos_inv.items[0].rate, 405)
|
||||
|
||||
pos_inv.ignore_pricing_rule = 1
|
||||
pos_inv.items[0].rate = 300
|
||||
pos_inv.save()
|
||||
self.assertEquals(pos_inv.ignore_pricing_rule, 1)
|
||||
# rate should change since pricing rules are ignored
|
||||
self.assertEquals(pos_inv.items[0].rate, 300)
|
||||
|
||||
item_price.delete()
|
||||
pos_inv.delete()
|
||||
pr.delete()
|
||||
|
||||
|
||||
def create_pos_invoice(**args):
|
||||
args = frappe._dict(args)
|
||||
pos_profile = None
|
||||
@ -557,7 +623,8 @@ def create_pos_invoice(**args):
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no
|
||||
})
|
||||
|
||||
if not args.do_not_save:
|
||||
@ -570,3 +637,8 @@ def create_pos_invoice(**args):
|
||||
pos_inv.payment_schedule = []
|
||||
|
||||
return pos_inv
|
||||
|
||||
def make_batch_item(item_name):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
if not frappe.db.exists(item_name):
|
||||
return make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1, is_stock_item=1))
|
@ -46,6 +46,7 @@
|
||||
"base_amount",
|
||||
"pricing_rules",
|
||||
"is_free_item",
|
||||
"grant_commission",
|
||||
"section_break_21",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@ -800,14 +801,22 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "grant_commission",
|
||||
"fieldtype": "Check",
|
||||
"label": "Grant Commission",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-04 17:34:49.924531",
|
||||
"modified": "2021-10-05 12:23:47.506290",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
|
@ -3,22 +3,20 @@
|
||||
|
||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||
|
||||
frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
frm.set_query("selling_price_list", function() {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
frm.set_query("tc_name", function() {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Profile', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("selling_price_list", function() {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
frm.set_query("tc_name", function() {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
frm.set_query("print_format", function() {
|
||||
return {
|
||||
filters: [
|
||||
@ -27,10 +25,16 @@ frappe.ui.form.on('POS Profile', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("account_for_change_amount", function() {
|
||||
frm.set_query("account_for_change_amount", function(doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
account_type: ['in', ["Cash", "Bank"]]
|
||||
account_type: ['in', ["Cash", "Bank"]],
|
||||
is_group: 0,
|
||||
company: doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -45,7 +49,7 @@ frappe.ui.form.on('POS Profile', {
|
||||
});
|
||||
|
||||
frm.set_query('company_address', function(doc) {
|
||||
if(!doc.company) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
@ -58,11 +62,79 @@ frappe.ui.form.on('POS Profile', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('income_account', function(doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
'is_group': 0,
|
||||
'company': doc.company,
|
||||
'account_type': "Income Account"
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('cost_center', function(doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
'company': doc.company,
|
||||
'is_group': 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('expense_account', function(doc) {
|
||||
if (!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"report_type": "Profit and Loss",
|
||||
"company": doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("select_print_heading", function() {
|
||||
return {
|
||||
filters: [
|
||||
['Print Heading', 'docstatus', '!=', 2]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("write_off_account", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
'report_type': 'Profit and Loss',
|
||||
'is_group': 0,
|
||||
'company': doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("write_off_cost_center", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
'is_group': 0,
|
||||
'company': doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.company) {
|
||||
if (frm.doc.company) {
|
||||
frm.trigger("toggle_display_account_head");
|
||||
}
|
||||
},
|
||||
@ -76,71 +148,4 @@ frappe.ui.form.on('POS Profile', {
|
||||
frm.toggle_display('expense_account',
|
||||
erpnext.is_perpetual_inventory_enabled(frm.doc.company));
|
||||
}
|
||||
})
|
||||
|
||||
// Income Account
|
||||
// --------------------------------
|
||||
cur_frm.fields_dict['income_account'].get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
filters:{
|
||||
'is_group': 0,
|
||||
'company': doc.company,
|
||||
'account_type': "Income Account"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// Cost Center
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict['cost_center'].get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
filters:{
|
||||
'company': doc.company,
|
||||
'is_group': 0
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// Expense Account
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict["expense_account"].get_query = function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"report_type": "Profit and Loss",
|
||||
"company": doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// ------------------ Get Print Heading ------------------------------------
|
||||
cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) {
|
||||
return{
|
||||
filters:[
|
||||
['Print Heading', 'docstatus', '!=', 2]
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
cur_frm.fields_dict.write_off_account.get_query = function(doc) {
|
||||
return{
|
||||
filters:{
|
||||
'report_type': 'Profit and Loss',
|
||||
'is_group': 0,
|
||||
'company': doc.company
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Write off cost center
|
||||
// -----------------------
|
||||
cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
||||
return{
|
||||
filters:{
|
||||
'is_group': 0,
|
||||
'company': doc.company
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
@ -166,7 +166,7 @@ class TestPricingRule(unittest.TestCase):
|
||||
"item_group": "Products",
|
||||
},
|
||||
{
|
||||
"item_group": "Seed",
|
||||
"item_group": "_Test Item Group",
|
||||
},
|
||||
],
|
||||
"selling": 1,
|
||||
@ -543,6 +543,75 @@ class TestPricingRule(unittest.TestCase):
|
||||
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
||||
item.delete()
|
||||
|
||||
def test_pricing_rule_for_different_currency(self):
|
||||
make_item("Test Sanitizer Item")
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Sanitizer Rule",
|
||||
"apply_on": "Item Code",
|
||||
"items": [{
|
||||
"item_code": "Test Sanitizer Item",
|
||||
}],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 0,
|
||||
"priority": 2,
|
||||
"margin_type": "Percentage",
|
||||
"margin_rate_or_amount": 0.0,
|
||||
"company": "_Test Company"
|
||||
}
|
||||
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.rate_or_discount = 'Rate'
|
||||
rule.rate = 100.0
|
||||
rule.insert()
|
||||
|
||||
rule1 = frappe.get_doc(pricing_rule_record)
|
||||
rule1.currency = 'USD'
|
||||
rule1.rate_or_discount = 'Rate'
|
||||
rule1.rate = 2.0
|
||||
rule1.priority = 1
|
||||
rule1.insert()
|
||||
|
||||
args = frappe._dict({
|
||||
"item_code": "Test Sanitizer Item",
|
||||
"company": "_Test Company",
|
||||
"price_list": "_Test Price List",
|
||||
"currency": "USD",
|
||||
"doctype": "Sales Invoice",
|
||||
"conversion_rate": 1,
|
||||
"price_list_currency": "_Test Currency",
|
||||
"plc_conversion_rate": 1,
|
||||
"order_type": "Sales",
|
||||
"customer": "_Test Customer",
|
||||
"name": None,
|
||||
"transaction_date": frappe.utils.nowdate()
|
||||
})
|
||||
|
||||
details = get_item_details(args)
|
||||
self.assertEqual(details.price_list_rate, 2.0)
|
||||
|
||||
|
||||
args = frappe._dict({
|
||||
"item_code": "Test Sanitizer Item",
|
||||
"company": "_Test Company",
|
||||
"price_list": "_Test Price List",
|
||||
"currency": "INR",
|
||||
"doctype": "Sales Invoice",
|
||||
"conversion_rate": 1,
|
||||
"price_list_currency": "_Test Currency",
|
||||
"plc_conversion_rate": 1,
|
||||
"order_type": "Sales",
|
||||
"customer": "_Test Customer",
|
||||
"name": None,
|
||||
"transaction_date": frappe.utils.nowdate()
|
||||
})
|
||||
|
||||
details = get_item_details(args)
|
||||
self.assertEqual(details.price_list_rate, 100.0)
|
||||
|
||||
def test_pricing_rule_for_transaction(self):
|
||||
make_item("Water Flask 1")
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
@ -581,7 +650,7 @@ def make_pricing_rule(**args):
|
||||
"rate": args.rate or 0.0,
|
||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||
"condition": args.condition or '',
|
||||
"priority": 1,
|
||||
"priority": args.priority or 1,
|
||||
"discount_amount": args.discount_amount or 0.0,
|
||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||
})
|
||||
@ -607,6 +676,8 @@ def make_pricing_rule(**args):
|
||||
if args.get(applicable_for):
|
||||
doc.db_set(applicable_for, args.get(applicable_for))
|
||||
|
||||
return doc
|
||||
|
||||
def setup_pricing_rule_data():
|
||||
if not frappe.db.exists('Campaign', '_Test Campaign'):
|
||||
frappe.get_doc({
|
||||
|
@ -1,28 +0,0 @@
|
||||
QUnit.module('Pricing Rule');
|
||||
|
||||
QUnit.test("test pricing rule", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make("Pricing Rule", [
|
||||
{title: 'Test Pricing Rule'},
|
||||
{item_code:'Test Product 2'},
|
||||
{selling:1},
|
||||
{applicable_for:'Customer'},
|
||||
{customer:'Test Customer 3'},
|
||||
{currency: frappe.defaults.get_default("currency")}
|
||||
{min_qty:1},
|
||||
{max_qty:20},
|
||||
{valid_upto: frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{discount_percentage:10},
|
||||
{for_price_list:'Standard Selling'}
|
||||
]);
|
||||
},
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.item_code=='Test Product 2');
|
||||
assert.ok(cur_frm.doc.customer=='Test Customer 3');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
QUnit.module('Pricing Rule');
|
||||
|
||||
QUnit.test("test pricing rule with different currency", function(assert) {
|
||||
assert.expect(3);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make("Pricing Rule", [
|
||||
{title: 'Test Pricing Rule 2'},
|
||||
{apply_on: 'Item Code'},
|
||||
{item_code:'Test Product 4'},
|
||||
{selling:1},
|
||||
{priority: 1},
|
||||
{min_qty:1},
|
||||
{max_qty:20},
|
||||
{valid_upto: frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{margin_type: 'Amount'},
|
||||
{margin_rate_or_amount: 20},
|
||||
{rate_or_discount: 'Rate'},
|
||||
{rate:200},
|
||||
{currency:'USD'}
|
||||
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(0.3),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.item_code=='Test Product 4');
|
||||
},
|
||||
|
||||
() => {
|
||||
return frappe.tests.make('Sales Order', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{currency: 'INR'},
|
||||
{items: [
|
||||
[
|
||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{'qty': 5},
|
||||
{'item_code': "Test Product 4"}
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(0.3),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].pricing_rule=='Test Pricing Rule 2', "Pricing rule correct");
|
||||
// margin not applied because different currency in pricing rule
|
||||
assert.ok(cur_frm.doc.items[0].margin_type==null, "Margin correct");
|
||||
},
|
||||
() => frappe.timeout(0.3),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
QUnit.module('Pricing Rule');
|
||||
|
||||
QUnit.test("test pricing rule with same currency", function(assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make("Pricing Rule", [
|
||||
{title: 'Test Pricing Rule 1'},
|
||||
{apply_on: 'Item Code'},
|
||||
{item_code:'Test Product 4'},
|
||||
{selling:1},
|
||||
{min_qty:1},
|
||||
{max_qty:20},
|
||||
{valid_upto: frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{rate_or_discount: 'Rate'},
|
||||
{rate:200},
|
||||
{currency:'USD'}
|
||||
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(0.3),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.item_code=='Test Product 4');
|
||||
},
|
||||
|
||||
() => {
|
||||
return frappe.tests.make('Sales Order', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{currency: 'USD'},
|
||||
{items: [
|
||||
[
|
||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{'qty': 5},
|
||||
{'item_code': "Test Product 4"}
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(0.3),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].pricing_rule=='Test Pricing Rule 1', "Pricing rule correct");
|
||||
assert.ok(cur_frm.doc.items[0].price_list_rate==200, "Item rate correct");
|
||||
// get_total
|
||||
assert.ok(cur_frm.doc.total== 1000, "Total correct");
|
||||
},
|
||||
() => frappe.timeout(0.3),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -264,6 +264,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
||||
else:
|
||||
p.variant_of = None
|
||||
|
||||
if len(pricing_rules) > 1:
|
||||
filtered_rules = list(filter(lambda x: x.currency==args.get('currency'), pricing_rules))
|
||||
if filtered_rules:
|
||||
pricing_rules = filtered_rules
|
||||
|
||||
# find pricing rule with highest priority
|
||||
if pricing_rules:
|
||||
max_priority = max(cint(p.priority) for p in pricing_rules)
|
||||
|
@ -130,6 +130,7 @@
|
||||
"allocate_advances_automatically",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"advance_tax",
|
||||
"payment_schedule_section",
|
||||
"payment_terms_template",
|
||||
"ignore_default_payment_terms_template",
|
||||
@ -1408,13 +1409,21 @@
|
||||
{
|
||||
"fieldname": "column_break_147",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "advance_tax",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 1,
|
||||
"label": "Advance Tax",
|
||||
"options": "Advance Tax",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-10-12 20:55:16.145651",
|
||||
"modified": "2021-11-25 13:31:02.716727",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -114,6 +114,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.set_status()
|
||||
self.validate_purchase_receipt_if_update_stock()
|
||||
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||
|
||||
def validate_release_date(self):
|
||||
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
||||
@ -294,8 +297,15 @@ class PurchaseInvoice(BuyingController):
|
||||
item.expense_account = stock_not_billed_account
|
||||
|
||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||
item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||
asset_category_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||
company = self.company)
|
||||
if not asset_category_account:
|
||||
form_link = get_link_to_form('Asset Category', asset_category)
|
||||
throw(
|
||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||
title=_("Missing Account")
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
@ -427,6 +437,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.update_project()
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.update_advance_tax_references()
|
||||
|
||||
self.process_common_party_accounting()
|
||||
|
||||
@ -472,8 +483,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||
|
||||
gl_entries = merge_similar_entries(gl_entries)
|
||||
@ -496,11 +505,11 @@ class PurchaseInvoice(BuyingController):
|
||||
# Checked both rounding_adjustment and rounded_total
|
||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total)
|
||||
else self.base_grand_total, self.precision("base_grand_total"))
|
||||
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
# Did not use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.credit_to,
|
||||
@ -508,8 +517,8 @@ class PurchaseInvoice(BuyingController):
|
||||
"party": self.supplier,
|
||||
"due_date": self.due_date,
|
||||
"against": self.against_expense_account,
|
||||
"credit": grand_total_in_company_currency,
|
||||
"credit_in_account_currency": grand_total_in_company_currency \
|
||||
"credit": base_grand_total,
|
||||
"credit_in_account_currency": base_grand_total \
|
||||
if self.party_account_currency==self.company_currency else grand_total,
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
@ -729,7 +738,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"account": self.stock_received_but_not_billed,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||
"remarks": self.remarks or "Accounting Entry for Stock",
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
"cost_center": self.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, item=item)
|
||||
@ -937,7 +946,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"cost_center": tax.cost_center,
|
||||
"against": self.supplier,
|
||||
"credit": valuation_tax[tax.name],
|
||||
"remarks": self.remarks or "Accounting Entry for Stock"
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock")
|
||||
}, item=tax))
|
||||
|
||||
@property
|
||||
@ -1074,6 +1083,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
def update_project(self):
|
||||
project_list = []
|
||||
@ -1150,7 +1160,10 @@ class PurchaseInvoice(BuyingController):
|
||||
if not self.tax_withholding_category:
|
||||
return
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||
tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||
|
||||
# Adjust TDS paid on advances
|
||||
self.allocate_advance_tds(tax_withholding_details, advance_taxes)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
@ -1174,6 +1187,39 @@ class PurchaseInvoice(BuyingController):
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
|
||||
self.set('advance_tax', [])
|
||||
for tax in advance_taxes:
|
||||
allocated_amount = 0
|
||||
pending_amount = flt(tax.tax_amount - tax.allocated_amount)
|
||||
if flt(tax_withholding_details.get('tax_amount')) >= pending_amount:
|
||||
tax_withholding_details['tax_amount'] -= pending_amount
|
||||
allocated_amount = pending_amount
|
||||
elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount:
|
||||
allocated_amount = tax_withholding_details['tax_amount']
|
||||
tax_withholding_details['tax_amount'] = 0
|
||||
|
||||
self.append('advance_tax', {
|
||||
'reference_type': 'Payment Entry',
|
||||
'reference_name': tax.parent,
|
||||
'reference_detail': tax.name,
|
||||
'account_head': tax.account_head,
|
||||
'allocated_amount': allocated_amount
|
||||
})
|
||||
|
||||
def update_advance_tax_references(self, cancel=0):
|
||||
for tax in self.get('advance_tax'):
|
||||
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
|
||||
|
||||
if cancel:
|
||||
frappe.qb.update(at).set(
|
||||
at.allocated_amount, at.allocated_amount - tax.allocated_amount
|
||||
).where(at.name == tax.reference_detail).run()
|
||||
else:
|
||||
frappe.qb.update(at).set(
|
||||
at.allocated_amount, at.allocated_amount + tax.allocated_amount
|
||||
).where(at.name == tax.reference_detail).run()
|
||||
|
||||
def set_status(self, update=False, status=None, update_modified=True):
|
||||
if self.is_new():
|
||||
if self.get('amended_from'):
|
||||
|
@ -1,74 +0,0 @@
|
||||
QUnit.module('Purchase Invoice');
|
||||
|
||||
QUnit.test("test purchase invoice", function(assert) {
|
||||
assert.expect(9);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Purchase Invoice', [
|
||||
{supplier: 'Test Supplier'},
|
||||
{bill_no: 'in123'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 5},
|
||||
{'item_code': 'Test Product 1'},
|
||||
{'rate':100},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{supplier_address: 'Test1-Billing'},
|
||||
{contact_person: 'Contact 3-Test Supplier'},
|
||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is Test'},
|
||||
{payment_terms_template: '_Test Payment Term Template UI'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
||||
// get tax details
|
||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
||||
// get tax account head details
|
||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
||||
// grand_total Calculated
|
||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
||||
|
||||
assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct");
|
||||
assert.ok(cur_frm.doc.payment_schedule.length > 0, "Payment Term Schedule is not empty");
|
||||
|
||||
},
|
||||
() => {
|
||||
let date = cur_frm.doc.due_date;
|
||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
||||
frappe.timeout(0.5);
|
||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_terms_schedule': ''}]),
|
||||
() => {
|
||||
let date = cur_frm.doc.due_date;
|
||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
||||
frappe.timeout(0.5);
|
||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_schedule': []}]),
|
||||
() => {
|
||||
let date = cur_frm.doc.due_date;
|
||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
||||
frappe.timeout(0.5);
|
||||
assert.ok(!cur_dialog, 'Message is not shown');
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -13,6 +13,7 @@ from erpnext.accounts.doctype.account.test_account import create_account, get_in
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
@ -35,6 +36,27 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
def tearDownClass(self):
|
||||
unlink_payment_on_cancel_of_invoice(0)
|
||||
|
||||
def test_purchase_invoice_received_qty(self):
|
||||
"""
|
||||
1. Test if received qty is validated against accepted + rejected
|
||||
2. Test if received qty is auto set on save
|
||||
"""
|
||||
pi = make_purchase_invoice(
|
||||
qty=1,
|
||||
rejected_qty=1,
|
||||
received_qty=3,
|
||||
item_code="_Test Item Home Desktop 200",
|
||||
rejected_warehouse = "_Test Rejected Warehouse - _TC",
|
||||
update_stock=True, do_not_save=True)
|
||||
self.assertRaises(QtyMismatchError, pi.save)
|
||||
|
||||
pi.items[0].received_qty = 0
|
||||
pi.save()
|
||||
self.assertEqual(pi.items[0].received_qty, 2)
|
||||
|
||||
# teardown
|
||||
pi.delete()
|
||||
|
||||
def test_gl_entries_without_perpetual_inventory(self):
|
||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||
pi = frappe.copy_doc(test_records[0])
|
||||
@ -811,29 +833,12 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
pi.shipping_rule = shipping_rule.name
|
||||
pi.insert()
|
||||
|
||||
shipping_amount = 0.0
|
||||
for condition in shipping_rule.get("conditions"):
|
||||
if not condition.to_value or (flt(condition.from_value) <= pi.net_total <= flt(condition.to_value)):
|
||||
shipping_amount = condition.shipping_amount
|
||||
|
||||
shipping_charge = {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"category": "Valuation and Total",
|
||||
"charge_type": "Actual",
|
||||
"account_head": shipping_rule.account,
|
||||
"cost_center": shipping_rule.cost_center,
|
||||
"tax_amount": shipping_amount,
|
||||
"description": shipping_rule.name,
|
||||
"add_deduct_tax": "Add"
|
||||
}
|
||||
pi.append("taxes", shipping_charge)
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.net_total, 1250)
|
||||
|
||||
self.assertEqual(pi.total_taxes_and_charges, 462.3)
|
||||
self.assertEqual(pi.grand_total, 1712.3)
|
||||
self.assertEqual(pi.total_taxes_and_charges, 354.1)
|
||||
self.assertEqual(pi.grand_total, 1604.1)
|
||||
|
||||
def test_make_pi_without_terms(self):
|
||||
pi = make_purchase_invoice(do_not_save=1)
|
||||
@ -981,7 +986,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
|
||||
pi.set_posting_time = 1
|
||||
pi.posting_date = '2019-03-15'
|
||||
pi.posting_date = '2019-01-10'
|
||||
pi.items[0].enable_deferred_expense = 1
|
||||
pi.items[0].service_start_date = "2019-01-10"
|
||||
pi.items[0].service_end_date = "2019-03-15"
|
||||
@ -1155,25 +1160,21 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
# Create Purchase Order with TDS applied
|
||||
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
|
||||
posting_date='2021-09-15')
|
||||
po.apply_tds = 1
|
||||
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
# Update Unrealized Profit / Loss Account which is used as default advance tax account
|
||||
frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
|
||||
|
||||
# Create Payment Entry Against the order
|
||||
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
|
||||
payment_entry.paid_from = 'Cash - _TC'
|
||||
payment_entry.apply_tax_withholding_amount = 1
|
||||
payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||
payment_entry.save()
|
||||
payment_entry.submit()
|
||||
|
||||
# Check GLE for Payment Entry
|
||||
expected_gle = [
|
||||
['_Test Account Excise Duty - _TC', 3000, 0],
|
||||
['Cash - _TC', 0, 27000],
|
||||
['Creditors - _TC', 27000, 0],
|
||||
['Creditors - _TC', 30000, 0],
|
||||
['TDS Payable - _TC', 0, 3000],
|
||||
]
|
||||
|
||||
@ -1199,9 +1200,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
# Zero net effect on final TDS Payable on invoice
|
||||
expected_gle = [
|
||||
['_Test Account Cost for Goods Sold - _TC', 30000],
|
||||
['_Test Account Excise Duty - _TC', -3000],
|
||||
['Creditors - _TC', -27000],
|
||||
['TDS Payable - _TC', 0]
|
||||
['Creditors - _TC', -30000]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
|
||||
@ -1214,6 +1213,14 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||
|
||||
payment_entry.load_from_db()
|
||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000)
|
||||
|
||||
purchase_invoice.cancel()
|
||||
|
||||
payment_entry.load_from_db()
|
||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||
from `tabGL Entry`
|
||||
@ -1229,7 +1236,7 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
def update_tax_witholding_category(company, account):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
fiscal_year = get_fiscal_year(fiscal_year='2021')
|
||||
fiscal_year = get_fiscal_year(date=nowdate())
|
||||
|
||||
if not frappe.db.get_value('Tax Withholding Rate',
|
||||
{'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]),
|
||||
|
@ -22,10 +22,10 @@
|
||||
"received_qty",
|
||||
"qty",
|
||||
"rejected_qty",
|
||||
"stock_uom",
|
||||
"col_break2",
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"stock_uom",
|
||||
"stock_qty",
|
||||
"sec_break1",
|
||||
"price_list_rate",
|
||||
@ -175,7 +175,8 @@
|
||||
{
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty"
|
||||
"label": "Received Qty",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@ -223,7 +224,7 @@
|
||||
{
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Stock Qty",
|
||||
"label": "Accepted Qty in Stock UOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
@ -870,10 +871,11 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-01 16:04:03.538643",
|
||||
"modified": "2021-11-15 17:04:07.191013",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
|
@ -1,28 +0,0 @@
|
||||
QUnit.module('Sales Taxes and Charges Template');
|
||||
|
||||
QUnit.test("test sales taxes and charges template", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Purchase Taxes and Charges Template', [
|
||||
{title: "TEST In State GST"},
|
||||
{taxes:[
|
||||
[
|
||||
{charge_type:"On Net Total"},
|
||||
{account_head:"CGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
|
||||
],
|
||||
[
|
||||
{charge_type:"On Net Total"},
|
||||
{account_head:"SGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.title=='TEST In State GST');
|
||||
assert.ok(cur_frm.doc.name=='TEST In State GST - FT');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -516,15 +516,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
// project name
|
||||
//--------------------------
|
||||
cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_project_name",
|
||||
filters: {'customer': doc.customer}
|
||||
}
|
||||
}
|
||||
|
||||
// Income Account in Details Table
|
||||
// --------------------------------
|
||||
cur_frm.set_query("income_account", "items", function(doc) {
|
||||
@ -978,7 +969,7 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
|
||||
if (frm.doc.is_debit_note) {
|
||||
frm.set_df_property('return_against', 'label', 'Adjustment Against');
|
||||
frm.set_df_property('return_against', 'label', __('Adjustment Against'));
|
||||
}
|
||||
|
||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||
@ -988,10 +979,10 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
|
||||
frm.add_custom_button(__('Healthcare Services'), function() {
|
||||
get_healthcare_services_to_invoice(frm);
|
||||
},"Get Items From");
|
||||
},__("Get Items From"));
|
||||
frm.add_custom_button(__('Prescriptions'), function() {
|
||||
get_drugs_to_invoice(frm);
|
||||
},"Get Items From");
|
||||
},__("Get Items From"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -182,6 +182,7 @@
|
||||
"sales_team_section_break",
|
||||
"sales_partner",
|
||||
"column_break10",
|
||||
"amount_eligible_for_commission",
|
||||
"commission_rate",
|
||||
"total_commission",
|
||||
"section_break2",
|
||||
@ -650,7 +651,7 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@ -2019,6 +2020,12 @@
|
||||
"label": "Total Billing Hours",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@ -2031,7 +2038,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-10-11 20:19:38.667508",
|
||||
"modified": "2021-12-23 20:19:38.667508",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
@ -2086,4 +2093,4 @@
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +155,8 @@ class SalesInvoice(SellingController):
|
||||
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated:
|
||||
validate_loyalty_points(self, self.loyalty_points)
|
||||
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
|
||||
def validate_fixed_asset(self):
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
||||
@ -842,8 +844,6 @@ class SalesInvoice(SellingController):
|
||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_discount_gl_entries(gl_entries)
|
||||
|
||||
@ -862,11 +862,11 @@ class SalesInvoice(SellingController):
|
||||
# Checked both rounding_adjustment and rounded_total
|
||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total)
|
||||
else self.base_grand_total, self.precision("base_grand_total"))
|
||||
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
# Didnot use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.debit_to,
|
||||
@ -874,8 +874,8 @@ class SalesInvoice(SellingController):
|
||||
"party": self.customer,
|
||||
"due_date": self.due_date,
|
||||
"against": self.against_income_account,
|
||||
"debit": grand_total_in_company_currency,
|
||||
"debit_in_account_currency": grand_total_in_company_currency \
|
||||
"debit": base_grand_total,
|
||||
"debit_in_account_currency": base_grand_total \
|
||||
if self.party_account_currency==self.company_currency else grand_total,
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
@ -1049,6 +1049,8 @@ class SalesInvoice(SellingController):
|
||||
frappe.flags.is_reverse_depr_entry = False
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
schedule.journal_entry = None
|
||||
depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry)
|
||||
asset.finance_books[0].value_after_depreciation += depreciation_amount
|
||||
asset.save()
|
||||
|
||||
def get_posting_date_of_sales_invoice(self):
|
||||
@ -1071,6 +1073,12 @@ class SalesInvoice(SellingController):
|
||||
|
||||
return False
|
||||
|
||||
def get_depreciation_amount_in_je(self, journal_entry):
|
||||
if journal_entry.accounts[0].debit_in_account_currency:
|
||||
return journal_entry.accounts[0].debit_in_account_currency
|
||||
else:
|
||||
return journal_entry.accounts[0].credit_in_account_currency
|
||||
|
||||
@property
|
||||
def enable_discount_accounting(self):
|
||||
if not hasattr(self, "_enable_discount_accounting"):
|
||||
|
@ -1,73 +0,0 @@
|
||||
QUnit.module('Sales Invoice');
|
||||
|
||||
QUnit.test("test sales Invoice", function(assert) {
|
||||
assert.expect(9);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 5},
|
||||
{'item_code': 'Test Product 1'},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{customer_address: 'Test1-Billing'},
|
||||
{shipping_address_name: 'Test1-Shipping'},
|
||||
{contact_person: 'Contact 1-Test Customer 1'},
|
||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is Test'},
|
||||
{payment_terms_template: '_Test Payment Term Template UI'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
||||
// get tax details
|
||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
||||
// get tax account head details
|
||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
||||
// grand_total Calculated
|
||||
assert.ok(cur_frm.doc.grand_total==590, "Grand Total correct");
|
||||
|
||||
assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct");
|
||||
assert.ok(cur_frm.doc.payment_schedule.length > 0, "Payment Term Schedule is not empty");
|
||||
|
||||
},
|
||||
() => {
|
||||
let date = cur_frm.doc.due_date;
|
||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
||||
frappe.timeout(0.5);
|
||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_terms_schedule': ''}]),
|
||||
() => {
|
||||
let date = cur_frm.doc.due_date;
|
||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
||||
frappe.timeout(0.5);
|
||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_schedule': []}]),
|
||||
() => {
|
||||
let date = cur_frm.doc.due_date;
|
||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
||||
frappe.timeout(0.5);
|
||||
assert.ok(!cur_dialog, 'Message is not shown');
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -20,6 +20,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_comp
|
||||
from erpnext.accounts.utils import PaymentEntryUnlinkError
|
||||
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
|
||||
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
||||
from erpnext.controllers.accounts_controller import update_invoice_status
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
from erpnext.regional.india.utils import get_ewb_data
|
||||
@ -1603,28 +1604,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
si.shipping_rule = shipping_rule.name
|
||||
si.insert()
|
||||
|
||||
shipping_amount = 0.0
|
||||
for condition in shipping_rule.get("conditions"):
|
||||
if not condition.to_value or (flt(condition.from_value) <= si.net_total <= flt(condition.to_value)):
|
||||
shipping_amount = condition.shipping_amount
|
||||
|
||||
shipping_charge = {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"category": "Valuation and Total",
|
||||
"charge_type": "Actual",
|
||||
"account_head": shipping_rule.account,
|
||||
"cost_center": shipping_rule.cost_center,
|
||||
"tax_amount": shipping_amount,
|
||||
"description": shipping_rule.name
|
||||
}
|
||||
si.append("taxes", shipping_charge)
|
||||
si.save()
|
||||
|
||||
self.assertEqual(si.net_total, 1250)
|
||||
|
||||
self.assertEqual(si.total_taxes_and_charges, 577.05)
|
||||
self.assertEqual(si.grand_total, 1827.05)
|
||||
self.assertEqual(si.total_taxes_and_charges, 468.85)
|
||||
self.assertEqual(si.grand_total, 1718.85)
|
||||
|
||||
|
||||
|
||||
@ -2316,6 +2301,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||
make_customer,
|
||||
)
|
||||
from erpnext.accounts.doctype.party_link.party_link import create_party_link
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
|
||||
# create a customer
|
||||
@ -2324,13 +2310,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
supplier = create_supplier(supplier_name="_Test Common Supplier").name
|
||||
|
||||
# create a party link between customer & supplier
|
||||
# set primary role as supplier
|
||||
party_link = frappe.new_doc("Party Link")
|
||||
party_link.primary_role = "Supplier"
|
||||
party_link.primary_party = supplier
|
||||
party_link.secondary_role = "Customer"
|
||||
party_link.secondary_party = customer
|
||||
party_link.save()
|
||||
party_link = create_party_link("Supplier", supplier, customer)
|
||||
|
||||
# enable common party accounting
|
||||
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
|
||||
@ -2406,6 +2386,64 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Paid")
|
||||
|
||||
def test_update_invoice_status(self):
|
||||
today = nowdate()
|
||||
|
||||
# Sales Invoice without Payment Schedule
|
||||
si = create_sales_invoice(posting_date=add_days(today, -5))
|
||||
|
||||
# Sales Invoice with Payment Schedule
|
||||
si_with_payment_schedule = create_sales_invoice(do_not_submit=True)
|
||||
si_with_payment_schedule.extend("payment_schedule", [
|
||||
{
|
||||
"due_date": add_days(today, -5),
|
||||
"invoice_portion": 50,
|
||||
"payment_amount": si_with_payment_schedule.grand_total / 2
|
||||
},
|
||||
{
|
||||
"due_date": add_days(today, 5),
|
||||
"invoice_portion": 50,
|
||||
"payment_amount": si_with_payment_schedule.grand_total / 2
|
||||
}
|
||||
])
|
||||
si_with_payment_schedule.submit()
|
||||
|
||||
|
||||
for invoice in (si, si_with_payment_schedule):
|
||||
invoice.db_set("status", "Unpaid")
|
||||
update_invoice_status()
|
||||
invoice.reload()
|
||||
self.assertEqual(invoice.status, "Overdue")
|
||||
|
||||
invoice.db_set("status", "Unpaid and Discounted")
|
||||
update_invoice_status()
|
||||
invoice.reload()
|
||||
self.assertEqual(invoice.status, "Overdue and Discounted")
|
||||
|
||||
|
||||
def test_sales_commission(self):
|
||||
si = frappe.copy_doc(test_records[0])
|
||||
item = copy.deepcopy(si.get('items')[0])
|
||||
item.update({
|
||||
"qty": 1,
|
||||
"rate": 500,
|
||||
"grant_commission": 1
|
||||
})
|
||||
si.append("items", item)
|
||||
|
||||
# Test valid values
|
||||
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
|
||||
si.commission_rate = commission_rate
|
||||
si.save()
|
||||
self.assertEqual(si.amount_eligible_for_commission, 500)
|
||||
self.assertEqual(si.total_commission, total_commission)
|
||||
|
||||
# Test invalid values
|
||||
for commission_rate in (101, -1):
|
||||
si.reload()
|
||||
si.commission_rate = commission_rate
|
||||
self.assertRaises(frappe.ValidationError, si.save)
|
||||
|
||||
def test_sales_invoice_submission_post_account_freezing_date(self):
|
||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
@ -2418,6 +2456,32 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
||||
|
||||
def test_over_billing_case_against_delivery_note(self):
|
||||
'''
|
||||
Test a case where duplicating the item with qty = 1 in the invoice
|
||||
allows overbilling even if it is disabled
|
||||
'''
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance')
|
||||
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0)
|
||||
|
||||
dn = create_delivery_note()
|
||||
dn.submit()
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
# make a copy of first item and add it to invoice
|
||||
item_copy = frappe.copy_doc(si.items[0])
|
||||
si.append('items', item_copy)
|
||||
si.save()
|
||||
|
||||
with self.assertRaises(frappe.ValidationError) as err:
|
||||
si.submit()
|
||||
|
||||
self.assertTrue("cannot overbill" in str(err.exception).lower())
|
||||
|
||||
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
|
@ -1,42 +0,0 @@
|
||||
QUnit.module('Sales Invoice');
|
||||
|
||||
QUnit.test("test sales Invoice", function(assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 5},
|
||||
{'item_code': 'Test Product 1'},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{customer_address: 'Test1-Billing'},
|
||||
{shipping_address_name: 'Test1-Shipping'},
|
||||
{contact_person: 'Contact 1-Test Customer 1'},
|
||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is Test'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
||||
// get tax details
|
||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
||||
// get tax account head details
|
||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
||||
// grand_total Calculated
|
||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
||||
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,35 +0,0 @@
|
||||
QUnit.module('Accounts');
|
||||
|
||||
QUnit.test("test sales invoice with margin", function(assert) {
|
||||
assert.expect(3);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{selling_price_list: 'Test-Selling-USD'},
|
||||
{currency: 'USD'},
|
||||
{items: [
|
||||
[
|
||||
{'item_code': 'Test Product 4'},
|
||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{'qty': 1},
|
||||
{'margin_type': 'Percentage'},
|
||||
{'margin_rate_or_amount': 20}
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.items[0].rate_with_margin == 240, "Margin rate correct");
|
||||
assert.ok(cur_frm.doc.items[0].base_rate_with_margin == cur_frm.doc.conversion_rate * 240, "Base margin rate correct");
|
||||
assert.ok(cur_frm.doc.total == 240, "Amount correct");
|
||||
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
QUnit.module('Sales Invoice');
|
||||
|
||||
QUnit.test("test sales Invoice with payment", function(assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 5},
|
||||
{'item_code': 'Test Product 1'},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{customer_address: 'Test1-Billing'},
|
||||
{shipping_address_name: 'Test1-Shipping'},
|
||||
{contact_person: 'Contact 1-Test Customer 1'},
|
||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is Test'},
|
||||
{payment_terms_template: '_Test Payment Term Template UI'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
||||
// get tax details
|
||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
||||
// grand_total Calculated
|
||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
||||
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.tests.click_button('Make'),
|
||||
() => frappe.tests.click_link('Payment'),
|
||||
() => frappe.timeout(0.2),
|
||||
() => { cur_frm.set_value('mode_of_payment','Cash');},
|
||||
() => { cur_frm.set_value('paid_to','Cash - '+frappe.get_abbr(frappe.defaults.get_default('Company')));},
|
||||
() => {cur_frm.set_value('reference_no','TEST1234');},
|
||||
() => {cur_frm.set_value('reference_date',frappe.datetime.add_days(frappe.datetime.nowdate(), 0));},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get payment details
|
||||
assert.ok(cur_frm.doc.paid_amount==590, "Paid Amount Correct");
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
QUnit.module('Sales Invoice');
|
||||
|
||||
QUnit.test("test sales Invoice with payment request", function(assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 5},
|
||||
{'item_code': 'Test Product 1'},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{customer_address: 'Test1-Billing'},
|
||||
{shipping_address_name: 'Test1-Shipping'},
|
||||
{contact_person: 'Contact 1-Test Customer 1'},
|
||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is Test'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
||||
// get tax details
|
||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
||||
// grand_total Calculated
|
||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
||||
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.tests.click_button('Make'),
|
||||
() => frappe.tests.click_link('Payment Request'),
|
||||
() => frappe.timeout(0.2),
|
||||
() => { cur_frm.set_value('print_format','GST Tax Invoice');},
|
||||
() => { cur_frm.set_value('email_to','test@gmail.com');},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get payment details
|
||||
assert.ok(cur_frm.doc.grand_total==590, "grand total Correct");
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
QUnit.module('Sales Invoice');
|
||||
|
||||
QUnit.test("test sales Invoice with serialize item", function(assert) {
|
||||
assert.expect(5);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Sales Invoice', [
|
||||
{customer: 'Test Customer 1'},
|
||||
{items: [
|
||||
[
|
||||
{'qty': 2},
|
||||
{'item_code': 'Test Product 4'},
|
||||
]
|
||||
]},
|
||||
{update_stock:1},
|
||||
{customer_address: 'Test1-Billing'},
|
||||
{shipping_address_name: 'Test1-Shipping'},
|
||||
{contact_person: 'Contact 1-Test Customer 1'},
|
||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
||||
{tc_name: 'Test Term 1'},
|
||||
{terms: 'This is Test'}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 4', "Item name correct");
|
||||
// get tax details
|
||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
||||
// get tax account head details
|
||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
||||
// get batch number
|
||||
assert.ok(cur_frm.doc.items[0].batch_no=='TEST-BATCH-001', " Batch Details correct");
|
||||
// grand_total Calculated
|
||||
assert.ok(cur_frm.doc.grand_total==218, "Grad Total correct");
|
||||
|
||||
},
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(0.3),
|
||||
() => done()
|
||||
]);
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user