fix: resolved conflicts
This commit is contained in:
commit
5abb997a27
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Root editor config file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Common settings
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# python, js indentation settings
|
||||||
|
[{*.py,*.js}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
20
.eslintrc
20
.eslintrc
@ -5,7 +5,7 @@
|
|||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 6,
|
"ecmaVersion": 9,
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
@ -15,6 +15,14 @@
|
|||||||
"tab",
|
"tab",
|
||||||
{ "SwitchCase": 1 }
|
{ "SwitchCase": 1 }
|
||||||
],
|
],
|
||||||
|
"brace-style": [
|
||||||
|
"error",
|
||||||
|
"1tbs"
|
||||||
|
],
|
||||||
|
"space-unary-ops": [
|
||||||
|
"error",
|
||||||
|
{ "words": true }
|
||||||
|
],
|
||||||
"linebreak-style": [
|
"linebreak-style": [
|
||||||
"error",
|
"error",
|
||||||
"unix"
|
"unix"
|
||||||
@ -44,12 +52,10 @@
|
|||||||
"no-control-regex": [
|
"no-control-regex": [
|
||||||
"off"
|
"off"
|
||||||
],
|
],
|
||||||
"spaced-comment": [
|
"space-before-blocks": "warn",
|
||||||
"warn"
|
"keyword-spacing": "warn",
|
||||||
],
|
"comma-spacing": "warn",
|
||||||
"no-trailing-spaces": [
|
"key-spacing": "warn"
|
||||||
"warn"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"root": true,
|
"root": true,
|
||||||
"globals": {
|
"globals": {
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Community Forum
|
||||||
|
url: https://discuss.erpnext.com/
|
||||||
|
about: For general QnA, discussions and community help.
|
||||||
48
.github/helper/documentation.py
vendored
Normal file
48
.github/helper/documentation.py
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
|
docs_repos = [
|
||||||
|
"frappe_docs",
|
||||||
|
"erpnext_documentation",
|
||||||
|
"erpnext_com",
|
||||||
|
"frappe_io",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def uri_validator(x):
|
||||||
|
result = urlparse(x)
|
||||||
|
return all([result.scheme, result.netloc, result.path])
|
||||||
|
|
||||||
|
def docs_link_exists(body):
|
||||||
|
for line in body.splitlines():
|
||||||
|
for word in line.split():
|
||||||
|
if word.startswith('http') and uri_validator(word):
|
||||||
|
parsed_url = urlparse(word)
|
||||||
|
if parsed_url.netloc == "github.com":
|
||||||
|
parts = parsed_url.path.split('/')
|
||||||
|
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pr = sys.argv[1]
|
||||||
|
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
|
||||||
|
|
||||||
|
if response.ok:
|
||||||
|
payload = response.json()
|
||||||
|
title = payload.get("title", "").lower()
|
||||||
|
head_sha = payload.get("head", {}).get("sha")
|
||||||
|
body = payload.get("body", "").lower()
|
||||||
|
|
||||||
|
if title.startswith("feat") and head_sha and "no-docs" not in body:
|
||||||
|
if docs_link_exists(body):
|
||||||
|
print("Documentation Link Found. You're Awesome! 🎉")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Documentation Link Not Found! ⚠️")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Skipping documentation checks... 🏃")
|
||||||
60
.github/helper/translation.py
vendored
Normal file
60
.github/helper/translation.py
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
errors_encounter = 0
|
||||||
|
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
||||||
|
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
|
||||||
|
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
|
||||||
|
f_string_pattern = re.compile(r"_\(f[\"']")
|
||||||
|
starts_with_f_pattern = re.compile(r"_\(f")
|
||||||
|
|
||||||
|
# skip first argument
|
||||||
|
files = sys.argv[1:]
|
||||||
|
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
|
||||||
|
|
||||||
|
for _file in files_to_scan:
|
||||||
|
with open(_file, 'r') as f:
|
||||||
|
print(f'Checking: {_file}')
|
||||||
|
file_lines = f.readlines()
|
||||||
|
for line_number, line in enumerate(file_lines, 1):
|
||||||
|
if 'frappe-lint: disable-translate' in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
start_matches = start_pattern.search(line)
|
||||||
|
if start_matches:
|
||||||
|
starts_with_f = starts_with_f_pattern.search(line)
|
||||||
|
|
||||||
|
if starts_with_f:
|
||||||
|
has_f_string = f_string_pattern.search(line)
|
||||||
|
if has_f_string:
|
||||||
|
errors_encounter += 1
|
||||||
|
print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}')
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = pattern.search(line)
|
||||||
|
error_found = False
|
||||||
|
|
||||||
|
if not match and line.endswith(',\n'):
|
||||||
|
# concat remaining text to validate multiline pattern
|
||||||
|
line = "".join(file_lines[line_number - 1:])
|
||||||
|
line = line[start_matches.start() + 1:]
|
||||||
|
match = pattern.match(line)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
error_found = True
|
||||||
|
print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}')
|
||||||
|
|
||||||
|
if not error_found and not words_pattern.search(line):
|
||||||
|
error_found = True
|
||||||
|
print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}')
|
||||||
|
|
||||||
|
if error_found:
|
||||||
|
errors_encounter += 1
|
||||||
|
|
||||||
|
if errors_encounter > 0:
|
||||||
|
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print('\nGood To Go!')
|
||||||
24
.github/workflows/docs-checker.yml
vendored
Normal file
24
.github/workflows/docs-checker.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: 'Documentation Required'
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [ opened, synchronize, reopened, edited ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Setup Environment'
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.6
|
||||||
|
|
||||||
|
- name: 'Clone repo'
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Validate Docs
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
|
run: |
|
||||||
|
pip install requests --quiet
|
||||||
|
python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER
|
||||||
22
.github/workflows/translation_linter.yml
vendored
Normal file
22
.github/workflows/translation_linter.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Frappe Linter
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- version-12-hotfix
|
||||||
|
- version-11-hotfix
|
||||||
|
jobs:
|
||||||
|
check_translation:
|
||||||
|
name: Translation Syntax Check
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup python3
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.6
|
||||||
|
- name: Validating Translation Syntax
|
||||||
|
run: |
|
||||||
|
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||||
|
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||||
|
python $GITHUB_WORKSPACE/.github/helper/translation.py $files
|
||||||
@ -9,5 +9,6 @@
|
|||||||
"root_login": "root",
|
"root_login": "root",
|
||||||
"root_password": "travis",
|
"root_password": "travis",
|
||||||
"host_name": "http://test_site:8000",
|
"host_name": "http://test_site:8000",
|
||||||
"install_apps": ["erpnext"]
|
"install_apps": ["erpnext"],
|
||||||
|
"throttle_user_limit": 100
|
||||||
}
|
}
|
||||||
@ -5,7 +5,7 @@
|
|||||||
<p>ERP made simple</p>
|
<p>ERP made simple</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://travis-ci.com/frappe/erpnext)
|
[](https://travis-ci.com/frappe/erpnext)
|
||||||
[](https://www.codetriage.com/frappe/erpnext)
|
[](https://www.codetriage.com/frappe/erpnext)
|
||||||
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
||||||
|
|
||||||
@ -16,7 +16,7 @@
|
|||||||
ERPNext as a monolith includes the following areas for managing businesses:
|
ERPNext as a monolith includes the following areas for managing businesses:
|
||||||
|
|
||||||
1. [Accounting](https://erpnext.com/open-source-accounting)
|
1. [Accounting](https://erpnext.com/open-source-accounting)
|
||||||
1. [Inventory](https://erpnext.com/distribution/inventory-management-system)
|
1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
|
||||||
1. [CRM](https://erpnext.com/open-source-crm)
|
1. [CRM](https://erpnext.com/open-source-crm)
|
||||||
1. [Sales](https://erpnext.com/open-source-sales-purchase)
|
1. [Sales](https://erpnext.com/open-source-sales-purchase)
|
||||||
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
|
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
|
||||||
|
|||||||
@ -1,58 +1,126 @@
|
|||||||
{
|
{
|
||||||
"custom_fields": [
|
"custom_fields": [
|
||||||
{
|
{
|
||||||
"_assign": null,
|
"_assign": null,
|
||||||
"_comments": null,
|
"_comments": null,
|
||||||
"_liked_by": null,
|
"_liked_by": null,
|
||||||
"_user_tags": null,
|
"_user_tags": null,
|
||||||
"allow_on_submit": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"bold": 0,
|
"allow_on_submit": 0,
|
||||||
"collapsible": 0,
|
"bold": 0,
|
||||||
"collapsible_depends_on": null,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"collapsible_depends_on": null,
|
||||||
"creation": "2018-12-28 22:29:21.828090",
|
"columns": 0,
|
||||||
"default": null,
|
"creation": "2018-12-28 22:29:21.828090",
|
||||||
"depends_on": null,
|
"default": null,
|
||||||
"description": null,
|
"depends_on": null,
|
||||||
"docstatus": 0,
|
"description": null,
|
||||||
"dt": "Address",
|
"docstatus": 0,
|
||||||
"fetch_from": null,
|
"dt": "Address",
|
||||||
"fieldname": "tax_category",
|
"fetch_from": null,
|
||||||
"fieldtype": "Link",
|
"fetch_if_empty": 0,
|
||||||
"hidden": 0,
|
"fieldname": "tax_category",
|
||||||
"idx": 14,
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 0,
|
"hidden": 0,
|
||||||
"ignore_xss_filter": 0,
|
"hide_border": 0,
|
||||||
"in_global_search": 0,
|
"hide_days": 0,
|
||||||
"in_list_view": 0,
|
"hide_seconds": 0,
|
||||||
"in_standard_filter": 0,
|
"idx": 15,
|
||||||
"insert_after": "fax",
|
"ignore_user_permissions": 0,
|
||||||
"label": "Tax Category",
|
"ignore_xss_filter": 0,
|
||||||
"modified": "2018-12-28 22:29:21.828090",
|
"in_global_search": 0,
|
||||||
"modified_by": "Administrator",
|
"in_list_view": 0,
|
||||||
"name": "Address-tax_category",
|
"in_preview": 0,
|
||||||
"no_copy": 0,
|
"in_standard_filter": 0,
|
||||||
"options": "Tax Category",
|
"insert_after": "fax",
|
||||||
"owner": "Administrator",
|
"label": "Tax Category",
|
||||||
"parent": null,
|
"length": 0,
|
||||||
"parentfield": null,
|
"mandatory_depends_on": null,
|
||||||
"parenttype": null,
|
"modified": "2018-12-28 22:29:21.828090",
|
||||||
"permlevel": 0,
|
"modified_by": "Administrator",
|
||||||
"precision": "",
|
"name": "Address-tax_category",
|
||||||
"print_hide": 0,
|
"no_copy": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"options": "Tax Category",
|
||||||
"print_width": null,
|
"owner": "Administrator",
|
||||||
"read_only": 0,
|
"parent": null,
|
||||||
"report_hide": 0,
|
"parentfield": null,
|
||||||
"reqd": 0,
|
"parenttype": null,
|
||||||
"search_index": 0,
|
"permlevel": 0,
|
||||||
"translatable": 0,
|
"precision": "",
|
||||||
"unique": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"print_width": null,
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_depends_on": null,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0,
|
||||||
|
"width": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_assign": null,
|
||||||
|
"_comments": null,
|
||||||
|
"_liked_by": null,
|
||||||
|
"_user_tags": null,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"collapsible_depends_on": null,
|
||||||
|
"columns": 0,
|
||||||
|
"creation": "2020-10-14 17:41:40.878179",
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": null,
|
||||||
|
"description": null,
|
||||||
|
"docstatus": 0,
|
||||||
|
"dt": "Address",
|
||||||
|
"fetch_from": null,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
|
"fieldname": "is_your_company_address",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"hide_border": 0,
|
||||||
|
"hide_days": 0,
|
||||||
|
"hide_seconds": 0,
|
||||||
|
"idx": 20,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_preview": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"insert_after": "linked_with",
|
||||||
|
"label": "Is Your Company Address",
|
||||||
|
"length": 0,
|
||||||
|
"mandatory_depends_on": null,
|
||||||
|
"modified": "2020-10-14 17:41:40.878179",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Address-is_your_company_address",
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": null,
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent": null,
|
||||||
|
"parentfield": null,
|
||||||
|
"parenttype": null,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"print_width": null,
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_depends_on": null,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0,
|
||||||
"width": null
|
"width": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"custom_perms": [],
|
"custom_perms": [],
|
||||||
"doctype": "Address",
|
"doctype": "Address",
|
||||||
"property_setters": [],
|
"property_setters": [],
|
||||||
"sync_on_migrate": 1
|
"sync_on_migrate": 1
|
||||||
}
|
}
|
||||||
42
erpnext/accounts/custom/address.py
Normal file
42
erpnext/accounts/custom/address.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.contacts.doctype.address.address import Address
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_templates
|
||||||
|
|
||||||
|
class ERPNextAddress(Address):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_reference()
|
||||||
|
super(ERPNextAddress, self).validate()
|
||||||
|
|
||||||
|
def link_address(self):
|
||||||
|
"""Link address based on owner"""
|
||||||
|
if self.is_your_company_address:
|
||||||
|
return
|
||||||
|
|
||||||
|
return super(ERPNextAddress, self).link_address()
|
||||||
|
|
||||||
|
def validate_reference(self):
|
||||||
|
if self.is_your_company_address and not [
|
||||||
|
row for row in self.links if row.link_doctype == "Company"
|
||||||
|
]:
|
||||||
|
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
||||||
|
title=_("Company Not Linked"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_shipping_address(company, address = None):
|
||||||
|
filters = [
|
||||||
|
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||||
|
["Dynamic Link", "link_name", "=", company],
|
||||||
|
["Address", "is_your_company_address", "=", 1]
|
||||||
|
]
|
||||||
|
fields = ["*"]
|
||||||
|
if address and frappe.db.get_value('Dynamic Link',
|
||||||
|
{'parent': address, 'link_name': company}):
|
||||||
|
filters.append(["Address", "name", "=", address])
|
||||||
|
|
||||||
|
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
||||||
|
|
||||||
|
if address:
|
||||||
|
address_as_dict = address[0]
|
||||||
|
name, address_template = get_address_templates(address_as_dict)
|
||||||
|
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)
|
||||||
@ -6,9 +6,8 @@ import frappe, json
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
|
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
|
||||||
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
||||||
from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
|
from frappe.utils.dashboard import cache_source
|
||||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
|
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
|
||||||
|
|
||||||
from frappe.utils.nestedset import get_descendants_of
|
from frappe.utils.nestedset import get_descendants_of
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Reports",
|
"label": "Reports",
|
||||||
"links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n }\n]"
|
"links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"DATEV Export\",\n \"name\": \"DATEV\",\n \"type\": \"report\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Bank Statement",
|
"label": "Bank Statement",
|
||||||
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -53,7 +53,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Goods and Services Tax (GST India)",
|
"label": "Goods and Services Tax (GST India)",
|
||||||
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"India\",\n \"label\": \"Lower Deduction Certificate\",\n \"name\": \"Lower Deduction Certificate\",\n \"type\": \"doctype\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -79,6 +79,11 @@
|
|||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Profitability",
|
"label": "Profitability",
|
||||||
"links": "[\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Gross Profit\",\n \"name\": \"Gross Profit\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profitability Analysis\",\n \"name\": \"Profitability Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"type\": \"report\"\n }\n]"
|
"links": "[\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Gross Profit\",\n \"name\": \"Gross Profit\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profitability Analysis\",\n \"name\": \"Profitability Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"type\": \"report\"\n }\n]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Value-Added Tax (VAT UAE)",
|
||||||
|
"links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n\n]"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"category": "Modules",
|
"category": "Modules",
|
||||||
@ -98,7 +103,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Accounting",
|
"label": "Accounting",
|
||||||
"modified": "2020-09-03 10:37:07.865801",
|
"modified": "2020-11-11 18:35:11.542909",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
@ -147,11 +152,6 @@
|
|||||||
"link_to": "Trial Balance",
|
"link_to": "Trial Balance",
|
||||||
"type": "Report"
|
"type": "Report"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Point of Sale",
|
|
||||||
"link_to": "point-of-sale",
|
|
||||||
"type": "Page"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Dashboard",
|
"label": "Dashboard",
|
||||||
"link_to": "Accounts",
|
"link_to": "Accounts",
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class Account(NestedSet):
|
|||||||
return
|
return
|
||||||
if not frappe.db.get_value("Account",
|
if not frappe.db.get_value("Account",
|
||||||
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
|
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
|
||||||
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
|
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
|
||||||
elif self.parent_account:
|
elif self.parent_account:
|
||||||
descendants = get_descendants_of('Company', self.company)
|
descendants = get_descendants_of('Company', self.company)
|
||||||
if not descendants: return
|
if not descendants: return
|
||||||
@ -117,7 +117,9 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||||
parent_acc_name_map[d["company"]] = d["name"]
|
parent_acc_name_map[d["company"]] = d["name"]
|
||||||
|
|
||||||
if not parent_acc_name_map: return
|
if not parent_acc_name_map: return
|
||||||
|
|
||||||
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
||||||
|
|
||||||
def validate_group_or_ledger(self):
|
def validate_group_or_ledger(self):
|
||||||
@ -162,9 +164,19 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
||||||
for company in descendants:
|
for company in descendants:
|
||||||
|
company_bold = frappe.bold(company)
|
||||||
|
parent_acc_name_bold = frappe.bold(parent_acc_name)
|
||||||
if not parent_acc_name_map.get(company):
|
if not parent_acc_name_map.get(company):
|
||||||
frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
||||||
.format(company, parent_acc_name))
|
.format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
|
||||||
|
|
||||||
|
# validate if parent of child company account to be added is a group
|
||||||
|
if (frappe.db.get_value("Account", self.parent_account, "is_group")
|
||||||
|
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
|
||||||
|
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
|
||||||
|
msg += "<br><br>"
|
||||||
|
msg += _("Please convert the parent account in corresponding child company to a group account.")
|
||||||
|
frappe.throw(msg, title=_("Invalid Parent Account"))
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
"account_name": self.account_name,
|
"account_name": self.account_name,
|
||||||
@ -289,10 +301,31 @@ def validate_account_number(name, account_number, company):
|
|||||||
.format(account_number, account_with_same_number))
|
.format(account_number, account_with_same_number))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_account_number(name, account_name, account_number=None):
|
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||||
|
|
||||||
account = frappe.db.get_value("Account", name, "company", as_dict=True)
|
account = frappe.db.get_value("Account", name, "company", as_dict=True)
|
||||||
if not account: return
|
if not account: return
|
||||||
|
|
||||||
|
old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
|
||||||
|
["account_name", "account_number"])
|
||||||
|
|
||||||
|
# check if account exists in parent company
|
||||||
|
ancestors = get_ancestors_of("Company", account.company)
|
||||||
|
allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
|
||||||
|
|
||||||
|
if ancestors and not allow_independent_account_creation:
|
||||||
|
for ancestor in ancestors:
|
||||||
|
if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
|
||||||
|
# same account in parent company exists
|
||||||
|
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
||||||
|
|
||||||
|
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
|
||||||
|
message += "<br>"
|
||||||
|
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
|
||||||
|
message += "<br><br>"
|
||||||
|
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
|
||||||
|
|
||||||
|
frappe.throw(message, title=_("Rename Not Allowed"))
|
||||||
|
|
||||||
validate_account_number(name, account_number, account.company)
|
validate_account_number(name, account_number, account.company)
|
||||||
if account_number:
|
if account_number:
|
||||||
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
||||||
@ -300,6 +333,12 @@ def update_account_number(name, account_name, account_number=None):
|
|||||||
frappe.db.set_value("Account", name, "account_number", "")
|
frappe.db.set_value("Account", name, "account_number", "")
|
||||||
frappe.db.set_value("Account", name, "account_name", account_name.strip())
|
frappe.db.set_value("Account", name, "account_name", account_name.strip())
|
||||||
|
|
||||||
|
if not from_descendant:
|
||||||
|
# Update and rename in child company accounts as well
|
||||||
|
descendants = get_descendants_of('Company', account.company)
|
||||||
|
if descendants:
|
||||||
|
sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
|
||||||
|
|
||||||
new_name = get_account_autoname(account_number, account_name, account.company)
|
new_name = get_account_autoname(account_number, account_name, account.company)
|
||||||
if name != new_name:
|
if name != new_name:
|
||||||
frappe.rename_doc("Account", name, new_name, force=1)
|
frappe.rename_doc("Account", name, new_name, force=1)
|
||||||
@ -330,3 +369,14 @@ def get_root_company(company):
|
|||||||
# return the topmost company in the hierarchy
|
# return the topmost company in the hierarchy
|
||||||
ancestors = get_ancestors_of('Company', company, "lft asc")
|
ancestors = get_ancestors_of('Company', company, "lft asc")
|
||||||
return [ancestors[0]] if ancestors else []
|
return [ancestors[0]] if ancestors else []
|
||||||
|
|
||||||
|
def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
|
||||||
|
filters = {
|
||||||
|
"company": ["in", descendants],
|
||||||
|
"account_name": old_acc_name,
|
||||||
|
}
|
||||||
|
if old_acc_number:
|
||||||
|
filters["account_number"] = old_acc_number
|
||||||
|
|
||||||
|
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||||
|
update_account_number(d["name"], account_name, account_number, from_descendant=True)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ frappe.provide("frappe.treeview_settings")
|
|||||||
|
|
||||||
frappe.treeview_settings["Account"] = {
|
frappe.treeview_settings["Account"] = {
|
||||||
breadcrumb: "Accounts",
|
breadcrumb: "Accounts",
|
||||||
title: __("Chart Of Accounts"),
|
title: __("Chart of Accounts"),
|
||||||
get_tree_root: false,
|
get_tree_root: false,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
@ -97,7 +97,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
treeview.page.add_inner_button(__("Journal Entry"), function() {
|
treeview.page.add_inner_button(__("Journal Entry"), function() {
|
||||||
frappe.new_doc('Journal Entry', {company: get_company()});
|
frappe.new_doc('Journal Entry', {company: get_company()});
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
treeview.page.add_inner_button(__("New Company"), function() {
|
treeview.page.add_inner_button(__("Company"), function() {
|
||||||
frappe.new_doc('Company');
|
frappe.new_doc('Company');
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
|
|
||||||
|
|||||||
@ -910,98 +910,8 @@
|
|||||||
},
|
},
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
},
|
},
|
||||||
"Passiva": {
|
"Passiva - Verbindlichkeiten": {
|
||||||
"root_type": "Liability",
|
"root_type": "Liability",
|
||||||
"A - Eigenkapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"I - Gezeichnetes Kapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gezeichnetes Kapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"account_number": "2900"
|
|
||||||
},
|
|
||||||
"Ausstehende Einlagen auf das gezeichnete Kapital": {
|
|
||||||
"account_number": "2910",
|
|
||||||
"is_group": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"II - Kapitalr\u00fccklage": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Kapitalr\u00fccklage": {
|
|
||||||
"account_number": "2920"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"III - Gewinnr\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"1 - gesetzliche R\u00fccklage": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gesetzliche R\u00fccklage": {
|
|
||||||
"account_number": "2930"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
|
||||||
"account_number": "2950"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"4 - andere Gewinnr\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
|
||||||
"account_number": "2963"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
|
||||||
"account_number": "2964"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
|
||||||
"account_number": "2965"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
|
||||||
"account_number": "2966"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2967"
|
|
||||||
},
|
|
||||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2968"
|
|
||||||
},
|
|
||||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2969"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnvortrag vor Verwendung": {
|
|
||||||
"account_number": "2970"
|
|
||||||
},
|
|
||||||
"Verlustvortrag vor Verwendung": {
|
|
||||||
"account_number": "2978"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"Einlagen stiller Gesellschafter": {
|
|
||||||
"account_number": "9295"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"B - R\u00fcckstellungen": {
|
"B - R\u00fcckstellungen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
||||||
@ -1618,6 +1528,143 @@
|
|||||||
},
|
},
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
},
|
},
|
||||||
|
"Passiva - Eigenkapital": {
|
||||||
|
"root_type": "Equity",
|
||||||
|
"A - Eigenkapital": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"I - Gezeichnetes Kapital": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gezeichnetes Kapital": {
|
||||||
|
"account_number": "2900",
|
||||||
|
"account_type": "Equity"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
|
||||||
|
"account_number": "2901"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
|
||||||
|
"account_number": "2902"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
|
||||||
|
"account_number": "2903"
|
||||||
|
},
|
||||||
|
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||||
|
"account_number": "2906"
|
||||||
|
},
|
||||||
|
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||||
|
"account_number": "2907"
|
||||||
|
},
|
||||||
|
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
|
||||||
|
"account_number": "2908"
|
||||||
|
},
|
||||||
|
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
|
||||||
|
"account_number": "2910"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"II - Kapitalr\u00fccklage": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Kapitalr\u00fccklage": {
|
||||||
|
"account_number": "2920"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
|
||||||
|
"account_number": "2925"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
|
||||||
|
"account_number": "2926"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
|
||||||
|
"account_number": "2927"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
|
||||||
|
"account_number": "2928"
|
||||||
|
},
|
||||||
|
"Nachschusskapital (Gegenkonto 1299)": {
|
||||||
|
"account_number": "2929"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"III - Gewinnr\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"1 - gesetzliche R\u00fccklage": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gesetzliche R\u00fccklage": {
|
||||||
|
"account_number": "2930"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||||
|
"account_number": "2935"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||||
|
"account_number": "2950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4 - andere Gewinnr\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Andere Gewinnr\u00fccklagen": {
|
||||||
|
"account_number": "2960"
|
||||||
|
},
|
||||||
|
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
|
||||||
|
"account_number": "2961"
|
||||||
|
},
|
||||||
|
"Eigenkapitalanteil von Wertaufholungen": {
|
||||||
|
"account_number": "2962"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||||
|
"account_number": "2963"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||||
|
"account_number": "2964"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||||
|
"account_number": "2965"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||||
|
"account_number": "2966"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2967"
|
||||||
|
},
|
||||||
|
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2968"
|
||||||
|
},
|
||||||
|
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2969"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gewinnvortrag vor Verwendung": {
|
||||||
|
"account_number": "2970"
|
||||||
|
},
|
||||||
|
"Verlustvortrag vor Verwendung": {
|
||||||
|
"account_number": "2978"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"Einlagen stiller Gesellschafter": {
|
||||||
|
"account_number": "9295"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"1 - Umsatzerl\u00f6se": {
|
"1 - Umsatzerl\u00f6se": {
|
||||||
"root_type": "Income",
|
"root_type": "Income",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
|||||||
@ -245,6 +245,9 @@ def get():
|
|||||||
"account_number": "2200"
|
"account_number": "2200"
|
||||||
},
|
},
|
||||||
_("Duties and Taxes"): {
|
_("Duties and Taxes"): {
|
||||||
|
_("TDS Payable"): {
|
||||||
|
"account_number": "2310"
|
||||||
|
},
|
||||||
"account_type": "Tax",
|
"account_type": "Tax",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"account_number": "2300"
|
"account_number": "2300"
|
||||||
|
|||||||
@ -5,8 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import unittest
|
import unittest
|
||||||
import frappe
|
import frappe
|
||||||
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
|
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
|
||||||
from erpnext.accounts.doctype.account.account import update_account_number
|
from erpnext.accounts.doctype.account.account import update_account_number, merge_account
|
||||||
from erpnext.accounts.doctype.account.account import merge_account
|
|
||||||
|
|
||||||
class TestAccount(unittest.TestCase):
|
class TestAccount(unittest.TestCase):
|
||||||
def test_rename_account(self):
|
def test_rename_account(self):
|
||||||
@ -99,7 +98,8 @@ class TestAccount(unittest.TestCase):
|
|||||||
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
|
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
|
||||||
|
|
||||||
def test_account_sync(self):
|
def test_account_sync(self):
|
||||||
del frappe.local.flags["ignore_root_company_validation"]
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
acc = frappe.new_doc("Account")
|
acc = frappe.new_doc("Account")
|
||||||
acc.account_name = "Test Sync Account"
|
acc.account_name = "Test Sync Account"
|
||||||
acc.parent_account = "Temporary Accounts - _TC3"
|
acc.parent_account = "Temporary Accounts - _TC3"
|
||||||
@ -111,7 +111,68 @@ class TestAccount(unittest.TestCase):
|
|||||||
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
||||||
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
||||||
|
|
||||||
def _make_test_records(verbose):
|
def test_add_account_to_a_group(self):
|
||||||
|
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 1)
|
||||||
|
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = "Test Group Account"
|
||||||
|
acc.parent_account = "Office Rent - _TC3"
|
||||||
|
acc.company = "_Test Company 3"
|
||||||
|
self.assertRaises(frappe.ValidationError, acc.insert)
|
||||||
|
|
||||||
|
frappe.db.set_value("Account", "Office Rent - _TC3", "is_group", 0)
|
||||||
|
|
||||||
|
def test_account_rename_sync(self):
|
||||||
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = "Test Rename Account"
|
||||||
|
acc.parent_account = "Temporary Accounts - _TC3"
|
||||||
|
acc.company = "_Test Company 3"
|
||||||
|
acc.insert()
|
||||||
|
|
||||||
|
# Rename account in parent company
|
||||||
|
update_account_number(acc.name, "Test Rename Sync Account", "1234")
|
||||||
|
|
||||||
|
# Check if renamed in children
|
||||||
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 4", "account_number": "1234"}))
|
||||||
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 5", "account_number": "1234"}))
|
||||||
|
|
||||||
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
|
||||||
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
|
||||||
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
|
||||||
|
|
||||||
|
def test_child_company_account_rename_sync(self):
|
||||||
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = "Test Group Account"
|
||||||
|
acc.parent_account = "Temporary Accounts - _TC3"
|
||||||
|
acc.is_group = 1
|
||||||
|
acc.company = "_Test Company 3"
|
||||||
|
acc.insert()
|
||||||
|
|
||||||
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 4"}))
|
||||||
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 5"}))
|
||||||
|
|
||||||
|
# Try renaming child company account
|
||||||
|
acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Group Account", "company": "_Test Company 5"})
|
||||||
|
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
|
||||||
|
|
||||||
|
# Rename child company account with allow_account_creation_against_child_company enabled
|
||||||
|
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
|
||||||
|
|
||||||
|
update_account_number(acc_tc_5, "Test Modified Account")
|
||||||
|
self.assertTrue(frappe.db.exists("Account", {'name': "Test Modified Account - _TC5", "company": "_Test Company 5"}))
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
|
||||||
|
|
||||||
|
to_delete = ["Test Group Account - _TC3", "Test Group Account - _TC4", "Test Modified Account - _TC5"]
|
||||||
|
for doc in to_delete:
|
||||||
|
frappe.delete_doc("Account", doc)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_test_records(verbose=None):
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.test_runner import make_test_objects
|
||||||
|
|
||||||
accounts = [
|
accounts = [
|
||||||
|
|||||||
@ -7,7 +7,7 @@ frappe.ui.form.on('Accounting Dimension', {
|
|||||||
frm.set_query('document_type', () => {
|
frm.set_query('document_type', () => {
|
||||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||||
invalid_doctypes.push('Accounting Dimension', 'Project',
|
invalid_doctypes.push('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail');
|
'Cost Center', 'Accounting Dimension Detail', 'Company');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail') :
|
'Cost Center', 'Accounting Dimension Detail', 'Company') :
|
||||||
|
|
||||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"description": "If enabled, the system will post accounting entries for inventory automatically.",
|
"description": "If enabled, the system will post accounting entries for inventory automatically",
|
||||||
"fieldname": "auto_accounting_for_stock",
|
"fieldname": "auto_accounting_for_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@ -48,23 +48,23 @@
|
|||||||
"label": "Make Accounting Entry For Every Stock Movement"
|
"label": "Make Accounting Entry For Every Stock Movement"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
|
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
|
||||||
"fieldname": "acc_frozen_upto",
|
"fieldname": "acc_frozen_upto",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Accounts Frozen Upto"
|
"label": "Accounts Frozen Till Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
|
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
|
||||||
"fieldname": "frozen_accounts_modifier",
|
"fieldname": "frozen_accounts_modifier",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
|
"label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
|
||||||
"options": "Role"
|
"options": "Role"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Billing Address",
|
"default": "Billing Address",
|
||||||
"description": "Address used to determine Tax Category in transactions.",
|
"description": "Address used to determine Tax Category in transactions",
|
||||||
"fieldname": "determine_address_tax_category_from",
|
"fieldname": "determine_address_tax_category_from",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Determine Address Tax Category From",
|
"label": "Determine Address Tax Category From",
|
||||||
@ -75,7 +75,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Role that is allowed to submit transactions that exceed credit limits set.",
|
"description": "This role is allowed to submit transactions that exceed credit limits",
|
||||||
"fieldname": "credit_controller",
|
"fieldname": "credit_controller",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -104,7 +104,7 @@
|
|||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Unlink Advance Payment on Cancelation of Order"
|
"label": "Unlink Advance Payment on Cancellation of Order"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
@ -127,7 +127,7 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "show_inclusive_tax_in_print",
|
"fieldname": "show_inclusive_tax_in_print",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Inclusive Tax In Print"
|
"label": "Show Inclusive Tax in Print"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
@ -165,7 +165,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Only select if you have setup Cash Flow Mapper documents",
|
"description": "Only select this if you have set up the Cash Flow Mapper documents",
|
||||||
"fieldname": "use_custom_cash_flow",
|
"fieldname": "use_custom_cash_flow",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Use Custom Cash Flow Format"
|
"label": "Use Custom Cash Flow Format"
|
||||||
@ -177,7 +177,7 @@
|
|||||||
"label": "Automatically Fetch Payment Terms"
|
"label": "Automatically Fetch Payment Terms"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
|
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||||
"fieldname": "over_billing_allowance",
|
"fieldname": "over_billing_allowance",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Over Billing Allowance (%)"
|
"label": "Over Billing Allowance (%)"
|
||||||
@ -199,7 +199,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
|
"description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense",
|
||||||
"fieldname": "book_deferred_entries_via_journal_entry",
|
"fieldname": "book_deferred_entries_via_journal_entry",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Book Deferred Entries Via Journal Entry"
|
"label": "Book Deferred Entries Via Journal Entry"
|
||||||
@ -214,7 +214,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Days",
|
"default": "Days",
|
||||||
"description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
|
"description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
|
||||||
"fieldname": "book_deferred_entries_based_on",
|
"fieldname": "book_deferred_entries_based_on",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Book Deferred Entries Based On",
|
"label": "Book Deferred Entries Based On",
|
||||||
@ -223,9 +223,10 @@
|
|||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-03 20:13:26.043092",
|
"modified": "2020-10-13 11:32:52.268826",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
@ -253,4 +254,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
|
|||||||
|
|
||||||
def populate_payment_entries(self):
|
def populate_payment_entries(self):
|
||||||
if self.bank_statement is None: return
|
if self.bank_statement is None: return
|
||||||
filename = self.bank_statement.split("/")[-1]
|
file_url = self.bank_statement
|
||||||
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
|
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
|
||||||
frappe.throw(_("Transactions already retreived from the statement"))
|
frappe.throw(_("Transactions already retreived from the statement"))
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
|
|||||||
if self.bank_settings:
|
if self.bank_settings:
|
||||||
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
|
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
|
||||||
statement_headers = self.get_statement_headers()
|
statement_headers = self.get_statement_headers()
|
||||||
transactions = get_transaction_entries(filename, statement_headers)
|
transactions = get_transaction_entries(file_url, statement_headers)
|
||||||
for entry in transactions:
|
for entry in transactions:
|
||||||
date = entry[statement_headers["Date"]].strip()
|
date = entry[statement_headers["Date"]].strip()
|
||||||
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
|
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
|
||||||
@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
|
|||||||
transaction[header] = ""
|
transaction[header] = ""
|
||||||
return transaction
|
return transaction
|
||||||
|
|
||||||
def get_transaction_entries(filename, headers):
|
def get_transaction_entries(file_url, headers):
|
||||||
header_index = {}
|
header_index = {}
|
||||||
rows, transactions = [], []
|
rows, transactions = [], []
|
||||||
|
|
||||||
if (filename.lower().endswith("xlsx")):
|
if (file_url.lower().endswith("xlsx")):
|
||||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
||||||
rows = read_xlsx_file_from_attached_file(file_id=filename)
|
rows = read_xlsx_file_from_attached_file(file_url=file_url)
|
||||||
elif (filename.lower().endswith("csv")):
|
elif (file_url.lower().endswith("csv")):
|
||||||
from frappe.utils.csvutils import read_csv_content
|
from frappe.utils.csvutils import read_csv_content
|
||||||
_file = frappe.get_doc("File", {"file_name": filename})
|
_file = frappe.get_doc("File", {"file_url": file_url})
|
||||||
filepath = _file.get_full_path()
|
filepath = _file.get_full_path()
|
||||||
with open(filepath,'rb') as csvfile:
|
with open(filepath,'rb') as csvfile:
|
||||||
rows = read_csv_content(csvfile.read())
|
rows = read_csv_content(csvfile.read())
|
||||||
elif (filename.lower().endswith("xls")):
|
elif (file_url.lower().endswith("xls")):
|
||||||
|
filename = file_url.split("/")[-1]
|
||||||
rows = get_rows_from_xls_file(filename)
|
rows = get_rows_from_xls_file(filename)
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Only .csv and .xlsx files are supported currently"))
|
frappe.throw(_("Only .csv and .xlsx files are supported currently"))
|
||||||
|
|||||||
@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
|
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
|
||||||
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
|
||||||
test_dependencies = ["Item", "Cost Center"]
|
test_dependencies = ["Item", "Cost Center"]
|
||||||
|
|
||||||
class TestBankTransaction(unittest.TestCase):
|
class TestBankTransaction(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
make_pos_profile()
|
||||||
add_transactions()
|
add_transactions()
|
||||||
add_payments()
|
add_payments()
|
||||||
|
|
||||||
@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
|
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
|
||||||
frappe.db.sql("""delete from `tabPayment Entry`""")
|
frappe.db.sql("""delete from `tabPayment Entry`""")
|
||||||
|
|
||||||
|
# Delete POS Profile
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
frappe.flags.test_bank_transactions_created = False
|
frappe.flags.test_bank_transactions_created = False
|
||||||
frappe.flags.test_payments_created = False
|
frappe.flags.test_payments_created = False
|
||||||
|
|
||||||
@ -91,15 +96,11 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
|
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
|
||||||
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
|
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
|
||||||
|
|
||||||
def add_transactions():
|
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||||
if frappe.flags.test_bank_transactions_created:
|
|
||||||
return
|
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
try:
|
try:
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Bank",
|
"doctype": "Bank",
|
||||||
"bank_name":"Citi Bank",
|
"bank_name":bank_name,
|
||||||
}).insert()
|
}).insert()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
@ -108,12 +109,19 @@ def add_transactions():
|
|||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Bank Account",
|
"doctype": "Bank Account",
|
||||||
"account_name":"Checking Account",
|
"account_name":"Checking Account",
|
||||||
"bank": "Citi Bank",
|
"bank": bank_name,
|
||||||
"account": "_Test Bank - _TC"
|
"account": account_name
|
||||||
}).insert()
|
}).insert()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def add_transactions():
|
||||||
|
if frappe.flags.test_bank_transactions_created:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
create_bank_account()
|
||||||
|
|
||||||
doc = frappe.get_doc({
|
doc = frappe.get_doc({
|
||||||
"doctype": "Bank Transaction",
|
"doctype": "Bank Transaction",
|
||||||
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -158,8 +158,11 @@ class TestBudget(unittest.TestCase):
|
|||||||
set_total_expense_zero(nowdate(), "cost_center")
|
set_total_expense_zero(nowdate(), "cost_center")
|
||||||
|
|
||||||
budget = make_budget(budget_against="Cost Center")
|
budget = make_budget(budget_against="Cost Center")
|
||||||
|
month = now_datetime().month
|
||||||
|
if month > 9:
|
||||||
|
month = 9
|
||||||
|
|
||||||
for i in range(now_datetime().month):
|
for i in range(month+1):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||||
|
|
||||||
@ -177,8 +180,11 @@ class TestBudget(unittest.TestCase):
|
|||||||
set_total_expense_zero(nowdate(), "project")
|
set_total_expense_zero(nowdate(), "project")
|
||||||
|
|
||||||
budget = make_budget(budget_against="Project")
|
budget = make_budget(budget_against="Project")
|
||||||
|
month = now_datetime().month
|
||||||
|
if month > 9:
|
||||||
|
month = 9
|
||||||
|
|
||||||
for i in range(now_datetime().month):
|
for i in range(month + 1):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
||||||
|
|
||||||
|
|||||||
@ -23,13 +23,13 @@ class CashierClosing(Document):
|
|||||||
where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
|
where posting_date=%s and posting_time>=%s and posting_time<=%s and owner=%s
|
||||||
""", (self.date, self.from_time, self.time, self.user))
|
""", (self.date, self.from_time, self.time, self.user))
|
||||||
self.outstanding_amount = flt(values[0][0] if values else 0)
|
self.outstanding_amount = flt(values[0][0] if values else 0)
|
||||||
|
|
||||||
def make_calculations(self):
|
def make_calculations(self):
|
||||||
total = 0.00
|
total = 0.00
|
||||||
for i in self.payments:
|
for i in self.payments:
|
||||||
total += flt(i.amount)
|
total += flt(i.amount)
|
||||||
|
|
||||||
self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns
|
self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
|
||||||
|
|
||||||
def validate_time(self):
|
def validate_time(self):
|
||||||
if self.from_time >= self.time:
|
if self.from_time >= self.time:
|
||||||
|
|||||||
@ -94,8 +94,7 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
|||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(r.message===false) {
|
if(r.message===false) {
|
||||||
frm.set_value("company", "");
|
frm.set_value("company", "");
|
||||||
frappe.throw(__(`Transactions against the company already exist!
|
frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."));
|
||||||
Chart Of accounts can be imported for company with no transactions`));
|
|
||||||
} else {
|
} else {
|
||||||
frm.trigger("refresh");
|
frm.trigger("refresh");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -195,7 +195,7 @@ def build_response_as_excel(writer):
|
|||||||
reader = csv.reader(f)
|
reader = csv.reader(f)
|
||||||
|
|
||||||
from frappe.utils.xlsxutils import make_xlsx
|
from frappe.utils.xlsxutils import make_xlsx
|
||||||
xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
|
xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template")
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
|
|||||||
@ -28,22 +28,22 @@ def test_create_test_data():
|
|||||||
"item_group": "_Test Item Group",
|
"item_group": "_Test Item Group",
|
||||||
"item_name": "_Test Tesla Car",
|
"item_name": "_Test Tesla Car",
|
||||||
"apply_warehouse_wise_reorder_level": 0,
|
"apply_warehouse_wise_reorder_level": 0,
|
||||||
"warehouse":"Stores - TCP1",
|
"warehouse":"Stores - _TC",
|
||||||
"gst_hsn_code": "999800",
|
"gst_hsn_code": "999800",
|
||||||
"valuation_rate": 5000,
|
"valuation_rate": 5000,
|
||||||
"standard_rate":5000,
|
"standard_rate":5000,
|
||||||
"item_defaults": [{
|
"item_defaults": [{
|
||||||
"company": "_Test Company with perpetual inventory",
|
"company": "_Test Company",
|
||||||
"default_warehouse": "Stores - TCP1",
|
"default_warehouse": "Stores - _TC",
|
||||||
"default_price_list":"_Test Price List",
|
"default_price_list":"_Test Price List",
|
||||||
"expense_account": "Cost of Goods Sold - TCP1",
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
"buying_cost_center": "Main - TCP1",
|
"buying_cost_center": "Main - _TC",
|
||||||
"selling_cost_center": "Main - TCP1",
|
"selling_cost_center": "Main - _TC",
|
||||||
"income_account": "Sales - TCP1"
|
"income_account": "Sales - _TC"
|
||||||
}],
|
}],
|
||||||
"show_in_website": 1,
|
"show_in_website": 1,
|
||||||
"route":"-test-tesla-car",
|
"route":"-test-tesla-car",
|
||||||
"website_warehouse": "Stores - TCP1"
|
"website_warehouse": "Stores - _TC"
|
||||||
})
|
})
|
||||||
item.insert()
|
item.insert()
|
||||||
# create test item price
|
# create test item price
|
||||||
@ -65,12 +65,12 @@ def test_create_test_data():
|
|||||||
"items": [{
|
"items": [{
|
||||||
"item_code": "_Test Tesla Car"
|
"item_code": "_Test Tesla Car"
|
||||||
}],
|
}],
|
||||||
"warehouse":"Stores - TCP1",
|
"warehouse":"Stores - _TC",
|
||||||
"coupon_code_based":1,
|
"coupon_code_based":1,
|
||||||
"selling": 1,
|
"selling": 1,
|
||||||
"rate_or_discount": "Discount Percentage",
|
"rate_or_discount": "Discount Percentage",
|
||||||
"discount_percentage": 30,
|
"discount_percentage": 30,
|
||||||
"company": "_Test Company with perpetual inventory",
|
"company": "_Test Company",
|
||||||
"currency":"INR",
|
"currency":"INR",
|
||||||
"for_price_list":"_Test Price List"
|
"for_price_list":"_Test Price List"
|
||||||
})
|
})
|
||||||
@ -85,7 +85,7 @@ def test_create_test_data():
|
|||||||
})
|
})
|
||||||
sales_partner.insert()
|
sales_partner.insert()
|
||||||
# create test item coupon code
|
# create test item coupon code
|
||||||
if not frappe.db.exists("Coupon Code","SAVE30"):
|
if not frappe.db.exists("Coupon Code", "SAVE30"):
|
||||||
coupon_code = frappe.get_doc({
|
coupon_code = frappe.get_doc({
|
||||||
"doctype": "Coupon Code",
|
"doctype": "Coupon Code",
|
||||||
"coupon_name":"SAVE30",
|
"coupon_name":"SAVE30",
|
||||||
@ -102,35 +102,27 @@ class TestCouponCode(unittest.TestCase):
|
|||||||
test_create_test_data()
|
test_create_test_data()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
def test_1_check_coupon_code_used_before_so(self):
|
def test_sales_order_with_coupon_code(self):
|
||||||
coupon_code = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
|
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
|
||||||
# reset used coupon code count
|
|
||||||
coupon_code.used=0
|
|
||||||
coupon_code.save()
|
|
||||||
# check no coupon code is used before sales order is made
|
|
||||||
self.assertEqual(coupon_code.get("used"),0)
|
|
||||||
|
|
||||||
def test_2_sales_order_with_coupon_code(self):
|
so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
|
||||||
so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
|
customer="_Test Customer", selling_price_list="_Test Price List",
|
||||||
customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1,
|
item_code="_Test Tesla Car", rate=5000, qty=1,
|
||||||
do_not_submit=True)
|
do_not_submit=True)
|
||||||
|
|
||||||
so = frappe.get_doc('Sales Order', so.name)
|
|
||||||
# check item price before coupon code is applied
|
|
||||||
self.assertEqual(so.items[0].rate, 5000)
|
self.assertEqual(so.items[0].rate, 5000)
|
||||||
|
|
||||||
so.coupon_code='SAVE30'
|
so.coupon_code='SAVE30'
|
||||||
so.sales_partner='_Test Coupon Partner'
|
so.sales_partner='_Test Coupon Partner'
|
||||||
so.save()
|
so.save()
|
||||||
|
|
||||||
# check item price after coupon code is applied
|
# check item price after coupon code is applied
|
||||||
self.assertEqual(so.items[0].rate, 3500)
|
self.assertEqual(so.items[0].rate, 3500)
|
||||||
|
|
||||||
so.submit()
|
so.submit()
|
||||||
|
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)
|
||||||
def test_3_check_coupon_code_used_after_so(self):
|
|
||||||
doc = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
|
|
||||||
# check no coupon code is used before sales order is made
|
|
||||||
self.assertEqual(doc.get("used"),1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,11 +9,7 @@ frappe.ui.form.on('Fiscal Year', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
let doc = frm.doc;
|
if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) {
|
||||||
frm.toggle_enable('year_start_date', doc.__islocal);
|
|
||||||
frm.toggle_enable('year_end_date', doc.__islocal);
|
|
||||||
|
|
||||||
if (!doc.__islocal && (doc.name != frappe.sys_defaults.fiscal_year)) {
|
|
||||||
frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm));
|
frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm));
|
||||||
frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'"));
|
frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'"));
|
||||||
} else {
|
} else {
|
||||||
@ -24,8 +20,10 @@ frappe.ui.form.on('Fiscal Year', {
|
|||||||
return frm.call('set_as_default');
|
return frm.call('set_as_default');
|
||||||
},
|
},
|
||||||
year_start_date: function(frm) {
|
year_start_date: function(frm) {
|
||||||
let year_end_date =
|
if (!frm.doc.is_short_year) {
|
||||||
frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1);
|
let year_end_date =
|
||||||
frm.set_value("year_end_date", year_end_date);
|
frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1);
|
||||||
|
frm.set_value("year_end_date", year_end_date);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,347 +1,126 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"allow_import": 1,
|
||||||
"allow_import": 1,
|
"autoname": "field:year",
|
||||||
"allow_rename": 0,
|
"creation": "2013-01-22 16:50:25",
|
||||||
"autoname": "field:year",
|
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
||||||
"beta": 0,
|
"doctype": "DocType",
|
||||||
"creation": "2013-01-22 16:50:25",
|
"document_type": "Setup",
|
||||||
"custom": 0,
|
"engine": "InnoDB",
|
||||||
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
"field_order": [
|
||||||
"docstatus": 0,
|
"year",
|
||||||
"doctype": "DocType",
|
"disabled",
|
||||||
"document_type": "Setup",
|
"is_short_year",
|
||||||
"editable_grid": 0,
|
"year_start_date",
|
||||||
|
"year_end_date",
|
||||||
|
"companies",
|
||||||
|
"auto_created"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"description": "For e.g. 2012, 2012-13",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "year",
|
||||||
"bold": 0,
|
"fieldtype": "Data",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Year Name",
|
||||||
"description": "For e.g. 2012, 2012-13",
|
"oldfieldname": "year",
|
||||||
"fieldname": "year",
|
"oldfieldtype": "Data",
|
||||||
"fieldtype": "Data",
|
"reqd": 1,
|
||||||
"hidden": 0,
|
"unique": 1
|
||||||
"ignore_user_permissions": 0,
|
},
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Year Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "year",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "disabled",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"label": "Disabled"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "disabled",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Disabled",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "year_start_date",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Date",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Year Start Date",
|
||||||
"columns": 0,
|
"no_copy": 1,
|
||||||
"fieldname": "year_start_date",
|
"oldfieldname": "year_start_date",
|
||||||
"fieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"hidden": 0,
|
"reqd": 1,
|
||||||
"ignore_user_permissions": 0,
|
"set_only_once": 1
|
||||||
"ignore_xss_filter": 0,
|
},
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Year Start Date",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "year_start_date",
|
|
||||||
"oldfieldtype": "Date",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "year_end_date",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Date",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Year End Date",
|
||||||
"columns": 0,
|
"no_copy": 1,
|
||||||
"fieldname": "year_end_date",
|
"reqd": 1,
|
||||||
"fieldtype": "Date",
|
"set_only_once": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Year End Date",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "companies",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Table",
|
||||||
"bold": 0,
|
"label": "Companies",
|
||||||
"collapsible": 0,
|
"options": "Fiscal Year Company"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "companies",
|
|
||||||
"fieldtype": "Table",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Companies",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Fiscal Year Company",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "auto_created",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"hidden": 1,
|
||||||
"columns": 0,
|
"label": "Auto Created",
|
||||||
"default": "0",
|
"no_copy": 1,
|
||||||
"fieldname": "auto_created",
|
"print_hide": 1,
|
||||||
"fieldtype": "Check",
|
"read_only": 1
|
||||||
"hidden": 1,
|
},
|
||||||
"ignore_user_permissions": 0,
|
{
|
||||||
"ignore_xss_filter": 0,
|
"default": "0",
|
||||||
"in_filter": 0,
|
"description": "Less than 12 months.",
|
||||||
"in_global_search": 0,
|
"fieldname": "is_short_year",
|
||||||
"in_list_view": 0,
|
"fieldtype": "Check",
|
||||||
"in_standard_filter": 0,
|
"label": "Is Short Year",
|
||||||
"label": "Auto Created",
|
"set_only_once": 1
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"icon": "fa fa-calendar",
|
||||||
"hide_heading": 0,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"icon": "fa fa-calendar",
|
"modified": "2020-11-05 12:16:53.081573",
|
||||||
"idx": 1,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Accounts",
|
||||||
"in_create": 0,
|
"name": "Fiscal Year",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-04-25 14:21:41.273354",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Fiscal Year",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"print": 1,
|
||||||
"email": 1,
|
"read": 1,
|
||||||
"export": 0,
|
"report": 1,
|
||||||
"if_owner": 0,
|
"role": "System Manager",
|
||||||
"import": 0,
|
"share": 1,
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Sales User"
|
||||||
"create": 0,
|
},
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Sales User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Purchase User"
|
||||||
"create": 0,
|
},
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Purchase User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Accounts User"
|
||||||
"create": 0,
|
},
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Accounts User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Stock User"
|
||||||
"create": 0,
|
},
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Stock User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Employee"
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Employee",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"show_name_in_global_search": 1,
|
||||||
"read_only": 0,
|
"sort_field": "name",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"show_name_in_global_search": 1,
|
|
||||||
"sort_field": "name",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -36,6 +36,11 @@ class FiscalYear(Document):
|
|||||||
frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))
|
frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))
|
||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
|
if self.is_short_year:
|
||||||
|
# Fiscal Year can be shorter than one year, in some jurisdictions
|
||||||
|
# under certain circumstances. For example, in the USA and Germany.
|
||||||
|
return
|
||||||
|
|
||||||
if getdate(self.year_start_date) > getdate(self.year_end_date):
|
if getdate(self.year_start_date) > getdate(self.year_end_date):
|
||||||
frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
|
frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
|
||||||
FiscalYearIncorrectDate)
|
FiscalYearIncorrectDate)
|
||||||
@ -116,12 +121,8 @@ def auto_create_fiscal_year():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_from_and_to_date(fiscal_year):
|
def get_from_and_to_date(fiscal_year):
|
||||||
from_and_to_date_tuple = frappe.db.sql("""select year_start_date, year_end_date
|
fields = [
|
||||||
from `tabFiscal Year` where name=%s""", (fiscal_year))[0]
|
"year_start_date as from_date",
|
||||||
|
"year_end_date as to_date"
|
||||||
from_and_to_date = {
|
]
|
||||||
"from_date": from_and_to_date_tuple[0],
|
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)
|
||||||
"to_date": from_and_to_date_tuple[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return from_and_to_date
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ test_records = frappe.get_test_records('Fiscal Year')
|
|||||||
test_ignore = ["Company"]
|
test_ignore = ["Company"]
|
||||||
|
|
||||||
class TestFiscalYear(unittest.TestCase):
|
class TestFiscalYear(unittest.TestCase):
|
||||||
|
|
||||||
def test_extra_year(self):
|
def test_extra_year(self):
|
||||||
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
|
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
|
||||||
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")
|
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"doctype": "Fiscal Year",
|
||||||
|
"year": "_Test Short Fiscal Year 2011",
|
||||||
|
"is_short_year": 1,
|
||||||
|
"year_end_date": "2011-04-01",
|
||||||
|
"year_start_date": "2011-12-31"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Fiscal Year",
|
"doctype": "Fiscal Year",
|
||||||
"year": "_Test Fiscal Year 2012",
|
"year": "_Test Fiscal Year 2012",
|
||||||
|
|||||||
@ -30,20 +30,22 @@ class GLEntry(Document):
|
|||||||
self.pl_must_have_cost_center()
|
self.pl_must_have_cost_center()
|
||||||
self.validate_cost_center()
|
self.validate_cost_center()
|
||||||
|
|
||||||
self.check_pl_account()
|
if not self.flags.from_repost:
|
||||||
self.validate_party()
|
self.check_pl_account()
|
||||||
self.validate_currency()
|
self.validate_party()
|
||||||
|
self.validate_currency()
|
||||||
|
|
||||||
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
|
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
|
||||||
self.validate_account_details(adv_adj)
|
if not from_repost:
|
||||||
self.validate_dimensions_for_pl_and_bs()
|
self.validate_account_details(adv_adj)
|
||||||
|
self.validate_dimensions_for_pl_and_bs()
|
||||||
|
|
||||||
validate_frozen_account(self.account, adv_adj)
|
validate_frozen_account(self.account, adv_adj)
|
||||||
validate_balance_type(self.account, adv_adj)
|
validate_balance_type(self.account, adv_adj)
|
||||||
|
|
||||||
# Update outstanding amt on against voucher
|
# Update outstanding amt on against voucher
|
||||||
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
|
if self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] \
|
||||||
and self.against_voucher and update_outstanding == 'Yes':
|
and self.against_voucher and update_outstanding == 'Yes' and not from_repost:
|
||||||
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
|
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
|
||||||
self.against_voucher)
|
self.against_voucher)
|
||||||
|
|
||||||
@ -106,8 +108,8 @@ class GLEntry(Document):
|
|||||||
from tabAccount where name=%s""", self.account, as_dict=1)[0]
|
from tabAccount where name=%s""", self.account, as_dict=1)[0]
|
||||||
|
|
||||||
if ret.is_group==1:
|
if ret.is_group==1:
|
||||||
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
|
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
|
||||||
transactions''').format(self.voucher_type, self.voucher_no, self.account))
|
.format(self.voucher_type, self.voucher_no, self.account))
|
||||||
|
|
||||||
if ret.docstatus==2:
|
if ret.docstatus==2:
|
||||||
frappe.throw(_("{0} {1}: Account {2} is inactive")
|
frappe.throw(_("{0} {1}: Account {2} is inactive")
|
||||||
@ -136,8 +138,8 @@ class GLEntry(Document):
|
|||||||
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
||||||
|
|
||||||
if self.cost_center and _check_is_group():
|
if self.cost_center and _check_is_group():
|
||||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
|
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
|
||||||
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||||
|
|
||||||
def validate_party(self):
|
def validate_party(self):
|
||||||
validate_party_frozen_disabled(self.party_type, self.party)
|
validate_party_frozen_disabled(self.party_type, self.party)
|
||||||
|
|||||||
@ -137,11 +137,12 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
"cost_center": erpnext.get_default_cost_center(self.company)
|
"cost_center": erpnext.get_default_cost_center(self.company)
|
||||||
})
|
})
|
||||||
|
|
||||||
je.append("accounts", {
|
if self.bank_charges:
|
||||||
"account": self.bank_charges_account,
|
je.append("accounts", {
|
||||||
"debit_in_account_currency": flt(self.bank_charges),
|
"account": self.bank_charges_account,
|
||||||
"cost_center": erpnext.get_default_cost_center(self.company)
|
"debit_in_account_currency": flt(self.bank_charges),
|
||||||
})
|
"cost_center": erpnext.get_default_cost_center(self.company)
|
||||||
|
})
|
||||||
|
|
||||||
je.append("accounts", {
|
je.append("accounts", {
|
||||||
"account": self.short_term_loan,
|
"account": self.short_term_loan,
|
||||||
|
|||||||
@ -80,6 +80,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
|||||||
short_term_loan=self.short_term_loan,
|
short_term_loan=self.short_term_loan,
|
||||||
bank_charges_account=self.bank_charges_account,
|
bank_charges_account=self.bank_charges_account,
|
||||||
bank_account=self.bank_account,
|
bank_account=self.bank_account,
|
||||||
|
bank_charges=100
|
||||||
)
|
)
|
||||||
|
|
||||||
je = inv_disc.create_disbursement_entry()
|
je = inv_disc.create_disbursement_entry()
|
||||||
@ -289,6 +290,7 @@ def create_invoice_discounting(invoices, **args):
|
|||||||
inv_disc.bank_account=args.bank_account
|
inv_disc.bank_account=args.bank_account
|
||||||
inv_disc.loan_start_date = args.start or nowdate()
|
inv_disc.loan_start_date = args.start or nowdate()
|
||||||
inv_disc.loan_period = args.period or 30
|
inv_disc.loan_period = args.period or 30
|
||||||
|
inv_disc.bank_charges = flt(args.bank_charges)
|
||||||
|
|
||||||
for d in invoices:
|
for d in invoices:
|
||||||
inv_disc.append("invoices", {
|
inv_disc.append("invoices", {
|
||||||
|
|||||||
@ -38,8 +38,8 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2020-06-18 20:27:42.615842",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "ahmad@havenir.com",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Item Tax Template",
|
"name": "Item Tax Template",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@ -20,7 +20,8 @@ def get_data():
|
|||||||
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'items': ['Item']
|
'label': _('Stock'),
|
||||||
|
'items': ['Item Groups', 'Item']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -210,7 +210,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
$.each(this.frm.doc.accounts || [], function(i, jvd) {
|
$.each(this.frm.doc.accounts || [], function(i, jvd) {
|
||||||
frappe.model.set_default_values(jvd);
|
frappe.model.set_default_values(jvd);
|
||||||
});
|
});
|
||||||
var posting_date = this.frm.posting_date;
|
var posting_date = this.frm.doc.posting_date;
|
||||||
if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
|
if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_auto_repeat": 1,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2013-03-25 10:53:52",
|
"creation": "2013-03-25 10:53:52",
|
||||||
@ -503,7 +504,7 @@
|
|||||||
"idx": 176,
|
"idx": 176,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-02 18:15:46.955697",
|
"modified": "2020-10-30 13:56:01.121995",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry",
|
"name": "Journal Entry",
|
||||||
|
|||||||
@ -6,14 +6,18 @@ import frappe, erpnext, json
|
|||||||
from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form
|
from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form
|
||||||
from frappe import msgprint, _, scrub
|
from frappe import msgprint, _, scrub
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
from erpnext.accounts.utils import get_balance_on, get_account_currency
|
from erpnext.accounts.utils import get_balance_on, get_stock_accounts, get_stock_and_account_balance, \
|
||||||
|
get_account_currency, check_if_stock_and_account_balance_synced
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
||||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \
|
||||||
|
import get_party_account_based_on_invoice_discounting
|
||||||
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
||||||
|
|
||||||
from six import string_types, iteritems
|
from six import string_types, iteritems
|
||||||
|
|
||||||
|
class StockAccountInvalidTransaction(frappe.ValidationError): pass
|
||||||
|
|
||||||
class JournalEntry(AccountsController):
|
class JournalEntry(AccountsController):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(JournalEntry, self).__init__(*args, **kwargs)
|
super(JournalEntry, self).__init__(*args, **kwargs)
|
||||||
@ -22,14 +26,19 @@ class JournalEntry(AccountsController):
|
|||||||
return self.voucher_type
|
return self.voucher_type
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if self.voucher_type == 'Opening Entry':
|
||||||
|
self.is_opening = 'Yes'
|
||||||
|
|
||||||
if not self.is_opening:
|
if not self.is_opening:
|
||||||
self.is_opening='No'
|
self.is_opening='No'
|
||||||
|
|
||||||
self.clearance_date = None
|
self.clearance_date = None
|
||||||
|
|
||||||
self.validate_party()
|
self.validate_party()
|
||||||
self.validate_entries_for_advance()
|
self.validate_entries_for_advance()
|
||||||
self.validate_multi_currency()
|
self.validate_multi_currency()
|
||||||
self.set_amounts_in_company_currency()
|
self.set_amounts_in_company_currency()
|
||||||
|
self.validate_debit_credit_amount()
|
||||||
self.validate_total_debit_and_credit()
|
self.validate_total_debit_and_credit()
|
||||||
self.validate_against_jv()
|
self.validate_against_jv()
|
||||||
self.validate_reference_doc()
|
self.validate_reference_doc()
|
||||||
@ -41,6 +50,7 @@ class JournalEntry(AccountsController):
|
|||||||
self.validate_empty_accounts_table()
|
self.validate_empty_accounts_table()
|
||||||
self.set_account_and_party_balance()
|
self.set_account_and_party_balance()
|
||||||
self.validate_inter_company_accounts()
|
self.validate_inter_company_accounts()
|
||||||
|
self.validate_stock_accounts()
|
||||||
if not self.title:
|
if not self.title:
|
||||||
self.title = self.get_title()
|
self.title = self.get_title()
|
||||||
|
|
||||||
@ -52,6 +62,8 @@ class JournalEntry(AccountsController):
|
|||||||
self.update_expense_claim()
|
self.update_expense_claim()
|
||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
|
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||||
|
self.company, self.doctype, self.name)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
||||||
@ -90,6 +102,16 @@ class JournalEntry(AccountsController):
|
|||||||
if account_currency == previous_account_currency:
|
if account_currency == previous_account_currency:
|
||||||
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
||||||
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
||||||
|
|
||||||
|
def validate_stock_accounts(self):
|
||||||
|
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
||||||
|
for account in stock_accounts:
|
||||||
|
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
|
||||||
|
self.posting_date, self.company)
|
||||||
|
|
||||||
|
if account_bal == stock_bal:
|
||||||
|
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||||
|
.format(account), StockAccountInvalidTransaction)
|
||||||
|
|
||||||
def update_inter_company_jv(self):
|
def update_inter_company_jv(self):
|
||||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||||
@ -335,8 +357,7 @@ class JournalEntry(AccountsController):
|
|||||||
currency=account_currency)
|
currency=account_currency)
|
||||||
|
|
||||||
if flt(voucher_total) < (flt(order.advance_paid) + total):
|
if flt(voucher_total) < (flt(order.advance_paid) + total):
|
||||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
|
frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
|
||||||
than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
|
|
||||||
|
|
||||||
def validate_invoices(self):
|
def validate_invoices(self):
|
||||||
"""Validate totals and docstatus for invoices"""
|
"""Validate totals and docstatus for invoices"""
|
||||||
@ -365,6 +386,11 @@ class JournalEntry(AccountsController):
|
|||||||
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
|
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
|
||||||
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
|
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
|
||||||
|
|
||||||
|
def validate_debit_credit_amount(self):
|
||||||
|
for d in self.get('accounts'):
|
||||||
|
if not flt(d.debit) and not flt(d.credit):
|
||||||
|
frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
|
||||||
|
|
||||||
def validate_total_debit_and_credit(self):
|
def validate_total_debit_and_credit(self):
|
||||||
self.set_total_debit_credit()
|
self.set_total_debit_credit()
|
||||||
if self.difference:
|
if self.difference:
|
||||||
@ -1023,7 +1049,7 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
|||||||
return journal_entry.as_dict()
|
return journal_entry.as_dict()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
|
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
def update_accounts(source, target, source_parent):
|
def update_accounts(source, target, source_parent):
|
||||||
@ -1049,6 +1075,6 @@ def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=
|
|||||||
},
|
},
|
||||||
"postprocess": update_accounts,
|
"postprocess": update_accounts,
|
||||||
},
|
},
|
||||||
}, target_doc, ignore_permissions=ignore_permissions)
|
}, target_doc)
|
||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
@ -6,7 +6,7 @@ import unittest, frappe
|
|||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.exceptions import InvalidAccountCurrency
|
from erpnext.exceptions import InvalidAccountCurrency
|
||||||
from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
|
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
|
||||||
|
|
||||||
class TestJournalEntry(unittest.TestCase):
|
class TestJournalEntry(unittest.TestCase):
|
||||||
def test_journal_entry_with_against_jv(self):
|
def test_journal_entry_with_against_jv(self):
|
||||||
@ -75,54 +75,46 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
|
|
||||||
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
|
elif test_voucher.doctype in ["Sales Order", "Purchase Order"]:
|
||||||
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
|
# if test_voucher is a Sales Order/Purchase Order, test error on cancellation of test_voucher
|
||||||
|
frappe.db.set_value("Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_advance_payment_on_cancelation_of_order", 0)
|
||||||
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
|
submitted_voucher = frappe.get_doc(test_voucher.doctype, test_voucher.name)
|
||||||
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
|
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
|
||||||
|
|
||||||
def test_jv_against_stock_account(self):
|
def test_jv_against_stock_account(self):
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
company = "_Test Company with perpetual inventory"
|
||||||
set_perpetual_inventory()
|
stock_account = get_inventory_account(company)
|
||||||
|
|
||||||
jv = frappe.copy_doc({
|
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||||
"cheque_date": nowdate(),
|
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
|
||||||
"cheque_no": "33",
|
diff = flt(account_bal) - flt(stock_bal)
|
||||||
"company": "_Test Company with perpetual inventory",
|
|
||||||
"doctype": "Journal Entry",
|
|
||||||
"accounts": [
|
|
||||||
{
|
|
||||||
"account": "Debtors - TCP1",
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": "_Test Customer",
|
|
||||||
"credit_in_account_currency": 400.0,
|
|
||||||
"debit_in_account_currency": 0.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "Main - TCP1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"account": "_Test Bank - TCP1",
|
|
||||||
"credit_in_account_currency": 0.0,
|
|
||||||
"debit_in_account_currency": 400.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "Main - TCP1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"naming_series": "_T-Journal Entry-",
|
|
||||||
"posting_date": nowdate(),
|
|
||||||
"user_remark": "test",
|
|
||||||
"voucher_type": "Bank Entry"
|
|
||||||
})
|
|
||||||
|
|
||||||
jv.get("accounts")[0].update({
|
if not diff:
|
||||||
"account": get_inventory_account('_Test Company with perpetual inventory'),
|
diff = 100
|
||||||
"company": "_Test Company with perpetual inventory",
|
|
||||||
"party_type": None,
|
jv = frappe.new_doc("Journal Entry")
|
||||||
"party": None
|
jv.company = company
|
||||||
|
jv.posting_date = nowdate()
|
||||||
|
jv.append("accounts", {
|
||||||
|
"account": stock_account,
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"debit_in_account_currency": 0 if diff > 0 else abs(diff),
|
||||||
|
"credit_in_account_currency": diff if diff > 0 else 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
jv.append("accounts", {
|
||||||
|
"account": "Stock Adjustment - TCP1",
|
||||||
|
"cost_center": "Main - TCP1",
|
||||||
|
"debit_in_account_currency": diff if diff > 0 else 0,
|
||||||
|
"credit_in_account_currency": 0 if diff > 0 else abs(diff)
|
||||||
|
})
|
||||||
|
jv.insert()
|
||||||
|
|
||||||
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
|
if account_bal == stock_bal:
|
||||||
jv.cancel()
|
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
|
||||||
set_perpetual_inventory(0)
|
frappe.db.rollback()
|
||||||
|
else:
|
||||||
|
jv.submit()
|
||||||
|
jv.cancel()
|
||||||
|
|
||||||
def test_multi_currency(self):
|
def test_multi_currency(self):
|
||||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||||
|
|||||||
@ -8,12 +8,10 @@ import unittest
|
|||||||
from frappe.utils import today, cint, flt, getdate
|
from frappe.utils import today, cint, flt, getdate
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
|
||||||
from erpnext.accounts.party import get_dashboard_info
|
from erpnext.accounts.party import get_dashboard_info
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
|
||||||
|
|
||||||
class TestLoyaltyProgram(unittest.TestCase):
|
class TestLoyaltyProgram(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
set_perpetual_inventory(0)
|
|
||||||
# create relevant item, customer, loyalty program, etc
|
# create relevant item, customer, loyalty program, etc
|
||||||
create_records()
|
create_records()
|
||||||
|
|
||||||
@ -195,88 +193,91 @@ def create_sales_invoice_record(qty=1):
|
|||||||
|
|
||||||
def create_records():
|
def create_records():
|
||||||
# create a new loyalty Account
|
# create a new loyalty Account
|
||||||
if frappe.db.exists("Account", "Loyalty - _TC"):
|
if not frappe.db.exists("Account", "Loyalty - _TC"):
|
||||||
return
|
frappe.get_doc({
|
||||||
|
"doctype": "Account",
|
||||||
frappe.get_doc({
|
"account_name": "Loyalty",
|
||||||
"doctype": "Account",
|
"parent_account": "Direct Expenses - _TC",
|
||||||
"account_name": "Loyalty",
|
"company": "_Test Company",
|
||||||
"parent_account": "Direct Expenses - _TC",
|
"is_group": 0,
|
||||||
"company": "_Test Company",
|
"account_type": "Expense Account",
|
||||||
"is_group": 0,
|
}).insert()
|
||||||
"account_type": "Expense Account",
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
# create a new loyalty program Single tier
|
# create a new loyalty program Single tier
|
||||||
frappe.get_doc({
|
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
|
||||||
"doctype": "Loyalty Program",
|
frappe.get_doc({
|
||||||
"loyalty_program_name": "Test Single Loyalty",
|
"doctype": "Loyalty Program",
|
||||||
"auto_opt_in": 1,
|
"loyalty_program_name": "Test Single Loyalty",
|
||||||
"from_date": today(),
|
"auto_opt_in": 1,
|
||||||
"loyalty_program_type": "Single Tier Program",
|
"from_date": today(),
|
||||||
"conversion_factor": 1,
|
"loyalty_program_type": "Single Tier Program",
|
||||||
"expiry_duration": 10,
|
"conversion_factor": 1,
|
||||||
"company": "_Test Company",
|
"expiry_duration": 10,
|
||||||
"cost_center": "Main - _TC",
|
"company": "_Test Company",
|
||||||
"expense_account": "Loyalty - _TC",
|
"cost_center": "Main - _TC",
|
||||||
"collection_rules": [{
|
"expense_account": "Loyalty - _TC",
|
||||||
'tier_name': 'Silver',
|
"collection_rules": [{
|
||||||
'collection_factor': 1000,
|
|
||||||
'min_spent': 1000
|
|
||||||
}]
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
# create a new customer
|
|
||||||
frappe.get_doc({
|
|
||||||
"customer_group": "_Test Customer Group",
|
|
||||||
"customer_name": "Test Loyalty Customer",
|
|
||||||
"customer_type": "Individual",
|
|
||||||
"doctype": "Customer",
|
|
||||||
"territory": "_Test Territory"
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
# create a new loyalty program Multiple tier
|
|
||||||
frappe.get_doc({
|
|
||||||
"doctype": "Loyalty Program",
|
|
||||||
"loyalty_program_name": "Test Multiple Loyalty",
|
|
||||||
"auto_opt_in": 1,
|
|
||||||
"from_date": today(),
|
|
||||||
"loyalty_program_type": "Multiple Tier Program",
|
|
||||||
"conversion_factor": 1,
|
|
||||||
"expiry_duration": 10,
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cost_center": "Main - _TC",
|
|
||||||
"expense_account": "Loyalty - _TC",
|
|
||||||
"collection_rules": [
|
|
||||||
{
|
|
||||||
'tier_name': 'Silver',
|
'tier_name': 'Silver',
|
||||||
'collection_factor': 1000,
|
'collection_factor': 1000,
|
||||||
'min_spent': 10000
|
'min_spent': 1000
|
||||||
},
|
}]
|
||||||
{
|
}).insert()
|
||||||
'tier_name': 'Gold',
|
|
||||||
'collection_factor': 1000,
|
# create a new customer
|
||||||
'min_spent': 19000
|
if not frappe.db.exists("Customer","Test Loyalty Customer"):
|
||||||
}
|
frappe.get_doc({
|
||||||
]
|
"customer_group": "_Test Customer Group",
|
||||||
}).insert()
|
"customer_name": "Test Loyalty Customer",
|
||||||
|
"customer_type": "Individual",
|
||||||
|
"doctype": "Customer",
|
||||||
|
"territory": "_Test Territory"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
# create a new loyalty program Multiple tier
|
||||||
|
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Loyalty Program",
|
||||||
|
"loyalty_program_name": "Test Multiple Loyalty",
|
||||||
|
"auto_opt_in": 1,
|
||||||
|
"from_date": today(),
|
||||||
|
"loyalty_program_type": "Multiple Tier Program",
|
||||||
|
"conversion_factor": 1,
|
||||||
|
"expiry_duration": 10,
|
||||||
|
"company": "_Test Company",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"expense_account": "Loyalty - _TC",
|
||||||
|
"collection_rules": [
|
||||||
|
{
|
||||||
|
'tier_name': 'Silver',
|
||||||
|
'collection_factor': 1000,
|
||||||
|
'min_spent': 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tier_name': 'Gold',
|
||||||
|
'collection_factor': 1000,
|
||||||
|
'min_spent': 19000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).insert()
|
||||||
|
|
||||||
# create an item
|
# create an item
|
||||||
item = frappe.get_doc({
|
if not frappe.db.exists("Item", "Loyal Item"):
|
||||||
"doctype": "Item",
|
frappe.get_doc({
|
||||||
"item_code": "Loyal Item",
|
"doctype": "Item",
|
||||||
"item_name": "Loyal Item",
|
"item_code": "Loyal Item",
|
||||||
"item_group": "All Item Groups",
|
"item_name": "Loyal Item",
|
||||||
"company": "_Test Company",
|
"item_group": "All Item Groups",
|
||||||
"is_stock_item": 1,
|
"company": "_Test Company",
|
||||||
"opening_stock": 100,
|
"is_stock_item": 1,
|
||||||
"valuation_rate": 10000,
|
"opening_stock": 100,
|
||||||
}).insert()
|
"valuation_rate": 10000,
|
||||||
|
}).insert()
|
||||||
|
|
||||||
# create item price
|
# create item price
|
||||||
frappe.get_doc({
|
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
|
||||||
"doctype": "Item Price",
|
frappe.get_doc({
|
||||||
"price_list": "Standard Selling",
|
"doctype": "Item Price",
|
||||||
"item_code": item.item_code,
|
"price_list": "Standard Selling",
|
||||||
"price_list_rate": 10000
|
"item_code": "Loyal Item",
|
||||||
}).insert()
|
"price_list_rate": 10000
|
||||||
|
}).insert()
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
frappe.ui.form.on('Mode of Payment', {
|
||||||
var d = locals[cdt][cdn];
|
setup: function(frm) {
|
||||||
return{
|
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||||
filters: [
|
let d = locals[cdt][cdn];
|
||||||
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
|
return {
|
||||||
['Account', 'is_group', '=', 0],
|
filters: [
|
||||||
['Account', 'company', '=', d.company]
|
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
|
||||||
]
|
['Account', 'is_group', '=', 0],
|
||||||
}
|
['Account', 'company', '=', d.company]
|
||||||
});
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:mode_of_payment",
|
"autoname": "field:mode_of_payment",
|
||||||
@ -28,7 +29,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
"options": "Cash\nBank\nGeneral"
|
"options": "Cash\nBank\nGeneral\nPhone"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "accounts",
|
"fieldname": "accounts",
|
||||||
@ -45,11 +46,13 @@
|
|||||||
],
|
],
|
||||||
"icon": "fa fa-credit-card",
|
"icon": "fa fa-credit-card",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-08-14 14:58:42.079115",
|
"index_web_pages_for_search": 1,
|
||||||
"modified_by": "sammish.thundiyil@gmail.com",
|
"links": [],
|
||||||
|
"modified": "2020-09-18 17:57:23.835236",
|
||||||
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Mode of Payment",
|
"name": "Mode of Payment",
|
||||||
"owner": "harshada@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) {
|
frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
'name': ['in', 'Customer,Supplier']
|
'name': ['in', 'Customer, Supplier']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -14,29 +14,46 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
if (frm.doc.company) {
|
if (frm.doc.company) {
|
||||||
frm.trigger('setup_company_filters');
|
frm.trigger('setup_company_filters');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frappe.realtime.on('opening_invoice_creation_progress', data => {
|
||||||
|
if (!frm.doc.import_in_progress) {
|
||||||
|
frm.dashboard.reset();
|
||||||
|
frm.doc.import_in_progress = true;
|
||||||
|
}
|
||||||
|
if (data.user != frappe.session.user) return;
|
||||||
|
if (data.count == data.total) {
|
||||||
|
setTimeout((title) => {
|
||||||
|
frm.doc.import_in_progress = false;
|
||||||
|
frm.clear_table("invoices");
|
||||||
|
frm.refresh_fields();
|
||||||
|
frm.page.clear_indicator();
|
||||||
|
frm.dashboard.hide_progress(title);
|
||||||
|
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
|
||||||
|
}, 1500, data.title);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||||
|
frm.page.set_indicator(__('In Progress'), 'orange');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.disable_save();
|
frm.disable_save();
|
||||||
frm.trigger("make_dashboard");
|
!frm.doc.import_in_progress && frm.trigger("make_dashboard");
|
||||||
frm.page.set_primary_action(__('Create Invoices'), () => {
|
frm.page.set_primary_action(__('Create Invoices'), () => {
|
||||||
let btn_primary = frm.page.btn_primary.get(0);
|
let btn_primary = frm.page.btn_primary.get(0);
|
||||||
return frm.call({
|
return frm.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
freeze: true,
|
|
||||||
btn: $(btn_primary),
|
btn: $(btn_primary),
|
||||||
method: "make_invoices",
|
method: "make_invoices",
|
||||||
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
|
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
|
||||||
callback: (r) => {
|
|
||||||
if(!r.exc){
|
|
||||||
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
|
|
||||||
frm.clear_table("invoices");
|
|
||||||
frm.refresh_fields();
|
|
||||||
frm.reload_doc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frm.doc.create_missing_party) {
|
||||||
|
frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup_company_filters: function(frm) {
|
setup_company_filters: function(frm) {
|
||||||
|
|||||||
@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
import traceback
|
||||||
|
from json import dumps
|
||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils.background_jobs import enqueue
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||||
|
|
||||||
|
|
||||||
@ -61,67 +64,48 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
prepare_invoice_summary(doctype, invoices)
|
prepare_invoice_summary(doctype, invoices)
|
||||||
|
|
||||||
return invoices_summary, max_count
|
return invoices_summary, max_count
|
||||||
|
|
||||||
def make_invoices(self):
|
def validate_company(self):
|
||||||
names = []
|
|
||||||
mandatory_error_msg = _("Row {0}: {1} is required to create the Opening {2} Invoices")
|
|
||||||
if not self.company:
|
if not self.company:
|
||||||
frappe.throw(_("Please select the Company"))
|
frappe.throw(_("Please select the Company"))
|
||||||
|
|
||||||
|
def set_missing_values(self, row):
|
||||||
|
row.qty = row.qty or 1.0
|
||||||
|
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
|
||||||
|
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
|
||||||
|
row.item_name = row.item_name or _("Opening Invoice Item")
|
||||||
|
row.posting_date = row.posting_date or nowdate()
|
||||||
|
row.due_date = row.due_date or nowdate()
|
||||||
|
|
||||||
company_details = frappe.get_cached_value('Company', self.company,
|
def validate_mandatory_invoice_fields(self, row):
|
||||||
["default_currency", "default_letter_head"], as_dict=1) or {}
|
if not frappe.db.exists(row.party_type, row.party):
|
||||||
|
if self.create_missing_party:
|
||||||
|
self.add_party(row.party_type, row.party)
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
|
||||||
|
|
||||||
|
mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
|
||||||
|
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
|
||||||
|
if not row.get(scrub(d)):
|
||||||
|
frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type))
|
||||||
|
|
||||||
|
def get_invoices(self):
|
||||||
|
invoices = []
|
||||||
for row in self.invoices:
|
for row in self.invoices:
|
||||||
if not row.qty:
|
if not row:
|
||||||
row.qty = 1.0
|
|
||||||
|
|
||||||
# always mandatory fields for the invoices
|
|
||||||
if not row.temporary_opening_account:
|
|
||||||
row.temporary_opening_account = get_temporary_opening_account(self.company)
|
|
||||||
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
|
|
||||||
|
|
||||||
# Allow to create invoice even if no party present in customer or supplier.
|
|
||||||
if not frappe.db.exists(row.party_type, row.party):
|
|
||||||
if self.create_missing_party:
|
|
||||||
self.add_party(row.party_type, row.party)
|
|
||||||
else:
|
|
||||||
frappe.throw(_("{0} {1} does not exist.").format(frappe.bold(row.party_type), frappe.bold(row.party)))
|
|
||||||
|
|
||||||
if not row.item_name:
|
|
||||||
row.item_name = _("Opening Invoice Item")
|
|
||||||
if not row.posting_date:
|
|
||||||
row.posting_date = nowdate()
|
|
||||||
if not row.due_date:
|
|
||||||
row.due_date = nowdate()
|
|
||||||
|
|
||||||
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
|
|
||||||
if not row.get(scrub(d)):
|
|
||||||
frappe.throw(mandatory_error_msg.format(row.idx, _(d), self.invoice_type))
|
|
||||||
|
|
||||||
args = self.get_invoice_dict(row=row)
|
|
||||||
if not args:
|
|
||||||
continue
|
continue
|
||||||
|
self.set_missing_values(row)
|
||||||
|
self.validate_mandatory_invoice_fields(row)
|
||||||
|
invoice = self.get_invoice_dict(row)
|
||||||
|
company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
|
||||||
if company_details:
|
if company_details:
|
||||||
args.update({
|
invoice.update({
|
||||||
"currency": company_details.get("default_currency"),
|
"currency": company_details.get("default_currency"),
|
||||||
"letter_head": company_details.get("default_letter_head")
|
"letter_head": company_details.get("default_letter_head")
|
||||||
})
|
})
|
||||||
|
invoices.append(invoice)
|
||||||
|
|
||||||
doc = frappe.get_doc(args).insert()
|
return invoices
|
||||||
doc.submit()
|
|
||||||
names.append(doc.name)
|
|
||||||
|
|
||||||
if len(self.invoices) > 5:
|
|
||||||
frappe.publish_realtime(
|
|
||||||
"progress", dict(
|
|
||||||
progress=[row.idx, len(self.invoices)],
|
|
||||||
title=_('Creating {0}').format(doc.doctype)
|
|
||||||
),
|
|
||||||
user=frappe.session.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return names
|
|
||||||
|
|
||||||
def add_party(self, party_type, party):
|
def add_party(self, party_type, party):
|
||||||
party_doc = frappe.new_doc(party_type)
|
party_doc = frappe.new_doc(party_type)
|
||||||
@ -140,14 +124,12 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
|
|
||||||
def get_invoice_dict(self, row=None):
|
def get_invoice_dict(self, row=None):
|
||||||
def get_item_dict():
|
def get_item_dict():
|
||||||
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company, "cost_center")
|
||||||
cost_center = row.get('cost_center') or frappe.get_cached_value('Company',
|
|
||||||
self.company, "cost_center")
|
|
||||||
|
|
||||||
if not cost_center:
|
if not cost_center:
|
||||||
frappe.throw(
|
frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
|
||||||
_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
|
|
||||||
)
|
income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
|
||||||
|
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
||||||
rate = flt(row.outstanding_amount) / flt(row.qty)
|
rate = flt(row.outstanding_amount) / flt(row.qty)
|
||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
@ -161,18 +143,9 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
"cost_center": cost_center
|
"cost_center": cost_center
|
||||||
})
|
})
|
||||||
|
|
||||||
if not row:
|
|
||||||
return None
|
|
||||||
|
|
||||||
party_type = "Customer"
|
|
||||||
income_expense_account_field = "income_account"
|
|
||||||
if self.invoice_type == "Purchase":
|
|
||||||
party_type = "Supplier"
|
|
||||||
income_expense_account_field = "expense_account"
|
|
||||||
|
|
||||||
item = get_item_dict()
|
item = get_item_dict()
|
||||||
|
|
||||||
args = frappe._dict({
|
invoice = frappe._dict({
|
||||||
"items": [item],
|
"items": [item],
|
||||||
"is_opening": "Yes",
|
"is_opening": "Yes",
|
||||||
"set_posting_time": 1,
|
"set_posting_time": 1,
|
||||||
@ -180,21 +153,77 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"due_date": row.due_date,
|
"due_date": row.due_date,
|
||||||
"posting_date": row.posting_date,
|
"posting_date": row.posting_date,
|
||||||
frappe.scrub(party_type): row.party,
|
frappe.scrub(row.party_type): row.party,
|
||||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
|
"is_pos": 0,
|
||||||
|
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||||
|
"update_stock": 0
|
||||||
})
|
})
|
||||||
|
|
||||||
accounting_dimension = get_accounting_dimensions()
|
accounting_dimension = get_accounting_dimensions()
|
||||||
|
|
||||||
for dimension in accounting_dimension:
|
for dimension in accounting_dimension:
|
||||||
args.update({
|
invoice.update({
|
||||||
dimension: item.get(dimension)
|
dimension: item.get(dimension)
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.invoice_type == "Sales":
|
return invoice
|
||||||
args["is_pos"] = 0
|
|
||||||
|
|
||||||
return args
|
def make_invoices(self):
|
||||||
|
self.validate_company()
|
||||||
|
invoices = self.get_invoices()
|
||||||
|
if len(invoices) < 50:
|
||||||
|
return start_import(invoices)
|
||||||
|
else:
|
||||||
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
|
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||||
|
if self.name not in enqueued_jobs:
|
||||||
|
enqueue(
|
||||||
|
start_import,
|
||||||
|
queue="default",
|
||||||
|
timeout=6000,
|
||||||
|
event="opening_invoice_creation",
|
||||||
|
job_name=self.name,
|
||||||
|
invoices=invoices,
|
||||||
|
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_import(invoices):
|
||||||
|
errors = 0
|
||||||
|
names = []
|
||||||
|
for idx, d in enumerate(invoices):
|
||||||
|
try:
|
||||||
|
publish(idx, len(invoices), d.doctype)
|
||||||
|
doc = frappe.get_doc(d)
|
||||||
|
doc.insert()
|
||||||
|
doc.submit()
|
||||||
|
frappe.db.commit()
|
||||||
|
names.append(doc.name)
|
||||||
|
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()
|
||||||
|
if errors:
|
||||||
|
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
|
||||||
|
.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
|
||||||
|
return names
|
||||||
|
|
||||||
|
def publish(index, total, doctype):
|
||||||
|
if total < 5: return
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"opening_invoice_creation_progress",
|
||||||
|
dict(
|
||||||
|
title=_("Opening Invoice Creation In Progress"),
|
||||||
|
message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
|
||||||
|
user=frappe.session.user,
|
||||||
|
count=index+1,
|
||||||
|
total=total
|
||||||
|
))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_temporary_opening_account(company=None):
|
def get_temporary_opening_account(company=None):
|
||||||
|
|||||||
@ -7,17 +7,24 @@ import frappe
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Supplier"]
|
test_dependencies = ["Customer", "Supplier"]
|
||||||
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
||||||
|
|
||||||
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||||
def make_invoices(self, invoice_type="Sales"):
|
def setUp(self):
|
||||||
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
|
make_company()
|
||||||
|
|
||||||
|
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
|
||||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||||
args = get_opening_invoice_creation_dict(invoice_type=invoice_type)
|
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
||||||
|
party_1=party_1, party_2=party_2)
|
||||||
doc.update(args)
|
doc.update(args)
|
||||||
return doc.make_invoices()
|
return doc.make_invoices()
|
||||||
|
|
||||||
def test_opening_sales_invoice_creation(self):
|
def test_opening_sales_invoice_creation(self):
|
||||||
invoices = self.make_invoices()
|
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
|
||||||
|
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||||
|
|
||||||
self.assertEqual(len(invoices), 2)
|
self.assertEqual(len(invoices), 2)
|
||||||
expected_value = {
|
expected_value = {
|
||||||
@ -27,6 +34,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.check_expected_values(invoices, expected_value)
|
self.check_expected_values(invoices, expected_value)
|
||||||
|
|
||||||
|
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||||
|
|
||||||
|
# Check if update stock is not enabled
|
||||||
|
self.assertEqual(si.update_stock, 0)
|
||||||
|
|
||||||
|
property_setter.delete()
|
||||||
|
|
||||||
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
||||||
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
||||||
|
|
||||||
@ -36,7 +50,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
|
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
|
||||||
|
|
||||||
def test_opening_purchase_invoice_creation(self):
|
def test_opening_purchase_invoice_creation(self):
|
||||||
invoices = self.make_invoices(invoice_type="Purchase")
|
invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
|
||||||
|
|
||||||
self.assertEqual(len(invoices), 2)
|
self.assertEqual(len(invoices), 2)
|
||||||
expected_value = {
|
expected_value = {
|
||||||
@ -44,7 +58,33 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
0: ["_Test Supplier", 300, "Overdue"],
|
0: ["_Test Supplier", 300, "Overdue"],
|
||||||
1: ["_Test Supplier 1", 250, "Overdue"],
|
1: ["_Test Supplier 1", 250, "Overdue"],
|
||||||
}
|
}
|
||||||
self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
|
self.check_expected_values(invoices, expected_value, "Purchase")
|
||||||
|
|
||||||
|
def test_opening_sales_invoice_creation_with_missing_debit_account(self):
|
||||||
|
company = "_Test Opening Invoice Company"
|
||||||
|
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
|
||||||
|
|
||||||
|
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
|
||||||
|
frappe.db.set_value("Company", company, "default_receivable_account", "")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
|
||||||
|
cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
|
||||||
|
"is_group": 1, "company": "_Test Opening Invoice Company"})
|
||||||
|
cc.insert(ignore_mandatory=True)
|
||||||
|
cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
|
||||||
|
"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
|
||||||
|
cc2.insert()
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
|
||||||
|
|
||||||
|
self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
|
||||||
|
|
||||||
|
# Check if missing debit account error raised
|
||||||
|
error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
|
||||||
|
self.assertTrue(error_log)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
||||||
|
|
||||||
def get_opening_invoice_creation_dict(**args):
|
def get_opening_invoice_creation_dict(**args):
|
||||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||||
@ -57,7 +97,7 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
{
|
{
|
||||||
"qty": 1.0,
|
"qty": 1.0,
|
||||||
"outstanding_amount": 300,
|
"outstanding_amount": 300,
|
||||||
"party": "_Test {0}".format(party),
|
"party": args.get("party_1") or "_Test {0}".format(party),
|
||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
@ -66,7 +106,7 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
{
|
{
|
||||||
"qty": 2.0,
|
"qty": 2.0,
|
||||||
"outstanding_amount": 250,
|
"outstanding_amount": 250,
|
||||||
"party": "_Test {0} 1".format(party),
|
"party": args.get("party_2") or "_Test {0} 1".format(party),
|
||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
@ -76,4 +116,31 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
})
|
})
|
||||||
|
|
||||||
invoice_dict.update(args)
|
invoice_dict.update(args)
|
||||||
return invoice_dict
|
return invoice_dict
|
||||||
|
|
||||||
|
def make_company():
|
||||||
|
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
|
return frappe.get_doc("Company", "_Test Opening Invoice Company")
|
||||||
|
|
||||||
|
company = frappe.new_doc("Company")
|
||||||
|
company.company_name = "_Test Opening Invoice Company"
|
||||||
|
company.abbr = "_TOIC"
|
||||||
|
company.default_currency = "INR"
|
||||||
|
company.country = "India"
|
||||||
|
company.insert()
|
||||||
|
return company
|
||||||
|
|
||||||
|
def make_customer(customer=None):
|
||||||
|
customer_name = customer or "Opening Customer"
|
||||||
|
customer = frappe.get_doc({
|
||||||
|
"doctype": "Customer",
|
||||||
|
"customer_name": customer_name,
|
||||||
|
"customer_group": "All Customer Groups",
|
||||||
|
"customer_type": "Company",
|
||||||
|
"territory": "All Territories"
|
||||||
|
})
|
||||||
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
customer.insert(ignore_permissions=True)
|
||||||
|
return customer.name
|
||||||
|
else:
|
||||||
|
return frappe.db.exists("Customer", customer_name)
|
||||||
@ -12,9 +12,10 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.set_query("paid_from", function() {
|
frm.set_query("paid_from", function() {
|
||||||
|
frm.events.validate_company(frm);
|
||||||
|
|
||||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
||||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"account_type": ["in", account_types],
|
"account_type": ["in", account_types],
|
||||||
@ -23,13 +24,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("party_type", function() {
|
frm.set_query("party_type", function() {
|
||||||
|
frm.events.validate_company(frm);
|
||||||
return{
|
return{
|
||||||
filters: {
|
filters: {
|
||||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("party_bank_account", function() {
|
frm.set_query("party_bank_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -39,6 +43,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("bank_account", function() {
|
frm.set_query("bank_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -47,6 +52,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("contact_person", function() {
|
frm.set_query("contact_person", function() {
|
||||||
if (frm.doc.party) {
|
if (frm.doc.party) {
|
||||||
return {
|
return {
|
||||||
@ -58,10 +64,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("paid_to", function() {
|
frm.set_query("paid_to", function() {
|
||||||
|
frm.events.validate_company(frm);
|
||||||
|
|
||||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
||||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"account_type": ["in", account_types],
|
"account_type": ["in", account_types],
|
||||||
@ -150,6 +158,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.events.show_general_ledger(frm);
|
frm.events.show_general_ledger(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validate_company: (frm) => {
|
||||||
|
if (!frm.doc.company){
|
||||||
|
frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_auto_repeat": 1,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2016-06-01 14:38:51.012597",
|
"creation": "2016-06-01 14:38:51.012597",
|
||||||
@ -63,6 +65,7 @@
|
|||||||
"cost_center",
|
"cost_center",
|
||||||
"section_break_12",
|
"section_break_12",
|
||||||
"status",
|
"status",
|
||||||
|
"custom_remarks",
|
||||||
"remarks",
|
"remarks",
|
||||||
"column_break_16",
|
"column_break_16",
|
||||||
"letter_head",
|
"letter_head",
|
||||||
@ -462,7 +465,8 @@
|
|||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"no_copy": 1
|
"no_copy": 1,
|
||||||
|
"read_only_depends_on": "eval:doc.custom_remarks == 0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_16",
|
"fieldname": "column_break_16",
|
||||||
@ -573,10 +577,18 @@
|
|||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "\nDraft\nSubmitted\nCancelled",
|
"options": "\nDraft\nSubmitted\nCancelled",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "custom_remarks",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Custom Remarks"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-12-08 13:02:30.016610",
|
"links": [],
|
||||||
|
"modified": "2020-10-30 13:56:20.007336",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@ -202,17 +202,32 @@ class PaymentEntry(AccountsController):
|
|||||||
# if account_type not in account_types:
|
# if account_type not in account_types:
|
||||||
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
|
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
|
||||||
|
|
||||||
def set_exchange_rate(self):
|
def set_exchange_rate(self, ref_doc=None):
|
||||||
|
self.set_source_exchange_rate(ref_doc)
|
||||||
|
self.set_target_exchange_rate(ref_doc)
|
||||||
|
|
||||||
|
def set_source_exchange_rate(self, ref_doc=None):
|
||||||
if self.paid_from and not self.source_exchange_rate:
|
if self.paid_from and not self.source_exchange_rate:
|
||||||
if self.paid_from_account_currency == self.company_currency:
|
if self.paid_from_account_currency == self.company_currency:
|
||||||
self.source_exchange_rate = 1
|
self.source_exchange_rate = 1
|
||||||
else:
|
else:
|
||||||
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
|
if ref_doc:
|
||||||
self.company_currency, self.posting_date)
|
if self.paid_from_account_currency == ref_doc.currency:
|
||||||
|
self.source_exchange_rate = ref_doc.get("exchange_rate")
|
||||||
|
|
||||||
|
if not self.source_exchange_rate:
|
||||||
|
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
|
||||||
|
self.company_currency, self.posting_date)
|
||||||
|
|
||||||
|
def set_target_exchange_rate(self, ref_doc=None):
|
||||||
if self.paid_to and not self.target_exchange_rate:
|
if self.paid_to and not self.target_exchange_rate:
|
||||||
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
|
if ref_doc:
|
||||||
self.company_currency, self.posting_date)
|
if self.paid_to_account_currency == ref_doc.currency:
|
||||||
|
self.target_exchange_rate = ref_doc.get("exchange_rate")
|
||||||
|
|
||||||
|
if not self.target_exchange_rate:
|
||||||
|
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
|
||||||
|
self.company_currency, self.posting_date)
|
||||||
|
|
||||||
def validate_mandatory(self):
|
def validate_mandatory(self):
|
||||||
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
|
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
|
||||||
@ -282,9 +297,10 @@ class PaymentEntry(AccountsController):
|
|||||||
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
|
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
|
||||||
|
|
||||||
for k, v in no_oustanding_refs.items():
|
for k, v in no_oustanding_refs.items():
|
||||||
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
|
frappe.msgprint(
|
||||||
If this is undesirable please cancel the corresponding Payment Entry.")
|
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
|
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
|
||||||
|
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||||
title=_("Warning"), indicator="orange")
|
title=_("Warning"), indicator="orange")
|
||||||
|
|
||||||
|
|
||||||
@ -453,7 +469,7 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
|
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
|
||||||
|
|
||||||
def set_remarks(self):
|
def set_remarks(self):
|
||||||
if self.remarks: return
|
if self.custom_remarks: return
|
||||||
|
|
||||||
if self.payment_type=="Internal Transfer":
|
if self.payment_type=="Internal Transfer":
|
||||||
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
|
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
|
||||||
@ -909,22 +925,24 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
||||||
elif reference_doctype != "Journal Entry":
|
elif reference_doctype != "Journal Entry":
|
||||||
if party_account_currency == company_currency:
|
if ref_doc.doctype == "Expense Claim":
|
||||||
if ref_doc.doctype == "Expense Claim":
|
|
||||||
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
|
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
|
||||||
elif ref_doc.doctype == "Employee Advance":
|
elif ref_doc.doctype == "Employee Advance":
|
||||||
total_amount = ref_doc.advance_amount
|
total_amount = ref_doc.advance_amount
|
||||||
else:
|
exchange_rate = ref_doc.get("exchange_rate")
|
||||||
|
if party_account_currency != ref_doc.currency:
|
||||||
|
total_amount = flt(total_amount) * flt(exchange_rate)
|
||||||
|
if not total_amount:
|
||||||
|
if party_account_currency == company_currency:
|
||||||
total_amount = ref_doc.base_grand_total
|
total_amount = ref_doc.base_grand_total
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
else:
|
else:
|
||||||
total_amount = ref_doc.grand_total
|
total_amount = ref_doc.grand_total
|
||||||
|
if not exchange_rate:
|
||||||
# Get the exchange rate from the original ref doc
|
# Get the exchange rate from the original ref doc
|
||||||
# or get it based on the posting date of the ref doc
|
# or get it based on the posting date of the ref doc.
|
||||||
exchange_rate = ref_doc.get("conversion_rate") or \
|
exchange_rate = ref_doc.get("conversion_rate") or \
|
||||||
get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
||||||
|
|
||||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
bill_no = ref_doc.get("bill_no")
|
bill_no = ref_doc.get("bill_no")
|
||||||
@ -932,11 +950,15 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
|
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
|
||||||
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
|
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
|
||||||
elif reference_doctype == "Employee Advance":
|
elif reference_doctype == "Employee Advance":
|
||||||
outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount)
|
outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
|
||||||
|
if party_account_currency != ref_doc.currency:
|
||||||
|
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
|
||||||
|
if party_account_currency == company_currency:
|
||||||
|
exchange_rate = 1
|
||||||
else:
|
else:
|
||||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||||
else:
|
else:
|
||||||
# Get the exchange rate based on the posting date of the ref doc
|
# Get the exchange rate based on the posting date of the ref doc.
|
||||||
exchange_rate = get_exchange_rate(party_account_currency,
|
exchange_rate = get_exchange_rate(party_account_currency,
|
||||||
company_currency, ref_doc.posting_date)
|
company_currency, ref_doc.posting_date)
|
||||||
|
|
||||||
@ -948,102 +970,104 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
"bill_no": bill_no
|
"bill_no": bill_no
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def get_amounts_based_on_reference_doctype(reference_doctype, ref_doc, party_account_currency, company_currency, reference_name):
|
||||||
|
total_amount, outstanding_amount, exchange_rate = None
|
||||||
|
if reference_doctype == "Fees":
|
||||||
|
total_amount = ref_doc.get("grand_total")
|
||||||
|
exchange_rate = 1
|
||||||
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
|
elif reference_doctype == "Dunning":
|
||||||
|
total_amount = ref_doc.get("dunning_amount")
|
||||||
|
exchange_rate = 1
|
||||||
|
outstanding_amount = ref_doc.get("dunning_amount")
|
||||||
|
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
|
||||||
|
total_amount = ref_doc.get("total_amount")
|
||||||
|
if ref_doc.multi_currency:
|
||||||
|
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
||||||
|
else:
|
||||||
|
exchange_rate = 1
|
||||||
|
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
||||||
|
|
||||||
|
return total_amount, outstanding_amount, exchange_rate
|
||||||
|
|
||||||
|
def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_currency, company_currency):
|
||||||
|
total_amount, outstanding_amount, exchange_rate = None
|
||||||
|
if ref_doc.doctype == "Expense Claim":
|
||||||
|
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
|
||||||
|
elif ref_doc.doctype == "Employee Advance":
|
||||||
|
total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc)
|
||||||
|
|
||||||
|
if not total_amount:
|
||||||
|
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
|
||||||
|
party_account_currency, company_currency, ref_doc)
|
||||||
|
|
||||||
|
if not exchange_rate:
|
||||||
|
# Get the exchange rate from the original ref doc
|
||||||
|
# or get it based on the posting date of the ref doc
|
||||||
|
exchange_rate = ref_doc.get("conversion_rate") or \
|
||||||
|
get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
|
||||||
|
|
||||||
|
outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
|
||||||
|
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency)
|
||||||
|
|
||||||
|
return total_amount, outstanding_amount, exchange_rate, bill_no
|
||||||
|
|
||||||
|
def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
|
||||||
|
total_amount = ref_doc.advance_amount
|
||||||
|
exchange_rate = ref_doc.get("exchange_rate")
|
||||||
|
if party_account_currency != ref_doc.currency:
|
||||||
|
total_amount = flt(total_amount) * flt(exchange_rate)
|
||||||
|
|
||||||
|
return total_amount, exchange_rate
|
||||||
|
|
||||||
|
def get_total_amount_exchange_rate_base_on_currency(party_account_currency, company_currency, ref_doc):
|
||||||
|
exchange_rate = None
|
||||||
|
if party_account_currency == company_currency:
|
||||||
|
total_amount = ref_doc.base_grand_total
|
||||||
|
exchange_rate = 1
|
||||||
|
else:
|
||||||
|
total_amount = ref_doc.grand_total
|
||||||
|
|
||||||
|
return total_amount, exchange_rate
|
||||||
|
|
||||||
|
def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency):
|
||||||
|
outstanding_amount, bill_no = None
|
||||||
|
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
|
bill_no = ref_doc.get("bill_no")
|
||||||
|
elif reference_doctype == "Expense Claim":
|
||||||
|
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
|
||||||
|
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
|
||||||
|
elif reference_doctype == "Employee Advance":
|
||||||
|
outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
|
||||||
|
if party_account_currency != ref_doc.currency:
|
||||||
|
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
|
||||||
|
if party_account_currency == company_currency:
|
||||||
|
exchange_rate = 1
|
||||||
|
else:
|
||||||
|
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||||
|
|
||||||
|
return outstanding_amount, exchange_rate, bill_no
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
||||||
|
reference_doc = None
|
||||||
doc = frappe.get_doc(dt, dn)
|
doc = frappe.get_doc(dt, dn)
|
||||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
||||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||||
|
|
||||||
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
|
party_type = set_party_type(dt)
|
||||||
party_type = "Customer"
|
party_account = set_party_account(dt, dn, doc, party_type)
|
||||||
elif dt in ("Purchase Invoice", "Purchase Order"):
|
party_account_currency = set_party_account_currency(dt, party_account, doc)
|
||||||
party_type = "Supplier"
|
payment_type = set_payment_type(dt, doc)
|
||||||
elif dt in ("Expense Claim", "Employee Advance"):
|
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
|
||||||
party_type = "Employee"
|
|
||||||
elif dt in ("Fees"):
|
|
||||||
party_type = "Student"
|
|
||||||
|
|
||||||
# party account
|
|
||||||
if dt == "Sales Invoice":
|
|
||||||
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
|
|
||||||
elif dt == "Purchase Invoice":
|
|
||||||
party_account = doc.credit_to
|
|
||||||
elif dt == "Fees":
|
|
||||||
party_account = doc.receivable_account
|
|
||||||
elif dt == "Employee Advance":
|
|
||||||
party_account = doc.advance_account
|
|
||||||
elif dt == "Expense Claim":
|
|
||||||
party_account = doc.payable_account
|
|
||||||
else:
|
|
||||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
|
||||||
|
|
||||||
if dt not in ("Sales Invoice", "Purchase Invoice"):
|
|
||||||
party_account_currency = get_account_currency(party_account)
|
|
||||||
else:
|
|
||||||
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
|
||||||
|
|
||||||
# payment type
|
|
||||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
|
||||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
|
||||||
payment_type = "Receive"
|
|
||||||
else:
|
|
||||||
payment_type = "Pay"
|
|
||||||
|
|
||||||
# amounts
|
|
||||||
grand_total = outstanding_amount = 0
|
|
||||||
if party_amount:
|
|
||||||
grand_total = outstanding_amount = party_amount
|
|
||||||
elif dt in ("Sales Invoice", "Purchase Invoice"):
|
|
||||||
if party_account_currency == doc.company_currency:
|
|
||||||
grand_total = doc.base_rounded_total or doc.base_grand_total
|
|
||||||
else:
|
|
||||||
grand_total = doc.rounded_total or doc.grand_total
|
|
||||||
outstanding_amount = doc.outstanding_amount
|
|
||||||
elif dt in ("Expense Claim"):
|
|
||||||
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
|
|
||||||
outstanding_amount = doc.grand_total \
|
|
||||||
- doc.total_amount_reimbursed
|
|
||||||
elif dt == "Employee Advance":
|
|
||||||
grand_total = doc.advance_amount
|
|
||||||
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
|
|
||||||
elif dt == "Fees":
|
|
||||||
grand_total = doc.grand_total
|
|
||||||
outstanding_amount = doc.outstanding_amount
|
|
||||||
elif dt == "Dunning":
|
|
||||||
grand_total = doc.grand_total
|
|
||||||
outstanding_amount = doc.grand_total
|
|
||||||
else:
|
|
||||||
if party_account_currency == doc.company_currency:
|
|
||||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
|
||||||
else:
|
|
||||||
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
|
|
||||||
outstanding_amount = grand_total - flt(doc.advance_paid)
|
|
||||||
|
|
||||||
# bank or cash
|
# bank or cash
|
||||||
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
|
bank = get_bank_cash_account(doc, bank_account)
|
||||||
account=bank_account)
|
|
||||||
|
|
||||||
if not bank:
|
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||||
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
|
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
|
||||||
account=bank_account)
|
|
||||||
|
|
||||||
paid_amount = received_amount = 0
|
|
||||||
if party_account_currency == bank.account_currency:
|
|
||||||
paid_amount = received_amount = abs(outstanding_amount)
|
|
||||||
elif payment_type == "Receive":
|
|
||||||
paid_amount = abs(outstanding_amount)
|
|
||||||
if bank_amount:
|
|
||||||
received_amount = bank_amount
|
|
||||||
else:
|
|
||||||
received_amount = paid_amount * doc.get('conversion_rate', 1)
|
|
||||||
else:
|
|
||||||
received_amount = abs(outstanding_amount)
|
|
||||||
if bank_amount:
|
|
||||||
paid_amount = bank_amount
|
|
||||||
else:
|
|
||||||
# if party account currency and bank currency is different then populate paid amount as well
|
|
||||||
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
|
||||||
|
|
||||||
pe = frappe.new_doc("Payment Entry")
|
pe = frappe.new_doc("Payment Entry")
|
||||||
pe.payment_type = payment_type
|
pe.payment_type = payment_type
|
||||||
@ -1115,10 +1139,120 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
pe.setup_party_account_field()
|
pe.setup_party_account_field()
|
||||||
pe.set_missing_values()
|
pe.set_missing_values()
|
||||||
if party_account and bank:
|
if party_account and bank:
|
||||||
pe.set_exchange_rate()
|
if dt == "Employee Advance":
|
||||||
|
reference_doc = doc
|
||||||
|
pe.set_exchange_rate(ref_doc=reference_doc)
|
||||||
pe.set_amounts()
|
pe.set_amounts()
|
||||||
return pe
|
return pe
|
||||||
|
|
||||||
|
def get_bank_cash_account(doc, bank_account):
|
||||||
|
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
|
||||||
|
account=bank_account)
|
||||||
|
|
||||||
|
if not bank:
|
||||||
|
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
|
||||||
|
account=bank_account)
|
||||||
|
|
||||||
|
return bank
|
||||||
|
|
||||||
|
def set_party_type(dt):
|
||||||
|
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
|
||||||
|
party_type = "Customer"
|
||||||
|
elif dt in ("Purchase Invoice", "Purchase Order"):
|
||||||
|
party_type = "Supplier"
|
||||||
|
elif dt in ("Expense Claim", "Employee Advance"):
|
||||||
|
party_type = "Employee"
|
||||||
|
elif dt in ("Fees"):
|
||||||
|
party_type = "Student"
|
||||||
|
return party_type
|
||||||
|
|
||||||
|
def set_party_account(dt, dn, doc, party_type):
|
||||||
|
if dt == "Sales Invoice":
|
||||||
|
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
|
||||||
|
elif dt == "Purchase Invoice":
|
||||||
|
party_account = doc.credit_to
|
||||||
|
elif dt == "Fees":
|
||||||
|
party_account = doc.receivable_account
|
||||||
|
elif dt == "Employee Advance":
|
||||||
|
party_account = doc.advance_account
|
||||||
|
elif dt == "Expense Claim":
|
||||||
|
party_account = doc.payable_account
|
||||||
|
else:
|
||||||
|
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||||
|
return party_account
|
||||||
|
|
||||||
|
def set_party_account_currency(dt, party_account, doc):
|
||||||
|
if dt not in ("Sales Invoice", "Purchase Invoice"):
|
||||||
|
party_account_currency = get_account_currency(party_account)
|
||||||
|
else:
|
||||||
|
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
||||||
|
return party_account_currency
|
||||||
|
|
||||||
|
def set_payment_type(dt, doc):
|
||||||
|
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||||
|
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||||
|
payment_type = "Receive"
|
||||||
|
else:
|
||||||
|
payment_type = "Pay"
|
||||||
|
return payment_type
|
||||||
|
|
||||||
|
def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc):
|
||||||
|
grand_total = outstanding_amount = 0
|
||||||
|
if party_amount:
|
||||||
|
grand_total = outstanding_amount = party_amount
|
||||||
|
elif dt in ("Sales Invoice", "Purchase Invoice"):
|
||||||
|
if party_account_currency == doc.company_currency:
|
||||||
|
grand_total = doc.base_rounded_total or doc.base_grand_total
|
||||||
|
else:
|
||||||
|
grand_total = doc.rounded_total or doc.grand_total
|
||||||
|
outstanding_amount = doc.outstanding_amount
|
||||||
|
elif dt in ("Expense Claim"):
|
||||||
|
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
|
||||||
|
outstanding_amount = doc.grand_total \
|
||||||
|
- doc.total_amount_reimbursed
|
||||||
|
elif dt == "Employee Advance":
|
||||||
|
grand_total = flt(doc.advance_amount)
|
||||||
|
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
|
||||||
|
if party_account_currency != doc.currency:
|
||||||
|
grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
|
||||||
|
outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
|
||||||
|
elif dt == "Fees":
|
||||||
|
grand_total = doc.grand_total
|
||||||
|
outstanding_amount = doc.outstanding_amount
|
||||||
|
elif dt == "Dunning":
|
||||||
|
grand_total = doc.grand_total
|
||||||
|
outstanding_amount = doc.grand_total
|
||||||
|
else:
|
||||||
|
if party_account_currency == doc.company_currency:
|
||||||
|
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||||
|
else:
|
||||||
|
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
|
||||||
|
outstanding_amount = grand_total - flt(doc.advance_paid)
|
||||||
|
return grand_total, outstanding_amount
|
||||||
|
|
||||||
|
def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc):
|
||||||
|
paid_amount = received_amount = 0
|
||||||
|
if party_account_currency == bank.account_currency:
|
||||||
|
paid_amount = received_amount = abs(outstanding_amount)
|
||||||
|
elif payment_type == "Receive":
|
||||||
|
paid_amount = abs(outstanding_amount)
|
||||||
|
if bank_amount:
|
||||||
|
received_amount = bank_amount
|
||||||
|
else:
|
||||||
|
received_amount = paid_amount * doc.get('conversion_rate', 1)
|
||||||
|
if dt == "Employee Advance":
|
||||||
|
received_amount = paid_amount * doc.get('exchange_rate', 1)
|
||||||
|
else:
|
||||||
|
received_amount = abs(outstanding_amount)
|
||||||
|
if bank_amount:
|
||||||
|
paid_amount = bank_amount
|
||||||
|
else:
|
||||||
|
# if party account currency and bank currency is different then populate paid amount as well
|
||||||
|
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
||||||
|
if dt == "Employee Advance":
|
||||||
|
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||||
|
return paid_amount, received_amount
|
||||||
|
|
||||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||||
references = []
|
references = []
|
||||||
for payment_term in payment_schedule:
|
for payment_term in payment_schedule:
|
||||||
@ -1172,30 +1306,23 @@ def make_payment_order(source_name, target_doc=None):
|
|||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.payment_order_type = "Payment Entry"
|
target.payment_order_type = "Payment Entry"
|
||||||
|
target.append('references', dict(
|
||||||
|
reference_doctype="Payment Entry",
|
||||||
|
reference_name=source.name,
|
||||||
|
bank_account=source.party_bank_account,
|
||||||
|
amount=source.paid_amount,
|
||||||
|
account=source.paid_to,
|
||||||
|
supplier=source.party,
|
||||||
|
mode_of_payment=source.mode_of_payment,
|
||||||
|
))
|
||||||
|
|
||||||
def update_item(source_doc, target_doc, source_parent):
|
doclist = get_mapped_doc("Payment Entry", source_name, {
|
||||||
target_doc.bank_account = source_parent.party_bank_account
|
|
||||||
target_doc.amount = source_doc.allocated_amount
|
|
||||||
target_doc.account = source_parent.paid_to
|
|
||||||
target_doc.payment_entry = source_parent.name
|
|
||||||
target_doc.supplier = source_parent.party
|
|
||||||
target_doc.mode_of_payment = source_parent.mode_of_payment
|
|
||||||
|
|
||||||
|
|
||||||
doclist = get_mapped_doc("Payment Entry", source_name, {
|
|
||||||
"Payment Entry": {
|
"Payment Entry": {
|
||||||
"doctype": "Payment Order",
|
"doctype": "Payment Order",
|
||||||
"validation": {
|
|
||||||
"docstatus": ["=", 1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Payment Entry Reference": {
|
|
||||||
"doctype": "Payment Order Reference",
|
|
||||||
"validation": {
|
"validation": {
|
||||||
"docstatus": ["=", 1]
|
"docstatus": ["=", 1]
|
||||||
},
|
},
|
||||||
"postprocess": update_item
|
}
|
||||||
},
|
|
||||||
|
|
||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
frappe.listview_settings['Payment Entry'] = {
|
frappe.listview_settings['Payment Entry'] = {
|
||||||
|
|
||||||
onload: function(listview) {
|
onload: function(listview) {
|
||||||
listview.page.fields_dict.party_type.get_query = function() {
|
if (listview.page.fields_dict.party_type) {
|
||||||
return {
|
listview.page.fields_dict.party_type.get_query = function() {
|
||||||
"filters": {
|
return {
|
||||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
"filters": {
|
||||||
}
|
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,313 +1,98 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2015-12-23 21:31:52.699821",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2015-12-23 21:31:52.699821",
|
"payment_gateway",
|
||||||
"custom": 0,
|
"payment_channel",
|
||||||
"docstatus": 0,
|
"is_default",
|
||||||
"doctype": "DocType",
|
"column_break_4",
|
||||||
"document_type": "",
|
"payment_account",
|
||||||
"editable_grid": 1,
|
"currency",
|
||||||
|
"payment_request_message",
|
||||||
|
"message",
|
||||||
|
"message_examples"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "payment_gateway",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "payment_gateway",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Payment Gateway",
|
||||||
"label": "Payment Gateway",
|
"options": "Payment Gateway",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "Payment Gateway",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "is_default",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"label": "Is Default"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "is_default",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Is Default",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_4",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Column Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_4",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "payment_account",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "payment_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
"label": "Payment Account",
|
||||||
"label": "Payment Account",
|
"options": "Account",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_from": "payment_account.account_currency",
|
"fetch_from": "payment_account.account_currency",
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Read Only",
|
||||||
"hidden": 0,
|
"label": "Currency"
|
||||||
"ignore_user_permissions": 0,
|
},
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Currency",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval: doc.payment_channel !== \"Phone\"",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "payment_request_message",
|
||||||
"bold": 0,
|
"fieldtype": "Section Break"
|
||||||
"collapsible": 0,
|
},
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "payment_request_message",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "Please click on the link below to make your payment",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "message",
|
||||||
"bold": 0,
|
"fieldtype": "Small Text",
|
||||||
"collapsible": 0,
|
"label": "Default Payment Request Message"
|
||||||
"columns": 0,
|
},
|
||||||
"default": "Please click on the link below to make your payment",
|
|
||||||
"fieldname": "message",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Payment Request Message",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "message_examples",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "HTML",
|
||||||
"bold": 0,
|
"label": "Message Examples",
|
||||||
"collapsible": 0,
|
"options": "<pre><h5>Message Example</h5>\n\n<p> Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.</p>\n\n<p> Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.</p>\n\n<p> We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! </p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n</pre>\n"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "message_examples",
|
{
|
||||||
"fieldtype": "HTML",
|
"default": "Email",
|
||||||
"hidden": 0,
|
"fieldname": "payment_channel",
|
||||||
"ignore_user_permissions": 0,
|
"fieldtype": "Select",
|
||||||
"ignore_xss_filter": 0,
|
"label": "Payment Channel",
|
||||||
"in_filter": 0,
|
"options": "\nEmail\nPhone"
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Message Examples",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "<pre><h5>Message Example</h5>\n\n<p> Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.</p>\n\n<p> Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.</p>\n\n<p> We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! </p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n</pre>\n",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2020-09-20 13:30:27.722852",
|
||||||
"idx": 0,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Accounts",
|
||||||
"in_create": 0,
|
"name": "Payment Gateway Account",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-05-16 22:43:34.970491",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Payment Gateway Account",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"export": 1,
|
||||||
"email": 1,
|
"print": 1,
|
||||||
"export": 1,
|
"read": 1,
|
||||||
"if_owner": 0,
|
"report": 1,
|
||||||
"import": 0,
|
"role": "Accounts Manager",
|
||||||
"permlevel": 0,
|
"share": 1,
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "Accounts Manager",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"sort_field": "modified",
|
||||||
"read_only": 0,
|
"sort_order": "DESC"
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -21,10 +21,15 @@ class PaymentOrder(Document):
|
|||||||
if cancel:
|
if cancel:
|
||||||
status = 'Initiated'
|
status = 'Initiated'
|
||||||
|
|
||||||
ref_field = "status" if self.payment_order_type == "Payment Request" else "payment_order_status"
|
if self.payment_order_type == "Payment Request":
|
||||||
|
ref_field = "status"
|
||||||
|
ref_doc_field = frappe.scrub(self.payment_order_type)
|
||||||
|
else:
|
||||||
|
ref_field = "payment_order_status"
|
||||||
|
ref_doc_field = "reference_name"
|
||||||
|
|
||||||
for d in self.references:
|
for d in self.references:
|
||||||
frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status)
|
frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
|||||||
@ -5,6 +5,45 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
from frappe.utils import getdate
|
||||||
|
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry, make_payment_order
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
class TestPaymentOrder(unittest.TestCase):
|
class TestPaymentOrder(unittest.TestCase):
|
||||||
pass
|
def setUp(self):
|
||||||
|
create_bank_account()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for bt in frappe.get_all("Payment Order"):
|
||||||
|
doc = frappe.get_doc("Payment Order", bt.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
def test_payment_order_creation_against_payment_entry(self):
|
||||||
|
purchase_invoice = make_purchase_invoice()
|
||||||
|
payment_entry = get_payment_entry("Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC")
|
||||||
|
payment_entry.reference_no = "_Test_Payment_Order"
|
||||||
|
payment_entry.reference_date = getdate()
|
||||||
|
payment_entry.party_bank_account = "Checking Account - Citi Bank"
|
||||||
|
payment_entry.insert()
|
||||||
|
payment_entry.submit()
|
||||||
|
|
||||||
|
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
|
||||||
|
reference_doc = doc.get("references")[0]
|
||||||
|
self.assertEquals(reference_doc.reference_name, payment_entry.name)
|
||||||
|
self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
|
||||||
|
self.assertEquals(reference_doc.supplier, "_Test Supplier")
|
||||||
|
self.assertEquals(reference_doc.amount, 250)
|
||||||
|
|
||||||
|
def create_payment_order_against_payment_entry(ref_doc, order_type):
|
||||||
|
payment_order = frappe.get_doc(dict(
|
||||||
|
doctype="Payment Order",
|
||||||
|
company="_Test Company",
|
||||||
|
payment_order_type=order_type,
|
||||||
|
company_bank_account="Checking Account - Citi Bank"
|
||||||
|
))
|
||||||
|
doc = make_payment_order(ref_doc.name, payment_order)
|
||||||
|
doc.save()
|
||||||
|
doc.submit()
|
||||||
|
return doc
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2018-07-20 16:38:06.630813",
|
"creation": "2018-07-20 16:38:06.630813",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -10,7 +11,6 @@
|
|||||||
"column_break_4",
|
"column_break_4",
|
||||||
"supplier",
|
"supplier",
|
||||||
"payment_request",
|
"payment_request",
|
||||||
"payment_entry",
|
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"bank_account_details",
|
"bank_account_details",
|
||||||
"bank_account",
|
"bank_account",
|
||||||
@ -103,17 +103,12 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "payment_entry",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Payment Entry",
|
|
||||||
"options": "Payment Entry",
|
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-08 13:56:25.724557",
|
"links": [],
|
||||||
|
"modified": "2020-09-04 08:29:51.014390",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Order Reference",
|
"name": "Payment Order Reference",
|
||||||
|
|||||||
@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", {
|
|||||||
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
|
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
|
||||||
onload: function() {
|
onload: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
|
this.frm.set_query("party", function() {
|
||||||
|
check_mandatory(me.frm);
|
||||||
|
});
|
||||||
|
|
||||||
this.frm.set_query("party_type", function() {
|
this.frm.set_query("party_type", function() {
|
||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.frm.set_query('receivable_payable_account', function() {
|
this.frm.set_query('receivable_payable_account', function() {
|
||||||
if(!me.frm.doc.company || !me.frm.doc.party_type) {
|
check_mandatory(me.frm);
|
||||||
frappe.msgprint(__("Please select Company and Party Type first"));
|
return {
|
||||||
} else {
|
filters: {
|
||||||
return{
|
"company": me.frm.doc.company,
|
||||||
filters: {
|
"is_group": 0,
|
||||||
"company": me.frm.doc.company,
|
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
||||||
"is_group": 0,
|
}
|
||||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.frm.set_query('bank_cash_account', function() {
|
this.frm.set_query('bank_cash_account', function() {
|
||||||
if(!me.frm.doc.company) {
|
check_mandatory(me.frm, true);
|
||||||
frappe.msgprint(__("Please select Company first"));
|
return {
|
||||||
} else {
|
filters:[
|
||||||
return{
|
['Account', 'company', '=', me.frm.doc.company],
|
||||||
filters:[
|
['Account', 'is_group', '=', 0],
|
||||||
['Account', 'company', '=', me.frm.doc.company],
|
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
||||||
['Account', 'is_group', '=', 0],
|
]
|
||||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
};
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.frm.set_value('party_type', '');
|
this.frm.set_value('party_type', '');
|
||||||
this.frm.set_value('party', '');
|
this.frm.set_value('party', '');
|
||||||
this.frm.set_value('receivable_payable_account', '');
|
this.frm.set_value('receivable_payable_account', '');
|
||||||
|
|
||||||
|
var check_mandatory = (frm, only_company=false) => {
|
||||||
|
var title = __("Mandatory");
|
||||||
|
if (only_company && !frm.doc.company) {
|
||||||
|
frappe.throw({message: __("Please Select a Company First"), title: title});
|
||||||
|
} else if (!frm.doc.company || !frm.doc.party_type) {
|
||||||
|
frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function() {
|
refresh: function() {
|
||||||
@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
|
|
||||||
party: function() {
|
party: function() {
|
||||||
var me = this
|
var me = this
|
||||||
if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
|
if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "erpnext.accounts.party.get_party_account",
|
method: "erpnext.accounts.party.get_party_account",
|
||||||
args: {
|
args: {
|
||||||
@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
party: me.frm.doc.party
|
party: me.frm.doc.party
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc && r.message) {
|
if (!r.exc && r.message) {
|
||||||
me.frm.set_value("receivable_payable_account", r.message);
|
me.frm.set_value("receivable_payable_account", r.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,6 +99,7 @@ class PaymentReconciliation(Document):
|
|||||||
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
||||||
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
||||||
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
||||||
|
and `tabGL Entry`.is_cancelled = 0
|
||||||
GROUP BY `tab{doc}`.name
|
GROUP BY `tab{doc}`.name
|
||||||
Having
|
Having
|
||||||
amount > 0
|
amount > 0
|
||||||
|
|||||||
@ -25,7 +25,7 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
|
|||||||
})
|
})
|
||||||
|
|
||||||
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
||||||
if(frm.doc.payment_request_type == 'Inward' &&
|
if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
|
||||||
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
||||||
frm.add_custom_button(__('Resend Payment Email'), function(){
|
frm.add_custom_button(__('Resend Payment Email'), function(){
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
@ -48,6 +48,7 @@
|
|||||||
"section_break_7",
|
"section_break_7",
|
||||||
"payment_gateway",
|
"payment_gateway",
|
||||||
"payment_account",
|
"payment_account",
|
||||||
|
"payment_channel",
|
||||||
"payment_order",
|
"payment_order",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
@ -230,6 +231,7 @@
|
|||||||
"label": "Recipient Message And Payment Details"
|
"label": "Recipient Message And Payment Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "print_format",
|
"fieldname": "print_format",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Print Format"
|
"label": "Print Format"
|
||||||
@ -241,6 +243,7 @@
|
|||||||
"label": "To"
|
"label": "To"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
@ -277,16 +280,18 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.payment_request_type == 'Inward'",
|
"depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "section_break_10",
|
"fieldname": "section_break_10",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "message",
|
"fieldname": "message",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Message"
|
"label": "Message"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "message_examples",
|
"fieldname": "message_examples",
|
||||||
"fieldtype": "HTML",
|
"fieldtype": "HTML",
|
||||||
"label": "Message Examples",
|
"label": "Message Examples",
|
||||||
@ -347,12 +352,21 @@
|
|||||||
"options": "Payment Request",
|
"options": "Payment Request",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "payment_gateway_account.payment_channel",
|
||||||
|
"fieldname": "payment_channel",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Payment Channel",
|
||||||
|
"options": "\nEmail\nPhone",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-17 14:06:42.185763",
|
"modified": "2020-09-18 12:24:14.178853",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class PaymentRequest(Document):
|
|||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
if (hasattr(ref_doc, "order_type") \
|
if (hasattr(ref_doc, "order_type") \
|
||||||
and getattr(ref_doc, "order_type") != "Shopping Cart"):
|
and getattr(ref_doc, "order_type") != "Shopping Cart"):
|
||||||
ref_amount = get_amount(ref_doc)
|
ref_amount = get_amount(ref_doc, self.payment_account)
|
||||||
|
|
||||||
if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
|
if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
|
||||||
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
|
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
|
||||||
@ -76,11 +76,25 @@ class PaymentRequest(Document):
|
|||||||
or self.flags.mute_email:
|
or self.flags.mute_email:
|
||||||
send_mail = False
|
send_mail = False
|
||||||
|
|
||||||
if send_mail:
|
if send_mail and self.payment_channel != "Phone":
|
||||||
self.set_payment_request_url()
|
self.set_payment_request_url()
|
||||||
self.send_email()
|
self.send_email()
|
||||||
self.make_communication_entry()
|
self.make_communication_entry()
|
||||||
|
|
||||||
|
elif self.payment_channel == "Phone":
|
||||||
|
controller = get_payment_gateway_controller(self.payment_gateway)
|
||||||
|
payment_record = dict(
|
||||||
|
reference_doctype="Payment Request",
|
||||||
|
reference_docname=self.name,
|
||||||
|
payment_reference=self.reference_name,
|
||||||
|
grand_total=self.grand_total,
|
||||||
|
sender=self.email_to,
|
||||||
|
currency=self.currency,
|
||||||
|
payment_gateway=self.payment_gateway
|
||||||
|
)
|
||||||
|
controller.validate_transaction_currency(self.currency)
|
||||||
|
controller.request_for_payment(**payment_record)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_if_payment_entry_exists()
|
self.check_if_payment_entry_exists()
|
||||||
self.set_as_cancelled()
|
self.set_as_cancelled()
|
||||||
@ -105,13 +119,14 @@ class PaymentRequest(Document):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def set_payment_request_url(self):
|
def set_payment_request_url(self):
|
||||||
if self.payment_account:
|
if self.payment_account and self.payment_channel != "Phone":
|
||||||
self.payment_url = self.get_payment_url()
|
self.payment_url = self.get_payment_url()
|
||||||
|
|
||||||
if self.payment_url:
|
if self.payment_url:
|
||||||
self.db_set('payment_url', self.payment_url)
|
self.db_set('payment_url', self.payment_url)
|
||||||
|
|
||||||
if self.payment_url or not self.payment_gateway_account:
|
if self.payment_url or not self.payment_gateway_account \
|
||||||
|
or (self.payment_gateway_account and self.payment_channel == "Phone"):
|
||||||
self.db_set('status', 'Initiated')
|
self.db_set('status', 'Initiated')
|
||||||
|
|
||||||
def get_payment_url(self):
|
def get_payment_url(self):
|
||||||
@ -140,10 +155,14 @@ class PaymentRequest(Document):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def set_as_paid(self):
|
def set_as_paid(self):
|
||||||
payment_entry = self.create_payment_entry()
|
if self.payment_channel == "Phone":
|
||||||
self.make_invoice()
|
self.db_set("status", "Paid")
|
||||||
|
|
||||||
return payment_entry
|
else:
|
||||||
|
payment_entry = self.create_payment_entry()
|
||||||
|
self.make_invoice()
|
||||||
|
|
||||||
|
return payment_entry
|
||||||
|
|
||||||
def create_payment_entry(self, submit=True):
|
def create_payment_entry(self, submit=True):
|
||||||
"""create entry"""
|
"""create entry"""
|
||||||
@ -151,7 +170,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
|
|
||||||
if self.reference_doctype == "Sales Invoice":
|
if self.reference_doctype in ["Sales Invoice", "POS Invoice"]:
|
||||||
party_account = ref_doc.debit_to
|
party_account = ref_doc.debit_to
|
||||||
elif self.reference_doctype == "Purchase Invoice":
|
elif self.reference_doctype == "Purchase Invoice":
|
||||||
party_account = ref_doc.credit_to
|
party_account = ref_doc.credit_to
|
||||||
@ -166,8 +185,8 @@ class PaymentRequest(Document):
|
|||||||
else:
|
else:
|
||||||
party_amount = self.grand_total
|
party_amount = self.grand_total
|
||||||
|
|
||||||
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name,
|
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name, party_amount=party_amount,
|
||||||
party_amount=party_amount, bank_account=self.payment_account, bank_amount=bank_amount)
|
bank_account=self.payment_account, bank_amount=bank_amount)
|
||||||
|
|
||||||
payment_entry.update({
|
payment_entry.update({
|
||||||
"reference_no": self.name,
|
"reference_no": self.name,
|
||||||
@ -255,7 +274,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
# if shopping cart enabled and in session
|
# if shopping cart enabled and in session
|
||||||
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
||||||
and frappe.local.session.user != "Guest"):
|
and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
|
||||||
|
|
||||||
success_url = shopping_cart_settings.payment_success_url
|
success_url = shopping_cart_settings.payment_success_url
|
||||||
if success_url:
|
if success_url:
|
||||||
@ -280,7 +299,9 @@ def make_payment_request(**args):
|
|||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
ref_doc = frappe.get_doc(args.dt, args.dn)
|
ref_doc = frappe.get_doc(args.dt, args.dn)
|
||||||
grand_total = get_amount(ref_doc)
|
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||||
|
|
||||||
|
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||||
if args.loyalty_points and args.dt == "Sales Order":
|
if args.loyalty_points and args.dt == "Sales Order":
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||||
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
|
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
|
||||||
@ -288,8 +309,6 @@ def make_payment_request(**args):
|
|||||||
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
||||||
grand_total = grand_total - loyalty_amount
|
grand_total = grand_total - loyalty_amount
|
||||||
|
|
||||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
|
||||||
|
|
||||||
bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
|
bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
|
||||||
if args.get('party_type') else '')
|
if args.get('party_type') else '')
|
||||||
|
|
||||||
@ -314,9 +333,11 @@ def make_payment_request(**args):
|
|||||||
"payment_gateway_account": gateway_account.get("name"),
|
"payment_gateway_account": gateway_account.get("name"),
|
||||||
"payment_gateway": gateway_account.get("payment_gateway"),
|
"payment_gateway": gateway_account.get("payment_gateway"),
|
||||||
"payment_account": gateway_account.get("payment_account"),
|
"payment_account": gateway_account.get("payment_account"),
|
||||||
|
"payment_channel": gateway_account.get("payment_channel"),
|
||||||
"payment_request_type": args.get("payment_request_type"),
|
"payment_request_type": args.get("payment_request_type"),
|
||||||
"currency": ref_doc.currency,
|
"currency": ref_doc.currency,
|
||||||
"grand_total": grand_total,
|
"grand_total": grand_total,
|
||||||
|
"mode_of_payment": args.mode_of_payment,
|
||||||
"email_to": args.recipient_id or ref_doc.owner,
|
"email_to": args.recipient_id or ref_doc.owner,
|
||||||
"subject": _("Payment Request for {0}").format(args.dn),
|
"subject": _("Payment Request for {0}").format(args.dn),
|
||||||
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
||||||
@ -344,7 +365,7 @@ def make_payment_request(**args):
|
|||||||
|
|
||||||
return pr.as_dict()
|
return pr.as_dict()
|
||||||
|
|
||||||
def get_amount(ref_doc):
|
def get_amount(ref_doc, payment_account=None):
|
||||||
"""get amount based on doctype"""
|
"""get amount based on doctype"""
|
||||||
dt = ref_doc.doctype
|
dt = ref_doc.doctype
|
||||||
if dt in ["Sales Order", "Purchase Order"]:
|
if dt in ["Sales Order", "Purchase Order"]:
|
||||||
@ -356,6 +377,12 @@ def get_amount(ref_doc):
|
|||||||
else:
|
else:
|
||||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||||
|
|
||||||
|
elif dt == "POS Invoice":
|
||||||
|
for pay in ref_doc.payments:
|
||||||
|
if pay.type == "Phone" and pay.account == payment_account:
|
||||||
|
grand_total = pay.amount
|
||||||
|
break
|
||||||
|
|
||||||
elif dt == "Fees":
|
elif dt == "Fees":
|
||||||
grand_total = ref_doc.outstanding_amount
|
grand_total = ref_doc.outstanding_amount
|
||||||
|
|
||||||
@ -366,6 +393,10 @@ def get_amount(ref_doc):
|
|||||||
frappe.throw(_("Payment Entry is already created"))
|
frappe.throw(_("Payment Entry is already created"))
|
||||||
|
|
||||||
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||||
|
"""
|
||||||
|
Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
|
||||||
|
and get the summation of existing paid payment request for Phone payment channel.
|
||||||
|
"""
|
||||||
existing_payment_request_amount = frappe.db.sql("""
|
existing_payment_request_amount = frappe.db.sql("""
|
||||||
select sum(grand_total)
|
select sum(grand_total)
|
||||||
from `tabPayment Request`
|
from `tabPayment Request`
|
||||||
@ -373,7 +404,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
|||||||
reference_doctype = %s
|
reference_doctype = %s
|
||||||
and reference_name = %s
|
and reference_name = %s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and status != 'Paid'
|
and (status != 'Paid'
|
||||||
|
or (payment_channel = 'Phone'
|
||||||
|
and status = 'Paid'))
|
||||||
""", (ref_dt, ref_dn))
|
""", (ref_dt, ref_dn))
|
||||||
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Provide the invoice portion in percent",
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@ -170,6 +171,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Give number of days according to prior selection",
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@ -305,7 +307,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-03-08 10:47:32.830478",
|
"modified": "2020-10-14 10:47:32.830478",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Term",
|
"name": "Payment Term",
|
||||||
@ -381,4 +383,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -291,11 +291,11 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-08-21 16:15:49.089450",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
"owner": "jai@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 1,
|
"amend": 1,
|
||||||
|
|||||||
@ -51,18 +51,53 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
args: {
|
args: {
|
||||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||||
|
pos_profile: frm.doc.pos_profile,
|
||||||
user: frm.doc.user
|
user: frm.doc.user
|
||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
let pos_docs = r.message;
|
let pos_docs = r.message;
|
||||||
set_form_data(pos_docs, frm)
|
set_form_data(pos_docs, frm);
|
||||||
refresh_fields(frm)
|
refresh_fields(frm);
|
||||||
set_html_data(frm)
|
set_html_data(frm);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
|
||||||
|
const removed_row = locals[cdt][cdn];
|
||||||
|
|
||||||
|
if (!removed_row.pos_invoice) return;
|
||||||
|
|
||||||
|
frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
|
||||||
|
cur_frm.doc.grand_total -= flt(doc.grand_total);
|
||||||
|
cur_frm.doc.net_total -= flt(doc.net_total);
|
||||||
|
cur_frm.doc.total_quantity -= flt(doc.total_qty);
|
||||||
|
refresh_payments(doc, cur_frm, 1);
|
||||||
|
refresh_taxes(doc, cur_frm, 1);
|
||||||
|
refresh_fields(cur_frm);
|
||||||
|
set_html_data(cur_frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.ui.form.on('POS Invoice Reference', {
|
||||||
|
pos_invoice(frm, cdt, cdn) {
|
||||||
|
const added_row = locals[cdt][cdn];
|
||||||
|
|
||||||
|
if (!added_row.pos_invoice) return;
|
||||||
|
|
||||||
|
frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
|
||||||
|
frm.doc.grand_total += flt(doc.grand_total);
|
||||||
|
frm.doc.net_total += flt(doc.net_total);
|
||||||
|
frm.doc.total_quantity += flt(doc.total_qty);
|
||||||
|
refresh_payments(doc, frm);
|
||||||
|
refresh_taxes(doc, frm);
|
||||||
|
refresh_fields(frm);
|
||||||
|
set_html_data(frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
frappe.ui.form.on('POS Closing Entry Detail', {
|
frappe.ui.form.on('POS Closing Entry Detail', {
|
||||||
closing_amount: (frm, cdt, cdn) => {
|
closing_amount: (frm, cdt, cdn) => {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
@ -76,8 +111,8 @@ function set_form_data(data, frm) {
|
|||||||
frm.doc.grand_total += flt(d.grand_total);
|
frm.doc.grand_total += flt(d.grand_total);
|
||||||
frm.doc.net_total += flt(d.net_total);
|
frm.doc.net_total += flt(d.net_total);
|
||||||
frm.doc.total_quantity += flt(d.total_qty);
|
frm.doc.total_quantity += flt(d.total_qty);
|
||||||
add_to_payments(d, frm);
|
refresh_payments(d, frm);
|
||||||
add_to_taxes(d, frm);
|
refresh_taxes(d, frm);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +125,12 @@ function add_to_pos_transaction(d, frm) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_to_payments(d, frm) {
|
function refresh_payments(d, frm, remove) {
|
||||||
d.payments.forEach(p => {
|
d.payments.forEach(p => {
|
||||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
||||||
if (payment) {
|
if (payment) {
|
||||||
payment.expected_amount += flt(p.amount);
|
if (!remove) payment.expected_amount += flt(p.amount);
|
||||||
|
else payment.expected_amount -= flt(p.amount);
|
||||||
} else {
|
} else {
|
||||||
frm.add_child("payment_reconciliation", {
|
frm.add_child("payment_reconciliation", {
|
||||||
mode_of_payment: p.mode_of_payment,
|
mode_of_payment: p.mode_of_payment,
|
||||||
@ -105,11 +141,12 @@ function add_to_payments(d, frm) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_to_taxes(d, frm) {
|
function refresh_taxes(d, frm, remove) {
|
||||||
d.taxes.forEach(t => {
|
d.taxes.forEach(t => {
|
||||||
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
||||||
if (tax) {
|
if (tax) {
|
||||||
tax.amount += flt(t.tax_amount);
|
if (!remove) tax.amount += flt(t.tax_amount);
|
||||||
|
else tax.amount -= flt(t.tax_amount);
|
||||||
} else {
|
} else {
|
||||||
frm.add_child("taxes", {
|
frm.add_child("taxes", {
|
||||||
account_head: t.account_head,
|
account_head: t.account_head,
|
||||||
|
|||||||
@ -14,19 +14,51 @@ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import
|
|||||||
|
|
||||||
class POSClosingEntry(Document):
|
class POSClosingEntry(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
user = frappe.get_all('POS Closing Entry',
|
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||||
filters = { 'user': self.user, 'docstatus': 1 },
|
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||||
|
|
||||||
|
self.validate_pos_closing()
|
||||||
|
self.validate_pos_invoices()
|
||||||
|
|
||||||
|
def validate_pos_closing(self):
|
||||||
|
user = frappe.get_all("POS Closing Entry",
|
||||||
|
filters = { "user": self.user, "docstatus": 1, "pos_profile": self.pos_profile },
|
||||||
or_filters = {
|
or_filters = {
|
||||||
'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
|
"period_start_date": ("between", [self.period_start_date, self.period_end_date]),
|
||||||
'period_end_date': ('between', [self.period_start_date, self.period_end_date])
|
"period_end_date": ("between", [self.period_start_date, self.period_end_date])
|
||||||
})
|
})
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
frappe.throw(_("POS Closing Entry {} against {} between selected period"
|
bold_already_exists = frappe.bold(_("already exists"))
|
||||||
.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
|
bold_user = frappe.bold(self.user)
|
||||||
|
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||||
|
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||||
|
|
||||||
|
def validate_pos_invoices(self):
|
||||||
|
invalid_rows = []
|
||||||
|
for d in self.pos_transactions:
|
||||||
|
invalid_row = {'idx': d.idx}
|
||||||
|
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||||
|
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||||
|
if pos_invoice.consolidated_invoice:
|
||||||
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||||
|
invalid_rows.append(invalid_row)
|
||||||
|
continue
|
||||||
|
if pos_invoice.pos_profile != self.pos_profile:
|
||||||
|
invalid_row.setdefault('msg', []).append(_("POS Profile doesn't matches {}").format(frappe.bold(self.pos_profile)))
|
||||||
|
if pos_invoice.docstatus != 1:
|
||||||
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is not {}').format(frappe.bold("submitted")))
|
||||||
|
if pos_invoice.owner != self.user:
|
||||||
|
invalid_row.setdefault('msg', []).append(_("POS Invoice isn't created by user {}").format(frappe.bold(self.owner)))
|
||||||
|
|
||||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
if invalid_row.get('msg'):
|
||||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
invalid_rows.append(invalid_row)
|
||||||
|
|
||||||
|
if not invalid_rows:
|
||||||
|
return
|
||||||
|
|
||||||
|
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
|
||||||
|
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
merge_pos_invoices(self.pos_transactions)
|
merge_pos_invoices(self.pos_transactions)
|
||||||
@ -47,16 +79,15 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
return [c['user'] for c in cashiers_list]
|
return [c['user'] for c in cashiers_list]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pos_invoices(start, end, user):
|
def get_pos_invoices(start, end, pos_profile, user):
|
||||||
data = frappe.db.sql("""
|
data = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||||
from
|
from
|
||||||
`tabPOS Invoice`
|
`tabPOS Invoice`
|
||||||
where
|
where
|
||||||
owner = %s and docstatus = 1 and
|
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||||
(consolidated_invoice is NULL or consolidated_invoice = '')
|
""", (user, pos_profile), as_dict=1)
|
||||||
""", (user), as_dict=1)
|
|
||||||
|
|
||||||
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
||||||
# need to get taxes and payments so can't avoid get_doc
|
# need to get taxes and payments so can't avoid get_doc
|
||||||
@ -76,7 +107,8 @@ def make_closing_entry_from_opening(opening_entry):
|
|||||||
closing_entry.net_total = 0
|
closing_entry.net_total = 0
|
||||||
closing_entry.total_quantity = 0
|
closing_entry.total_quantity = 0
|
||||||
|
|
||||||
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user)
|
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date,
|
||||||
|
closing_entry.pos_profile, closing_entry.user)
|
||||||
|
|
||||||
pos_transactions = []
|
pos_transactions = []
|
||||||
taxes = []
|
taxes = []
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
def init_user_and_profile():
|
def init_user_and_profile(**args):
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
test_user = frappe.get_doc('User', user)
|
test_user = frappe.get_doc('User', user)
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ def init_user_and_profile():
|
|||||||
test_user.add_roles(*roles)
|
test_user.add_roles(*roles)
|
||||||
frappe.set_user(user)
|
frappe.set_user(user)
|
||||||
|
|
||||||
pos_profile = make_pos_profile()
|
pos_profile = make_pos_profile(**args)
|
||||||
pos_profile.append('applicable_for_users', {
|
pos_profile.append('applicable_for_users', {
|
||||||
'default': 1,
|
'default': 1,
|
||||||
'user': user
|
'user': user
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"opening_amount",
|
"opening_amount",
|
||||||
"closing_amount",
|
|
||||||
"expected_amount",
|
"expected_amount",
|
||||||
|
"closing_amount",
|
||||||
"difference"
|
"difference"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -26,8 +26,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Expected Amount",
|
"label": "Expected Amount",
|
||||||
"options": "company:company_currency",
|
"options": "company:company_currency",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "difference",
|
"fieldname": "difference",
|
||||||
@ -55,9 +54,10 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:03:34.533607",
|
"modified": "2020-10-23 16:45:43.662034",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Closing Entry Detail",
|
"name": "POS Closing Entry Detail",
|
||||||
|
|||||||
@ -9,80 +9,63 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
this._super(doc);
|
this._super(doc);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload() {
|
onload(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
if(this.frm.doc.__islocal && this.frm.doc.is_pos) {
|
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||||
//Load pos profile data on the invoice if the default value of Is POS is 1
|
this.frm.script_manager.trigger("is_pos");
|
||||||
|
this.frm.refresh_fields();
|
||||||
me.frm.script_manager.trigger("is_pos");
|
|
||||||
me.frm.refresh_fields();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(doc) {
|
refresh(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
if (doc.docstatus == 1 && !doc.is_return) {
|
if (doc.docstatus == 1 && !doc.is_return) {
|
||||||
if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
|
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
|
||||||
cur_frm.add_custom_button(__('Return'),
|
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
this.make_sales_return, __('Create'));
|
|
||||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.frm.doc.is_return) {
|
if (doc.is_return && doc.__islocal) {
|
||||||
this.frm.return_print_format = "Sales Invoice Return";
|
this.frm.return_print_format = "Sales Invoice Return";
|
||||||
cur_frm.set_value('consolidated_invoice', '');
|
this.frm.set_value('consolidated_invoice', '');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
is_pos: function(frm){
|
is_pos: function() {
|
||||||
this.set_pos_data();
|
this.set_pos_data();
|
||||||
},
|
},
|
||||||
|
|
||||||
set_pos_data: function() {
|
set_pos_data: async function() {
|
||||||
if(this.frm.doc.is_pos) {
|
if(this.frm.doc.is_pos) {
|
||||||
this.frm.set_value("allocate_advances_automatically", 0);
|
this.frm.set_value("allocate_advances_automatically", 0);
|
||||||
if(!this.frm.doc.company) {
|
if(!this.frm.doc.company) {
|
||||||
this.frm.set_value("is_pos", 0);
|
this.frm.set_value("is_pos", 0);
|
||||||
frappe.msgprint(__("Please specify Company to proceed"));
|
frappe.msgprint(__("Please specify Company to proceed"));
|
||||||
} else {
|
} else {
|
||||||
var me = this;
|
const r = await this.frm.call({
|
||||||
return this.frm.call({
|
doc: this.frm.doc,
|
||||||
doc: me.frm.doc,
|
|
||||||
method: "set_missing_values",
|
method: "set_missing_values",
|
||||||
callback: function(r) {
|
freeze: true
|
||||||
if(!r.exc) {
|
|
||||||
if(r.message) {
|
|
||||||
me.frm.pos_print_format = r.message.print_format || "";
|
|
||||||
me.frm.meta.default_print_format = r.message.print_format || "";
|
|
||||||
me.frm.allow_edit_rate = r.message.allow_edit_rate;
|
|
||||||
me.frm.allow_edit_discount = r.message.allow_edit_discount;
|
|
||||||
me.frm.doc.campaign = r.message.campaign;
|
|
||||||
me.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
|
||||||
}
|
|
||||||
me.frm.script_manager.trigger("update_stock");
|
|
||||||
me.calculate_taxes_and_totals();
|
|
||||||
if(me.frm.doc.taxes_and_charges) {
|
|
||||||
me.frm.script_manager.trigger("taxes_and_charges");
|
|
||||||
}
|
|
||||||
frappe.model.set_default_values(me.frm.doc);
|
|
||||||
me.set_dynamic_labels();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
if(!r.exc) {
|
||||||
|
if(r.message) {
|
||||||
|
this.frm.pos_print_format = r.message.print_format || "";
|
||||||
|
this.frm.meta.default_print_format = r.message.print_format || "";
|
||||||
|
this.frm.doc.campaign = r.message.campaign;
|
||||||
|
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||||
|
}
|
||||||
|
this.frm.script_manager.trigger("update_stock");
|
||||||
|
this.calculate_taxes_and_totals();
|
||||||
|
this.frm.doc.taxes_and_charges && this.frm.script_manager.trigger("taxes_and_charges");
|
||||||
|
frappe.model.set_default_values(this.frm.doc);
|
||||||
|
this.set_dynamic_labels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else this.frm.trigger("refresh");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
customer() {
|
customer() {
|
||||||
if (!this.frm.doc.customer) return
|
if (!this.frm.doc.customer) return
|
||||||
|
const pos_profile = this.frm.doc.pos_profile;
|
||||||
if (this.frm.doc.is_pos){
|
|
||||||
var pos_profile = this.frm.doc.pos_profile;
|
|
||||||
}
|
|
||||||
var me = this;
|
|
||||||
if(this.frm.updating_party_details) return;
|
if(this.frm.updating_party_details) return;
|
||||||
erpnext.utils.get_party_details(this.frm,
|
erpnext.utils.get_party_details(this.frm,
|
||||||
"erpnext.accounts.party.get_party_details", {
|
"erpnext.accounts.party.get_party_details", {
|
||||||
@ -92,8 +75,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
account: this.frm.doc.debit_to,
|
account: this.frm.doc.debit_to,
|
||||||
price_list: this.frm.doc.selling_price_list,
|
price_list: this.frm.doc.selling_price_list,
|
||||||
pos_profile: pos_profile
|
pos_profile: pos_profile
|
||||||
}, function() {
|
}, () => {
|
||||||
me.apply_pricing_rule();
|
this.apply_pricing_rule();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -201,5 +184,22 @@ frappe.ui.form.on('POS Invoice', {
|
|||||||
}
|
}
|
||||||
frm.set_value("loyalty_amount", loyalty_amount);
|
frm.set_value("loyalty_amount", loyalty_amount);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
request_for_payment: function (frm) {
|
||||||
|
frm.save().then(() => {
|
||||||
|
frappe.dom.freeze();
|
||||||
|
frappe.call({
|
||||||
|
method: 'create_payment_request',
|
||||||
|
doc: frm.doc,
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
frappe.msgprint('Payment request failed');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
frappe.msgprint('Payment request sent successfully');
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_auto_repeat": 1,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2020-01-24 15:29:29.933693",
|
"creation": "2020-01-24 15:29:29.933693",
|
||||||
@ -460,7 +461,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -1580,7 +1581,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:39.337385",
|
"modified": "2020-10-30 13:56:51.056083",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@ -10,11 +10,10 @@ from erpnext.controllers.selling_controller import SellingController
|
|||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||||
get_loyalty_program_details_with_points, validate_loyalty_points
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option, get_mode_of_payment_info
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
@ -29,8 +28,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
# run on validate method of selling controller
|
# run on validate method of selling controller
|
||||||
super(SalesInvoice, self).validate()
|
super(SalesInvoice, self).validate()
|
||||||
self.validate_auto_set_posting_time()
|
self.validate_auto_set_posting_time()
|
||||||
self.validate_pos_paid_amount()
|
self.validate_mode_of_payment()
|
||||||
self.validate_pos_return()
|
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
self.validate_uom_is_integer("uom", "qty")
|
self.validate_uom_is_integer("uom", "qty")
|
||||||
self.validate_debit_to_acc()
|
self.validate_debit_to_acc()
|
||||||
@ -40,11 +38,12 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.validate_item_cost_centers()
|
self.validate_item_cost_centers()
|
||||||
self.validate_serialised_or_batched_item()
|
self.validate_serialised_or_batched_item()
|
||||||
self.validate_stock_availablility()
|
self.validate_stock_availablility()
|
||||||
self.validate_return_items()
|
self.validate_return_items_qty()
|
||||||
|
self.validate_non_stock_items()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.set_account_for_mode_of_payment()
|
self.set_account_for_mode_of_payment()
|
||||||
self.validate_pos()
|
self.validate_pos()
|
||||||
self.verify_payment_amount()
|
self.validate_payment_amount()
|
||||||
self.validate_loyalty_transaction()
|
self.validate_loyalty_transaction()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -57,6 +56,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
against_psi_doc.make_loyalty_point_entry()
|
against_psi_doc.make_loyalty_point_entry()
|
||||||
if self.redeem_loyalty_points and self.loyalty_points:
|
if self.redeem_loyalty_points and self.loyalty_points:
|
||||||
self.apply_loyalty_points()
|
self.apply_loyalty_points()
|
||||||
|
self.check_phone_payments()
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@ -69,77 +69,128 @@ class POSInvoice(SalesInvoice):
|
|||||||
against_psi_doc.delete_loyalty_point_entry()
|
against_psi_doc.delete_loyalty_point_entry()
|
||||||
against_psi_doc.make_loyalty_point_entry()
|
against_psi_doc.make_loyalty_point_entry()
|
||||||
|
|
||||||
def validate_stock_availablility(self):
|
def check_phone_payments(self):
|
||||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
for pay in self.payments:
|
||||||
|
if pay.type == "Phone" and pay.amount >= 0:
|
||||||
|
paid_amt = frappe.db.get_value("Payment Request",
|
||||||
|
filters=dict(
|
||||||
|
reference_doctype="POS Invoice", reference_name=self.name,
|
||||||
|
mode_of_payment=pay.mode_of_payment, status="Paid"),
|
||||||
|
fieldname="grand_total")
|
||||||
|
|
||||||
|
if pay.amount != paid_amt:
|
||||||
|
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||||
|
|
||||||
|
def validate_stock_availablility(self):
|
||||||
|
if self.is_return:
|
||||||
|
return
|
||||||
|
|
||||||
|
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||||
|
error_msg = []
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
|
msg = ""
|
||||||
if d.serial_no:
|
if d.serial_no:
|
||||||
filters = {
|
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||||
"item_code": d.item_code,
|
|
||||||
"warehouse": d.warehouse,
|
|
||||||
"delivery_document_no": "",
|
|
||||||
"sales_invoice": ""
|
|
||||||
}
|
|
||||||
if d.batch_no:
|
if d.batch_no:
|
||||||
filters["batch_no"] = d.batch_no
|
filters["batch_no"] = d.batch_no
|
||||||
reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
|
||||||
serial_nos = d.serial_no.split("\n")
|
|
||||||
serial_nos = ' '.join(serial_nos).split() # remove whitespaces
|
|
||||||
invalid_serial_nos = []
|
|
||||||
for s in serial_nos:
|
|
||||||
if s in reserved_serial_nos:
|
|
||||||
invalid_serial_nos.append(s)
|
|
||||||
|
|
||||||
if len(invalid_serial_nos):
|
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
|
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
|
||||||
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
|
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||||
|
if len(invalid_serial_nos) == 1:
|
||||||
|
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||||
|
.format(d.idx, bold_invalid_serial_nos))
|
||||||
|
elif invalid_serial_nos:
|
||||||
|
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||||
|
.format(d.idx, bold_invalid_serial_nos))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if allow_negative_stock:
|
if allow_negative_stock:
|
||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||||
if not (flt(available_stock) > 0):
|
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
|
if flt(available_stock) <= 0:
|
||||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||||
elif flt(available_stock) < flt(d.qty):
|
elif flt(available_stock) < flt(d.qty):
|
||||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
.format(d.idx, item_code, warehouse, qty))
|
||||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
if msg:
|
||||||
|
error_msg.append(msg)
|
||||||
|
|
||||||
|
if error_msg:
|
||||||
|
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
|
||||||
|
|
||||||
def validate_serialised_or_batched_item(self):
|
def validate_serialised_or_batched_item(self):
|
||||||
|
error_msg = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
serialized = d.get("has_serial_no")
|
serialized = d.get("has_serial_no")
|
||||||
batched = d.get("has_batch_no")
|
batched = d.get("has_batch_no")
|
||||||
no_serial_selected = not d.get("serial_no")
|
no_serial_selected = not d.get("serial_no")
|
||||||
no_batch_selected = not d.get("batch_no")
|
no_batch_selected = not d.get("batch_no")
|
||||||
|
|
||||||
|
msg = ""
|
||||||
|
item_code = frappe.bold(d.item_code)
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
if serialized and batched and (no_batch_selected or no_serial_selected):
|
if serialized and batched and (no_batch_selected or no_serial_selected):
|
||||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.'
|
msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.')
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
if serialized and no_serial_selected:
|
elif serialized and no_serial_selected:
|
||||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.'
|
msg = (_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.')
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
if batched and no_batch_selected:
|
elif batched and no_batch_selected:
|
||||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
|
msg = (_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.')
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
|
elif serialized and not no_serial_selected and len(serial_nos) != d.qty:
|
||||||
|
msg = (_("Row #{}: You must select {} serial numbers for item {}.").format(d.idx, frappe.bold(cint(d.qty)), item_code))
|
||||||
|
|
||||||
def validate_return_items(self):
|
if msg:
|
||||||
|
error_msg.append(msg)
|
||||||
|
|
||||||
|
if error_msg:
|
||||||
|
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||||
|
|
||||||
|
def validate_return_items_qty(self):
|
||||||
if not self.get("is_return"): return
|
if not self.get("is_return"): return
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.get("qty") > 0:
|
if d.get("qty") > 0:
|
||||||
frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
frappe.throw(
|
||||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
||||||
|
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")
|
||||||
|
)
|
||||||
|
if d.get("serial_no"):
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
|
for sr in serial_nos:
|
||||||
|
serial_no_exists = frappe.db.exists("POS Invoice Item", {
|
||||||
|
"parent": self.return_against,
|
||||||
|
"serial_no": ["like", d.get("serial_no")]
|
||||||
|
})
|
||||||
|
if not serial_no_exists:
|
||||||
|
bold_return_against = frappe.bold(self.return_against)
|
||||||
|
bold_serial_no = frappe.bold(sr)
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
|
||||||
|
.format(d.idx, bold_serial_no, bold_return_against)
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_non_stock_items(self):
|
||||||
|
for d in self.get("items"):
|
||||||
|
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||||
|
if not is_stock_item:
|
||||||
|
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format(
|
||||||
|
d.idx, frappe.bold(d.item_code)
|
||||||
|
), title=_("Invalid Item"))
|
||||||
|
|
||||||
def validate_pos_paid_amount(self):
|
def validate_mode_of_payment(self):
|
||||||
if len(self.payments) == 0 and self.is_pos:
|
if len(self.payments) == 0:
|
||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def validate_change_account(self):
|
def validate_change_account(self):
|
||||||
if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
if self.change_amount and self.account_for_change_amount and \
|
||||||
|
frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
||||||
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
||||||
|
|
||||||
def validate_change_amount(self):
|
def validate_change_amount(self):
|
||||||
@ -150,23 +201,21 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
||||||
|
|
||||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||||
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||||
|
|
||||||
def verify_payment_amount(self):
|
def validate_payment_amount(self):
|
||||||
|
total_amount_in_payments = 0
|
||||||
for entry in self.payments:
|
for entry in self.payments:
|
||||||
|
total_amount_in_payments += entry.amount
|
||||||
if not self.is_return and entry.amount < 0:
|
if not self.is_return and entry.amount < 0:
|
||||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||||
if self.is_return and entry.amount > 0:
|
if self.is_return and entry.amount > 0:
|
||||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||||
|
|
||||||
def validate_pos_return(self):
|
if self.is_return:
|
||||||
if self.is_pos and self.is_return:
|
|
||||||
total_amount_in_payments = 0
|
|
||||||
for payment in self.payments:
|
|
||||||
total_amount_in_payments += payment.amount
|
|
||||||
invoice_total = self.rounded_total or self.grand_total
|
invoice_total = self.rounded_total or self.grand_total
|
||||||
if total_amount_in_payments < invoice_total:
|
if total_amount_in_payments and total_amount_in_payments < invoice_total:
|
||||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
|
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
||||||
|
|
||||||
def validate_loyalty_transaction(self):
|
def validate_loyalty_transaction(self):
|
||||||
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
||||||
@ -218,57 +267,49 @@ class POSInvoice(SalesInvoice):
|
|||||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||||
if not self.pos_profile:
|
if not self.pos_profile:
|
||||||
pos_profile = get_pos_profile(self.company) or {}
|
pos_profile = get_pos_profile(self.company) or {}
|
||||||
|
if not pos_profile:
|
||||||
|
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||||
self.pos_profile = pos_profile.get('name')
|
self.pos_profile = pos_profile.get('name')
|
||||||
|
|
||||||
pos = {}
|
profile = {}
|
||||||
if self.pos_profile:
|
if self.pos_profile:
|
||||||
pos = frappe.get_doc('POS Profile', self.pos_profile)
|
profile = frappe.get_doc('POS Profile', self.pos_profile)
|
||||||
|
|
||||||
if not self.get('payments') and not for_validate:
|
if not self.get('payments') and not for_validate:
|
||||||
update_multi_mode_option(self, pos)
|
update_multi_mode_option(self, profile)
|
||||||
|
|
||||||
if not self.account_for_change_amount:
|
if self.is_return and not for_validate:
|
||||||
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
add_return_modes(self, profile)
|
||||||
|
|
||||||
if pos:
|
|
||||||
if not for_validate:
|
|
||||||
self.tax_category = pos.get("tax_category")
|
|
||||||
|
|
||||||
|
if profile:
|
||||||
if not for_validate and not self.customer:
|
if not for_validate and not self.customer:
|
||||||
self.customer = pos.customer
|
self.customer = profile.customer
|
||||||
|
|
||||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
self.ignore_pricing_rule = profile.ignore_pricing_rule
|
||||||
if pos.get('account_for_change_amount'):
|
self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
|
||||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
self.set_warehouse = profile.get('warehouse') or self.set_warehouse
|
||||||
if pos.get('warehouse'):
|
|
||||||
self.set_warehouse = pos.get('warehouse')
|
|
||||||
|
|
||||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
'write_off_cost_center', 'apply_discount_on', 'cost_center', 'tax_category',
|
||||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
'ignore_pricing_rule', 'company_address', 'update_stock'):
|
||||||
self.set(fieldname, pos.get(fieldname))
|
if not for_validate:
|
||||||
|
self.set(fieldname, profile.get(fieldname))
|
||||||
if pos.get("company_address"):
|
|
||||||
self.company_address = pos.get("company_address")
|
|
||||||
|
|
||||||
if self.customer:
|
if self.customer:
|
||||||
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
||||||
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
||||||
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
|
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
||||||
else:
|
else:
|
||||||
selling_price_list = pos.get('selling_price_list')
|
selling_price_list = profile.get('selling_price_list')
|
||||||
|
|
||||||
if selling_price_list:
|
if selling_price_list:
|
||||||
self.set('selling_price_list', selling_price_list)
|
self.set('selling_price_list', selling_price_list)
|
||||||
|
|
||||||
if not for_validate:
|
|
||||||
self.update_stock = cint(pos.get("update_stock"))
|
|
||||||
|
|
||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get('item_code'):
|
if item.get('item_code'):
|
||||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
profile_details = get_pos_profile_item_details(profile.get("company"), frappe._dict(item.as_dict()), profile)
|
||||||
for fname, val in iteritems(profile_details):
|
for fname, val in iteritems(profile_details):
|
||||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||||
item.set(fname, val)
|
item.set(fname, val)
|
||||||
@ -281,10 +322,13 @@ class POSInvoice(SalesInvoice):
|
|||||||
if self.taxes_and_charges and not len(self.get("taxes")):
|
if self.taxes_and_charges and not len(self.get("taxes")):
|
||||||
self.set_taxes()
|
self.set_taxes()
|
||||||
|
|
||||||
return pos
|
if not self.account_for_change_amount:
|
||||||
|
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
pos = self.set_pos_fields(for_validate)
|
profile = self.set_pos_fields(for_validate)
|
||||||
|
|
||||||
if not self.debit_to:
|
if not self.debit_to:
|
||||||
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||||
@ -294,17 +338,15 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
super(SalesInvoice, self).set_missing_values(for_validate)
|
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
print_format = pos.get("print_format") if pos else None
|
print_format = profile.get("print_format") if profile else None
|
||||||
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
||||||
print_format = 'POS Invoice'
|
print_format = 'POS Invoice'
|
||||||
|
|
||||||
if pos:
|
if profile:
|
||||||
return {
|
return {
|
||||||
"print_format": print_format,
|
"print_format": print_format,
|
||||||
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
|
"campaign": profile.get("campaign"),
|
||||||
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
|
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||||
"campaign": pos.get("campaign"),
|
|
||||||
"allow_print_before_pay": pos.get("allow_print_before_pay")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_account_for_mode_of_payment(self):
|
def set_account_for_mode_of_payment(self):
|
||||||
@ -313,6 +355,32 @@ class POSInvoice(SalesInvoice):
|
|||||||
if not pay.account:
|
if not pay.account:
|
||||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||||
|
|
||||||
|
def create_payment_request(self):
|
||||||
|
for pay in self.payments:
|
||||||
|
if pay.type == "Phone":
|
||||||
|
if pay.amount <= 0:
|
||||||
|
frappe.throw(_("Payment amount cannot be less than or equal to 0"))
|
||||||
|
|
||||||
|
if not self.contact_mobile:
|
||||||
|
frappe.throw(_("Please enter the phone number first"))
|
||||||
|
|
||||||
|
payment_gateway = frappe.db.get_value("Payment Gateway Account", {
|
||||||
|
"payment_account": pay.account,
|
||||||
|
})
|
||||||
|
record = {
|
||||||
|
"payment_gateway": payment_gateway,
|
||||||
|
"dt": "POS Invoice",
|
||||||
|
"dn": self.name,
|
||||||
|
"payment_request_type": "Inward",
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": self.customer,
|
||||||
|
"mode_of_payment": pay.mode_of_payment,
|
||||||
|
"recipient_id": self.contact_mobile,
|
||||||
|
"submit_doc": True
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_payment_request(**record)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_stock_availability(item_code, warehouse):
|
def get_stock_availability(item_code, warehouse):
|
||||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||||
@ -334,11 +402,9 @@ def get_stock_availability(item_code, warehouse):
|
|||||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||||
|
|
||||||
if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
|
if sle_qty and pos_sales_qty:
|
||||||
return sle_qty - pos_sales_qty
|
return sle_qty - pos_sales_qty
|
||||||
else:
|
else:
|
||||||
# when sle_qty is 0
|
|
||||||
# when sle_qty > 0 and pos_sales_qty is 0
|
|
||||||
return sle_qty
|
return sle_qty
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -371,4 +437,19 @@ def make_merge_log(invoices):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if merge_log.get('pos_invoices'):
|
if merge_log.get('pos_invoices'):
|
||||||
return merge_log.as_dict()
|
return merge_log.as_dict()
|
||||||
|
|
||||||
|
def add_return_modes(doc, pos_profile):
|
||||||
|
def append_payment(payment_mode):
|
||||||
|
payment = doc.append('payments', {})
|
||||||
|
payment.default = payment_mode.default
|
||||||
|
payment.mode_of_payment = payment_mode.parent
|
||||||
|
payment.account = payment_mode.default_account
|
||||||
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
|
for pos_payment_method in pos_profile.get('payments'):
|
||||||
|
pos_payment_method = pos_payment_method.as_dict()
|
||||||
|
mode_of_payment = pos_payment_method.mode_of_payment
|
||||||
|
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
|
||||||
|
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||||
|
append_payment(payment_mode[0])
|
||||||
@ -7,6 +7,8 @@ import frappe
|
|||||||
import unittest, copy, time
|
import unittest, copy, time
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|
||||||
class TestPOSInvoice(unittest.TestCase):
|
class TestPOSInvoice(unittest.TestCase):
|
||||||
def test_timestamp_change(self):
|
def test_timestamp_change(self):
|
||||||
@ -182,8 +184,9 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
def test_pos_returns_with_repayment(self):
|
def test_pos_returns_with_repayment(self):
|
||||||
pos = create_pos_invoice(qty = 10, do_not_save=True)
|
pos = create_pos_invoice(qty = 10, do_not_save=True)
|
||||||
|
|
||||||
|
pos.set('payments', [])
|
||||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
|
||||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500, 'default': 1})
|
||||||
pos.insert()
|
pos.insert()
|
||||||
pos.submit()
|
pos.submit()
|
||||||
|
|
||||||
@ -200,8 +203,9 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
||||||
cost_center = "Main - _TC", do_not_save=True)
|
cost_center = "Main - _TC", do_not_save=True)
|
||||||
|
|
||||||
|
pos.set('payments', [])
|
||||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
|
||||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60})
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60, 'default': 1})
|
||||||
|
|
||||||
pos.insert()
|
pos.insert()
|
||||||
pos.submit()
|
pos.submit()
|
||||||
@ -219,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
se = make_serialized_item(company='_Test Company with perpetual inventory',
|
se = make_serialized_item(company='_Test Company',
|
||||||
target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1')
|
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||||
|
|
||||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||||
|
|
||||||
pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
pos.get("items")[0].serial_no = serial_nos[0]
|
pos.get("items")[0].serial_no = serial_nos[0]
|
||||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||||
|
|
||||||
pos.insert()
|
pos.insert()
|
||||||
pos.submit()
|
pos.submit()
|
||||||
|
|
||||||
pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
|
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||||
|
|
||||||
@ -284,6 +288,119 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
|
after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
|
||||||
self.assertEqual(after_redeem_lp_details.loyalty_points, 9)
|
self.assertEqual(after_redeem_lp_details.loyalty_points, 9)
|
||||||
|
|
||||||
|
def test_merging_into_sales_invoice_with_discount(self):
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270
|
||||||
|
})
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
merge_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
|
self.assertEqual(rounded_total, 3470)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
|
})
|
||||||
|
pos_inv.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
|
||||||
|
pos_inv2.additional_discount_percentage = 10
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 540
|
||||||
|
})
|
||||||
|
pos_inv2.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
merge_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
|
self.assertEqual(rounded_total, 840)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_merging_with_validate_selling_price(self):
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||||
|
|
||||||
|
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||||
|
|
||||||
|
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
|
})
|
||||||
|
pos_inv.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
|
||||||
|
})
|
||||||
|
pos_inv2.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
merge_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv2.load_from_db()
|
||||||
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||||
|
self.assertEqual(rounded_total, 400)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
|
||||||
|
|
||||||
def create_pos_invoice(**args):
|
def create_pos_invoice(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
pos_profile = None
|
pos_profile = None
|
||||||
@ -292,12 +409,11 @@ def create_pos_invoice(**args):
|
|||||||
pos_profile.save()
|
pos_profile.save()
|
||||||
|
|
||||||
pos_inv = frappe.new_doc("POS Invoice")
|
pos_inv = frappe.new_doc("POS Invoice")
|
||||||
|
pos_inv.update(args)
|
||||||
pos_inv.update_stock = 1
|
pos_inv.update_stock = 1
|
||||||
pos_inv.is_pos = 1
|
pos_inv.is_pos = 1
|
||||||
pos_inv.pos_profile = args.pos_profile or pos_profile.name
|
pos_inv.pos_profile = args.pos_profile or pos_profile.name
|
||||||
|
|
||||||
pos_inv.set_missing_values()
|
|
||||||
|
|
||||||
if args.posting_date:
|
if args.posting_date:
|
||||||
pos_inv.set_posting_time = 1
|
pos_inv.set_posting_time = 1
|
||||||
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
|
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
|
||||||
@ -311,6 +427,8 @@ def create_pos_invoice(**args):
|
|||||||
pos_inv.conversion_rate = args.conversion_rate or 1
|
pos_inv.conversion_rate = args.conversion_rate or 1
|
||||||
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
|
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
|
||||||
|
|
||||||
|
pos_inv.set_missing_values()
|
||||||
|
|
||||||
pos_inv.append("items", {
|
pos_inv.append("items", {
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
|
|||||||
@ -24,11 +24,27 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
def validate_pos_invoice_status(self):
|
def validate_pos_invoice_status(self):
|
||||||
for d in self.pos_invoices:
|
for d in self.pos_invoices:
|
||||||
status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus'])
|
status, docstatus, is_return, return_against = frappe.db.get_value(
|
||||||
|
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
||||||
|
|
||||||
|
bold_pos_invoice = frappe.bold(d.pos_invoice)
|
||||||
|
bold_status = frappe.bold(status)
|
||||||
if docstatus != 1:
|
if docstatus != 1:
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice))
|
||||||
if status in ['Consolidated']:
|
if status == "Consolidated":
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status))
|
||||||
|
if is_return and return_against and return_against not in [d.pos_invoice for d in self.pos_invoices]:
|
||||||
|
bold_return_against = frappe.bold(return_against)
|
||||||
|
return_against_status = frappe.db.get_value('POS Invoice', return_against, "status")
|
||||||
|
if return_against_status != "Consolidated":
|
||||||
|
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||||
|
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||||
|
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ")
|
||||||
|
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
||||||
|
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
||||||
|
msg += "<br><br>"
|
||||||
|
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
@ -36,12 +52,12 @@ class POSInvoiceMergeLog(Document):
|
|||||||
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
|
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
|
||||||
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
||||||
|
|
||||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
sales_invoice, credit_note = "", ""
|
||||||
|
if sales:
|
||||||
|
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||||
|
|
||||||
if len(returns):
|
if returns:
|
||||||
credit_note = self.process_merging_into_credit_note(returns)
|
credit_note = self.process_merging_into_credit_note(returns)
|
||||||
else:
|
|
||||||
credit_note = ""
|
|
||||||
|
|
||||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||||
|
|
||||||
@ -87,17 +103,28 @@ class POSInvoiceMergeLog(Document):
|
|||||||
loyalty_amount_sum += doc.loyalty_amount
|
loyalty_amount_sum += doc.loyalty_amount
|
||||||
|
|
||||||
for item in doc.get('items'):
|
for item in doc.get('items'):
|
||||||
items.append(item)
|
found = False
|
||||||
|
for i in items:
|
||||||
|
if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and
|
||||||
|
i.uom == item.uom and i.net_rate == item.net_rate):
|
||||||
|
found = True
|
||||||
|
i.qty = i.qty + item.qty
|
||||||
|
if not found:
|
||||||
|
item.rate = item.net_rate
|
||||||
|
items.append(item)
|
||||||
|
|
||||||
for tax in doc.get('taxes'):
|
for tax in doc.get('taxes'):
|
||||||
found = False
|
found = False
|
||||||
for t in taxes:
|
for t in taxes:
|
||||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate:
|
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount)
|
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount)
|
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
tax.charge_type = 'Actual'
|
tax.charge_type = 'Actual'
|
||||||
|
tax.included_in_print_rate = 0
|
||||||
|
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||||
|
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||||
taxes.append(tax)
|
taxes.append(tax)
|
||||||
|
|
||||||
for payment in doc.get('payments'):
|
for payment in doc.get('payments'):
|
||||||
@ -118,6 +145,8 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.set('items', items)
|
invoice.set('items', items)
|
||||||
invoice.set('payments', payments)
|
invoice.set('payments', payments)
|
||||||
invoice.set('taxes', taxes)
|
invoice.set('taxes', taxes)
|
||||||
|
invoice.additional_discount_percentage = 0
|
||||||
|
invoice.discount_amount = 0.0
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
|||||||
@ -5,21 +5,37 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint, get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.controllers.status_updater import StatusUpdater
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
|
|
||||||
class POSOpeningEntry(StatusUpdater):
|
class POSOpeningEntry(StatusUpdater):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_pos_profile_and_cashier()
|
self.validate_pos_profile_and_cashier()
|
||||||
|
self.validate_payment_method_account()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def validate_pos_profile_and_cashier(self):
|
def validate_pos_profile_and_cashier(self):
|
||||||
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
||||||
frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company)))
|
frappe.throw(_("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company))
|
||||||
|
|
||||||
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
||||||
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
|
||||||
|
|
||||||
|
def validate_payment_method_account(self):
|
||||||
|
invalid_modes = []
|
||||||
|
for d in self.balance_details:
|
||||||
|
account = frappe.db.get_value("Mode of Payment Account",
|
||||||
|
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||||
|
if not account:
|
||||||
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
@ -6,6 +6,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"default",
|
"default",
|
||||||
|
"allow_in_returns",
|
||||||
"mode_of_payment"
|
"mode_of_payment"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -24,11 +25,19 @@
|
|||||||
"label": "Mode of Payment",
|
"label": "Mode of Payment",
|
||||||
"options": "Mode of Payment",
|
"options": "Mode of Payment",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_in_returns",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Allow In Returns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:41.704844",
|
"modified": "2020-10-20 12:58:46.114456",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Payment Method",
|
"name": "POS Payment Method",
|
||||||
|
|||||||
@ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
|||||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||||
return erpnext.queries.warehouse(frm.doc);
|
return erpnext.queries.warehouse(frm.doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.call({
|
|
||||||
method: "erpnext.accounts.doctype.pos_profile.pos_profile.get_series",
|
|
||||||
callback: function(r) {
|
|
||||||
if(!r.exc) {
|
|
||||||
set_field_options("naming_series", r.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('POS Profile', {
|
frappe.ui.form.on('POS Profile', {
|
||||||
@ -44,6 +35,15 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("taxes_and_charges", function() {
|
||||||
|
return {
|
||||||
|
filters: [
|
||||||
|
['Sales Taxes and Charges Template', 'company', '=', frm.doc.company],
|
||||||
|
['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query('company_address', function(doc) {
|
frm.set_query('company_address', function(doc) {
|
||||||
if(!doc.company) {
|
if(!doc.company) {
|
||||||
frappe.throw(__('Please set Company'));
|
frappe.throw(__('Please set Company'));
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"disabled",
|
"disabled",
|
||||||
"section_break_2",
|
"section_break_2",
|
||||||
"naming_series",
|
|
||||||
"customer",
|
"customer",
|
||||||
"company",
|
"company",
|
||||||
"country",
|
"country",
|
||||||
@ -23,6 +22,9 @@
|
|||||||
"section_break_11",
|
"section_break_11",
|
||||||
"payments",
|
"payments",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
|
"hide_images",
|
||||||
|
"hide_unavailable_items",
|
||||||
|
"auto_add_item_to_cart",
|
||||||
"item_groups",
|
"item_groups",
|
||||||
"column_break_16",
|
"column_break_16",
|
||||||
"customer_groups",
|
"customer_groups",
|
||||||
@ -59,17 +61,6 @@
|
|||||||
"fieldname": "section_break_2",
|
"fieldname": "section_break_2",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Series",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "naming_series",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "[Select]",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "customer",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -135,7 +126,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_14",
|
"fieldname": "section_break_14",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Configuration"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Only show Items from these Item Groups",
|
"description": "Only show Items from these Item Groups",
|
||||||
@ -302,28 +294,48 @@
|
|||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"mandatory_depends_on": "update_stock",
|
|
||||||
"oldfieldname": "warehouse",
|
"oldfieldname": "warehouse",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse",
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "update_stock",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Update Stock"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "ignore_pricing_rule",
|
"fieldname": "ignore_pricing_rule",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Pricing Rule"
|
"label": "Ignore Pricing Rule"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "update_stock",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Update Stock",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "hide_unavailable_items",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Hide Unavailable Items"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "hide_images",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Hide Images"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "auto_add_item_to_cart",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Automatically Add Filtered Item To Cart"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-29 12:20:30.977272",
|
"modified": "2020-12-10 13:59:28.877572",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
@ -350,4 +362,4 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC"
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import msgprint, _
|
from frappe import msgprint, _
|
||||||
from frappe.utils import cint, now
|
from frappe.utils import cint, now, get_link_to_form
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ class POSProfile(Document):
|
|||||||
self.validate_default_profile()
|
self.validate_default_profile()
|
||||||
self.validate_all_link_fields()
|
self.validate_all_link_fields()
|
||||||
self.validate_duplicate_groups()
|
self.validate_duplicate_groups()
|
||||||
self.check_default_payment()
|
self.validate_payment_methods()
|
||||||
|
|
||||||
def validate_default_profile(self):
|
def validate_default_profile(self):
|
||||||
for row in self.applicable_for_users:
|
for row in self.applicable_for_users:
|
||||||
@ -52,14 +52,33 @@ class POSProfile(Document):
|
|||||||
if len(customer_groups) != len(set(customer_groups)):
|
if len(customer_groups) != len(set(customer_groups)):
|
||||||
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
|
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
|
||||||
|
|
||||||
def check_default_payment(self):
|
def validate_payment_methods(self):
|
||||||
if self.payments:
|
if not self.payments:
|
||||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||||
if not default_mode_of_payment:
|
|
||||||
frappe.throw(_("Set default mode of payment"))
|
|
||||||
|
|
||||||
if len(default_mode_of_payment) > 1:
|
default_mode = [d.default for d in self.payments if d.default]
|
||||||
frappe.throw(_("Multiple default mode of payment is not allowed"))
|
if not default_mode:
|
||||||
|
frappe.throw(_("Please select a default mode of payment"))
|
||||||
|
|
||||||
|
if len(default_mode) > 1:
|
||||||
|
frappe.throw(_("You can only select one mode of payment as default"))
|
||||||
|
|
||||||
|
invalid_modes = []
|
||||||
|
for d in self.payments:
|
||||||
|
account = frappe.db.get_value(
|
||||||
|
"Mode of Payment Account",
|
||||||
|
{"parent": d.mode_of_payment, "company": self.company},
|
||||||
|
"default_account"
|
||||||
|
)
|
||||||
|
if not account:
|
||||||
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
@ -100,10 +119,6 @@ def get_child_nodes(group_type, root):
|
|||||||
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
|
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
|
||||||
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
|
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_series():
|
|
||||||
return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s"
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
|||||||
@ -70,6 +70,7 @@ def get_items_list(pos_profile, company):
|
|||||||
""".format(cond=cond), tuple([company] + args_list), as_dict=1)
|
""".format(cond=cond), tuple([company] + args_list), as_dict=1)
|
||||||
|
|
||||||
def make_pos_profile(**args):
|
def make_pos_profile(**args):
|
||||||
|
frappe.db.sql("delete from `tabPOS Payment Method`")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@ -7,10 +7,9 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get_invoice_fields: function(frm) {
|
get_invoice_fields: function(frm) {
|
||||||
frappe.model.with_doctype("Sales Invoice", () => {
|
frappe.model.with_doctype("POS Invoice", () => {
|
||||||
var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || ['Button'].includes(d.fieldtype)) {
|
||||||
d.fieldtype === 'Table') {
|
|
||||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -25,7 +24,7 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
frappe.ui.form.on("POS Field", {
|
frappe.ui.form.on("POS Field", {
|
||||||
fieldname: function(frm, doctype, name) {
|
fieldname: function(frm, doctype, name) {
|
||||||
var doc = frappe.get_doc(doctype, name);
|
var doc = frappe.get_doc(doctype, name);
|
||||||
var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
var df = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||||
return doc.fieldname == d.fieldname ? d : null;
|
return doc.fieldname == d.fieldname ? d : null;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
|
|||||||
@ -42,56 +42,56 @@ frappe.ui.form.on('Pricing Rule', {
|
|||||||
<tr><td>
|
<tr><td>
|
||||||
<h4>
|
<h4>
|
||||||
<i class="fa fa-hand-right"></i>
|
<i class="fa fa-hand-right"></i>
|
||||||
${__('Notes')}
|
{{__('Notes')}}
|
||||||
</h4>
|
</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
${__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}
|
{{__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}
|
{{__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('Discount Percentage can be applied either against a Price List or for all Price List.')}
|
{{__('Discount Percentage can be applied either against a Price List or for all Price List.')}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}
|
{{__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<h4><i class="fa fa-question-sign"></i>
|
<h4><i class="fa fa-question-sign"></i>
|
||||||
${__('How Pricing Rule is applied?')}
|
{{__('How Pricing Rule is applied?')}}
|
||||||
</h4>
|
</h4>
|
||||||
<ol>
|
<ol>
|
||||||
<li>
|
<li>
|
||||||
${__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}
|
{{__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}
|
{{__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('Pricing Rules are further filtered based on quantity.')}
|
{{__('Pricing Rules are further filtered based on quantity.')}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}
|
{{__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}
|
{{__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}}
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
${__('Item Code > Item Group > Brand')}
|
{{__('Item Code > Item Group > Brand')}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('Customer > Customer Group > Territory')}
|
{{__('Customer > Customer Group > Territory')}}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('Supplier > Supplier Type')}
|
{{__('Supplier > Supplier Type')}}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
${__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}
|
{{__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}}
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:title",
|
"autoname": "field:title",
|
||||||
@ -71,6 +72,7 @@
|
|||||||
"section_break_13",
|
"section_break_13",
|
||||||
"threshold_percentage",
|
"threshold_percentage",
|
||||||
"priority",
|
"priority",
|
||||||
|
"condition",
|
||||||
"column_break_66",
|
"column_break_66",
|
||||||
"apply_multiple_pricing_rules",
|
"apply_multiple_pricing_rules",
|
||||||
"apply_discount_on_rate",
|
"apply_discount_on_rate",
|
||||||
@ -404,6 +406,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
@ -467,6 +470,7 @@
|
|||||||
"options": "UOM"
|
"options": "UOM"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
||||||
"fieldname": "free_item_rate",
|
"fieldname": "free_item_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rate"
|
"label": "Rate"
|
||||||
@ -502,10 +506,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
"depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
||||||
"fieldname": "apply_discount_on_rate",
|
"fieldname": "apply_discount_on_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply Discount on Rate"
|
"label": "Apply Discount on Discounted Rate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@ -550,11 +554,18 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Promotional Scheme",
|
"label": "Promotional Scheme",
|
||||||
"options": "Promotional Scheme"
|
"options": "Promotional Scheme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Simple Python Expression, Example: territory != 'All Territories'",
|
||||||
|
"fieldname": "condition",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Condition"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-12-18 17:29:22.957077",
|
"links": [],
|
||||||
|
"modified": "2020-12-04 00:36:24.698219",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
|||||||
@ -6,9 +6,10 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import json
|
import json
|
||||||
import copy
|
import copy
|
||||||
|
import re
|
||||||
|
|
||||||
from frappe import throw, _
|
from frappe import throw, _
|
||||||
from frappe.utils import flt, cint, getdate
|
from frappe.utils import flt, cint, getdate
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
@ -30,6 +31,7 @@ class PricingRule(Document):
|
|||||||
self.validate_max_discount()
|
self.validate_max_discount()
|
||||||
self.validate_price_list_with_currency()
|
self.validate_price_list_with_currency()
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
|
self.validate_condition()
|
||||||
|
|
||||||
if not self.margin_type: self.margin_rate_or_amount = 0.0
|
if not self.margin_type: self.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
@ -58,6 +60,15 @@ class PricingRule(Document):
|
|||||||
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
||||||
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
||||||
|
|
||||||
|
if self.apply_discount_on_rate:
|
||||||
|
if not self.priority:
|
||||||
|
throw(_("As the field {0} is enabled, the field {1} is mandatory.")
|
||||||
|
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
|
||||||
|
|
||||||
|
if self.priority and cint(self.priority) == 1:
|
||||||
|
throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.")
|
||||||
|
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
|
||||||
|
|
||||||
def validate_applicable_for_selling_or_buying(self):
|
def validate_applicable_for_selling_or_buying(self):
|
||||||
if not self.selling and not self.buying:
|
if not self.selling and not self.buying:
|
||||||
throw(_("Atleast one of the Selling or Buying must be selected"))
|
throw(_("Atleast one of the Selling or Buying must be selected"))
|
||||||
@ -140,6 +151,10 @@ class PricingRule(Document):
|
|||||||
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
|
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
|
||||||
frappe.throw(_("Valid from date must be less than valid upto date"))
|
frappe.throw(_("Valid from date must be less than valid upto date"))
|
||||||
|
|
||||||
|
def validate_condition(self):
|
||||||
|
if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition):
|
||||||
|
frappe.throw(_("Invalid condition expression"))
|
||||||
|
|
||||||
#--------------------------------------------------------------------------------
|
#--------------------------------------------------------------------------------
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -220,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
|
|
||||||
item_details = frappe._dict({
|
item_details = frappe._dict({
|
||||||
"doctype": args.doctype,
|
"doctype": args.doctype,
|
||||||
|
"has_margin": False,
|
||||||
"name": args.name,
|
"name": args.name,
|
||||||
"parent": args.parent,
|
"parent": args.parent,
|
||||||
"parenttype": args.parenttype,
|
"parenttype": args.parenttype,
|
||||||
"child_docname": args.get('child_docname'),
|
"child_docname": args.get('child_docname')
|
||||||
"discount_percentage_on_rate": [],
|
|
||||||
"discount_amount_on_rate": []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if args.ignore_pricing_rule or not args.item_code:
|
if args.ignore_pricing_rule or not args.item_code:
|
||||||
@ -273,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
else:
|
else:
|
||||||
get_product_discount_rule(pricing_rule, item_details, args, doc)
|
get_product_discount_rule(pricing_rule, item_details, args, doc)
|
||||||
|
|
||||||
|
if not item_details.get("has_margin"):
|
||||||
|
item_details.margin_type = None
|
||||||
|
item_details.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
item_details.has_pricing_rule = 1
|
item_details.has_pricing_rule = 1
|
||||||
|
|
||||||
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
|
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
|
||||||
@ -324,20 +342,28 @@ def get_pricing_rule_details(args, pricing_rule):
|
|||||||
def apply_price_discount_rule(pricing_rule, item_details, args):
|
def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||||
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
||||||
|
|
||||||
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
|
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
|
||||||
or (pricing_rule.margin_type == 'Percentage')):
|
or (pricing_rule.margin_type == 'Percentage')):
|
||||||
item_details.margin_type = pricing_rule.margin_type
|
item_details.margin_type = pricing_rule.margin_type
|
||||||
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
item_details.has_margin = True
|
||||||
else:
|
|
||||||
item_details.margin_type = None
|
if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None:
|
||||||
item_details.margin_rate_or_amount = 0.0
|
item_details.margin_rate_or_amount += pricing_rule.margin_rate_or_amount
|
||||||
|
else:
|
||||||
|
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||||
|
|
||||||
if pricing_rule.rate_or_discount == 'Rate':
|
if pricing_rule.rate_or_discount == 'Rate':
|
||||||
pricing_rule_rate = 0.0
|
pricing_rule_rate = 0.0
|
||||||
if pricing_rule.currency == args.currency:
|
if pricing_rule.currency == args.currency:
|
||||||
pricing_rule_rate = pricing_rule.rate
|
pricing_rule_rate = pricing_rule.rate
|
||||||
|
|
||||||
|
if pricing_rule_rate:
|
||||||
|
# Override already set price list rate (from item price)
|
||||||
|
# if pricing_rule_rate > 0
|
||||||
|
item_details.update({
|
||||||
|
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
|
||||||
|
})
|
||||||
item_details.update({
|
item_details.update({
|
||||||
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
|
|
||||||
"discount_percentage": 0.0
|
"discount_percentage": 0.0
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -345,9 +371,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
if pricing_rule.rate_or_discount != apply_on: continue
|
if pricing_rule.rate_or_discount != apply_on: continue
|
||||||
|
|
||||||
field = frappe.scrub(apply_on)
|
field = frappe.scrub(apply_on)
|
||||||
if pricing_rule.apply_discount_on_rate:
|
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
||||||
discount_field = "{0}_on_rate".format(field)
|
# Apply discount on discounted rate
|
||||||
item_details[discount_field].append(pricing_rule.get(field, 0))
|
item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
|
||||||
else:
|
else:
|
||||||
if field not in item_details:
|
if field not in item_details:
|
||||||
item_details.setdefault(field, 0)
|
item_details.setdefault(field, 0)
|
||||||
@ -355,14 +381,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
item_details[field] += (pricing_rule.get(field, 0)
|
item_details[field] += (pricing_rule.get(field, 0)
|
||||||
if pricing_rule else args.get(field, 0))
|
if pricing_rule else args.get(field, 0))
|
||||||
|
|
||||||
def set_discount_amount(rate, item_details):
|
|
||||||
for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']:
|
|
||||||
for d in item_details.get(field):
|
|
||||||
dis_amount = (rate * d / 100
|
|
||||||
if field == 'discount_percentage_on_rate' else d)
|
|
||||||
rate -= dis_amount
|
|
||||||
item_details.rate = rate
|
|
||||||
|
|
||||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
||||||
get_pricing_rule_items)
|
get_pricing_rule_items)
|
||||||
|
|||||||
@ -385,7 +385,7 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
so.load_from_db()
|
so.load_from_db()
|
||||||
self.assertEqual(so.items[1].is_free_item, 1)
|
self.assertEqual(so.items[1].is_free_item, 1)
|
||||||
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
||||||
|
|
||||||
def test_cumulative_pricing_rule(self):
|
def test_cumulative_pricing_rule(self):
|
||||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
|
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
|
||||||
test_record = {
|
test_record = {
|
||||||
@ -430,6 +430,113 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(details)
|
self.assertTrue(details)
|
||||||
|
|
||||||
|
def test_pricing_rule_for_condition(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
|
||||||
|
make_pricing_rule(selling=1, margin_type="Percentage", \
|
||||||
|
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
|
||||||
|
|
||||||
|
# Incorrect Customer and Correct is_return value
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
|
||||||
|
si.items[0].price_list_rate = 1000
|
||||||
|
si.submit()
|
||||||
|
item = si.items[0]
|
||||||
|
self.assertEquals(item.rate, 100)
|
||||||
|
|
||||||
|
# Correct Customer and Incorrect is_return value
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
|
||||||
|
si.items[0].price_list_rate = 1000
|
||||||
|
si.submit()
|
||||||
|
item = si.items[0]
|
||||||
|
self.assertEquals(item.rate, 100)
|
||||||
|
|
||||||
|
# Correct Customer and correct is_return value
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
|
||||||
|
si.items[0].price_list_rate = 1000
|
||||||
|
si.submit()
|
||||||
|
item = si.items[0]
|
||||||
|
self.assertEquals(item.rate, 900)
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules(self):
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||||
|
title="_Test Pricing Rule 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
|
||||||
|
apply_multiple_pricing_rules=1)
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
self.assertEqual(si.items[0].discount_percentage, 30)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||||
|
title="_Test Pricing Rule 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, priority=2,
|
||||||
|
apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
self.assertEqual(si.items[0].discount_percentage, 28)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
|
def test_item_price_with_pricing_rule(self):
|
||||||
|
item = make_item("Water Flask")
|
||||||
|
make_item_price("Water Flask", "_Test Price List", 100)
|
||||||
|
|
||||||
|
pricing_rule_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Water Flask Rule",
|
||||||
|
"apply_on": "Item Code",
|
||||||
|
"items": [{
|
||||||
|
"item_code": "Water Flask",
|
||||||
|
}],
|
||||||
|
"selling": 1,
|
||||||
|
"currency": "INR",
|
||||||
|
"rate_or_discount": "Rate",
|
||||||
|
"rate": 0,
|
||||||
|
"margin_type": "Percentage",
|
||||||
|
"margin_rate_or_amount": 2,
|
||||||
|
"company": "_Test Company"
|
||||||
|
}
|
||||||
|
rule = frappe.get_doc(pricing_rule_record)
|
||||||
|
rule.insert()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save=True, item_code="Water Flask")
|
||||||
|
si.selling_price_list = "_Test Price List"
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
# If rate in Rule is 0, give preference to Item Price if it exists
|
||||||
|
self.assertEqual(si.items[0].price_list_rate, 100)
|
||||||
|
self.assertEqual(si.items[0].margin_rate_or_amount, 2)
|
||||||
|
self.assertEqual(si.items[0].rate_with_margin, 102)
|
||||||
|
self.assertEqual(si.items[0].rate, 102)
|
||||||
|
|
||||||
|
si.delete()
|
||||||
|
rule.delete()
|
||||||
|
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
||||||
|
item.delete()
|
||||||
|
|
||||||
|
def test_pricing_rule_for_transaction(self):
|
||||||
|
make_item("Water Flask 1")
|
||||||
|
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||||
|
make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
|
||||||
|
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
|
||||||
|
|
||||||
|
si = create_sales_invoice(qty=5, do_not_submit=True)
|
||||||
|
self.assertEquals(len(si.items), 2)
|
||||||
|
self.assertEquals(si.items[1].rate, 10)
|
||||||
|
|
||||||
|
si1 = create_sales_invoice(qty=2, do_not_submit=True)
|
||||||
|
self.assertEquals(len(si1.items), 1)
|
||||||
|
|
||||||
|
for doc in [si, si1]:
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@ -441,21 +548,30 @@ def make_pricing_rule(**args):
|
|||||||
"applicable_for": args.applicable_for,
|
"applicable_for": args.applicable_for,
|
||||||
"selling": args.selling or 0,
|
"selling": args.selling or 0,
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
|
||||||
"buying": args.buying or 0,
|
"buying": args.buying or 0,
|
||||||
"min_qty": args.min_qty or 0.0,
|
"min_qty": args.min_qty or 0.0,
|
||||||
"max_qty": args.max_qty or 0.0,
|
"max_qty": args.max_qty or 0.0,
|
||||||
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
|
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
|
||||||
"discount_percentage": args.discount_percentage or 0.0,
|
"discount_percentage": args.discount_percentage or 0.0,
|
||||||
"rate": args.rate or 0.0,
|
"rate": args.rate or 0.0,
|
||||||
"margin_type": args.margin_type,
|
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0
|
"condition": args.condition or '',
|
||||||
|
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for field in ["free_item", "free_qty", "free_item_rate", "priority",
|
||||||
|
"margin_type", "price_or_product_discount"]:
|
||||||
|
if args.get(field):
|
||||||
|
doc.set(field, args.get(field))
|
||||||
|
|
||||||
apply_on = doc.apply_on.replace(' ', '_').lower()
|
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||||
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
||||||
doc.append(child_table.get(doc.apply_on), {
|
|
||||||
apply_on: args.get(apply_on) or "_Test Item"
|
if doc.apply_on != "Transaction":
|
||||||
})
|
doc.append(child_table.get(doc.apply_on), {
|
||||||
|
apply_on: args.get(apply_on) or "_Test Item"
|
||||||
|
})
|
||||||
|
|
||||||
doc.insert(ignore_permissions=True)
|
doc.insert(ignore_permissions=True)
|
||||||
if args.get(apply_on) and apply_on != "item_code":
|
if args.get(apply_on) and apply_on != "item_code":
|
||||||
|
|||||||
@ -14,9 +14,8 @@ import frappe
|
|||||||
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
||||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from frappe import _, throw
|
from frappe import _, bold
|
||||||
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
|
from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
|
||||||
|
|
||||||
|
|
||||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||||
|
|
||||||
@ -37,9 +36,12 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
rules = []
|
rules = []
|
||||||
|
|
||||||
|
pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc)
|
||||||
|
|
||||||
if not pricing_rules: return []
|
if not pricing_rules: return []
|
||||||
|
|
||||||
if apply_multiple_pricing_rules(pricing_rules):
|
if apply_multiple_pricing_rules(pricing_rules):
|
||||||
|
pricing_rules = sorted_by_priority(pricing_rules)
|
||||||
for pricing_rule in pricing_rules:
|
for pricing_rule in pricing_rules:
|
||||||
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
||||||
if pricing_rule:
|
if pricing_rule:
|
||||||
@ -51,6 +53,37 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
def sorted_by_priority(pricing_rules):
|
||||||
|
# If more than one pricing rules, then sort by priority
|
||||||
|
pricing_rules_list = []
|
||||||
|
pricing_rule_dict = {}
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
if not pricing_rule.get("priority"): continue
|
||||||
|
|
||||||
|
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
|
||||||
|
|
||||||
|
for key in sorted(pricing_rule_dict):
|
||||||
|
pricing_rules_list.append(pricing_rule_dict.get(key))
|
||||||
|
|
||||||
|
return pricing_rules_list or pricing_rules
|
||||||
|
|
||||||
|
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
||||||
|
filtered_pricing_rules = []
|
||||||
|
if doc:
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
if pricing_rule.condition:
|
||||||
|
try:
|
||||||
|
if frappe.safe_eval(pricing_rule.condition, None, doc.as_dict()):
|
||||||
|
filtered_pricing_rules.append(pricing_rule)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
filtered_pricing_rules.append(pricing_rule)
|
||||||
|
else:
|
||||||
|
filtered_pricing_rules = pricing_rules
|
||||||
|
|
||||||
|
return filtered_pricing_rules
|
||||||
|
|
||||||
def _get_pricing_rules(apply_on, args, values):
|
def _get_pricing_rules(apply_on, args, values):
|
||||||
apply_on_field = frappe.scrub(apply_on)
|
apply_on_field = frappe.scrub(apply_on)
|
||||||
|
|
||||||
@ -131,7 +164,15 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
|||||||
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
||||||
|
|
||||||
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
||||||
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||||
|
|
||||||
|
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||||
|
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||||
|
root_name = frappe.db.get_list(parenttype,
|
||||||
|
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
|
||||||
|
|
||||||
|
if root_name and root_name[0][0]:
|
||||||
|
parent_groups.append(root_name[0][0])
|
||||||
|
|
||||||
if parent_groups:
|
if parent_groups:
|
||||||
if allow_blank: parent_groups.append('')
|
if allow_blank: parent_groups.append('')
|
||||||
@ -265,12 +306,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
|
|||||||
fieldname = field
|
fieldname = field
|
||||||
|
|
||||||
if fieldname:
|
if fieldname:
|
||||||
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
|
msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
|
||||||
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
|
.format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
|
||||||
|
|
||||||
if fieldname in ['min_amt', 'max_amt']:
|
if fieldname in ['min_amt', 'max_amt']:
|
||||||
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item.
|
msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
|
||||||
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
|
.format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
|
||||||
|
bold(item_code), bold(args.rule_description)))
|
||||||
|
|
||||||
frappe.msgprint(msg)
|
frappe.msgprint(msg)
|
||||||
|
|
||||||
@ -423,6 +465,9 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
|
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
|
||||||
doc.total, pricing_rules)
|
doc.total, pricing_rules)
|
||||||
|
|
||||||
|
if not pricing_rules:
|
||||||
|
remove_free_item(doc)
|
||||||
|
|
||||||
for d in pricing_rules:
|
for d in pricing_rules:
|
||||||
if d.price_or_product_discount == 'Price':
|
if d.price_or_product_discount == 'Price':
|
||||||
if d.apply_discount_on:
|
if d.apply_discount_on:
|
||||||
@ -446,6 +491,12 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
get_product_discount_rule(d, item_details, doc=doc)
|
get_product_discount_rule(d, item_details, doc=doc)
|
||||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||||
doc.set_missing_values()
|
doc.set_missing_values()
|
||||||
|
doc.calculate_taxes_and_totals()
|
||||||
|
|
||||||
|
def remove_free_item(doc):
|
||||||
|
for d in doc.items:
|
||||||
|
if d.is_free_item:
|
||||||
|
doc.remove(d)
|
||||||
|
|
||||||
def get_applied_pricing_rules(pricing_rules):
|
def get_applied_pricing_rules(pricing_rules):
|
||||||
if pricing_rules:
|
if pricing_rules:
|
||||||
@ -458,7 +509,7 @@ def get_applied_pricing_rules(pricing_rules):
|
|||||||
|
|
||||||
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||||
free_item = pricing_rule.free_item
|
free_item = pricing_rule.free_item
|
||||||
if pricing_rule.same_item:
|
if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
|
||||||
free_item = item_details.item_code or args.item_code
|
free_item = item_details.item_code or args.item_code
|
||||||
|
|
||||||
if not free_item:
|
if not free_item:
|
||||||
|
|||||||
@ -21,7 +21,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
|||||||
item.no_of_months = 12
|
item.no_of_months = 12
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
|
si = create_sales_invoice(item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True)
|
||||||
si.items[0].enable_deferred_revenue = 1
|
si.items[0].enable_deferred_revenue = 1
|
||||||
si.items[0].service_start_date = "2019-01-10"
|
si.items[0].service_start_date = "2019-01-10"
|
||||||
si.items[0].service_end_date = "2019-03-15"
|
si.items[0].service_end_date = "2019-03-15"
|
||||||
|
|||||||
@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
return (doc.qty<=doc.received_qty) ? "green" : "orange";
|
return (doc.qty<=doc.received_qty) ? "green" : "orange";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frm.set_query("unrealized_profit_loss_account", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: doc.company,
|
||||||
|
is_group: 0,
|
||||||
|
root_type: "Liability",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onload: function() {
|
onload: function() {
|
||||||
this._super();
|
this._super();
|
||||||
@ -25,6 +35,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
this.frm.set_df_property("credit_to", "print_hide", 0);
|
this.frm.set_df_property("credit_to", "print_hide", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger supplier event on load if supplier is available
|
||||||
|
// The reason for this is PI can be created from PR or PO and supplier is pre populated
|
||||||
|
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||||
|
this.frm.trigger('supplier');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc) {
|
refresh: function(doc) {
|
||||||
@ -93,6 +109,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
target: me.frm,
|
target: me.frm,
|
||||||
setters: {
|
setters: {
|
||||||
supplier: me.frm.doc.supplier || undefined,
|
supplier: me.frm.doc.supplier || undefined,
|
||||||
|
schedule_date: undefined
|
||||||
},
|
},
|
||||||
get_query_filters: {
|
get_query_filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
@ -101,16 +118,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, __("Get items from"));
|
}, __("Get Items From"));
|
||||||
|
|
||||||
this.frm.add_custom_button(__('Purchase Receipt'), function() {
|
this.frm.add_custom_button(__('Purchase Receipt'), function() {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
|
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
|
||||||
source_doctype: "Purchase Receipt",
|
source_doctype: "Purchase Receipt",
|
||||||
target: me.frm,
|
target: me.frm,
|
||||||
date_field: "posting_date",
|
|
||||||
setters: {
|
setters: {
|
||||||
supplier: me.frm.doc.supplier || undefined,
|
supplier: me.frm.doc.supplier || undefined,
|
||||||
|
posting_date: undefined
|
||||||
},
|
},
|
||||||
get_query_filters: {
|
get_query_filters: {
|
||||||
docstatus: 1,
|
docstatus: 1,
|
||||||
@ -119,7 +136,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
is_return: 0
|
is_return: 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, __("Get items from"));
|
}, __("Get Items From"));
|
||||||
}
|
}
|
||||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
|
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
|
||||||
|
|
||||||
@ -135,6 +152,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
unblock_invoice: function() {
|
unblock_invoice: function() {
|
||||||
|
|||||||
@ -126,6 +126,7 @@
|
|||||||
"write_off_cost_center",
|
"write_off_cost_center",
|
||||||
"advances_section",
|
"advances_section",
|
||||||
"allocate_advances_automatically",
|
"allocate_advances_automatically",
|
||||||
|
"adjust_advance_taxes",
|
||||||
"get_advances",
|
"get_advances",
|
||||||
"advances",
|
"advances",
|
||||||
"payment_schedule_section",
|
"payment_schedule_section",
|
||||||
@ -151,9 +152,11 @@
|
|||||||
"is_opening",
|
"is_opening",
|
||||||
"against_expense_account",
|
"against_expense_account",
|
||||||
"column_break_63",
|
"column_break_63",
|
||||||
|
"unrealized_profit_loss_account",
|
||||||
"status",
|
"status",
|
||||||
"inter_company_invoice_reference",
|
"inter_company_invoice_reference",
|
||||||
"is_internal_supplier",
|
"is_internal_supplier",
|
||||||
|
"represents_company",
|
||||||
"remarks",
|
"remarks",
|
||||||
"subscription_section",
|
"subscription_section",
|
||||||
"from_date",
|
"from_date",
|
||||||
@ -361,6 +364,7 @@
|
|||||||
"fieldname": "bill_date",
|
"fieldname": "bill_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Supplier Invoice Date",
|
"label": "Supplier Invoice Date",
|
||||||
|
"no_copy": 1,
|
||||||
"oldfieldname": "bill_date",
|
"oldfieldname": "bill_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
@ -1221,7 +1225,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
|
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1328,13 +1332,37 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
"options": "Project"
|
"options": "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Taxes paid while advance payment will be adjusted against this invoice",
|
||||||
|
"fieldname": "adjust_advance_taxes",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Adjust Advance Taxes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.is_internal_supplier",
|
||||||
|
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||||
|
"fieldname": "unrealized_profit_loss_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Unrealized Profit / Loss Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.is_internal_supplier",
|
||||||
|
"description": "Company which internal supplier represents",
|
||||||
|
"fetch_from": "supplier.represents_company",
|
||||||
|
"fieldname": "represents_company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Represents Company",
|
||||||
|
"options": "Company"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-03 23:20:04.466153",
|
"modified": "2020-12-11 12:46:12.796378",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
@ -1396,4 +1424,4 @@
|
|||||||
"timeline_field": "supplier",
|
"timeline_field": "supplier",
|
||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@ -132,6 +132,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not self.due_date:
|
if not self.due_date:
|
||||||
self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date)
|
self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date)
|
||||||
|
|
||||||
|
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||||
|
if tds_category and not for_validate:
|
||||||
|
self.apply_tds = 1
|
||||||
|
self.tax_withholding_category = tds_category
|
||||||
|
|
||||||
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
def check_conversion_rate(self):
|
def check_conversion_rate(self):
|
||||||
@ -142,18 +147,25 @@ class PurchaseInvoice(BuyingController):
|
|||||||
throw(_("Conversion rate cannot be 0 or 1"))
|
throw(_("Conversion rate cannot be 0 or 1"))
|
||||||
|
|
||||||
def validate_credit_to_acc(self):
|
def validate_credit_to_acc(self):
|
||||||
|
if not self.credit_to:
|
||||||
|
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
|
||||||
|
if not self.credit_to:
|
||||||
|
self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
|
||||||
|
|
||||||
account = frappe.db.get_value("Account", self.credit_to,
|
account = frappe.db.get_value("Account", self.credit_to,
|
||||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||||
|
|
||||||
if account.report_type != "Balance Sheet":
|
if account.report_type != "Balance Sheet":
|
||||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
frappe.throw(
|
||||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
_("Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.")
|
||||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||||
|
)
|
||||||
|
|
||||||
if self.supplier and account.account_type != "Payable":
|
if self.supplier and account.account_type != "Payable":
|
||||||
frappe.throw(_("Please ensure {} account is a Payable account. \
|
frappe.throw(
|
||||||
Change the account type to Payable or select a different account.")
|
_("Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.")
|
||||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||||
|
)
|
||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
@ -194,8 +206,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
["Purchase Receipt", "purchase_receipt", "pr_detail"]
|
["Purchase Receipt", "purchase_receipt", "pr_detail"]
|
||||||
])
|
])
|
||||||
|
|
||||||
def validate_warehouse(self):
|
def validate_warehouse(self, for_validate=True):
|
||||||
if self.update_stock:
|
if self.update_stock and for_validate:
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.warehouse:
|
if not d.warehouse:
|
||||||
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
|
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
|
||||||
@ -221,7 +233,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if self.update_stock:
|
if self.update_stock:
|
||||||
self.validate_item_code()
|
self.validate_item_code()
|
||||||
self.validate_warehouse()
|
self.validate_warehouse(for_validate)
|
||||||
if auto_accounting_for_stock:
|
if auto_accounting_for_stock:
|
||||||
warehouse_account = get_warehouse_account_map(self.company)
|
warehouse_account = get_warehouse_account_map(self.company)
|
||||||
|
|
||||||
@ -239,10 +251,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if self.update_stock and (not item.from_warehouse):
|
if self.update_stock and (not item.from_warehouse):
|
||||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because account {2}
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
||||||
is not linked to warehouse {3} or it is not the default inventory account'''.format(
|
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]),
|
msg += _("or it is not the default inventory account")
|
||||||
frappe.bold(item.expense_account), frappe.bold(item.warehouse))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||||
else:
|
else:
|
||||||
@ -254,19 +266,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if negative_expense_booked_in_pr:
|
if negative_expense_booked_in_pr:
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||||
expense is booked against this account in Purchase Receipt {2}'''.format(
|
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
||||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
else:
|
else:
|
||||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} as no Purchase
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||||
Receipt is created against Item {2}. This is done to handle accounting for cases
|
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
||||||
when Purchase Receipt is created after Purchase Invoice'''.format(
|
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
|
|
||||||
@ -294,10 +306,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.purchase_order:
|
if not d.purchase_order:
|
||||||
throw(_("""Purchase Order Required for item {0}
|
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||||
To submit the invoice without purchase order please set
|
msg += "<br><br>"
|
||||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Order Required')),
|
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
||||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
|
throw(msg, title=_("Mandatory Purchase Order"))
|
||||||
|
|
||||||
def pr_required(self):
|
def pr_required(self):
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
@ -308,10 +321,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.purchase_receipt and d.item_code in stock_items:
|
if not d.purchase_receipt and d.item_code in stock_items:
|
||||||
throw(_("""Purchase Receipt Required for item {0}
|
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||||
To submit the invoice without purchase receipt please set
|
msg += "<br><br>"
|
||||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Receipt Required')),
|
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
||||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
|
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||||
|
|
||||||
def validate_write_off_account(self):
|
def validate_write_off_account(self):
|
||||||
if self.write_off_amount and not self.write_off_account:
|
if self.write_off_amount and not self.write_off_account:
|
||||||
@ -396,12 +410,13 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# this sequence because outstanding may get -negative
|
# this sequence because outstanding may get -negative
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
|
if self.update_stock == 1:
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
self.update_project()
|
self.update_project()
|
||||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
if not self.grand_total:
|
|
||||||
return
|
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
|
|
||||||
@ -409,7 +424,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
|
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
|
||||||
|
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
|
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
|
||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||||
|
|
||||||
@ -424,9 +439,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||||
if self.auto_accounting_for_stock:
|
if self.auto_accounting_for_stock:
|
||||||
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
||||||
|
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
||||||
else:
|
else:
|
||||||
self.stock_received_but_not_billed = None
|
self.stock_received_but_not_billed = None
|
||||||
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
self.expenses_included_in_valuation = None
|
||||||
|
|
||||||
self.negative_expense_to_be_booked = 0.0
|
self.negative_expense_to_be_booked = 0.0
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
@ -437,15 +454,15 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.get_asset_gl_entry(gl_entries)
|
self.get_asset_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||||
|
|
||||||
gl_entries = merge_similar_entries(gl_entries)
|
gl_entries = merge_similar_entries(gl_entries)
|
||||||
|
|
||||||
self.make_payment_gl_entries(gl_entries)
|
self.make_payment_gl_entries(gl_entries)
|
||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
def check_asset_cwip_enabled(self):
|
def check_asset_cwip_enabled(self):
|
||||||
@ -462,31 +479,30 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||||
|
|
||||||
if grand_total:
|
if grand_total and not self.is_internal_transfer():
|
||||||
# Didnot use base_grand_total to book rounding loss gle
|
# Didnot use base_grand_total to book rounding loss gle
|
||||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||||
self.precision("grand_total"))
|
self.precision("grand_total"))
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": self.credit_to,
|
"account": self.credit_to,
|
||||||
"party_type": "Supplier",
|
"party_type": "Supplier",
|
||||||
"party": self.supplier,
|
"party": self.supplier,
|
||||||
"due_date": self.due_date,
|
"due_date": self.due_date,
|
||||||
"against": self.against_expense_account,
|
"against": self.against_expense_account,
|
||||||
"credit": grand_total_in_company_currency,
|
"credit": grand_total_in_company_currency,
|
||||||
"credit_in_account_currency": grand_total_in_company_currency \
|
"credit_in_account_currency": grand_total_in_company_currency \
|
||||||
if self.party_account_currency==self.company_currency else grand_total,
|
if self.party_account_currency==self.company_currency else grand_total,
|
||||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||||
"against_voucher_type": self.doctype,
|
"against_voucher_type": self.doctype,
|
||||||
"project": self.project,
|
"project": self.project,
|
||||||
"cost_center": self.cost_center
|
"cost_center": self.cost_center
|
||||||
}, self.party_account_currency, item=self)
|
}, self.party_account_currency, item=self)
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_item_gl_entries(self, gl_entries):
|
def make_item_gl_entries(self, gl_entries):
|
||||||
# item gl entries
|
# item gl entries
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
|
||||||
if self.update_stock and self.auto_accounting_for_stock:
|
if self.update_stock and self.auto_accounting_for_stock:
|
||||||
warehouse_account = get_warehouse_account_map(self.company)
|
warehouse_account = get_warehouse_account_map(self.company)
|
||||||
|
|
||||||
@ -514,7 +530,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item, voucher_wise_stock_value, account_currency)
|
item, voucher_wise_stock_value, account_currency)
|
||||||
|
|
||||||
if item.from_warehouse:
|
if item.from_warehouse:
|
||||||
|
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
"account": warehouse_account[item.warehouse]['account'],
|
"account": warehouse_account[item.warehouse]['account'],
|
||||||
"against": warehouse_account[item.from_warehouse]["account"],
|
"against": warehouse_account[item.from_warehouse]["account"],
|
||||||
@ -534,16 +549,18 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
|
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||||
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
|
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
|
||||||
|
|
||||||
gl_entries.append(
|
# Do not book expense for transfer within same company transfer
|
||||||
self.get_gl_dict({
|
if not self.is_internal_transfer():
|
||||||
"account": item.expense_account,
|
gl_entries.append(
|
||||||
"against": self.supplier,
|
self.get_gl_dict({
|
||||||
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
"account": item.expense_account,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"against": self.supplier,
|
||||||
"cost_center": item.cost_center,
|
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||||
"project": item.project
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
}, account_currency, item=item)
|
"cost_center": item.cost_center,
|
||||||
)
|
"project": item.project
|
||||||
|
}, account_currency, item=item)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@ -708,7 +725,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item.item_tax_amount / self.conversion_rate)
|
item.item_tax_amount / self.conversion_rate)
|
||||||
}, item=item))
|
}, item=item))
|
||||||
else:
|
else:
|
||||||
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
|
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||||
|
asset_category=item.asset_category,company=self.company)
|
||||||
|
|
||||||
cwip_account_currency = get_account_currency(cwip_account)
|
cwip_account_currency = get_account_currency(cwip_account)
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
@ -819,7 +837,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
}, account_currency, item=tax)
|
}, account_currency, item=tax)
|
||||||
)
|
)
|
||||||
# accumulate valuation tax
|
# accumulate valuation tax
|
||||||
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
|
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
|
||||||
|
and not self.is_internal_transfer():
|
||||||
if self.auto_accounting_for_stock and not tax.cost_center:
|
if self.auto_accounting_for_stock and not tax.cost_center:
|
||||||
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
|
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
|
||||||
valuation_tax.setdefault(tax.name, 0)
|
valuation_tax.setdefault(tax.name, 0)
|
||||||
@ -863,8 +882,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": valuation_tax[tax.name],
|
"credit": valuation_tax[tax.name],
|
||||||
"remarks": self.remarks or "Accounting Entry for Stock"
|
"remarks": self.remarks or "Accounting Entry for Stock"
|
||||||
}, item=tax)
|
}, item=tax))
|
||||||
)
|
|
||||||
|
def make_internal_transfer_gl_entries(self, gl_entries):
|
||||||
|
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
|
||||||
|
account_currency = get_account_currency(self.unrealized_profit_loss_account)
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": self.unrealized_profit_loss_account,
|
||||||
|
"against": self.supplier,
|
||||||
|
"credit": flt(self.total_taxes_and_charges),
|
||||||
|
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
||||||
|
"cost_center": self.cost_center
|
||||||
|
}, account_currency, item=self))
|
||||||
|
|
||||||
def make_payment_gl_entries(self, gl_entries):
|
def make_payment_gl_entries(self, gl_entries):
|
||||||
# Make Cash GL Entries
|
# Make Cash GL Entries
|
||||||
@ -969,11 +999,15 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
|
if self.update_stock == 1:
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
self.update_project()
|
self.update_project()
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
|
|
||||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||||
|
|
||||||
def update_project(self):
|
def update_project(self):
|
||||||
project_list = []
|
project_list = []
|
||||||
@ -1024,7 +1058,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
|
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
|
||||||
|
|
||||||
for pr in set(updated_pr):
|
for pr in set(updated_pr):
|
||||||
frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified)
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
|
||||||
|
pr_doc = frappe.get_doc("Purchase Receipt", pr)
|
||||||
|
update_billing_percentage(pr_doc, update_modified=update_modified)
|
||||||
|
|
||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
self.due_date = None
|
self.due_date = None
|
||||||
@ -1080,7 +1116,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
elif self.docstatus == 1:
|
elif self.docstatus == 1:
|
||||||
if outstanding_amount > 0 and due_date < nowdate:
|
if self.is_internal_transfer():
|
||||||
|
self.status = 'Internal Transfer'
|
||||||
|
elif outstanding_amount > 0 and due_date < nowdate:
|
||||||
self.status = "Overdue"
|
self.status = "Overdue"
|
||||||
elif outstanding_amount > 0 and due_date >= nowdate:
|
elif outstanding_amount > 0 and due_date >= nowdate:
|
||||||
self.status = "Unpaid"
|
self.status = "Unpaid"
|
||||||
|
|||||||
@ -4,23 +4,25 @@
|
|||||||
// render
|
// render
|
||||||
frappe.listview_settings['Purchase Invoice'] = {
|
frappe.listview_settings['Purchase Invoice'] = {
|
||||||
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
||||||
"currency", "is_return", "release_date", "on_hold"],
|
"currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
|
if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
|
||||||
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
|
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
|
||||||
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
} else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||||
if(cint(doc.on_hold) && !doc.release_date) {
|
if(cint(doc.on_hold) && !doc.release_date) {
|
||||||
return [__("On Hold"), "darkgrey"];
|
return [__("On Hold"), "darkgrey"];
|
||||||
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
|
} else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
|
||||||
return [__("Temporarily on Hold"), "darkgrey"];
|
return [__("Temporarily on Hold"), "darkgrey"];
|
||||||
} else if(frappe.datetime.get_diff(doc.due_date) < 0) {
|
} else if (frappe.datetime.get_diff(doc.due_date) < 0) {
|
||||||
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
||||||
} else {
|
} else {
|
||||||
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
|
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
|
||||||
}
|
}
|
||||||
} else if(cint(doc.is_return)) {
|
} else if (cint(doc.is_return)) {
|
||||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||||
} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
|
} else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
|
||||||
|
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
|
||||||
|
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
|
||||||
return [__("Paid"), "green", "outstanding_amount,=,0"];
|
return [__("Paid"), "green", "outstanding_amount,=,0"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,7 @@ import frappe.model
|
|||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from frappe.utils import cint, flt, today, nowdate, add_days, getdate
|
from frappe.utils import cint, flt, today, nowdate, add_days, getdate
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt, get_taxes
|
||||||
test_records as pr_test_records, make_purchase_receipt, get_taxes
|
|
||||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
|
||||||
@ -33,13 +32,10 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_gl_entries_without_perpetual_inventory(self):
|
def test_gl_entries_without_perpetual_inventory(self):
|
||||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||||
wrapper = frappe.copy_doc(test_records[0])
|
pi = frappe.copy_doc(test_records[0])
|
||||||
set_perpetual_inventory(0, wrapper.company)
|
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(pi.company)))
|
||||||
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(wrapper.company)))
|
pi.insert()
|
||||||
wrapper.insert()
|
pi.submit()
|
||||||
wrapper.submit()
|
|
||||||
wrapper.load_from_db()
|
|
||||||
dl = wrapper
|
|
||||||
|
|
||||||
expected_gl_entries = {
|
expected_gl_entries = {
|
||||||
"_Test Payable - _TC": [0, 1512.0],
|
"_Test Payable - _TC": [0, 1512.0],
|
||||||
@ -54,12 +50,16 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
"Round Off - _TC": [0, 0.3]
|
"Round Off - _TC": [0, 0.3]
|
||||||
}
|
}
|
||||||
gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||||
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", dl.name, as_dict=1)
|
where voucher_type = 'Purchase Invoice' and voucher_no = %s""", pi.name, as_dict=1)
|
||||||
for d in gl_entries:
|
for d in gl_entries:
|
||||||
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
|
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
|
||||||
|
|
||||||
def test_gl_entries_with_perpetual_inventory(self):
|
def test_gl_entries_with_perpetual_inventory(self):
|
||||||
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10)
|
pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
|
||||||
|
warehouse= "Stores - TCP1", cost_center = "Main - TCP1",
|
||||||
|
expense_account ="_Test Account Cost for Goods Sold - TCP1",
|
||||||
|
get_taxes_and_charges=True, qty=10)
|
||||||
|
|
||||||
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
|
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
|
||||||
|
|
||||||
self.check_gle_for_pi(pi.name)
|
self.check_gle_for_pi(pi.name)
|
||||||
@ -198,8 +198,6 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
|
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", get_taxes_and_charges=True,)
|
||||||
|
|
||||||
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
|
|
||||||
|
|
||||||
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
|
pi = make_purchase_invoice(company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1", warehouse= "Stores - TCP1", cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", get_taxes_and_charges=True, qty=10,do_not_save= "True")
|
||||||
|
|
||||||
for d in pi.items:
|
for d in pi.items:
|
||||||
@ -247,17 +245,11 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
|
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
|
||||||
|
|
||||||
def test_gl_entries_with_aia_for_non_stock_items(self):
|
def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self):
|
||||||
pi = frappe.copy_doc(test_records[1])
|
pi = make_purchase_invoice(item_code = "_Test Non Stock Item",
|
||||||
set_perpetual_inventory(1, pi.company)
|
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||||
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
|
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||||
pi.get("items")[0].item_code = "_Test Non Stock Item"
|
|
||||||
pi.get("items")[0].expense_account = "_Test Account Cost for Goods Sold - _TC"
|
|
||||||
pi.get("taxes").pop(0)
|
|
||||||
pi.get("taxes").pop(1)
|
|
||||||
pi.insert()
|
|
||||||
pi.submit()
|
|
||||||
pi.load_from_db()
|
|
||||||
self.assertTrue(pi.status, "Unpaid")
|
self.assertTrue(pi.status, "Unpaid")
|
||||||
|
|
||||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||||
@ -265,17 +257,15 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
order by account asc""", pi.name, as_dict=1)
|
order by account asc""", pi.name, as_dict=1)
|
||||||
self.assertTrue(gl_entries)
|
self.assertTrue(gl_entries)
|
||||||
|
|
||||||
expected_values = sorted([
|
expected_values = [
|
||||||
["_Test Payable - _TC", 0, 620],
|
["_Test Account Cost for Goods Sold - TCP1", 250.0, 0],
|
||||||
["_Test Account Cost for Goods Sold - _TC", 500.0, 0],
|
["Creditors - TCP1", 0, 250]
|
||||||
["_Test Account VAT - _TC", 120.0, 0],
|
]
|
||||||
])
|
|
||||||
|
|
||||||
for i, gle in enumerate(gl_entries):
|
for i, gle in enumerate(gl_entries):
|
||||||
self.assertEqual(expected_values[i][0], gle.account)
|
self.assertEqual(expected_values[i][0], gle.account)
|
||||||
self.assertEqual(expected_values[i][1], gle.debit)
|
self.assertEqual(expected_values[i][1], gle.debit)
|
||||||
self.assertEqual(expected_values[i][2], gle.credit)
|
self.assertEqual(expected_values[i][2], gle.credit)
|
||||||
set_perpetual_inventory(0, pi.company)
|
|
||||||
|
|
||||||
def test_purchase_invoice_calculation(self):
|
def test_purchase_invoice_calculation(self):
|
||||||
pi = frappe.copy_doc(test_records[0])
|
pi = frappe.copy_doc(test_records[0])
|
||||||
@ -457,12 +447,13 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
pi.cancel()
|
pi.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
|
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
|
||||||
|
|
||||||
def test_return_purchase_invoice(self):
|
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
||||||
set_perpetual_inventory()
|
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||||
|
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||||
|
|
||||||
pi = make_purchase_invoice()
|
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
|
||||||
|
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2)
|
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||||
|
|
||||||
|
|
||||||
# check gl entries for return
|
# check gl entries for return
|
||||||
@ -473,19 +464,15 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
self.assertTrue(gl_entries)
|
self.assertTrue(gl_entries)
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"Creditors - _TC": [100.0, 0.0],
|
"Creditors - TCP1": [100.0, 0.0],
|
||||||
"Stock Received But Not Billed - _TC": [0.0, 100.0],
|
"Stock Received But Not Billed - TCP1": [0.0, 100.0],
|
||||||
}
|
}
|
||||||
|
|
||||||
for gle in gl_entries:
|
for gle in gl_entries:
|
||||||
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
||||||
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||||
|
|
||||||
set_perpetual_inventory(0)
|
|
||||||
|
|
||||||
def test_multi_currency_gle(self):
|
def test_multi_currency_gle(self):
|
||||||
set_perpetual_inventory(0)
|
|
||||||
|
|
||||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
|
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
|
||||||
currency="USD", conversion_rate=50)
|
currency="USD", conversion_rate=50)
|
||||||
|
|
||||||
@ -640,10 +627,9 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(len(pi.get("supplied_items")), 2)
|
self.assertEqual(len(pi.get("supplied_items")), 2)
|
||||||
|
|
||||||
rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
|
rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
|
||||||
self.assertEqual(pi.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
|
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
|
||||||
|
|
||||||
def test_rejected_serial_no(self):
|
def test_rejected_serial_no(self):
|
||||||
set_perpetual_inventory(0)
|
|
||||||
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
|
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
|
||||||
rejected_qty=1, rate=500, update_stock=1,
|
rejected_qty=1, rate=500, update_stock=1,
|
||||||
rejected_warehouse = "_Test Rejected Warehouse - _TC")
|
rejected_warehouse = "_Test Rejected Warehouse - _TC")
|
||||||
@ -998,11 +984,12 @@ def make_purchase_invoice(**args):
|
|||||||
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
|
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": 1.0,
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
"stock_uom": "_Test UOM",
|
"stock_uom": args.uom or "_Test UOM",
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"project": args.project,
|
"project": args.project,
|
||||||
"rejected_warehouse": args.rejected_warehouse or "",
|
"rejected_warehouse": args.rejected_warehouse or "",
|
||||||
"rejected_serial_no": args.rejected_serial_no or ""
|
"rejected_serial_no": args.rejected_serial_no or "",
|
||||||
|
"asset_location": args.location or ""
|
||||||
})
|
})
|
||||||
|
|
||||||
if args.get_taxes_and_charges:
|
if args.get_taxes_and_charges:
|
||||||
@ -1039,7 +1026,8 @@ def make_purchase_invoice_against_cost_center(**args):
|
|||||||
pi.is_return = args.is_return
|
pi.is_return = args.is_return
|
||||||
pi.credit_to = args.return_against or "Creditors - _TC"
|
pi.credit_to = args.return_against or "Creditors - _TC"
|
||||||
pi.is_subcontracted = args.is_subcontracted or "No"
|
pi.is_subcontracted = args.is_subcontracted or "No"
|
||||||
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
if args.supplier_warehouse:
|
||||||
|
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
|
||||||
pi.append("items", {
|
pi.append("items", {
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
|
|||||||
@ -210,7 +210,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-12 14:53:47.679439",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges",
|
"name": "Purchase Taxes and Charges",
|
||||||
|
|||||||
@ -78,7 +78,7 @@
|
|||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges Template",
|
"name": "Purchase Taxes and Charges Template",
|
||||||
"owner": "wasim@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
|||||||
@ -1,92 +1,38 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
"creation": "2016-07-27 17:24:24.956896",
|
||||||
"allow_rename": 0,
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"editable_grid": 1,
|
||||||
"creation": "2016-07-27 17:24:24.956896",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"docstatus": 0,
|
"company",
|
||||||
"doctype": "DocType",
|
"account"
|
||||||
"document_type": "",
|
],
|
||||||
"editable_grid": 1,
|
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"fieldname": "company",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Company",
|
||||||
"fieldname": "company",
|
"options": "Company"
|
||||||
"fieldtype": "Link",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Company",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
|
||||||
"bold": 0,
|
"fieldname": "account",
|
||||||
"collapsible": 0,
|
"fieldtype": "Link",
|
||||||
"columns": 0,
|
"in_list_view": 1,
|
||||||
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
|
"label": "Account",
|
||||||
"fieldname": "default_account",
|
"options": "Account"
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Default Account",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
"istable": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"idx": 0,
|
"modified": "2020-10-18 17:57:57.110257",
|
||||||
"image_view": 0,
|
"modified_by": "Administrator",
|
||||||
"in_create": 0,
|
"module": "Accounts",
|
||||||
|
"name": "Salary Component Account",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
"permissions": [],
|
||||||
"istable": 1,
|
"sort_field": "modified",
|
||||||
"max_attachments": 0,
|
"sort_order": "DESC"
|
||||||
"modified": "2016-09-02 07:49:06.567389",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Salary Component Account",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
{% include "erpnext/regional/india/taxes.js" %}
|
{% include "erpnext/regional/india/taxes.js" %}
|
||||||
|
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||||
|
|
||||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||||
|
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||||
|
|
||||||
frappe.ui.form.on("Sales Invoice", {
|
frappe.ui.form.on("Sales Invoice", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
|||||||
@ -199,7 +199,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, __("Get items from"));
|
}, __("Get Items From"));
|
||||||
},
|
},
|
||||||
|
|
||||||
quotation_btn: function() {
|
quotation_btn: function() {
|
||||||
@ -223,7 +223,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
company: me.frm.doc.company
|
company: me.frm.doc.company
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, __("Get items from"));
|
}, __("Get Items From"));
|
||||||
},
|
},
|
||||||
|
|
||||||
delivery_note_btn: function() {
|
delivery_note_btn: function() {
|
||||||
@ -251,7 +251,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, __("Get items from"));
|
}, __("Get Items From"));
|
||||||
},
|
},
|
||||||
|
|
||||||
tc_name: function() {
|
tc_name: function() {
|
||||||
@ -580,6 +580,16 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("unrealized_profit_loss_account", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
is_group: 0,
|
||||||
|
root_type: "Liability",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Delivery Note': 'Delivery',
|
'Delivery Note': 'Delivery',
|
||||||
'Sales Invoice': 'Sales Return',
|
'Sales Invoice': 'Sales Return',
|
||||||
@ -812,10 +822,10 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
|
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
|
||||||
frm.add_custom_button(__('Healthcare Services'), function() {
|
frm.add_custom_button(__('Healthcare Services'), function() {
|
||||||
get_healthcare_services_to_invoice(frm);
|
get_healthcare_services_to_invoice(frm);
|
||||||
},"Get items from");
|
},"Get Items From");
|
||||||
frm.add_custom_button(__('Prescriptions'), function() {
|
frm.add_custom_button(__('Prescriptions'), function() {
|
||||||
get_drugs_to_invoice(frm);
|
get_drugs_to_invoice(frm);
|
||||||
},"Get items from");
|
},"Get Items From");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1080,7 +1090,7 @@ var get_drugs_to_invoice = function(frm) {
|
|||||||
description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.',
|
description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.',
|
||||||
get_query: function(doc) {
|
get_query: function(doc) {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
patient: dialog.get_value("patient"),
|
patient: dialog.get_value("patient"),
|
||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
docstatus: 1
|
docstatus: 1
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"is_return",
|
"is_return",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
|
"company_tax_id",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"posting_time",
|
"posting_time",
|
||||||
"set_posting_time",
|
"set_posting_time",
|
||||||
@ -156,6 +157,7 @@
|
|||||||
"more_information",
|
"more_information",
|
||||||
"inter_company_invoice_reference",
|
"inter_company_invoice_reference",
|
||||||
"is_internal_customer",
|
"is_internal_customer",
|
||||||
|
"represents_company",
|
||||||
"customer_group",
|
"customer_group",
|
||||||
"campaign",
|
"campaign",
|
||||||
"is_discounted",
|
"is_discounted",
|
||||||
@ -169,6 +171,7 @@
|
|||||||
"c_form_applicable",
|
"c_form_applicable",
|
||||||
"c_form_no",
|
"c_form_no",
|
||||||
"column_break8",
|
"column_break8",
|
||||||
|
"unrealized_profit_loss_account",
|
||||||
"remarks",
|
"remarks",
|
||||||
"sales_team_section_break",
|
"sales_team_section_break",
|
||||||
"sales_partner",
|
"sales_partner",
|
||||||
@ -1653,7 +1656,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
|
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1825,7 +1828,7 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Sales Team1",
|
"label": "Sales Contributions and Incentives",
|
||||||
"oldfieldname": "sales_team",
|
"oldfieldname": "sales_team",
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"options": "Sales Team",
|
"options": "Sales Team",
|
||||||
@ -1926,6 +1929,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:(doc.is_pos && doc.is_consolidated)",
|
||||||
"fieldname": "is_consolidated",
|
"fieldname": "is_consolidated",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Consolidated",
|
"label": "Is Consolidated",
|
||||||
@ -1940,13 +1944,38 @@
|
|||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Is Internal Customer",
|
"label": "Is Internal Customer",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "company.tax_id",
|
||||||
|
"fieldname": "company_tax_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Company Tax ID",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.is_internal_customer",
|
||||||
|
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||||
|
"fieldname": "unrealized_profit_loss_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Unrealized Profit / Loss Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.is_internal_customer",
|
||||||
|
"description": "Company which internal customer represents",
|
||||||
|
"fetch_from": "customer.represents_company",
|
||||||
|
"fieldname": "represents_company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Represents Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 181,
|
"idx": 181,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-27 01:56:28.532140",
|
"modified": "2020-12-11 12:48:31.769958",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
|
||||||
from frappe import _, msgprint, throw
|
from frappe import _, msgprint, throw
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
@ -179,6 +179,9 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
# this sequence because outstanding may get -ve
|
# this sequence because outstanding may get -ve
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
|
if self.update_stock == 1:
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
||||||
@ -229,9 +232,9 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
|
super(SalesInvoice, self).before_cancel()
|
||||||
self.update_time_sheet(None)
|
self.update_time_sheet(None)
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
super(SalesInvoice, self).on_cancel()
|
super(SalesInvoice, self).on_cancel()
|
||||||
|
|
||||||
@ -258,6 +261,10 @@ class SalesInvoice(SellingController):
|
|||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
|
if self.update_stock == 1:
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
|
|
||||||
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
|
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
|
||||||
@ -279,7 +286,7 @@ class SalesInvoice(SellingController):
|
|||||||
if "Healthcare" in active_domains:
|
if "Healthcare" in active_domains:
|
||||||
manage_invoice_submit_cancel(self, "on_cancel")
|
manage_invoice_submit_cancel(self, "on_cancel")
|
||||||
|
|
||||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||||
|
|
||||||
def update_status_updater_args(self):
|
def update_status_updater_args(self):
|
||||||
if cint(self.update_stock):
|
if cint(self.update_stock):
|
||||||
@ -405,6 +412,8 @@ class SalesInvoice(SellingController):
|
|||||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||||
if not self.pos_profile:
|
if not self.pos_profile:
|
||||||
pos_profile = get_pos_profile(self.company) or {}
|
pos_profile = get_pos_profile(self.company) or {}
|
||||||
|
if not pos_profile:
|
||||||
|
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||||
self.pos_profile = pos_profile.get('name')
|
self.pos_profile = pos_profile.get('name')
|
||||||
|
|
||||||
pos = {}
|
pos = {}
|
||||||
@ -428,7 +437,7 @@ class SalesInvoice(SellingController):
|
|||||||
if pos.get('account_for_change_amount'):
|
if pos.get('account_for_change_amount'):
|
||||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||||
|
|
||||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||||
@ -472,6 +481,11 @@ class SalesInvoice(SellingController):
|
|||||||
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
|
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
|
||||||
|
|
||||||
def validate_debit_to_acc(self):
|
def validate_debit_to_acc(self):
|
||||||
|
if not self.debit_to:
|
||||||
|
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||||
|
if not self.debit_to:
|
||||||
|
self.raise_missing_debit_credit_account_error("Customer", self.customer)
|
||||||
|
|
||||||
account = frappe.get_cached_value("Account", self.debit_to,
|
account = frappe.get_cached_value("Account", self.debit_to,
|
||||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||||
|
|
||||||
@ -479,14 +493,14 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||||
|
|
||||||
if account.report_type != "Balance Sheet":
|
if account.report_type != "Balance Sheet":
|
||||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
frappe.throw(msg, title=_("Invalid Account"))
|
||||||
|
|
||||||
if self.customer and account.account_type != "Receivable":
|
if self.customer and account.account_type != "Receivable":
|
||||||
frappe.throw(_("Please ensure {} account is a Receivable account. \
|
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||||
Change the account type to Receivable or select a different account.")
|
msg += _("Change the account type to Receivable or select a different account.")
|
||||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
frappe.throw(msg, title=_("Invalid Account"))
|
||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
@ -535,7 +549,12 @@ class SalesInvoice(SellingController):
|
|||||||
self.against_income_account = ','.join(against_acc)
|
self.against_income_account = ','.join(against_acc)
|
||||||
|
|
||||||
def add_remarks(self):
|
def add_remarks(self):
|
||||||
if not self.remarks: self.remarks = 'No Remarks'
|
if not self.remarks:
|
||||||
|
if self.po_no and self.po_date:
|
||||||
|
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
|
||||||
|
formatdate(self.po_date))
|
||||||
|
else:
|
||||||
|
self.remarks = _("No Remarks")
|
||||||
|
|
||||||
def validate_auto_set_posting_time(self):
|
def validate_auto_set_posting_time(self):
|
||||||
# Don't auto set the posting date and time if invoice is amended
|
# Don't auto set the posting date and time if invoice is amended
|
||||||
@ -572,7 +591,8 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def validate_pos(self):
|
def validate_pos(self):
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \
|
invoice_total = self.rounded_total or self.grand_total
|
||||||
|
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \
|
||||||
1.0/(10.0**(self.precision("grand_total") + 1.0)):
|
1.0/(10.0**(self.precision("grand_total") + 1.0)):
|
||||||
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
||||||
|
|
||||||
@ -714,22 +734,20 @@ class SalesInvoice(SellingController):
|
|||||||
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
|
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
|
||||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||||
|
|
||||||
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
|
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
|
||||||
|
|
||||||
# if POS and amount is written off, updating outstanding amt after posting all gl entries
|
# if POS and amount is written off, updating outstanding amt after posting all gl entries
|
||||||
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
|
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
|
||||||
cint(self.redeem_loyalty_points)) else "Yes"
|
cint(self.redeem_loyalty_points)) else "Yes"
|
||||||
|
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False)
|
make_gl_entries(gl_entries, update_outstanding=update_outstanding, merge_entries=False, from_repost=from_repost)
|
||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||||
|
|
||||||
@ -750,6 +768,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_customer_gl_entry(gl_entries)
|
self.make_customer_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
|
|
||||||
@ -769,7 +788,7 @@ class SalesInvoice(SellingController):
|
|||||||
# Checked both rounding_adjustment and rounded_total
|
# Checked both rounding_adjustment and rounded_total
|
||||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
||||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||||
if grand_total:
|
if grand_total and not self.is_internal_transfer():
|
||||||
# Didnot use base_grand_total to book rounding loss gle
|
# Didnot use base_grand_total to book rounding loss gle
|
||||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||||
self.precision("grand_total"))
|
self.precision("grand_total"))
|
||||||
@ -808,6 +827,18 @@ class SalesInvoice(SellingController):
|
|||||||
}, account_currency, item=tax)
|
}, account_currency, item=tax)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def make_internal_transfer_gl_entries(self, gl_entries):
|
||||||
|
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
|
||||||
|
account_currency = get_account_currency(self.unrealized_profit_loss_account)
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": self.unrealized_profit_loss_account,
|
||||||
|
"against": self.customer,
|
||||||
|
"debit": flt(self.total_taxes_and_charges),
|
||||||
|
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
||||||
|
"cost_center": self.cost_center
|
||||||
|
}, account_currency, item=self))
|
||||||
|
|
||||||
def make_item_gl_entries(self, gl_entries):
|
def make_item_gl_entries(self, gl_entries):
|
||||||
# income account gl entries
|
# income account gl entries
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
@ -830,22 +861,24 @@ class SalesInvoice(SellingController):
|
|||||||
asset.db_set("disposal_date", self.posting_date)
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||||
else:
|
else:
|
||||||
income_account = (item.income_account
|
# Do not book income for transfer within same company
|
||||||
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
|
if not self.is_internal_transfer():
|
||||||
|
income_account = (item.income_account
|
||||||
|
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
|
||||||
|
|
||||||
account_currency = get_account_currency(income_account)
|
account_currency = get_account_currency(income_account)
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": income_account,
|
"account": income_account,
|
||||||
"against": self.customer,
|
"against": self.customer,
|
||||||
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||||
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
|
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||||
if account_currency==self.company_currency
|
if account_currency==self.company_currency
|
||||||
else flt(item.net_amount, item.precision("net_amount"))),
|
else flt(item.net_amount, item.precision("net_amount"))),
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project
|
"project": item.project or self.project
|
||||||
}, account_currency, item=item)
|
}, account_currency, item=item)
|
||||||
)
|
)
|
||||||
|
|
||||||
# expense account gl entries
|
# expense account gl entries
|
||||||
if cint(self.update_stock) and \
|
if cint(self.update_stock) and \
|
||||||
@ -1140,8 +1173,10 @@ class SalesInvoice(SellingController):
|
|||||||
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
||||||
if against_lp_entry:
|
if against_lp_entry:
|
||||||
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
||||||
frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed.
|
frappe.throw(
|
||||||
First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list))
|
_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}''')
|
||||||
|
.format(self.doctype, self.doctype, invoice_list)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
||||||
# Set loyalty program
|
# Set loyalty program
|
||||||
@ -1255,7 +1290,9 @@ class SalesInvoice(SellingController):
|
|||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
elif self.docstatus == 1:
|
elif self.docstatus == 1:
|
||||||
if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
|
if self.is_internal_transfer():
|
||||||
|
self.status = 'Internal Transfer'
|
||||||
|
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
|
||||||
self.status = "Overdue and Discounted"
|
self.status = "Overdue and Discounted"
|
||||||
elif outstanding_amount > 0 and due_date < nowdate:
|
elif outstanding_amount > 0 and due_date < nowdate:
|
||||||
self.status = "Overdue"
|
self.status = "Overdue"
|
||||||
@ -1372,7 +1409,7 @@ def get_bank_cash_account(mode_of_payment, company):
|
|||||||
{"parent": mode_of_payment, "company": company}, "default_account")
|
{"parent": mode_of_payment, "company": company}, "default_account")
|
||||||
if not account:
|
if not account:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||||
.format(mode_of_payment))
|
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||||
return {
|
return {
|
||||||
"account": account
|
"account": account
|
||||||
}
|
}
|
||||||
@ -1398,6 +1435,7 @@ def make_delivery_note(source_name, target_doc=None):
|
|||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.ignore_pricing_rule = 1
|
target.ignore_pricing_rule = 1
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
|
target.run_method("set_po_nos")
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|
||||||
def update_item(source_doc, target_doc, source_parent):
|
def update_item(source_doc, target_doc, source_parent):
|
||||||
@ -1519,9 +1557,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
if doctype in ["Sales Invoice", "Sales Order"]:
|
if doctype in ["Sales Invoice", "Sales Order"]:
|
||||||
source_doc = frappe.get_doc(doctype, source_name)
|
source_doc = frappe.get_doc(doctype, source_name)
|
||||||
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
||||||
|
source_document_warehouse_field = 'target_warehouse'
|
||||||
|
target_document_warehouse_field = 'from_warehouse'
|
||||||
else:
|
else:
|
||||||
source_doc = frappe.get_doc(doctype, source_name)
|
source_doc = frappe.get_doc(doctype, source_name)
|
||||||
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
|
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
|
||||||
|
source_document_warehouse_field = 'from_warehouse'
|
||||||
|
target_document_warehouse_field = 'target_warehouse'
|
||||||
|
|
||||||
validate_inter_company_transaction(source_doc, doctype)
|
validate_inter_company_transaction(source_doc, doctype)
|
||||||
details = get_inter_company_details(source_doc, doctype)
|
details = get_inter_company_details(source_doc, doctype)
|
||||||
@ -1548,6 +1590,26 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
if currency:
|
if currency:
|
||||||
target_doc.currency = currency
|
target_doc.currency = currency
|
||||||
|
|
||||||
|
item_field_map = {
|
||||||
|
"doctype": target_doctype + " Item",
|
||||||
|
"field_no_map": [
|
||||||
|
"income_account",
|
||||||
|
"expense_account",
|
||||||
|
"cost_center",
|
||||||
|
"warehouse"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if source_doc.get('update_stock'):
|
||||||
|
item_field_map.update({
|
||||||
|
'field_map': {
|
||||||
|
source_document_warehouse_field: target_document_warehouse_field,
|
||||||
|
'batch_no': 'batch_no',
|
||||||
|
'serial_no': 'serial_no'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
doclist = get_mapped_doc(doctype, source_name, {
|
doclist = get_mapped_doc(doctype, source_name, {
|
||||||
doctype: {
|
doctype: {
|
||||||
"doctype": target_doctype,
|
"doctype": target_doctype,
|
||||||
@ -1556,15 +1618,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
"taxes_and_charges"
|
"taxes_and_charges"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
doctype +" Item": {
|
doctype +" Item": item_field_map
|
||||||
"doctype": target_doctype + " Item",
|
|
||||||
"field_no_map": [
|
|
||||||
"income_account",
|
|
||||||
"expense_account",
|
|
||||||
"cost_center",
|
|
||||||
"warehouse"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
@ -1612,18 +1666,24 @@ def update_multi_mode_option(doc, pos_profile):
|
|||||||
payment.type = payment_mode.type
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
doc.set('payments', [])
|
doc.set('payments', [])
|
||||||
if not pos_profile or not pos_profile.get('payments'):
|
invalid_modes = []
|
||||||
for payment_mode in get_all_mode_of_payments(doc):
|
|
||||||
append_payment(payment_mode)
|
|
||||||
return
|
|
||||||
|
|
||||||
for pos_payment_method in pos_profile.get('payments'):
|
for pos_payment_method in pos_profile.get('payments'):
|
||||||
pos_payment_method = pos_payment_method.as_dict()
|
pos_payment_method = pos_payment_method.as_dict()
|
||||||
|
|
||||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||||
if payment_mode:
|
if not payment_mode:
|
||||||
payment_mode[0].default = pos_payment_method.default
|
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
|
||||||
append_payment(payment_mode[0])
|
continue
|
||||||
|
|
||||||
|
payment_mode[0].default = pos_payment_method.default
|
||||||
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def get_all_mode_of_payments(doc):
|
def get_all_mode_of_payments(doc):
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
|
|||||||
@ -13,8 +13,7 @@ def get_data():
|
|||||||
'Auto Repeat': 'reference_document',
|
'Auto Repeat': 'reference_document',
|
||||||
},
|
},
|
||||||
'internal_links': {
|
'internal_links': {
|
||||||
'Sales Order': ['items', 'sales_order'],
|
'Sales Order': ['items', 'sales_order']
|
||||||
'Delivery Note': ['items', 'delivery_note']
|
|
||||||
},
|
},
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = {
|
|||||||
"Credit Note Issued": "darkgrey",
|
"Credit Note Issued": "darkgrey",
|
||||||
"Unpaid and Discounted": "orange",
|
"Unpaid and Discounted": "orange",
|
||||||
"Overdue and Discounted": "red",
|
"Overdue and Discounted": "red",
|
||||||
"Overdue": "red"
|
"Overdue": "red",
|
||||||
|
"Internal Transfer": "darkgrey"
|
||||||
};
|
};
|
||||||
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
|
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
|
||||||
},
|
},
|
||||||
|
|||||||
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