Merge branch 'develop' into subcontracting
This commit is contained in:
commit
213113bc00
@ -5,7 +5,7 @@
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
|
@ -26,3 +26,6 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
|
||||
|
||||
# bulk format python code with black
|
||||
494bd9ef78313436f0424b918f200dab8fc7c20b
|
||||
|
||||
# bulk format python code with black
|
||||
baec607ff5905b1c67531096a9cf50ec7ff00a5d
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -12,10 +12,18 @@ Welcome to ERPNext issue tracker! Before creating an issue, please heed the foll
|
||||
|
||||
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 feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
|
||||
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
|
||||
|
||||
|
||||
Please keep in mind that we get many many requests and we can't possibly work on all of them, we prioritize development based on the goals of the product and organization. Feature requests are still welcome as it helps us in research when we do decide to work on the requested feature.
|
||||
|
||||
If you're in urgent need to a feature, please try the following channels to get paid developments done quickly:
|
||||
1. Certified ERPNext partners: https://erpnext.com/partners
|
||||
2. Developer community on ERPNext forums: https://discuss.erpnext.com/c/developers/5
|
||||
3. Telegram group for ERPNext/Frappe development work: https://t.me/erpnext_opps
|
||||
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
7
.github/helper/install.sh
vendored
7
.github/helper/install.sh
vendored
@ -2,6 +2,13 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Check for merge conflicts before proceeding
|
||||
python -m compileall -f "${GITHUB_WORKSPACE}"
|
||||
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
|
||||
then echo "Found merge conflicts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ~ || exit
|
||||
|
||||
sudo apt-get install redis-server libcups2-dev
|
||||
|
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@ -25,7 +25,7 @@ pulls:
|
||||
ready. Thank you for contributing.
|
||||
|
||||
issues:
|
||||
daysUntilStale: 60
|
||||
daysUntilStale: 90
|
||||
daysUntilClose: 7
|
||||
exemptLabels:
|
||||
- valid
|
||||
|
2
.github/try-on-f-cloud-button.svg
vendored
2
.github/try-on-f-cloud-button.svg
vendored
@ -1,4 +1,4 @@
|
||||
<svg width="201" height="60" viewBox="0 0 201 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="4 2 193 52">
|
||||
<g filter="url(#filter0_dd)">
|
||||
<rect x="4" y="2" width="193" height="52" rx="6" fill="#2490EF"/>
|
||||
<path d="M28 22.2891H32.8786V35.5H36.2088V22.2891H41.0874V19.5H28V22.2891Z" fill="white"/>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.3 KiB |
31
.github/workflows/release.yml
vendored
Normal file
31
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js v14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
- name: Create Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GIT_AUTHOR_NAME: "Frappe PR Bot"
|
||||
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
||||
GIT_COMMITTER_NAME: "Frappe PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
||||
run: npx semantic-release
|
18
.github/workflows/server-tests-mariadb.yml
vendored
18
.github/workflows/server-tests-mariadb.yml
vendored
@ -118,10 +118,26 @@ jobs:
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ matrix.container }}
|
||||
path: /home/runner/frappe-bench/sites/coverage.xml
|
||||
|
||||
coverage:
|
||||
name: Coverage Wrap Up
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
name: MariaDB
|
||||
fail_ci_if_error: true
|
||||
files: /home/runner/frappe-bench/sites/coverage.xml
|
||||
verbose: true
|
||||
|
34
.mergify.yml
34
.mergify.yml
@ -88,3 +88,37 @@ pull_request_rules:
|
||||
- version-12-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: Automatic merge on CI success and review
|
||||
conditions:
|
||||
- status-success=linters
|
||||
- status-success=Sider
|
||||
- status-success=Semantic Pull Request
|
||||
- status-success=Patch Test
|
||||
- status-success=Python Unit Tests (1)
|
||||
- status-success=Python Unit Tests (2)
|
||||
- status-success=Python Unit Tests (3)
|
||||
- label!=dont-merge
|
||||
- label!=squash
|
||||
- "#approved-reviews-by>=1"
|
||||
actions:
|
||||
merge:
|
||||
method: merge
|
||||
- name: Automatic squash on CI success and review
|
||||
conditions:
|
||||
- status-success=linters
|
||||
- status-success=Sider
|
||||
- status-success=Patch Test
|
||||
- status-success=Python Unit Tests (1)
|
||||
- status-success=Python Unit Tests (2)
|
||||
- status-success=Python Unit Tests (3)
|
||||
- label!=dont-merge
|
||||
- label=squash
|
||||
- "#approved-reviews-by>=1"
|
||||
actions:
|
||||
merge:
|
||||
method: squash
|
||||
commit_message_template: |
|
||||
{{ title }} (#{{ number }})
|
||||
|
||||
{{ body }}
|
||||
|
24
.releaserc
Normal file
24
.releaserc
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"branches": ["version-13"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
"releaseRules": [
|
||||
{"breaking": true, "release": false}
|
||||
]
|
||||
},
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec", {
|
||||
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git", {
|
||||
"assets": ["erpnext/__init__.py"],
|
||||
"message": "chore(release): Bumped to Version ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
79
README.md
79
README.md
@ -1,11 +1,14 @@
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/frappe/erpnext/develop/erpnext/public/images/erpnext-logo.png" height="128">
|
||||
<a href="https://erpnext.com">
|
||||
<img src="https://raw.githubusercontent.com/frappe/erpnext/develop/erpnext/public/images/erpnext-logo.png" height="128">
|
||||
</a>
|
||||
<h2>ERPNext</h2>
|
||||
<p align="center">
|
||||
<p>ERP made simple</p>
|
||||
</p>
|
||||
|
||||
[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
||||
[](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
|
||||
[](https://www.codetriage.com/frappe/erpnext)
|
||||
[](https://codecov.io/gh/frappe/erpnext)
|
||||
[](https://hub.docker.com/r/frappe/erpnext-worker)
|
||||
@ -31,40 +34,39 @@ ERPNext as a monolith includes the following areas for managing businesses:
|
||||
1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext)
|
||||
1. [And More](https://erpnext.com/docs/user/manual/en/)
|
||||
|
||||
ERPNext requires MariaDB.
|
||||
|
||||
ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript.
|
||||
|
||||
- [User Guide](https://erpnext.com/docs/user)
|
||||
- [Discussion Forum](https://discuss.erpnext.com/)
|
||||
## Installation
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<a href="https://frappecloud.com/deploy?apps=frappe,erpnext&source=erpnext_readme">
|
||||
<div align="center" style="max-height: 40px;">
|
||||
<a href="https://frappecloud.com/erpnext/signup">
|
||||
<img src=".github/try-on-f-cloud-button.svg" height="40">
|
||||
</a>
|
||||
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/main/pwd.yml">
|
||||
<img src="https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png" alt="Try in PWD" height="37"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
> Login for the PWD site: (username: Administrator, password: admin)
|
||||
|
||||
### Containerized Installation
|
||||
|
||||
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
|
||||
|
||||
### Full Install
|
||||
### Manual Install
|
||||
|
||||
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
||||
|
||||
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
## Learning and community
|
||||
|
||||
GNU/General Public License (see [license.txt](license.txt))
|
||||
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
|
||||
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
|
||||
3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers.
|
||||
4. [Telegram Group](https://t.me/erpnexthelp) - Get instant help from huge community of users.
|
||||
|
||||
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
@ -72,49 +74,14 @@ The ERPNext code is licensed as GNU General Public License (v3) and the Document
|
||||
1. [Report Security Vulnerabilities](https://erpnext.com/security)
|
||||
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
|
||||
1. [Translations](https://translate.erpnext.com)
|
||||
1. [Chart of Accounts](https://charts.erpnext.com)
|
||||
|
||||
---
|
||||
|
||||
## Learning
|
||||
## License
|
||||
|
||||
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
|
||||
GNU/General Public License (see [license.txt](license.txt))
|
||||
|
||||
---
|
||||
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
|
||||
|
||||
## Logo and Trademark
|
||||
## Logo and Trademark Policy
|
||||
|
||||
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
|
||||
|
||||
### Introduction
|
||||
|
||||
Frappe Technologies Pvt. Ltd. (Frappe) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind:
|
||||
|
||||
- We’d like to make it easy for anyone to use the ERPNext name or logo for community-oriented efforts that help spread and improve ERPNext.
|
||||
- We’d like to make it clear how ERPNext-related businesses and projects can (and cannot) use the ERPNext name and logo.
|
||||
- We’d like to make it hard for anyone to use the ERPNext name and logo to unfairly profit from, trick or confuse people who are looking for official ERPNext resources.
|
||||
|
||||
### Frappe Trademark Usage Policy
|
||||
|
||||
Permission from Frappe is required to use the ERPNext name or logo as part of any project, product, service, domain or company name.
|
||||
|
||||
We will grant permission to use the ERPNext name and logo for projects that meet the following criteria:
|
||||
|
||||
- The primary purpose of your project is to promote the spread and improvement of the ERPNext software.
|
||||
- Your project is non-commercial in nature (it can make money to cover its costs or contribute to non-profit entities, but it cannot be run as a for-profit project or business).
|
||||
Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
|
||||
- If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
|
||||
|
||||
Use of the ERPNext name and logo is additionally allowed in the following situations:
|
||||
|
||||
All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by ERPNext or Frappe Technologies or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.”
|
||||
|
||||
Similarly, it’s OK to use the ERPNext logo as part of a page that describes your products or services, but it is not OK to use it as part of your company or product logo or branding itself. Under no circumstances is it permitted to use ERPNext as part of a top-level domain name.
|
||||
|
||||
We do not allow the use of the trademark in advertising, including AdSense/AdWords.
|
||||
|
||||
Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
|
||||
|
||||
When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification.
|
||||
|
||||
(inspired by WordPress)
|
||||
Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md).
|
||||
|
36
TRADEMARK_POLICY.md
Normal file
36
TRADEMARK_POLICY.md
Normal file
@ -0,0 +1,36 @@
|
||||
## Logo and Trademark Policy
|
||||
|
||||
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
|
||||
|
||||
### Introduction
|
||||
|
||||
Frappe Technologies Pvt. Ltd. (Frappe) owns and oversees the trademarks for the ERPNext name and logos. We have developed this trademark usage policy with the following goals in mind:
|
||||
|
||||
- We’d like to make it easy for anyone to use the ERPNext name or logo for community-oriented efforts that help spread and improve ERPNext.
|
||||
- We’d like to make it clear how ERPNext-related businesses and projects can (and cannot) use the ERPNext name and logo.
|
||||
- We’d like to make it hard for anyone to use the ERPNext name and logo to unfairly profit from, trick or confuse people who are looking for official ERPNext resources.
|
||||
|
||||
### Frappe Trademark Usage Policy
|
||||
|
||||
Permission from Frappe is required to use the ERPNext name or logo as part of any project, product, service, domain or company name.
|
||||
|
||||
We will grant permission to use the ERPNext name and logo for projects that meet the following criteria:
|
||||
|
||||
- The primary purpose of your project is to promote the spread and improvement of the ERPNext software.
|
||||
- Your project is non-commercial in nature (it can make money to cover its costs or contribute to non-profit entities, but it cannot be run as a for-profit project or business).
|
||||
Your project neither promotes nor is associated with entities that currently fail to comply with the GPL license under which ERPNext is distributed.
|
||||
- If your project meets these criteria, you will be permitted to use the ERPNext name and logo to promote your project in any way you see fit with one exception: Please do not use ERPNext as part of a domain name.
|
||||
|
||||
Use of the ERPNext name and logo is additionally allowed in the following situations:
|
||||
|
||||
All other ERPNext-related businesses or projects can use the ERPNext name and logo to refer to and explain their services, but they cannot use them as part of a product, project, service, domain, or company name and they cannot use them in any way that suggests an affiliation with or endorsement by ERPNext or Frappe Technologies or the ERPNext open source project. For example, a consulting company can describe its business as “123 Web Services, offering ERPNext consulting for small businesses,” but cannot call its business “The ERPNext Consulting Company.”
|
||||
|
||||
Similarly, it’s OK to use the ERPNext logo as part of a page that describes your products or services, but it is not OK to use it as part of your company or product logo or branding itself. Under no circumstances is it permitted to use ERPNext as part of a top-level domain name.
|
||||
|
||||
We do not allow the use of the trademark in advertising, including AdSense/AdWords.
|
||||
|
||||
Please note that it is not the goal of this policy to limit commercial activity around ERPNext. We encourage ERPNext-based businesses, and we would love to see hundreds of them.
|
||||
|
||||
When in doubt about your use of the ERPNext name or logo, please contact Frappe Technologies for clarification.
|
||||
|
||||
(inspired by WordPress)
|
@ -21,7 +21,6 @@ coverage:
|
||||
comment:
|
||||
layout: "diff, files"
|
||||
require_changes: true
|
||||
after_n_builds: 3
|
||||
|
||||
ignore:
|
||||
- "erpnext/demo"
|
||||
|
@ -386,7 +386,6 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
end_date,
|
||||
@ -540,19 +539,11 @@ def make_gl_entries(
|
||||
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,
|
||||
)
|
||||
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
|
||||
raise e
|
||||
else:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(
|
||||
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
|
||||
message=traceback,
|
||||
)
|
||||
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
|
||||
|
||||
@ -570,7 +561,6 @@ def book_revenue_via_journal_entry(
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
posting_date,
|
||||
@ -591,6 +581,7 @@ def book_revenue_via_journal_entry(
|
||||
journal_entry.voucher_type = (
|
||||
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
|
||||
)
|
||||
journal_entry.process_deferred_accounting = deferred_process
|
||||
|
||||
debit_entry = {
|
||||
"account": credit_account,
|
||||
@ -633,12 +624,7 @@ def book_revenue_via_journal_entry(
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(
|
||||
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
|
||||
message=traceback,
|
||||
)
|
||||
|
||||
doc.log_error(f"Error while processing deferred accounting for Invoice {doc.name}")
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
|
||||
|
||||
|
@ -160,7 +160,7 @@ frappe.treeview_settings["Account"] = {
|
||||
let root_company = treeview.page.fields_dict.root_company.get_value();
|
||||
|
||||
if(root_company) {
|
||||
frappe.throw(__("Please add the account to root level Company - ") + root_company);
|
||||
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
|
||||
} else {
|
||||
treeview.new_node();
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
if doctype == "Budget":
|
||||
add_dimension_to_budget_doctype(df.copy(), doc)
|
||||
else:
|
||||
create_custom_field(doctype, df)
|
||||
create_custom_field(doctype, df, ignore_validate=True)
|
||||
|
||||
count += 1
|
||||
|
||||
@ -115,7 +115,7 @@ def add_dimension_to_budget_doctype(df, doc):
|
||||
}
|
||||
)
|
||||
|
||||
create_custom_field("Budget", df)
|
||||
create_custom_field("Budget", df, ignore_validate=True)
|
||||
|
||||
property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
|
||||
|
||||
@ -205,10 +205,16 @@ def get_doctypes_with_dimensions():
|
||||
return frappe.get_hooks("accounting_dimension_doctypes")
|
||||
|
||||
|
||||
def get_accounting_dimensions(as_list=True):
|
||||
def get_accounting_dimensions(as_list=True, filters=None):
|
||||
|
||||
if not filters:
|
||||
filters = {"disabled": 0}
|
||||
|
||||
if frappe.flags.accounting_dimensions is None:
|
||||
frappe.flags.accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters=filters,
|
||||
)
|
||||
|
||||
if as_list:
|
||||
@ -228,17 +234,19 @@ def get_checks_for_pl_and_bs_accounts():
|
||||
return dimensions
|
||||
|
||||
|
||||
def get_dimension_with_children(doctype, dimension):
|
||||
def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
if isinstance(dimension, list):
|
||||
dimension = dimension[0]
|
||||
if isinstance(dimensions, str):
|
||||
dimensions = [dimensions]
|
||||
|
||||
all_dimensions = []
|
||||
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
||||
children = frappe.get_all(
|
||||
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
|
||||
)
|
||||
all_dimensions += [c.name for c in children]
|
||||
|
||||
for dimension in dimensions:
|
||||
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
||||
children = frappe.get_all(
|
||||
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
|
||||
)
|
||||
all_dimensions += [c.name for c in children]
|
||||
|
||||
return all_dimensions
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
"automatically_fetch_payment_terms",
|
||||
"column_break_17",
|
||||
"enable_common_party_accounting",
|
||||
"enable_discount_accounting",
|
||||
"report_setting_section",
|
||||
"use_custom_cash_flow",
|
||||
"deferred_accounting_settings_section",
|
||||
@ -272,13 +271,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Ledger Entries for Change Amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||
"fieldname": "enable_discount_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discount Accounting"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
||||
@ -354,7 +346,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-04 12:32:36.805652",
|
||||
"modified": "2022-04-08 14:45:06.796418",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -28,7 +28,6 @@ class AccountsSettings(Document):
|
||||
|
||||
self.validate_stale_days()
|
||||
self.enable_payment_schedule_in_print()
|
||||
self.toggle_discount_accounting_fields()
|
||||
self.validate_pending_reposts()
|
||||
|
||||
def validate_stale_days(self):
|
||||
@ -52,74 +51,6 @@ class AccountsSettings(Document):
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
def toggle_discount_accounting_fields(self):
|
||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||
|
||||
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
"discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
"additional_discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
doctype,
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
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)
|
||||
|
@ -27,7 +27,6 @@
|
||||
"bank_account_no",
|
||||
"address_and_contact",
|
||||
"address_html",
|
||||
"website",
|
||||
"column_break_13",
|
||||
"contact_html",
|
||||
"integration_details_section",
|
||||
@ -156,11 +155,6 @@
|
||||
"fieldtype": "HTML",
|
||||
"label": "Address HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "website",
|
||||
"fieldtype": "Data",
|
||||
"label": "Website"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
@ -208,7 +202,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-10-23 16:48:06.303658",
|
||||
"modified": "2022-05-04 15:49:42.620630",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Account",
|
||||
@ -243,5 +237,6 @@
|
||||
"search_fields": "bank,account",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -5,7 +5,10 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, fmt_money, getdate, nowdate
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, getdate
|
||||
|
||||
import erpnext
|
||||
|
||||
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
|
||||
|
||||
@ -76,6 +79,53 @@ class BankClearance(Document):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
|
||||
|
||||
loan_disbursements = (
|
||||
frappe.qb.from_(loan_disbursement)
|
||||
.select(
|
||||
ConstantColumn("Loan Disbursement").as_("payment_document"),
|
||||
loan_disbursement.name.as_("payment_entry"),
|
||||
loan_disbursement.disbursed_amount.as_("credit"),
|
||||
ConstantColumn(0).as_("debit"),
|
||||
loan_disbursement.reference_number.as_("cheque_number"),
|
||||
loan_disbursement.reference_date.as_("cheque_date"),
|
||||
loan_disbursement.disbursement_date.as_("posting_date"),
|
||||
loan_disbursement.applicant.as_("against_account"),
|
||||
)
|
||||
.where(loan_disbursement.docstatus == 1)
|
||||
.where(loan_disbursement.disbursement_date >= self.from_date)
|
||||
.where(loan_disbursement.disbursement_date <= self.to_date)
|
||||
.where(loan_disbursement.clearance_date.isnull())
|
||||
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
||||
.orderby(loan_disbursement.disbursement_date)
|
||||
.orderby(loan_disbursement.name, frappe.qb.desc)
|
||||
).run(as_dict=1)
|
||||
|
||||
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||
|
||||
loan_repayments = (
|
||||
frappe.qb.from_(loan_repayment)
|
||||
.select(
|
||||
ConstantColumn("Loan Repayment").as_("payment_document"),
|
||||
loan_repayment.name.as_("payment_entry"),
|
||||
loan_repayment.amount_paid.as_("debit"),
|
||||
ConstantColumn(0).as_("credit"),
|
||||
loan_repayment.reference_number.as_("cheque_number"),
|
||||
loan_repayment.reference_date.as_("cheque_date"),
|
||||
loan_repayment.applicant.as_("against_account"),
|
||||
loan_repayment.posting_date,
|
||||
)
|
||||
.where(loan_repayment.docstatus == 1)
|
||||
.where(loan_repayment.clearance_date.isnull())
|
||||
.where(loan_repayment.repay_from_salary == 0)
|
||||
.where(loan_repayment.posting_date >= self.from_date)
|
||||
.where(loan_repayment.posting_date <= self.to_date)
|
||||
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
|
||||
.orderby(loan_repayment.posting_date)
|
||||
.orderby(loan_repayment.name, frappe.qb.desc)
|
||||
).run(as_dict=1)
|
||||
|
||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||
if self.include_pos_transactions:
|
||||
pos_sales_invoices = frappe.db.sql(
|
||||
@ -114,20 +164,29 @@ class BankClearance(Document):
|
||||
|
||||
entries = sorted(
|
||||
list(payment_entries)
|
||||
+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
|
||||
key=lambda k: k["posting_date"] or getdate(nowdate()),
|
||||
+ list(journal_entries)
|
||||
+ list(pos_sales_invoices)
|
||||
+ list(pos_purchase_invoices)
|
||||
+ list(loan_disbursements)
|
||||
+ list(loan_repayments),
|
||||
key=lambda k: getdate(k["posting_date"]),
|
||||
)
|
||||
|
||||
self.set("payment_entries", [])
|
||||
self.total_amount = 0.0
|
||||
default_currency = erpnext.get_default_currency()
|
||||
|
||||
for d in entries:
|
||||
row = self.append("payment_entries", {})
|
||||
|
||||
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
|
||||
|
||||
if not d.get("account_currency"):
|
||||
d.account_currency = default_currency
|
||||
|
||||
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
|
||||
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
|
||||
d.posting_date = getdate(d.posting_date)
|
||||
|
||||
d.pop("credit")
|
||||
d.pop("debit")
|
||||
|
@ -1,9 +1,96 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_months, getdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.loan_management.doctype.loan.test_loan import (
|
||||
create_loan,
|
||||
create_loan_accounts,
|
||||
create_loan_type,
|
||||
create_repayment_entry,
|
||||
make_loan_disbursement_entry,
|
||||
)
|
||||
|
||||
|
||||
class TestBankClearance(unittest.TestCase):
|
||||
pass
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_bank_account()
|
||||
create_loan_accounts()
|
||||
create_loan_masters()
|
||||
add_transactions()
|
||||
|
||||
# Basic test case to test if bank clearance tool doesn't break
|
||||
# Detailed test can be added later
|
||||
def test_bank_clearance(self):
|
||||
bank_clearance = frappe.get_doc("Bank Clearance")
|
||||
bank_clearance.account = "_Test Bank Clearance - _TC"
|
||||
bank_clearance.from_date = add_months(getdate(), -1)
|
||||
bank_clearance.to_date = getdate()
|
||||
bank_clearance.get_payment_entries()
|
||||
self.assertEqual(len(bank_clearance.payment_entries), 3)
|
||||
|
||||
|
||||
def make_bank_account():
|
||||
if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_type": "Bank",
|
||||
"account_name": "_Test Bank Clearance",
|
||||
"company": "_Test Company",
|
||||
"parent_account": "Bank Accounts - _TC",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_loan_masters():
|
||||
create_loan_type(
|
||||
"Clearance Loan",
|
||||
2000000,
|
||||
13.5,
|
||||
25,
|
||||
0,
|
||||
5,
|
||||
"Cash",
|
||||
"_Test Bank Clearance - _TC",
|
||||
"_Test Bank Clearance - _TC",
|
||||
"Loan Account - _TC",
|
||||
"Interest Income Account - _TC",
|
||||
"Penalty Income Account - _TC",
|
||||
)
|
||||
|
||||
|
||||
def add_transactions():
|
||||
make_payment_entry()
|
||||
make_loan()
|
||||
|
||||
|
||||
def make_loan():
|
||||
loan = create_loan(
|
||||
"_Test Customer",
|
||||
"Clearance Loan",
|
||||
280000,
|
||||
"Repay Over Number of Periods",
|
||||
20,
|
||||
applicant_type="Customer",
|
||||
)
|
||||
loan.submit()
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
|
||||
repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
|
||||
repayment_entry.save()
|
||||
repayment_entry.submit()
|
||||
|
||||
|
||||
def make_payment_entry():
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
|
||||
pe.reference_no = "Conrad Oct 18"
|
||||
pe.reference_date = "2018-10-24"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
@ -467,6 +467,7 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
|
||||
loan_repayment.posting_date,
|
||||
)
|
||||
.where(loan_repayment.docstatus == 1)
|
||||
.where(loan_repayment.repay_from_salary == 0)
|
||||
.where(loan_repayment.clearance_date.isnull())
|
||||
.where(loan_repayment.payment_account == bank_account)
|
||||
)
|
||||
|
@ -200,7 +200,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.length > 0) {
|
||||
frm.add_custom_button("Report Error", () => {
|
||||
frm.add_custom_button(__("Report Error"), () => {
|
||||
let fake_xhr = {
|
||||
responseText: JSON.stringify({
|
||||
exc: result[0].error,
|
||||
|
@ -136,7 +136,7 @@ def start_import(
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
data_import.db_set("status", "Error")
|
||||
frappe.log_error(title=data_import.name)
|
||||
data_import.log_error("Bank Statement Import failed")
|
||||
finally:
|
||||
frappe.flags.in_import = False
|
||||
|
||||
|
@ -56,7 +56,7 @@ def create_bank_entries(columns, data, bank_account):
|
||||
bank_transaction.submit()
|
||||
success += 1
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
bank_transaction.log_error("Bank entry creation failed")
|
||||
errors += 1
|
||||
|
||||
return {"success": success, "errors": errors}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
@ -16,6 +17,6 @@ class CashFlowMapping(Document):
|
||||
]
|
||||
if len(checked_fields) > 1:
|
||||
frappe.throw(
|
||||
frappe._("You can only select a maximum of one option from the list of check boxes."),
|
||||
title="Error",
|
||||
_("You can only select a maximum of one option from the list of check boxes."),
|
||||
title=_("Error"),
|
||||
)
|
||||
|
@ -11,6 +11,8 @@ from frappe.utils import nowdate
|
||||
class CurrencyExchangeSettings(Document):
|
||||
def validate(self):
|
||||
self.set_parameters_and_result()
|
||||
if frappe.flags.in_test or frappe.flags.in_install or frappe.flags.in_setup_wizard:
|
||||
return
|
||||
response, value = self.validate_parameters()
|
||||
self.validate_result(response, value)
|
||||
|
||||
@ -35,9 +37,6 @@ class CurrencyExchangeSettings(Document):
|
||||
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(
|
||||
@ -59,18 +58,14 @@ class CurrencyExchangeSettings(Document):
|
||||
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)
|
||||
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))
|
||||
|
@ -3,6 +3,6 @@
|
||||
|
||||
frappe.ui.form.on('GL Entry', {
|
||||
refresh: function(frm) {
|
||||
|
||||
frm.page.btn_secondary.hide()
|
||||
}
|
||||
});
|
||||
|
@ -269,6 +269,11 @@ class GLEntry(Document):
|
||||
if not self.fiscal_year:
|
||||
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
|
||||
|
||||
def on_cancel(self):
|
||||
msg = _("Individual GL Entry cannot be cancelled.")
|
||||
msg += "<br>" + _("Please cancel related transaction.")
|
||||
frappe.throw(msg)
|
||||
|
||||
|
||||
def validate_balance_type(account, adv_adj=False):
|
||||
if not adv_adj and account:
|
||||
|
@ -10,6 +10,7 @@
|
||||
"sgst_account",
|
||||
"igst_account",
|
||||
"cess_account",
|
||||
"utgst_account",
|
||||
"is_reverse_charge_account"
|
||||
],
|
||||
"fields": [
|
||||
@ -64,12 +65,18 @@
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Reverse Charge Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "utgst_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "UTGST Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-09 12:30:25.889993",
|
||||
"modified": "2022-04-07 12:59:14.039768",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST Account",
|
||||
@ -78,5 +85,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-03-25 10:53:52",
|
||||
"creation": "2022-01-25 10:29:58.717206",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
@ -13,6 +13,7 @@
|
||||
"voucher_type",
|
||||
"naming_series",
|
||||
"finance_book",
|
||||
"process_deferred_accounting",
|
||||
"reversal_of",
|
||||
"tax_withholding_category",
|
||||
"column_break1",
|
||||
@ -524,13 +525,20 @@
|
||||
"label": "Reversal Of",
|
||||
"options": "Journal Entry",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "process_deferred_accounting",
|
||||
"fieldtype": "Link",
|
||||
"label": "Process Deferred Accounting",
|
||||
"options": "Process Deferred Accounting",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-04 13:39:36.485954",
|
||||
"modified": "2022-04-06 17:18:46.865259",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
@ -578,6 +586,7 @@
|
||||
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
@ -18,7 +18,6 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.utils import (
|
||||
check_if_stock_and_account_balance_synced,
|
||||
get_account_currency,
|
||||
get_balance_on,
|
||||
get_stock_accounts,
|
||||
@ -88,9 +87,6 @@ class JournalEntry(AccountsController):
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
self.update_status_for_full_and_final_statement()
|
||||
check_if_stock_and_account_balance_synced(
|
||||
self.posting_date, self.company, self.doctype, self.name
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
||||
|
@ -62,7 +62,7 @@ def start_merge(docname):
|
||||
)
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error(title=ledger_merge.name)
|
||||
ledger_merge.log_error("Ledger merge failed")
|
||||
finally:
|
||||
if successful_merges == total:
|
||||
ledger_merge.db_set("status", "Success")
|
||||
|
@ -42,12 +42,7 @@ class ModeofPayment(Document):
|
||||
pos_profiles = list(map(lambda x: x[0], pos_profiles))
|
||||
|
||||
if pos_profiles:
|
||||
message = (
|
||||
"POS Profile "
|
||||
+ frappe.bold(", ".join(pos_profiles))
|
||||
+ " contains \
|
||||
Mode of Payment "
|
||||
+ frappe.bold(str(self.name))
|
||||
+ ". Please remove them to disable this mode."
|
||||
)
|
||||
frappe.throw(_(message), title="Not Allowed")
|
||||
message = _(
|
||||
"POS Profile {} contains Mode of Payment {}. Please remove them to disable this mode."
|
||||
).format(frappe.bold(", ".join(pos_profiles)), frappe.bold(str(self.name)))
|
||||
frappe.throw(message, title=_("Not Allowed"))
|
||||
|
@ -2,9 +2,6 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import traceback
|
||||
from json import dumps
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.model.document import Document
|
||||
@ -114,10 +111,13 @@ class OpeningInvoiceCreationTool(Document):
|
||||
)
|
||||
or {}
|
||||
)
|
||||
|
||||
default_currency = frappe.db.get_value(row.party_type, row.party, "default_currency")
|
||||
|
||||
if company_details:
|
||||
invoice.update(
|
||||
{
|
||||
"currency": company_details.get("default_currency"),
|
||||
"currency": default_currency or company_details.get("default_currency"),
|
||||
"letter_head": company_details.get("default_letter_head"),
|
||||
}
|
||||
)
|
||||
@ -244,11 +244,7 @@ def start_import(invoices):
|
||||
except Exception:
|
||||
errors += 1
|
||||
frappe.db.rollback()
|
||||
message = "\n".join(
|
||||
["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]
|
||||
)
|
||||
frappe.log_error(title="Error while creating Opening Invoice", message=message)
|
||||
frappe.db.commit()
|
||||
doc.log_error("Opening invoice creation failed")
|
||||
if errors:
|
||||
frappe.msgprint(
|
||||
_("You had {} errors while creating opening invoices. Check {} for more details").format(
|
||||
|
@ -3,6 +3,7 @@
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"account"
|
||||
@ -11,6 +12,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
@ -27,7 +29,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-07 18:13:08.833822",
|
||||
"modified": "2022-04-04 12:31:02.994197",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
@ -35,5 +37,6 @@
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
@ -112,8 +112,6 @@ frappe.ui.form.on('Payment Entry', {
|
||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||
} else if (frm.doc.party_type == "Employee") {
|
||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
||||
} else if (frm.doc.party_type == "Student") {
|
||||
var doctypes = ["Fees"];
|
||||
} else {
|
||||
var doctypes = ["Journal Entry"];
|
||||
}
|
||||
@ -224,10 +222,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
(frm.doc.total_allocated_amount > party_amount)));
|
||||
|
||||
frm.toggle_display("set_exchange_gain_loss",
|
||||
(frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount &&
|
||||
((frm.doc.paid_from_account_currency != company_currency ||
|
||||
frm.doc.paid_to_account_currency != company_currency) &&
|
||||
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)));
|
||||
frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount);
|
||||
|
||||
frm.refresh_fields();
|
||||
},
|
||||
@ -758,8 +753,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
if(
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee")
|
||||
) {
|
||||
if(total_positive_outstanding > total_negative_outstanding)
|
||||
if (!frm.doc.paid_amount)
|
||||
@ -801,8 +795,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
if (
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee")
|
||||
) {
|
||||
if(total_positive_outstanding_including_order > paid_amount) {
|
||||
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
||||
|
@ -183,9 +183,7 @@ class PaymentEntry(AccountsController):
|
||||
if not self.party:
|
||||
frappe.throw(_("Party is mandatory"))
|
||||
|
||||
_party_name = (
|
||||
"title" if self.party_type in ("Student", "Shareholder") else self.party_type.lower() + "_name"
|
||||
)
|
||||
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||
|
||||
if self.party:
|
||||
@ -298,9 +296,7 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
|
||||
|
||||
def validate_reference_documents(self):
|
||||
if self.party_type == "Student":
|
||||
valid_reference_doctypes = "Fees"
|
||||
elif self.party_type == "Customer":
|
||||
if self.party_type == "Customer":
|
||||
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
|
||||
elif self.party_type == "Supplier":
|
||||
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
|
||||
@ -338,8 +334,6 @@ class PaymentEntry(AccountsController):
|
||||
ref_party_account = (
|
||||
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
|
||||
)
|
||||
elif self.party_type == "Student":
|
||||
ref_party_account = ref_doc.receivable_account
|
||||
elif self.party_type == "Supplier":
|
||||
ref_party_account = ref_doc.credit_to
|
||||
elif self.party_type == "Employee":
|
||||
@ -352,6 +346,12 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
|
||||
frappe.throw(
|
||||
_("{0} {1} is on hold").format(d.reference_doctype, d.reference_name),
|
||||
title=_("Invalid Invoice"),
|
||||
)
|
||||
|
||||
if ref_doc.docstatus != 1:
|
||||
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
|
||||
|
||||
@ -1259,20 +1259,19 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
# Get all SO / PO which are not fully billed or against which full advance not paid
|
||||
orders_to_be_billed = []
|
||||
if args.get("party_type") != "Student":
|
||||
orders_to_be_billed = get_orders_to_be_billed(
|
||||
args.get("posting_date"),
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("company"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
filters=args,
|
||||
)
|
||||
orders_to_be_billed = get_orders_to_be_billed(
|
||||
args.get("posting_date"),
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("company"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
filters=args,
|
||||
)
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
negative_outstanding_invoices = []
|
||||
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
|
||||
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
@ -1496,9 +1495,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
|
||||
account_currency = get_account_currency(party_account)
|
||||
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
|
||||
_party_name = (
|
||||
"title" if party_type in ("Student", "Shareholder") else party_type.lower() + "_name"
|
||||
)
|
||||
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
|
||||
party_name = frappe.db.get_value(party_type, party, _party_name)
|
||||
party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center)
|
||||
if party_type in ["Customer", "Supplier"]:
|
||||
@ -1560,7 +1557,7 @@ def get_company_defaults(company):
|
||||
def get_outstanding_on_journal_entry(name):
|
||||
res = frappe.db.sql(
|
||||
"SELECT "
|
||||
'CASE WHEN party_type IN ("Customer", "Student") '
|
||||
'CASE WHEN party_type IN ("Customer") '
|
||||
"THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) "
|
||||
"ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) "
|
||||
"END as outstanding_amount "
|
||||
@ -1917,8 +1914,6 @@ def set_party_type(dt):
|
||||
party_type = "Supplier"
|
||||
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
|
||||
party_type = "Employee"
|
||||
elif dt == "Fees":
|
||||
party_type = "Student"
|
||||
return party_type
|
||||
|
||||
|
||||
|
@ -743,6 +743,21 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
|
||||
)
|
||||
|
||||
def test_payment_entry_against_onhold_purchase_invoice(self):
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = "2016-01-01"
|
||||
|
||||
# block invoice after creating payment entry
|
||||
# since `get_payment_entry` will not attach blocked invoice to payment
|
||||
pi.block_invoice()
|
||||
with self.assertRaises(frappe.ValidationError) as err:
|
||||
pe.save()
|
||||
|
||||
self.assertTrue("is on hold" in str(err.exception).lower())
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
@ -38,6 +38,15 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_query("cost_center", () => {
|
||||
return {
|
||||
"filters": {
|
||||
"company": this.frm.doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
@ -24,6 +24,7 @@
|
||||
"invoice_limit",
|
||||
"payment_limit",
|
||||
"bank_cash_account",
|
||||
"cost_center",
|
||||
"sec_break1",
|
||||
"invoices",
|
||||
"column_break_15",
|
||||
@ -178,13 +179,19 @@
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "icon-resize-horizontal",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-10-04 20:27:11.114194",
|
||||
"modified": "2022-04-29 15:37:10.246831",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation",
|
||||
@ -209,5 +216,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
@ -332,6 +332,9 @@ class PaymentReconciliation(Document):
|
||||
def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
|
||||
condition = " and company = '{0}' ".format(self.company)
|
||||
|
||||
if self.get("cost_center") and (get_invoices or get_payments or get_return_invoices):
|
||||
condition = " and cost_center = '{0}' ".format(self.cost_center)
|
||||
|
||||
if get_invoices:
|
||||
condition += (
|
||||
" and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date))
|
||||
@ -350,9 +353,13 @@ class PaymentReconciliation(Document):
|
||||
)
|
||||
|
||||
if self.minimum_invoice_amount:
|
||||
condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_invoice_amount))
|
||||
condition += " and {dr_or_cr} >= {amount}".format(
|
||||
dr_or_cr=dr_or_cr, amount=flt(self.minimum_invoice_amount)
|
||||
)
|
||||
if self.maximum_invoice_amount:
|
||||
condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_invoice_amount))
|
||||
condition += " and {dr_or_cr} <= {amount}".format(
|
||||
dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
|
||||
)
|
||||
|
||||
elif get_return_invoices:
|
||||
condition = " and doc.company = '{0}' ".format(self.company)
|
||||
@ -367,15 +374,19 @@ class PaymentReconciliation(Document):
|
||||
else ""
|
||||
)
|
||||
dr_or_cr = (
|
||||
"gl.debit_in_account_currency"
|
||||
"debit_in_account_currency"
|
||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
||||
else "gl.credit_in_account_currency"
|
||||
else "credit_in_account_currency"
|
||||
)
|
||||
|
||||
if self.minimum_invoice_amount:
|
||||
condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_payment_amount))
|
||||
condition += " and gl.{dr_or_cr} >= {amount}".format(
|
||||
dr_or_cr=dr_or_cr, amount=flt(self.minimum_payment_amount)
|
||||
)
|
||||
if self.maximum_invoice_amount:
|
||||
condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_payment_amount))
|
||||
condition += " and gl.{dr_or_cr} <= {amount}".format(
|
||||
dr_or_cr=dr_or_cr, amount=flt(self.maximum_payment_amount)
|
||||
)
|
||||
|
||||
else:
|
||||
condition += (
|
||||
|
@ -1,9 +1,96 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_days, getdate
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
|
||||
class TestPaymentReconciliation(unittest.TestCase):
|
||||
pass
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_customer()
|
||||
make_invoice_and_payment()
|
||||
|
||||
def test_payment_reconciliation(self):
|
||||
payment_reco = frappe.get_doc("Payment Reconciliation")
|
||||
payment_reco.company = "_Test Company"
|
||||
payment_reco.party_type = "Customer"
|
||||
payment_reco.party = "_Test Payment Reco Customer"
|
||||
payment_reco.receivable_payable_account = "Debtors - _TC"
|
||||
payment_reco.from_invoice_date = add_days(getdate(), -1)
|
||||
payment_reco.to_invoice_date = getdate()
|
||||
payment_reco.from_payment_date = add_days(getdate(), -1)
|
||||
payment_reco.to_payment_date = getdate()
|
||||
payment_reco.maximum_invoice_amount = 1000
|
||||
payment_reco.maximum_payment_amount = 1000
|
||||
payment_reco.invoice_limit = 10
|
||||
payment_reco.payment_limit = 10
|
||||
payment_reco.bank_cash_account = "_Test Bank - _TC"
|
||||
payment_reco.cost_center = "_Test Cost Center - _TC"
|
||||
payment_reco.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(payment_reco.get("invoices")), 1)
|
||||
self.assertEqual(len(payment_reco.get("payments")), 1)
|
||||
|
||||
payment_entry = payment_reco.get("payments")[0].reference_name
|
||||
invoice = payment_reco.get("invoices")[0].invoice_number
|
||||
|
||||
payment_reco.allocate_entries(
|
||||
{
|
||||
"payments": [payment_reco.get("payments")[0].as_dict()],
|
||||
"invoices": [payment_reco.get("invoices")[0].as_dict()],
|
||||
}
|
||||
)
|
||||
payment_reco.reconcile()
|
||||
|
||||
payment_entry_doc = frappe.get_doc("Payment Entry", payment_entry)
|
||||
self.assertEqual(payment_entry_doc.get("references")[0].reference_name, invoice)
|
||||
|
||||
|
||||
def make_customer():
|
||||
if not frappe.db.get_value("Customer", "_Test Payment Reco Customer"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_name": "_Test Payment Reco Customer",
|
||||
"customer_type": "Individual",
|
||||
"customer_group": "_Test Customer Group",
|
||||
"territory": "_Test Territory",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_invoice_and_payment():
|
||||
si = create_sales_invoice(
|
||||
customer="_Test Payment Reco Customer", qty=1, rate=690, do_not_save=True
|
||||
)
|
||||
si.cost_center = "_Test Cost Center - _TC"
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
pe = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Payment Entry",
|
||||
"payment_type": "Receive",
|
||||
"party_type": "Customer",
|
||||
"party": "_Test Payment Reco Customer",
|
||||
"company": "_Test Company",
|
||||
"paid_from_account_currency": "INR",
|
||||
"paid_to_account_currency": "INR",
|
||||
"source_exchange_rate": 1,
|
||||
"target_exchange_rate": 1,
|
||||
"reference_no": "1",
|
||||
"reference_date": getdate(),
|
||||
"received_amount": 690,
|
||||
"paid_amount": 690,
|
||||
"paid_from": "Debtors - _TC",
|
||||
"paid_to": "_Test Bank - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
}
|
||||
)
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
@ -64,13 +64,15 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
pos_opening_entry(frm) {
|
||||
if (frm.doc.pos_opening_entry && frm.doc.period_start_date && frm.doc.period_end_date && frm.doc.user) {
|
||||
reset_values(frm);
|
||||
frm.trigger("set_opening_amounts");
|
||||
frm.trigger("get_pos_invoices");
|
||||
frappe.run_serially([
|
||||
() => frm.trigger("set_opening_amounts"),
|
||||
() => frm.trigger("get_pos_invoices")
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
set_opening_amounts(frm) {
|
||||
frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry)
|
||||
return frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry)
|
||||
.then(({ balance_details }) => {
|
||||
balance_details.forEach(detail => {
|
||||
frm.add_child("payment_reconciliation", {
|
||||
@ -83,7 +85,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
},
|
||||
|
||||
get_pos_invoices(frm) {
|
||||
frappe.call({
|
||||
return frappe.call({
|
||||
method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices',
|
||||
args: {
|
||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||
|
@ -61,13 +61,13 @@ class POSProfile(Document):
|
||||
|
||||
if len(item_groups) != len(set(item_groups)):
|
||||
frappe.throw(
|
||||
_("Duplicate item group found in the item group table"), title="Duplicate Item Group"
|
||||
_("Duplicate item group found in the item group table"), title=_("Duplicate Item Group")
|
||||
)
|
||||
|
||||
if len(customer_groups) != len(set(customer_groups)):
|
||||
frappe.throw(
|
||||
_("Duplicate customer group found in the cutomer group table"),
|
||||
title="Duplicate Customer Group",
|
||||
title=_("Duplicate Customer Group"),
|
||||
)
|
||||
|
||||
def validate_payment_methods(self):
|
||||
|
@ -35,10 +35,11 @@ class PricingRule(Document):
|
||||
self.margin_rate_or_amount = 0.0
|
||||
|
||||
def validate_duplicate_apply_on(self):
|
||||
field = apply_on_dict.get(self.apply_on)
|
||||
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
|
||||
if len(values) != len(set(values)):
|
||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||
if self.apply_on != "Transaction":
|
||||
field = apply_on_dict.get(self.apply_on)
|
||||
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
|
||||
if len(values) != len(set(values)):
|
||||
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
|
||||
|
||||
def validate_mandatory(self):
|
||||
for apply_on, field in apply_on_dict.items():
|
||||
|
@ -11,7 +11,7 @@ from erpnext.accounts.deferred_revenue import (
|
||||
convert_deferred_expense_to_expense,
|
||||
convert_deferred_revenue_to_income,
|
||||
)
|
||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
|
||||
class ProcessDeferredAccounting(Document):
|
||||
@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document):
|
||||
filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
|
||||
)
|
||||
|
||||
make_reverse_gl_entries(gl_entries=gl_entries)
|
||||
make_gl_entries(gl_entries=gl_entries, cancel=1)
|
||||
|
@ -8,7 +8,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
},
|
||||
refresh: function(frm){
|
||||
if(!frm.doc.__islocal) {
|
||||
frm.add_custom_button('Send Emails',function(){
|
||||
frm.add_custom_button(__('Send Emails'), function(){
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
||||
args: {
|
||||
@ -24,7 +24,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
}
|
||||
});
|
||||
});
|
||||
frm.add_custom_button('Download',function(){
|
||||
frm.add_custom_button(__('Download'), function(){
|
||||
var url = frappe.urllib.get_full_url(
|
||||
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
||||
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
||||
|
@ -34,8 +34,9 @@ class ProcessStatementOfAccounts(Document):
|
||||
frappe.throw(_("Customers not selected."))
|
||||
|
||||
if self.enable_auto_email:
|
||||
self.to_date = self.start_date
|
||||
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
|
||||
if self.start_date and getdate(self.start_date) >= getdate(today()):
|
||||
self.to_date = self.start_date
|
||||
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
|
||||
|
||||
|
||||
def get_report_pdf(doc, consolidated=True):
|
||||
|
@ -30,6 +30,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
onload() {
|
||||
super.onload();
|
||||
|
||||
// Ignore linked advances
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry'];
|
||||
|
||||
if(!this.frm.doc.__islocal) {
|
||||
// show credit_to in print format
|
||||
if(!this.frm.doc.supplier && this.frm.doc.credit_to) {
|
||||
@ -570,6 +573,10 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
});
|
||||
},
|
||||
|
||||
is_subcontracted: function(frm) {
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
|
||||
},
|
||||
|
||||
update_stock: function(frm) {
|
||||
hide_fields(frm.doc);
|
||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
||||
|
@ -668,7 +668,7 @@ class PurchaseInvoice(BuyingController):
|
||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
@ -810,7 +810,9 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if provisional_accounting_for_non_stock_items:
|
||||
if item.purchase_receipt:
|
||||
provisional_account = self.get_company_default("default_provisional_account")
|
||||
provisional_account = frappe.db.get_value(
|
||||
"Purchase Receipt Item", item.pr_detail, "provisional_expense_account"
|
||||
) or self.get_company_default("default_provisional_account")
|
||||
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
|
||||
|
||||
if not purchase_receipt_doc:
|
||||
@ -833,7 +835,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if expense_booked_in_pr:
|
||||
# Intentionally passing purchase invoice item to handle partial billing
|
||||
purchase_receipt_doc.add_provisional_gl_entry(
|
||||
item, gl_entries, self.posting_date, reverse=1
|
||||
item, gl_entries, self.posting_date, provisional_account, reverse=1
|
||||
)
|
||||
|
||||
if not self.is_internal_transfer():
|
||||
@ -1156,7 +1158,7 @@ class PurchaseInvoice(BuyingController):
|
||||
# tax table gl entries
|
||||
valuation_tax = {}
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
@ -1249,7 +1251,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def enable_discount_accounting(self):
|
||||
if not hasattr(self, "_enable_discount_accounting"):
|
||||
self._enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
return self._enable_discount_accounting
|
||||
@ -1366,7 +1368,9 @@ class PurchaseInvoice(BuyingController):
|
||||
if (
|
||||
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
|
||||
):
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||
self.company, "Purchase Invoice", self.name
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
|
@ -5,6 +5,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
@ -336,8 +337,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||
|
||||
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
|
||||
discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
@ -353,10 +354,10 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
|
||||
additional_discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
@ -1427,7 +1428,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
|
||||
|
||||
def test_provisional_accounting_entry(self):
|
||||
item = create_item("_Test Non Stock Item", is_stock_item=0)
|
||||
create_item("_Test Non Stock Item", is_stock_item=0)
|
||||
|
||||
provisional_account = create_account(
|
||||
account_name="Provision Account",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
@ -1450,6 +1452,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
self.assertEquals(pr.items[0].provisional_expense_account, "Provision Account - _TC")
|
||||
|
||||
# Check GLE for Purchase Invoice
|
||||
expected_gle = [
|
||||
["Cost of Goods Sold - _TC", 250, 0, add_days(pr.posting_date, -1)],
|
||||
@ -1530,12 +1534,6 @@ def unlink_payment_on_cancel_of_invoice(enable=1):
|
||||
accounts_settings.save()
|
||||
|
||||
|
||||
def enable_discount_accounting(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
accounts_settings.enable_discount_accounting = enable
|
||||
accounts_settings.save()
|
||||
|
||||
|
||||
def make_purchase_invoice(**args):
|
||||
pi = frappe.new_doc("Purchase Invoice")
|
||||
args = frappe._dict(args)
|
||||
|
@ -33,7 +33,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
var me = this;
|
||||
super.onload();
|
||||
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry'];
|
||||
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
// show debit_to in print format
|
||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||
@ -822,29 +824,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
},
|
||||
|
||||
// Healthcare
|
||||
patient: function(frm) {
|
||||
if (frappe.boot.active_domains.includes("Healthcare")){
|
||||
if(frm.doc.patient){
|
||||
frappe.call({
|
||||
method: "frappe.client.get_value",
|
||||
args:{
|
||||
doctype: "Patient",
|
||||
filters: {
|
||||
"name": frm.doc.patient
|
||||
},
|
||||
fieldname: "customer"
|
||||
},
|
||||
callback:function(r) {
|
||||
if(r && r.message.customer){
|
||||
frm.set_value("customer", r.message.customer);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
project: function(frm) {
|
||||
if (frm.doc.project) {
|
||||
frm.events.add_timesheet_data(frm, {
|
||||
@ -974,25 +953,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
if (frm.doc.is_debit_note) {
|
||||
frm.set_df_property('return_against', 'label', __('Adjustment Against'));
|
||||
}
|
||||
|
||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||
frm.set_df_property("patient", "hidden", 0);
|
||||
frm.set_df_property("patient_name", "hidden", 0);
|
||||
frm.set_df_property("ref_practitioner", "hidden", 0);
|
||||
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"));
|
||||
frm.add_custom_button(__('Prescriptions'), function() {
|
||||
get_drugs_to_invoice(frm);
|
||||
},__("Get Items From"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
frm.set_df_property("patient", "hidden", 1);
|
||||
frm.set_df_property("patient_name", "hidden", 1);
|
||||
frm.set_df_property("ref_practitioner", "hidden", 1);
|
||||
}
|
||||
},
|
||||
|
||||
create_invoice_discounting: function(frm) {
|
||||
|
@ -1051,7 +1051,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
@ -1097,7 +1097,7 @@ class SalesInvoice(SellingController):
|
||||
def make_item_gl_entries(self, gl_entries):
|
||||
# income account gl entries
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
for item in self.get("items"):
|
||||
@ -1276,7 +1276,7 @@ class SalesInvoice(SellingController):
|
||||
def enable_discount_accounting(self):
|
||||
if not hasattr(self, "_enable_discount_accounting"):
|
||||
self._enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
return self._enable_discount_accounting
|
||||
@ -1412,7 +1412,7 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("Select change amount account"), title="Mandatory Field")
|
||||
frappe.throw(_("Select change amount account"), title=_("Mandatory Field"))
|
||||
|
||||
def make_write_off_gl_entry(self, gl_entries):
|
||||
# write off entries, applicable if only pos
|
||||
@ -1466,7 +1466,9 @@ class SalesInvoice(SellingController):
|
||||
and self.base_rounding_adjustment
|
||||
and not self.is_internal_transfer()
|
||||
):
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||
self.company, "Sales Invoice", self.name
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
|
@ -12,7 +12,10 @@ def get_data():
|
||||
"Sales Invoice": "return_against",
|
||||
"Auto Repeat": "reference_document",
|
||||
},
|
||||
"internal_links": {"Sales Order": ["items", "sales_order"]},
|
||||
"internal_links": {
|
||||
"Sales Order": ["items", "sales_order"],
|
||||
"Timesheet": ["timesheets", "time_sheet"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Payment"),
|
||||
|
@ -7,6 +7,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
|
||||
import erpnext
|
||||
@ -1977,6 +1978,13 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_rounding_adjustment_3(self):
|
||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||
create_dimension,
|
||||
disable_dimension,
|
||||
)
|
||||
|
||||
create_dimension()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.items = []
|
||||
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
|
||||
@ -2004,6 +2012,10 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"included_in_print_rate": 1,
|
||||
},
|
||||
)
|
||||
|
||||
si.cost_center = "_Test Cost Center 2 - _TC"
|
||||
si.location = "Block 1"
|
||||
|
||||
si.save()
|
||||
si.submit()
|
||||
self.assertEqual(si.net_total, 4007.16)
|
||||
@ -2039,6 +2051,18 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
self.assertEqual(debit_credit_diff, 0)
|
||||
|
||||
round_off_gle = frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{"voucher_type": "Sales Invoice", "voucher_no": si.name, "account": "Round Off - _TC"},
|
||||
["cost_center", "location"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC")
|
||||
self.assertEqual(round_off_gle.location, "Block 1")
|
||||
|
||||
disable_dimension()
|
||||
|
||||
def test_sales_invoice_with_shipping_rule(self):
|
||||
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
||||
|
||||
@ -2240,6 +2264,14 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
|
||||
|
||||
def test_deferred_revenue_missing_account(self):
|
||||
si = create_sales_invoice(posting_date="2019-01-10", do_not_submit=True)
|
||||
si.items[0].enable_deferred_revenue = 1
|
||||
si.items[0].service_start_date = "2019-01-10"
|
||||
si.items[0].service_end_date = "2019-03-15"
|
||||
|
||||
self.assertRaises(frappe.ValidationError, si.save)
|
||||
|
||||
def test_fixed_deferred_revenue(self):
|
||||
deferred_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
@ -2616,6 +2648,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
# reset
|
||||
einvoice_settings = frappe.get_doc("E Invoice Settings")
|
||||
einvoice_settings.enable = 0
|
||||
einvoice_settings.save()
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
@ -2676,12 +2709,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC"
|
||||
)
|
||||
|
||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||
def test_sales_invoice_with_discount_accounting_enabled(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
||||
enable_discount_accounting,
|
||||
)
|
||||
|
||||
enable_discount_accounting()
|
||||
|
||||
discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
@ -2697,14 +2726,10 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
||||
enable_discount_accounting,
|
||||
)
|
||||
|
||||
enable_discount_accounting()
|
||||
additional_discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
@ -2735,7 +2760,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
def test_asset_depreciation_on_sale_with_pro_rata(self):
|
||||
"""
|
||||
@ -3104,7 +3128,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
acc_settings = frappe.get_single("Accounts Settings")
|
||||
acc_settings.book_deferred_entries_via_journal_entry = 0
|
||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||
acc_settings.submit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)
|
||||
@ -3116,6 +3140,95 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.reload()
|
||||
self.assertTrue(si.items[0].serial_no)
|
||||
|
||||
def test_sales_invoice_with_disabled_account(self):
|
||||
try:
|
||||
account = frappe.get_doc("Account", "VAT 5% - _TC")
|
||||
account.disabled = 1
|
||||
account.save()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.posting_date = add_days(getdate(), 1)
|
||||
si.taxes = []
|
||||
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT 5% - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "VAT @ 5.0",
|
||||
"rate": 9,
|
||||
},
|
||||
)
|
||||
si.save()
|
||||
|
||||
with self.assertRaises(frappe.ValidationError) as err:
|
||||
si.submit()
|
||||
|
||||
self.assertTrue(
|
||||
"Cannot create accounting entries against disabled accounts" in str(err.exception)
|
||||
)
|
||||
|
||||
finally:
|
||||
account.disabled = 0
|
||||
account.save()
|
||||
|
||||
def test_gain_loss_with_advance_entry(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
|
||||
unlink_enabled = frappe.db.get_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1
|
||||
)
|
||||
|
||||
jv = make_journal_entry("_Test Receivable USD - _TC", "_Test Bank - _TC", -7000, save=False)
|
||||
|
||||
jv.accounts[0].exchange_rate = 70
|
||||
jv.accounts[0].credit_in_account_currency = 100
|
||||
jv.accounts[0].party_type = "Customer"
|
||||
jv.accounts[0].party = "_Test Customer USD"
|
||||
|
||||
jv.save()
|
||||
jv.submit()
|
||||
|
||||
si = create_sales_invoice(
|
||||
customer="_Test Customer USD",
|
||||
debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD",
|
||||
conversion_rate=75,
|
||||
do_not_save=1,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
si.append(
|
||||
"advances",
|
||||
{
|
||||
"reference_type": "Journal Entry",
|
||||
"reference_name": jv.name,
|
||||
"reference_row": jv.accounts[0].name,
|
||||
"advance_amount": 100,
|
||||
"allocated_amount": 100,
|
||||
"ref_exchange_rate": 70,
|
||||
},
|
||||
)
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Receivable USD - _TC", 7500.0, 500],
|
||||
["Exchange Gain/Loss - _TC", 500.0, 0.0],
|
||||
["Sales - _TC", 0.0, 7500.0],
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, nowdate())
|
||||
|
||||
frappe.db.set_value(
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
)
|
||||
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
|
@ -722,9 +722,7 @@ def process(data):
|
||||
frappe.db.commit()
|
||||
except frappe.ValidationError:
|
||||
frappe.db.rollback()
|
||||
frappe.db.begin()
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
frappe.db.commit()
|
||||
subscription.log_error("Subscription failed")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -163,12 +163,15 @@ def get_party_details(party, party_type, args=None):
|
||||
def get_tax_template(posting_date, args):
|
||||
"""Get matching tax rule"""
|
||||
args = frappe._dict(args)
|
||||
conditions = [
|
||||
"""(from_date is null or from_date <= '{0}')
|
||||
and (to_date is null or to_date >= '{0}')""".format(
|
||||
posting_date
|
||||
conditions = []
|
||||
|
||||
if posting_date:
|
||||
conditions.append(
|
||||
f"""(from_date is null or from_date <= '{posting_date}')
|
||||
and (to_date is null or to_date >= '{posting_date}')"""
|
||||
)
|
||||
]
|
||||
else:
|
||||
conditions.append("(from_date is null) and (to_date is null)")
|
||||
|
||||
conditions.append(
|
||||
"ifnull(tax_category, '') = {0}".format(frappe.db.escape(cstr(args.get("tax_category"))))
|
||||
|
@ -34,7 +34,9 @@ class TaxWithholdingCategory(Document):
|
||||
|
||||
def validate_thresholds(self):
|
||||
for d in self.get("rates"):
|
||||
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:
|
||||
if (
|
||||
d.cumulative_threshold and d.single_threshold and d.cumulative_threshold < d.single_threshold
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(
|
||||
d.idx
|
||||
|
@ -31,6 +31,7 @@ def make_gl_entries(
|
||||
if gl_map:
|
||||
if not cancel:
|
||||
validate_accounting_period(gl_map)
|
||||
validate_disabled_accounts(gl_map)
|
||||
gl_map = process_gl_map(gl_map, merge_entries)
|
||||
if gl_map and len(gl_map) > 1:
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
@ -45,6 +46,26 @@ def make_gl_entries(
|
||||
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||
|
||||
|
||||
def validate_disabled_accounts(gl_map):
|
||||
accounts = [d.account for d in gl_map if d.account]
|
||||
|
||||
Account = frappe.qb.DocType("Account")
|
||||
|
||||
disabled_accounts = (
|
||||
frappe.qb.from_(Account)
|
||||
.where(Account.name.isin(accounts) & Account.disabled == 1)
|
||||
.select(Account.name, Account.disabled)
|
||||
).run(as_dict=True)
|
||||
|
||||
if disabled_accounts:
|
||||
account_list = "<br>"
|
||||
account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
|
||||
frappe.throw(
|
||||
_("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
|
||||
title=_("Disabled Account Selected"),
|
||||
)
|
||||
|
||||
|
||||
def validate_accounting_period(gl_map):
|
||||
accounting_periods = frappe.db.sql(
|
||||
""" SELECT
|
||||
@ -355,7 +376,7 @@ def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_
|
||||
|
||||
def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||
gl_map[0].company
|
||||
gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
|
||||
)
|
||||
round_off_account_exists = False
|
||||
round_off_gle = frappe._dict()
|
||||
@ -392,14 +413,43 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
||||
}
|
||||
)
|
||||
|
||||
update_accounting_dimensions(round_off_gle)
|
||||
|
||||
if not round_off_account_exists:
|
||||
gl_map.append(round_off_gle)
|
||||
|
||||
|
||||
def get_round_off_account_and_cost_center(company):
|
||||
def update_accounting_dimensions(round_off_gle):
|
||||
dimensions = get_accounting_dimensions()
|
||||
meta = frappe.get_meta(round_off_gle["voucher_type"])
|
||||
has_all_dimensions = True
|
||||
|
||||
for dimension in dimensions:
|
||||
if not meta.has_field(dimension):
|
||||
has_all_dimensions = False
|
||||
|
||||
if dimensions and has_all_dimensions:
|
||||
dimension_values = frappe.db.get_value(
|
||||
round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
|
||||
)
|
||||
|
||||
for dimension in dimensions:
|
||||
round_off_gle[dimension] = dimension_values.get(dimension)
|
||||
|
||||
|
||||
def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
|
||||
round_off_account, round_off_cost_center = frappe.get_cached_value(
|
||||
"Company", company, ["round_off_account", "round_off_cost_center"]
|
||||
) or [None, None]
|
||||
|
||||
meta = frappe.get_meta(voucher_type)
|
||||
|
||||
# Give first preference to parent cost center for round off GLE
|
||||
if meta.has_field("cost_center"):
|
||||
parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
|
||||
if parent_cost_center:
|
||||
round_off_cost_center = parent_cost_center
|
||||
|
||||
if not round_off_account:
|
||||
frappe.throw(_("Please mention Round Off Account in Company"))
|
||||
|
||||
|
@ -831,9 +831,9 @@ def get_party_shipping_address(doctype, name):
|
||||
"where "
|
||||
"dl.link_doctype=%s "
|
||||
"and dl.link_name=%s "
|
||||
'and dl.parenttype="Address" '
|
||||
"and dl.parenttype='Address' "
|
||||
"and ifnull(ta.disabled, 0) = 0 and"
|
||||
'(ta.address_type="Shipping" or ta.is_shipping_address=1) '
|
||||
"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
|
||||
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
|
||||
(doctype, name),
|
||||
)
|
||||
@ -881,11 +881,11 @@ def get_default_contact(doctype, name):
|
||||
"""
|
||||
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
|
||||
FROM `tabDynamic Link` dl
|
||||
INNER JOIN tabContact c ON c.name = dl.parent
|
||||
INNER JOIN `tabContact` c ON c.name = dl.parent
|
||||
WHERE
|
||||
dl.link_doctype=%s AND
|
||||
dl.link_name=%s AND
|
||||
dl.parenttype = "Contact"
|
||||
dl.parenttype = 'Contact'
|
||||
ORDER BY is_primary_contact DESC, is_billing_contact DESC
|
||||
""",
|
||||
(doctype, name),
|
||||
|
@ -1,7 +1,8 @@
|
||||
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
|
||||
<div class="page-break">
|
||||
{% if doc.signed_einvoice %}
|
||||
{%- set einvoice = json.loads(doc.signed_einvoice) -%}
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
@ -170,4 +171,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center" style="color: var(--gray-500); font-size: 14px;">
|
||||
You must generate IRN before you can preview GST E-Invoice.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -201,17 +201,17 @@ def get_report_summary(
|
||||
net_provisional_profit_loss += provisional_profit_loss.get(key)
|
||||
|
||||
return [
|
||||
{"value": net_asset, "label": "Total Asset", "datatype": "Currency", "currency": currency},
|
||||
{"value": net_asset, "label": _("Total Asset"), "datatype": "Currency", "currency": currency},
|
||||
{
|
||||
"value": net_liability,
|
||||
"label": "Total Liability",
|
||||
"label": _("Total Liability"),
|
||||
"datatype": "Currency",
|
||||
"currency": currency,
|
||||
},
|
||||
{"value": net_equity, "label": "Total Equity", "datatype": "Currency", "currency": currency},
|
||||
{"value": net_equity, "label": _("Total Equity"), "datatype": "Currency", "currency": currency},
|
||||
{
|
||||
"value": net_provisional_profit_loss,
|
||||
"label": "Provisional Profit / Loss (Credit)",
|
||||
"label": _("Provisional Profit / Loss (Credit)"),
|
||||
"indicator": "Green" if net_provisional_profit_loss > 0 else "Red",
|
||||
"datatype": "Currency",
|
||||
"currency": currency,
|
||||
|
@ -203,7 +203,7 @@ def get_loan_entries(filters):
|
||||
posting_date = (loan_doc.posting_date).as_("posting_date")
|
||||
account = loan_doc.payment_account
|
||||
|
||||
entries = (
|
||||
query = (
|
||||
frappe.qb.from_(loan_doc)
|
||||
.select(
|
||||
ConstantColumn(doctype).as_("payment_document"),
|
||||
@ -217,9 +217,12 @@ def get_loan_entries(filters):
|
||||
.where(account == filters.get("account"))
|
||||
.where(posting_date <= getdate(filters.get("report_date")))
|
||||
.where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date")))
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
if doctype == "Loan Repayment":
|
||||
query.where(loan_doc.repay_from_salary == 0)
|
||||
|
||||
entries = query.run(as_dict=1)
|
||||
loan_docs.extend(entries)
|
||||
|
||||
return loan_docs
|
||||
|
@ -97,8 +97,8 @@ def get_columns(filters):
|
||||
if filters["period"] == "Yearly":
|
||||
labels = [
|
||||
_("Budget") + " " + str(year[0]),
|
||||
_("Actual ") + " " + str(year[0]),
|
||||
_("Variance ") + " " + str(year[0]),
|
||||
_("Actual") + " " + str(year[0]),
|
||||
_("Variance") + " " + str(year[0]),
|
||||
]
|
||||
for label in labels:
|
||||
columns.append(
|
||||
|
@ -230,7 +230,7 @@ def get_columns(dimension_list):
|
||||
columns.append(
|
||||
{
|
||||
"fieldname": "total",
|
||||
"label": "Total",
|
||||
"label": _("Total"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 150,
|
||||
|
@ -507,7 +507,7 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
)
|
||||
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
additional_conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
|
||||
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
|
||||
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
|
||||
|
||||
|
@ -133,7 +133,7 @@ def set_account_currency(filters):
|
||||
else:
|
||||
account_currency = (
|
||||
None
|
||||
if filters.party_type in ["Employee", "Student", "Shareholder", "Member"]
|
||||
if filters.party_type in ["Employee", "Shareholder", "Member"]
|
||||
else frappe.db.get_value(filters.party_type, filters.party[0], "default_currency")
|
||||
)
|
||||
|
||||
@ -275,7 +275,7 @@ def get_conditions(filters):
|
||||
)
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
conditions.append("{0} in (%({0})s)".format(dimension.fieldname))
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
@ -435,7 +435,13 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
||||
gle_map[group_by_value].entries.append(gle)
|
||||
|
||||
elif group_by_voucher_consolidated:
|
||||
keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
|
||||
keylist = [
|
||||
gle.get("voucher_type"),
|
||||
gle.get("voucher_no"),
|
||||
gle.get("account"),
|
||||
gle.get("party_type"),
|
||||
gle.get("party"),
|
||||
]
|
||||
if filters.get("include_dimensions"):
|
||||
for dim in accounting_dimensions:
|
||||
keylist.append(gle.get(dim))
|
||||
|
@ -29,7 +29,7 @@ def get_columns():
|
||||
"options": "Item Group",
|
||||
"width": 150,
|
||||
},
|
||||
{"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": "Item", "width": 150},
|
||||
{"fieldname": "item", "fieldtype": "Link", "options": "Item", "label": _("Item"), "width": 150},
|
||||
{"fieldname": "item_name", "fieldtype": "Data", "label": _("Item Name"), "width": 150},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
|
@ -115,9 +115,9 @@ def get_columns(filters):
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
|
||||
{"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
|
||||
{"fieldname": "range1", "label": "0-30", "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "range2", "label": "30-60", "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "range3", "label": "60-90", "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "range2", "label": _("30-60"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "range3", "label": _("60-90"), "fieldtype": "Currency", "width": 140},
|
||||
{"fieldname": "range4", "label": _("90 Above"), "fieldtype": "Currency", "width": 140},
|
||||
{
|
||||
"fieldname": "delay_in_payment",
|
||||
|
@ -62,7 +62,7 @@ def get_pos_entries(filters, group_by_field):
|
||||
"""
|
||||
SELECT
|
||||
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
||||
p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount,
|
||||
p.owner, p.base_grand_total as grand_total, p.base_paid_amount - p.change_amount as paid_amount,
|
||||
p.customer, p.is_return {select_mop_field}
|
||||
FROM
|
||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||
|
@ -124,11 +124,10 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
expense_accounts = (
|
||||
tax_accounts
|
||||
) = (
|
||||
expense_columns
|
||||
) = tax_columns = unrealized_profit_loss_accounts = unrealized_profit_loss_account_columns = []
|
||||
|
||||
expense_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list(
|
||||
@ -163,10 +162,11 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
unrealized_profit_loss_account_columns = [
|
||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
||||
]
|
||||
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(account + ":Currency/currency:120")
|
||||
tax_columns = [
|
||||
(account + ":Currency/currency:120")
|
||||
for account in tax_accounts
|
||||
if account not in expense_accounts
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
@ -237,7 +237,7 @@ def get_conditions(filters):
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
|
||||
+ "and ifnull(`tabPurchase Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
|
@ -405,7 +405,7 @@ def get_conditions(filters):
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice Item`.{0}, '') in (%({0})s))".format(dimension.fieldname)
|
||||
+ "and ifnull(`tabSales Invoice Item`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
|
@ -188,9 +188,9 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
additional_conditions += "and {0} in %({0})s".format(dimension.fieldname)
|
||||
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
|
||||
else:
|
||||
additional_conditions += "and {0} in (%({0})s)".format(dimension.fieldname)
|
||||
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
|
||||
|
||||
query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
|
||||
|
||||
|
@ -23,8 +23,6 @@ def execute(filters=None):
|
||||
def get_data(filters, show_party_name):
|
||||
if filters.get("party_type") in ("Customer", "Supplier", "Employee", "Member"):
|
||||
party_name_field = "{0}_name".format(frappe.scrub(filters.get("party_type")))
|
||||
elif filters.get("party_type") == "Student":
|
||||
party_name_field = "first_name"
|
||||
elif filters.get("party_type") == "Shareholder":
|
||||
party_name_field = "title"
|
||||
else:
|
||||
|
@ -1,10 +1,17 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.test_runner import make_test_objects
|
||||
|
||||
from erpnext.accounts.party import get_party_shipping_address
|
||||
from erpnext.accounts.utils import get_future_stock_vouchers, get_voucherwise_gl_entries
|
||||
from erpnext.accounts.utils import (
|
||||
get_future_stock_vouchers,
|
||||
get_voucherwise_gl_entries,
|
||||
sort_stock_vouchers_by_posting_date,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
@ -47,6 +54,25 @@ class TestUtils(unittest.TestCase):
|
||||
msg="get_voucherwise_gl_entries not returning expected GLes",
|
||||
)
|
||||
|
||||
def test_stock_voucher_sorting(self):
|
||||
vouchers = []
|
||||
|
||||
item = make_item().name
|
||||
|
||||
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}
|
||||
|
||||
se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
|
||||
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
|
||||
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
|
||||
|
||||
for doc in (se1, se2, se3):
|
||||
vouchers.append((doc.doctype, doc.name))
|
||||
|
||||
vouchers.append(("Stock Entry", "Wat"))
|
||||
|
||||
sorted_vouchers = sort_stock_vouchers_by_posting_date(list(reversed(vouchers)))
|
||||
self.assertEqual(sorted_vouchers, vouchers)
|
||||
|
||||
|
||||
ADDRESS_RECORDS = [
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
from json import loads
|
||||
from typing import List, Tuple
|
||||
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
@ -18,10 +19,6 @@ from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.stock.utils import get_stock_value_on
|
||||
|
||||
|
||||
class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class FiscalYearError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
@ -1126,6 +1123,9 @@ def update_gl_entries_after(
|
||||
def repost_gle_for_stock_vouchers(
|
||||
stock_vouchers, posting_date, company=None, warehouse_account=None
|
||||
):
|
||||
if not stock_vouchers:
|
||||
return
|
||||
|
||||
def _delete_gl_entries(voucher_type, voucher_no):
|
||||
frappe.db.sql(
|
||||
"""delete from `tabGL Entry`
|
||||
@ -1133,6 +1133,8 @@ def repost_gle_for_stock_vouchers(
|
||||
(voucher_type, voucher_no),
|
||||
)
|
||||
|
||||
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
|
||||
|
||||
if not warehouse_account:
|
||||
warehouse_account = get_warehouse_account_map(company)
|
||||
|
||||
@ -1153,6 +1155,27 @@ def repost_gle_for_stock_vouchers(
|
||||
_delete_gl_entries(voucher_type, voucher_no)
|
||||
|
||||
|
||||
def sort_stock_vouchers_by_posting_date(
|
||||
stock_vouchers: List[Tuple[str, str]]
|
||||
) -> List[Tuple[str, str]]:
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
voucher_nos = [v[1] for v in stock_vouchers]
|
||||
|
||||
sles = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
|
||||
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
|
||||
.groupby(sle.voucher_type, sle.voucher_no)
|
||||
).run(as_dict=True)
|
||||
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]
|
||||
|
||||
unknown_vouchers = set(stock_vouchers) - set(sorted_vouchers)
|
||||
if unknown_vouchers:
|
||||
sorted_vouchers.extend(unknown_vouchers)
|
||||
|
||||
return sorted_vouchers
|
||||
|
||||
|
||||
def get_future_stock_vouchers(
|
||||
posting_date, posting_time, for_warehouses=None, for_items=None, company=None
|
||||
):
|
||||
@ -1246,47 +1269,6 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
||||
return matched
|
||||
|
||||
|
||||
def check_if_stock_and_account_balance_synced(
|
||||
posting_date, company, voucher_type=None, voucher_no=None
|
||||
):
|
||||
if not cint(erpnext.is_perpetual_inventory_enabled(company)):
|
||||
return
|
||||
|
||||
accounts = get_stock_accounts(company, voucher_type, voucher_no)
|
||||
stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
|
||||
|
||||
for account in accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||
account, posting_date, company
|
||||
)
|
||||
|
||||
if abs(account_bal - stock_bal) > 0.1:
|
||||
precision = get_field_precision(
|
||||
frappe.get_meta("GL Entry").get_field("debit"),
|
||||
currency=frappe.get_cached_value("Company", company, "default_currency"),
|
||||
)
|
||||
|
||||
diff = flt(stock_bal - account_bal, precision)
|
||||
|
||||
error_reason = _(
|
||||
"Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}."
|
||||
).format(stock_bal, account_bal, frappe.bold(account), posting_date)
|
||||
error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}").format(
|
||||
frappe.bold(diff), frappe.bold(posting_date)
|
||||
)
|
||||
|
||||
frappe.msgprint(
|
||||
msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
|
||||
raise_exception=StockValueAndAccountBalanceOutOfSync,
|
||||
title=_("Values Out Of Sync"),
|
||||
primary_action={
|
||||
"label": _("Make Journal Entry"),
|
||||
"client_action": "erpnext.route_to_adjustment_jv",
|
||||
"args": get_journal_entry(account, stock_adjustment_account, diff),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_stock_accounts(company, voucher_type=None, voucher_no=None):
|
||||
stock_accounts = [
|
||||
d.name
|
||||
|
@ -87,7 +87,7 @@ class AssetCategory(Document):
|
||||
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
|
||||
|
||||
if missing_cwip_accounts_for_company:
|
||||
msg = _("""To enable Capital Work in Progress Accounting, """)
|
||||
msg = _("""To enable Capital Work in Progress Accounting,""") + " "
|
||||
msg += _("""you must select Capital Work in Progress Account in accounts table""")
|
||||
msg += "<br><br>"
|
||||
msg += _("You can also set default CWIP account in Company {}").format(
|
||||
|
@ -46,10 +46,9 @@ class AssetMovement(Document):
|
||||
if d.target_location:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Issuing cannot be done to a location. \
|
||||
Please enter employee who has issued Asset {0}"
|
||||
"Issuing cannot be done to a location. Please enter employee who has issued Asset {0}"
|
||||
).format(d.asset),
|
||||
title="Incorrect Movement Purpose",
|
||||
title=_("Incorrect Movement Purpose"),
|
||||
)
|
||||
if not d.to_employee:
|
||||
frappe.throw(_("Employee is required while issuing Asset {0}").format(d.asset))
|
||||
@ -58,10 +57,9 @@ class AssetMovement(Document):
|
||||
if d.to_employee:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Transferring cannot be done to an Employee. \
|
||||
Please enter location where Asset {0} has to be transferred"
|
||||
"Transferring cannot be done to an Employee. Please enter location where Asset {0} has to be transferred"
|
||||
).format(d.asset),
|
||||
title="Incorrect Movement Purpose",
|
||||
title=_("Incorrect Movement Purpose"),
|
||||
)
|
||||
if not d.target_location:
|
||||
frappe.throw(_("Target Location is required while transferring Asset {0}").format(d.asset))
|
||||
@ -89,8 +87,7 @@ class AssetMovement(Document):
|
||||
if d.to_employee and d.target_location:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Asset {0} cannot be received at a location and \
|
||||
given to employee in a single movement"
|
||||
"Asset {0} cannot be received at a location and given to employee in a single movement"
|
||||
).format(d.asset)
|
||||
)
|
||||
|
||||
|
@ -32,7 +32,7 @@ frappe.ui.form.on('Asset Repair', {
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus) {
|
||||
frm.add_custom_button("View General Ledger", function() {
|
||||
frm.add_custom_button(__("View General Ledger"), function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": frm.doc.name
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ class AssetValueAdjustment(Document):
|
||||
_("Asset Value Adjustment cannot be posted before Asset's purchase date <b>{0}</b>.").format(
|
||||
formatdate(asset_purchase_date)
|
||||
),
|
||||
title="Incorrect Date",
|
||||
title=_("Incorrect Date"),
|
||||
)
|
||||
|
||||
def set_difference_amount(self):
|
||||
|
@ -20,6 +20,7 @@
|
||||
"maintain_same_rate",
|
||||
"allow_multiple_items",
|
||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"enable_discount_accounting",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
"column_break_11",
|
||||
@ -133,6 +134,13 @@
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||
"fieldname": "enable_discount_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discount Accounting for Buying"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@ -140,7 +148,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-27 17:57:58.367048",
|
||||
"modified": "2022-04-14 15:56:42.340223",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
@ -5,10 +5,15 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
|
||||
class BuyingSettings(Document):
|
||||
def on_update(self):
|
||||
self.toggle_discount_accounting_fields()
|
||||
|
||||
def validate(self):
|
||||
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
||||
frappe.db.set_default(key, self.get(key, ""))
|
||||
@ -21,3 +26,60 @@ class BuyingSettings(Document):
|
||||
self.get("supp_master_name") == "Naming Series",
|
||||
hide_name_field=False,
|
||||
)
|
||||
|
||||
def toggle_discount_accounting_fields(self):
|
||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
@ -452,4 +452,4 @@ function set_schedule_date(frm) {
|
||||
}
|
||||
}
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
frappe.provide("erpnext.buying");
|
||||
|
@ -25,6 +25,10 @@
|
||||
"order_confirmation_no",
|
||||
"order_confirmation_date",
|
||||
"amended_from",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"drop_ship",
|
||||
"customer",
|
||||
"customer_name",
|
||||
@ -1135,6 +1139,28 @@
|
||||
{
|
||||
"fieldname": "section_break_45",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions "
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
|
@ -891,4 +891,4 @@ def get_requested_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC")
|
||||
|
||||
test_dependencies = ["BOM", "Item Price"]
|
||||
|
||||
test_records = frappe.get_test_records("Purchase Order")
|
||||
test_records = frappe.get_test_records("Purchase Order")
|
||||
|
@ -31,7 +31,7 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
if (frm.doc.docstatus === 1) {
|
||||
|
||||
frm.add_custom_button(__('Supplier Quotation'),
|
||||
function(){ frm.trigger("make_suppplier_quotation") }, __("Create"));
|
||||
function(){ frm.trigger("make_supplier_quotation") }, __("Create"));
|
||||
|
||||
|
||||
frm.add_custom_button(__("Send Emails to Suppliers"), function() {
|
||||
@ -87,16 +87,24 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
|
||||
},
|
||||
|
||||
make_suppplier_quotation: function(frm) {
|
||||
make_supplier_quotation: function(frm) {
|
||||
var doc = frm.doc;
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Create Supplier Quotation"),
|
||||
fields: [
|
||||
{ "fieldtype": "Select", "label": __("Supplier"),
|
||||
{ "fieldtype": "Link",
|
||||
"label": __("Supplier"),
|
||||
"fieldname": "supplier",
|
||||
"options": doc.suppliers.map(d => d.supplier),
|
||||
"options": 'Supplier',
|
||||
"reqd": 1,
|
||||
"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: [
|
||||
["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
primary_action_label: __("Create"),
|
||||
primary_action: (args) => {
|
||||
|
@ -32,7 +32,9 @@
|
||||
"terms",
|
||||
"printing_settings",
|
||||
"select_print_heading",
|
||||
"letter_head"
|
||||
"letter_head",
|
||||
"more_info",
|
||||
"opportunity"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -193,6 +195,23 @@
|
||||
"options": "Letter Head",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-file-text",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "opportunity",
|
||||
"fieldtype": "Link",
|
||||
"label": "Opportunity",
|
||||
"options": "Opportunity",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
@ -258,7 +277,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-24 17:47:49.909000",
|
||||
"modified": "2022-04-06 17:47:49.909000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
@ -327,4 +346,4 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,6 @@ class TestRequestforQuotation(FrappeTestCase):
|
||||
)
|
||||
sq.submit()
|
||||
|
||||
frappe.form_dict = frappe.local("form_dict")
|
||||
frappe.form_dict.name = rfq.name
|
||||
|
||||
self.assertEqual(check_supplier_has_docname_access(supplier_wt_appos[0].get("supplier")), True)
|
||||
|
@ -18,16 +18,16 @@
|
||||
"tax_id",
|
||||
"tax_category",
|
||||
"tax_withholding_category",
|
||||
"is_transporter",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"image",
|
||||
"column_break0",
|
||||
"supplier_group",
|
||||
"supplier_type",
|
||||
"allow_purchase_invoice_creation_without_purchase_order",
|
||||
"allow_purchase_invoice_creation_without_purchase_receipt",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"disabled",
|
||||
"is_transporter",
|
||||
"warn_rfqs",
|
||||
"warn_pos",
|
||||
"prevent_rfqs",
|
||||
@ -38,12 +38,6 @@
|
||||
"default_currency",
|
||||
"column_break_10",
|
||||
"default_price_list",
|
||||
"section_credit_limit",
|
||||
"payment_terms",
|
||||
"cb_21",
|
||||
"on_hold",
|
||||
"hold_type",
|
||||
"release_date",
|
||||
"address_contacts",
|
||||
"address_html",
|
||||
"column_break1",
|
||||
@ -57,6 +51,12 @@
|
||||
"primary_address",
|
||||
"default_payable_accounts",
|
||||
"accounts",
|
||||
"section_credit_limit",
|
||||
"payment_terms",
|
||||
"cb_21",
|
||||
"on_hold",
|
||||
"hold_type",
|
||||
"release_date",
|
||||
"default_tax_withholding_config",
|
||||
"column_break2",
|
||||
"website",
|
||||
@ -258,7 +258,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_credit_limit",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Credit Limit"
|
||||
"label": "Payment Terms"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_terms",
|
||||
@ -432,7 +432,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2021-10-20 22:03:33.147249",
|
||||
"modified": "2022-04-16 18:02:27.838623",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
@ -497,6 +497,7 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
@ -213,7 +213,8 @@ def make_all_scorecards(docname):
|
||||
end_date = get_scorecard_date(sc.period, start_date)
|
||||
if scp_count > 0:
|
||||
frappe.msgprint(
|
||||
_("Created {0} scorecards for {1} between: ").format(scp_count, sc.supplier)
|
||||
_("Created {0} scorecards for {1} between:").format(scp_count, sc.supplier)
|
||||
+ " "
|
||||
+ str(first_start_date)
|
||||
+ " - "
|
||||
+ str(last_end_date)
|
||||
|
@ -80,6 +80,6 @@ def _get_variables(criteria):
|
||||
)[0]
|
||||
my_variables.append(var)
|
||||
except Exception:
|
||||
frappe.throw(_("Unable to find variable: ") + str(match.group(1)), InvalidFormulaVariable)
|
||||
frappe.throw(_("Unable to find variable:") + " " + str(match.group(1)), InvalidFormulaVariable)
|
||||
|
||||
return my_variables
|
||||
|
@ -48,7 +48,7 @@ def get_chart_data(data, conditions, filters):
|
||||
"data": {
|
||||
"labels": labels,
|
||||
"datasets": [
|
||||
{"name": _("{0}").format(filters.get("period")) + _(" Purchase Value"), "values": datapoints}
|
||||
{"name": _(filters.get("period")) + " " + _("Purchase Value"), "values": datapoints}
|
||||
],
|
||||
},
|
||||
"type": "line",
|
||||
|
@ -35,7 +35,6 @@ frappe.query_reports["Subcontract Order Summary"] = {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
|
||||
company: frappe.query_report.get_filter_value('company')
|
||||
}
|
||||
}
|
||||
|
@ -114,11 +114,11 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
onload: (report) => {
|
||||
// Create a button for setting the default supplier
|
||||
report.page.add_inner_button(__("Select Default Supplier"), () => {
|
||||
let reporter = frappe.query_reports["Quoted Item Comparison"];
|
||||
let reporter = frappe.query_reports["Supplier Quotation Comparison"];
|
||||
|
||||
//Always make a new one so that the latest values get updated
|
||||
reporter.make_default_supplier_dialog(report);
|
||||
}, 'Tools');
|
||||
}, __("Tools"));
|
||||
|
||||
},
|
||||
make_default_supplier_dialog: (report) => {
|
||||
@ -126,7 +126,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
if(!report.data) return;
|
||||
|
||||
let filters = report.get_values();
|
||||
let item_code = filters.item;
|
||||
let item_code = filters.item_code;
|
||||
|
||||
// Get a list of the suppliers (with a blank as well) for the user to select
|
||||
let suppliers = $.map(report.data, (row, idx)=>{ return row.supplier_name })
|
||||
@ -152,7 +152,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
]
|
||||
});
|
||||
|
||||
dialog.set_primary_action("Set Default Supplier", () => {
|
||||
dialog.set_primary_action(__("Set Default Supplier"), () => {
|
||||
let values = dialog.get_values();
|
||||
if(values) {
|
||||
// Set the default_supplier field of the appropriate Item to the selected supplier
|
||||
|
@ -1,192 +0,0 @@
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Student"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Student",
|
||||
"onboard": 1,
|
||||
},
|
||||
{"type": "doctype", "name": "Guardian"},
|
||||
{"type": "doctype", "name": "Student Log"},
|
||||
{"type": "doctype", "name": "Student Group"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Admission"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Student Applicant"},
|
||||
{"type": "doctype", "name": "Web Academy Applicant"},
|
||||
{"type": "doctype", "name": "Student Admission"},
|
||||
{"type": "doctype", "name": "Program Enrollment"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Attendance"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Student Attendance"},
|
||||
{"type": "doctype", "name": "Student Leave Application"},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Absent Student Report",
|
||||
"doctype": "Student Attendance",
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Student Batch-Wise Attendance",
|
||||
"doctype": "Student Attendance",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Tools"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Student Attendance Tool"},
|
||||
{"type": "doctype", "name": "Assessment Result Tool"},
|
||||
{"type": "doctype", "name": "Student Group Creation Tool"},
|
||||
{"type": "doctype", "name": "Program Enrollment Tool"},
|
||||
{"type": "doctype", "name": "Course Scheduling Tool"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Assessment"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Assessment Plan"},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Assessment Group",
|
||||
"link": "Tree/Assessment Group",
|
||||
},
|
||||
{"type": "doctype", "name": "Assessment Result"},
|
||||
{"type": "doctype", "name": "Assessment Criteria"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Assessment Reports"),
|
||||
"items": [
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Course wise Assessment Report",
|
||||
"doctype": "Assessment Result",
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Final Assessment Grades",
|
||||
"doctype": "Assessment Result",
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Assessment Plan Status",
|
||||
"doctype": "Assessment Plan",
|
||||
},
|
||||
{"type": "doctype", "name": "Student Report Generation Tool"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Fees"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Fees"},
|
||||
{"type": "doctype", "name": "Fee Schedule"},
|
||||
{"type": "doctype", "name": "Fee Structure"},
|
||||
{"type": "doctype", "name": "Fee Category"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Schedule"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Course Schedule", "route": "/app/List/Course Schedule/Calendar"},
|
||||
{"type": "doctype", "name": "Course Scheduling Tool"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Masters"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Program",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Course",
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Topic",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Instructor",
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Room",
|
||||
"onboard": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Content Masters"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Article"},
|
||||
{"type": "doctype", "name": "Video"},
|
||||
{"type": "doctype", "name": "Quiz"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("LMS Activity"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Course Enrollment"},
|
||||
{"type": "doctype", "name": "Course Activity"},
|
||||
{"type": "doctype", "name": "Quiz Activity"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Settings"),
|
||||
"items": [
|
||||
{"type": "doctype", "name": "Student Category"},
|
||||
{"type": "doctype", "name": "Student Batch Name"},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Grading Scale",
|
||||
"onboard": 1,
|
||||
},
|
||||
{"type": "doctype", "name": "Academic Term"},
|
||||
{"type": "doctype", "name": "Academic Year"},
|
||||
{"type": "doctype", "name": "Education Settings"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": _("Other Reports"),
|
||||
"items": [
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Student and Guardian Contact Details",
|
||||
"doctype": "Program Enrollment",
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "Student Monthly Attendance Sheet",
|
||||
"doctype": "Student Attendance",
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"name": "Student Fee Collection",
|
||||
"doctype": "Fees",
|
||||
"is_query_report": True,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
@ -148,6 +148,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.disable_pricing_rule_on_internal_transfer()
|
||||
self.set_incoming_rate()
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
@ -180,6 +181,7 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
self.validate_deferred_start_and_end_date()
|
||||
|
||||
self.validate_deferred_income_expense_account()
|
||||
self.set_inter_company_account()
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
@ -208,6 +210,27 @@ class AccountsController(TransactionBase):
|
||||
(self.doctype, self.name),
|
||||
)
|
||||
|
||||
def validate_deferred_income_expense_account(self):
|
||||
field_map = {
|
||||
"Sales Invoice": "deferred_revenue_account",
|
||||
"Purchase Invoice": "deferred_expense_account",
|
||||
}
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
|
||||
if not item.get(field_map.get(self.doctype)):
|
||||
default_deferred_account = frappe.db.get_value(
|
||||
"Company", self.company, "default_" + field_map.get(self.doctype)
|
||||
)
|
||||
if not default_deferred_account:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Please update deferred revenue/expense account in item row or default account in company master"
|
||||
).format(item.idx)
|
||||
)
|
||||
else:
|
||||
item.set(field_map.get(self.doctype), default_deferred_account)
|
||||
|
||||
def validate_deferred_start_and_end_date(self):
|
||||
for d in self.items:
|
||||
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
|
||||
@ -360,6 +383,14 @@ class AccountsController(TransactionBase):
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
def disable_pricing_rule_on_internal_transfer(self):
|
||||
if not self.get("ignore_pricing_rule") and self.is_internal_transfer():
|
||||
self.ignore_pricing_rule = 1
|
||||
frappe.msgprint(
|
||||
_("Disabled pricing rules since this {} is an internal transfer").format(self.doctype),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
def validate_due_date(self):
|
||||
if self.get("is_pos"):
|
||||
return
|
||||
@ -1057,9 +1088,14 @@ class AccountsController(TransactionBase):
|
||||
return amount, base_amount
|
||||
|
||||
def make_discount_gl_entries(self, gl_entries):
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
)
|
||||
if self.doctype == "Purchase Invoice":
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
elif self.doctype == "Sales Invoice":
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
if enable_discount_accounting:
|
||||
if self.doctype == "Purchase Invoice":
|
||||
@ -1094,11 +1130,10 @@ class AccountsController(TransactionBase):
|
||||
{
|
||||
"account": item.discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: flt(discount_amount, item.precision("discount_amount")),
|
||||
dr_or_cr
|
||||
+ "_in_account_currency": flt(
|
||||
dr_or_cr: flt(
|
||||
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
|
||||
),
|
||||
dr_or_cr + "_in_account_currency": flt(discount_amount, item.precision("discount_amount")),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project,
|
||||
},
|
||||
@ -1113,11 +1148,11 @@ class AccountsController(TransactionBase):
|
||||
{
|
||||
"account": income_or_expense_account,
|
||||
"against": supplier_or_customer,
|
||||
rev_dr_cr: flt(discount_amount, item.precision("discount_amount")),
|
||||
rev_dr_cr
|
||||
+ "_in_account_currency": flt(
|
||||
rev_dr_cr: flt(
|
||||
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
|
||||
),
|
||||
rev_dr_cr
|
||||
+ "_in_account_currency": flt(discount_amount, item.precision("discount_amount")),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
@ -1711,6 +1746,8 @@ class AccountsController(TransactionBase):
|
||||
internal_party_field = "is_internal_customer"
|
||||
elif self.doctype in ("Purchase Invoice", "Purchase Receipt", "Purchase Order"):
|
||||
internal_party_field = "is_internal_supplier"
|
||||
else:
|
||||
return False
|
||||
|
||||
if self.get(internal_party_field) and (self.represents_company == self.company):
|
||||
return True
|
||||
@ -1975,12 +2012,13 @@ def get_advance_journal_entries(
|
||||
|
||||
reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
|
||||
|
||||
# nosemgrep
|
||||
journal_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
"Journal Entry" as reference_type, t1.name as reference_name,
|
||||
t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
|
||||
t2.reference_name as against_order
|
||||
t2.reference_name as against_order, t2.exchange_rate
|
||||
from
|
||||
`tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where
|
||||
@ -2423,11 +2461,21 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row
|
||||
)
|
||||
|
||||
def validate_quantity(child_item, d):
|
||||
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
||||
def validate_quantity(child_item, new_data):
|
||||
if not flt(new_data.get("qty")):
|
||||
frappe.throw(
|
||||
_("Row # {0}: Quantity for Item {1} cannot be zero").format(
|
||||
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
|
||||
),
|
||||
title=_("Invalid Qty"),
|
||||
)
|
||||
|
||||
if parent_doctype == "Sales Order" and flt(new_data.get("qty")) < flt(child_item.delivered_qty):
|
||||
frappe.throw(_("Cannot set quantity less than delivered quantity"))
|
||||
|
||||
if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
|
||||
if parent_doctype == "Purchase Order" and flt(new_data.get("qty")) < flt(
|
||||
child_item.received_qty
|
||||
):
|
||||
frappe.throw(_("Cannot set quantity less than received quantity"))
|
||||
|
||||
data = json.loads(trans_items)
|
||||
|
@ -252,9 +252,7 @@ class BuyingController(StockController):
|
||||
|
||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount
|
||||
+ item.item_tax_amount
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
@ -300,14 +298,15 @@ class BuyingController(StockController):
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if self.is_subcontracted:
|
||||
@ -398,7 +397,10 @@ class BuyingController(StockController):
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.item_code in stock_items and d.warehouse:
|
||||
if d.item_code not in stock_items:
|
||||
continue
|
||||
|
||||
if d.warehouse:
|
||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
if pr_qty:
|
||||
@ -423,6 +425,7 @@ class BuyingController(StockController):
|
||||
sle = self.get_sl_entries(
|
||||
d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
|
||||
)
|
||||
|
||||
if self.is_return:
|
||||
outgoing_rate = get_rate_for_return(
|
||||
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||
@ -452,18 +455,18 @@ class BuyingController(StockController):
|
||||
|
||||
sl_entries.append(from_warehouse_sle)
|
||||
|
||||
if flt(d.rejected_qty) != 0:
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"warehouse": d.rejected_warehouse,
|
||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"incoming_rate": 0.0,
|
||||
},
|
||||
)
|
||||
if flt(d.rejected_qty) != 0:
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"warehouse": d.rejected_warehouse,
|
||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"incoming_rate": 0.0,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
|
@ -162,6 +162,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
{account_type_condition}
|
||||
AND is_group = 0
|
||||
AND company = %(company)s
|
||||
AND disabled = %(disabled)s
|
||||
AND (account_currency = %(currency)s or ifnull(account_currency, '') = '')
|
||||
AND `{searchfield}` LIKE %(txt)s
|
||||
{mcond}
|
||||
@ -175,6 +176,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
dict(
|
||||
account_types=filters.get("account_type"),
|
||||
company=filters.get("company"),
|
||||
disabled=filters.get("disabled", 0),
|
||||
currency=company_currency,
|
||||
txt="%{}%".format(txt),
|
||||
offset=start,
|
||||
|
@ -334,7 +334,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
doc = frappe.get_doc(target)
|
||||
doc.is_return = 1
|
||||
doc.return_against = source.name
|
||||
doc.ignore_pricing_rule = 1
|
||||
doc.set_warehouse = ""
|
||||
if doctype == "Sales Invoice" or doctype == "POS Invoice":
|
||||
doc.is_pos = source.is_pos
|
||||
|
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