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
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
@ -15,6 +15,14 @@
|
||||
"tab",
|
||||
{ "SwitchCase": 1 }
|
||||
],
|
||||
"brace-style": [
|
||||
"error",
|
||||
"1tbs"
|
||||
],
|
||||
"space-unary-ops": [
|
||||
"error",
|
||||
{ "words": true }
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
@ -44,12 +52,10 @@
|
||||
"no-control-regex": [
|
||||
"off"
|
||||
],
|
||||
"spaced-comment": [
|
||||
"warn"
|
||||
],
|
||||
"no-trailing-spaces": [
|
||||
"warn"
|
||||
]
|
||||
"space-before-blocks": "warn",
|
||||
"keyword-spacing": "warn",
|
||||
"comma-spacing": "warn",
|
||||
"key-spacing": "warn"
|
||||
},
|
||||
"root": true,
|
||||
"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_password": "travis",
|
||||
"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>
|
||||
|
||||
[![Build Status](https://travis-ci.com/frappe/erpnext.svg)](https://travis-ci.com/frappe/erpnext)
|
||||
[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext)
|
||||
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?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:
|
||||
|
||||
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. [Sales](https://erpnext.com/open-source-sales-purchase)
|
||||
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
|
||||
|
@ -5,6 +5,7 @@
|
||||
"_comments": null,
|
||||
"_liked_by": null,
|
||||
"_user_tags": null,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -17,17 +18,24 @@
|
||||
"docstatus": 0,
|
||||
"dt": "Address",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "tax_category",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"idx": 14,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"idx": 15,
|
||||
"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": "fax",
|
||||
"label": "Tax Category",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2018-12-28 22:29:21.828090",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Address-tax_category",
|
||||
@ -43,6 +51,66 @@
|
||||
"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,
|
||||
|
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.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 frappe.utils.dashboard import cache_source, get_from_date_from_timespan
|
||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
|
||||
|
||||
from frappe.utils.dashboard import cache_source
|
||||
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -23,7 +23,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"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,
|
||||
@ -43,7 +43,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"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,
|
||||
@ -53,7 +53,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"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,
|
||||
@ -79,6 +79,11 @@
|
||||
"hidden": 0,
|
||||
"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]"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@ -98,7 +103,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Accounting",
|
||||
"modified": "2020-09-03 10:37:07.865801",
|
||||
"modified": "2020-11-11 18:35:11.542909",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
@ -147,11 +152,6 @@
|
||||
"link_to": "Trial Balance",
|
||||
"type": "Report"
|
||||
},
|
||||
{
|
||||
"label": "Point of Sale",
|
||||
"link_to": "point-of-sale",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"label": "Dashboard",
|
||||
"link_to": "Accounts",
|
||||
|
@ -101,7 +101,7 @@ class Account(NestedSet):
|
||||
return
|
||||
if not frappe.db.get_value("Account",
|
||||
{'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:
|
||||
descendants = get_descendants_of('Company', self.company)
|
||||
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):
|
||||
parent_acc_name_map[d["company"]] = d["name"]
|
||||
|
||||
if not parent_acc_name_map: return
|
||||
|
||||
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
||||
|
||||
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):
|
||||
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):
|
||||
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))
|
||||
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
||||
.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 = {
|
||||
"account_name": self.account_name,
|
||||
@ -289,10 +301,31 @@ def validate_account_number(name, account_number, company):
|
||||
.format(account_number, account_with_same_number))
|
||||
|
||||
@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)
|
||||
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)
|
||||
if account_number:
|
||||
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_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)
|
||||
if name != new_name:
|
||||
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
|
||||
ancestors = get_ancestors_of('Company', company, "lft asc")
|
||||
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"] = {
|
||||
breadcrumb: "Accounts",
|
||||
title: __("Chart Of Accounts"),
|
||||
title: __("Chart of Accounts"),
|
||||
get_tree_root: false,
|
||||
filters: [
|
||||
{
|
||||
@ -97,7 +97,7 @@ frappe.treeview_settings["Account"] = {
|
||||
treeview.page.add_inner_button(__("Journal Entry"), function() {
|
||||
frappe.new_doc('Journal Entry', {company: get_company()});
|
||||
}, __('Create'));
|
||||
treeview.page.add_inner_button(__("New Company"), function() {
|
||||
treeview.page.add_inner_button(__("Company"), function() {
|
||||
frappe.new_doc('Company');
|
||||
}, __('Create'));
|
||||
|
||||
|
@ -910,98 +910,8 @@
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"Passiva": {
|
||||
"Passiva - Verbindlichkeiten": {
|
||||
"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": {
|
||||
"is_group": 1,
|
||||
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
||||
@ -1618,6 +1528,143 @@
|
||||
},
|
||||
"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": {
|
||||
"root_type": "Income",
|
||||
"is_group": 1,
|
||||
|
@ -245,6 +245,9 @@ def get():
|
||||
"account_number": "2200"
|
||||
},
|
||||
_("Duties and Taxes"): {
|
||||
_("TDS Payable"): {
|
||||
"account_number": "2310"
|
||||
},
|
||||
"account_type": "Tax",
|
||||
"is_group": 1,
|
||||
"account_number": "2300"
|
||||
|
@ -5,8 +5,7 @@ from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
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 merge_account
|
||||
from erpnext.accounts.doctype.account.account import update_account_number, merge_account
|
||||
|
||||
class TestAccount(unittest.TestCase):
|
||||
def test_rename_account(self):
|
||||
@ -99,7 +98,8 @@ class TestAccount(unittest.TestCase):
|
||||
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
|
||||
|
||||
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.account_name = "Test Sync Account"
|
||||
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_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
|
||||
|
||||
accounts = [
|
||||
|
@ -7,7 +7,7 @@ frappe.ui.form.on('Accounting Dimension', {
|
||||
frm.set_query('document_type', () => {
|
||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||
invalid_doctypes.push('Accounting Dimension', 'Project',
|
||||
'Cost Center', 'Accounting Dimension Detail');
|
||||
'Cost Center', 'Accounting Dimension Detail', 'Company');
|
||||
|
||||
return {
|
||||
filters: {
|
||||
|
@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
||||
|
||||
def validate(self):
|
||||
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)
|
||||
frappe.throw(msg)
|
||||
|
@ -40,7 +40,7 @@
|
||||
"fields": [
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
@ -48,23 +48,23 @@
|
||||
"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",
|
||||
"fieldtype": "Date",
|
||||
"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",
|
||||
"fieldname": "frozen_accounts_modifier",
|
||||
"fieldtype": "Link",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Select",
|
||||
"label": "Determine Address Tax Category From",
|
||||
@ -75,7 +75,7 @@
|
||||
"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",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -104,7 +104,7 @@
|
||||
"default": "1",
|
||||
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unlink Advance Payment on Cancelation of Order"
|
||||
"label": "Unlink Advance Payment on Cancellation of Order"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@ -127,7 +127,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "show_inclusive_tax_in_print",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Inclusive Tax In Print"
|
||||
"label": "Show Inclusive Tax in Print"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
@ -165,7 +165,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Custom Cash Flow Format"
|
||||
@ -177,7 +177,7 @@
|
||||
"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",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Over Billing Allowance (%)"
|
||||
@ -199,7 +199,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Check",
|
||||
"label": "Book Deferred Entries Via Journal Entry"
|
||||
@ -214,7 +214,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Select",
|
||||
"label": "Book Deferred Entries Based On",
|
||||
@ -223,9 +223,10 @@
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 20:13:26.043092",
|
||||
"modified": "2020-10-13 11:32:52.268826",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
|
||||
|
||||
def populate_payment_entries(self):
|
||||
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):
|
||||
frappe.throw(_("Transactions already retreived from the statement"))
|
||||
|
||||
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
|
||||
if self.bank_settings:
|
||||
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
|
||||
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:
|
||||
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"]))
|
||||
@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
|
||||
transaction[header] = ""
|
||||
return transaction
|
||||
|
||||
def get_transaction_entries(filename, headers):
|
||||
def get_transaction_entries(file_url, headers):
|
||||
header_index = {}
|
||||
rows, transactions = [], []
|
||||
|
||||
if (filename.lower().endswith("xlsx")):
|
||||
if (file_url.lower().endswith("xlsx")):
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
||||
rows = read_xlsx_file_from_attached_file(file_id=filename)
|
||||
elif (filename.lower().endswith("csv")):
|
||||
rows = read_xlsx_file_from_attached_file(file_url=file_url)
|
||||
elif (file_url.lower().endswith("csv")):
|
||||
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()
|
||||
with open(filepath,'rb') as csvfile:
|
||||
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)
|
||||
else:
|
||||
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.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.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
test_dependencies = ["Item", "Cost Center"]
|
||||
|
||||
class TestBankTransaction(unittest.TestCase):
|
||||
def setUp(self):
|
||||
make_pos_profile()
|
||||
add_transactions()
|
||||
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`""")
|
||||
|
||||
# Delete POS Profile
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
frappe.flags.test_bank_transactions_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.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
|
||||
|
||||
def add_transactions():
|
||||
if frappe.flags.test_bank_transactions_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
"doctype": "Bank",
|
||||
"bank_name":"Citi Bank",
|
||||
"bank_name":bank_name,
|
||||
}).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
@ -108,12 +109,19 @@ def add_transactions():
|
||||
frappe.get_doc({
|
||||
"doctype": "Bank Account",
|
||||
"account_name":"Checking Account",
|
||||
"bank": "Citi Bank",
|
||||
"account": "_Test Bank - _TC"
|
||||
"bank": bank_name,
|
||||
"account": account_name
|
||||
}).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
def add_transactions():
|
||||
if frappe.flags.test_bank_transactions_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
create_bank_account()
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Bank Transaction",
|
||||
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||
|
@ -1,785 +1,204 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-05-16 11:42:29.632528",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"budget_against",
|
||||
"company",
|
||||
"cost_center",
|
||||
"project",
|
||||
"fiscal_year",
|
||||
"column_break_3",
|
||||
"monthly_distribution",
|
||||
"amended_from",
|
||||
"section_break_6",
|
||||
"applicable_on_material_request",
|
||||
"action_if_annual_budget_exceeded_on_mr",
|
||||
"action_if_accumulated_monthly_budget_exceeded_on_mr",
|
||||
"column_break_13",
|
||||
"applicable_on_purchase_order",
|
||||
"action_if_annual_budget_exceeded_on_po",
|
||||
"action_if_accumulated_monthly_budget_exceeded_on_po",
|
||||
"section_break_16",
|
||||
"applicable_on_booking_actual_expenses",
|
||||
"action_if_annual_budget_exceeded",
|
||||
"action_if_accumulated_monthly_budget_exceeded",
|
||||
"section_break_21",
|
||||
"accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Cost Center",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "budget_against",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Budget Against",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nCost Center\nProject",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 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,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.budget_against == 'Cost Center'",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Cost Center",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cost Center",
|
||||
"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
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.budget_against == 'Project'",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Project",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Project",
|
||||
"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
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Fiscal Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Fiscal Year",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list([\"Stop\", \"Warn\"], doc.action_if_accumulated_monthly_budget_exceeded_on_po || doc.action_if_accumulated_monthly_budget_exceeded_on_mr || doc.action_if_accumulated_monthly_budget_exceeded_on_actual)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "monthly_distribution",
|
||||
"fieldtype": "Link",
|
||||
"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": "Monthly Distribution",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Monthly Distribution",
|
||||
"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
|
||||
"options": "Monthly Distribution"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"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": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Budget",
|
||||
"permlevel": 0,
|
||||
"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
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"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": "Control Action",
|
||||
"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
|
||||
"label": "Control Action"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"default": "0",
|
||||
"fieldname": "applicable_on_material_request",
|
||||
"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": "Applicable on Material Request",
|
||||
"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
|
||||
"label": "Applicable on Material Request"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Stop",
|
||||
"depends_on": "eval:doc.applicable_on_material_request == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "action_if_annual_budget_exceeded_on_mr",
|
||||
"fieldtype": "Select",
|
||||
"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": "Action if Annual Budget Exceeded on MR",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStop\nWarn\nIgnore",
|
||||
"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
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Warn",
|
||||
"depends_on": "eval:doc.applicable_on_material_request == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "action_if_accumulated_monthly_budget_exceeded_on_mr",
|
||||
"fieldtype": "Select",
|
||||
"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": "Action if Accumulated Monthly Budget Exceeded on MR",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStop\nWarn\nIgnore",
|
||||
"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
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_13",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"default": "0",
|
||||
"fieldname": "applicable_on_purchase_order",
|
||||
"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": "Applicable on Purchase Order",
|
||||
"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
|
||||
"label": "Applicable on Purchase Order"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Stop",
|
||||
"depends_on": "eval:doc.applicable_on_purchase_order == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "action_if_annual_budget_exceeded_on_po",
|
||||
"fieldtype": "Select",
|
||||
"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": "Action if Annual Budget Exceeded on PO",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStop\nWarn\nIgnore",
|
||||
"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
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Warn",
|
||||
"depends_on": "eval:doc.applicable_on_purchase_order == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "action_if_accumulated_monthly_budget_exceeded_on_po",
|
||||
"fieldtype": "Select",
|
||||
"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": "Action if Accumulated Monthly Budget Exceeded on PO",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStop\nWarn\nIgnore",
|
||||
"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
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_16",
|
||||
"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,
|
||||
"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
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"default": "0",
|
||||
"fieldname": "applicable_on_booking_actual_expenses",
|
||||
"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": "Applicable on booking actual expenses",
|
||||
"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
|
||||
"label": "Applicable on booking actual expenses"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Stop",
|
||||
"depends_on": "eval:doc.applicable_on_booking_actual_expenses == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "action_if_annual_budget_exceeded",
|
||||
"fieldtype": "Select",
|
||||
"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": "Action if Annual Budget Exceeded on Actual",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStop\nWarn\nIgnore",
|
||||
"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
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Warn",
|
||||
"depends_on": "eval:doc.applicable_on_booking_actual_expenses == 1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "action_if_accumulated_monthly_budget_exceeded",
|
||||
"fieldtype": "Select",
|
||||
"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": "Action if Accumulated Monthly Budget Exceeded on Actual",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nStop\nWarn\nIgnore",
|
||||
"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
|
||||
"options": "\nStop\nWarn\nIgnore"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_21",
|
||||
"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,
|
||||
"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
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "accounts",
|
||||
"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": "Budget Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Budget 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
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-22 12:06:02.323099",
|
||||
"links": [],
|
||||
"modified": "2020-10-06 15:13:54.055854",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Budget",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -789,26 +208,17 @@
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -158,8 +158,11 @@ class TestBudget(unittest.TestCase):
|
||||
set_total_expense_zero(nowdate(), "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",
|
||||
"_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")
|
||||
|
||||
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",
|
||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
||||
|
||||
|
@ -29,7 +29,7 @@ class CashierClosing(Document):
|
||||
for i in self.payments:
|
||||
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):
|
||||
if self.from_time >= self.time:
|
||||
|
@ -94,8 +94,7 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
callback: function(r) {
|
||||
if(r.message===false) {
|
||||
frm.set_value("company", "");
|
||||
frappe.throw(__(`Transactions against the company already exist!
|
||||
Chart Of accounts can be imported for company with no transactions`));
|
||||
frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."));
|
||||
} else {
|
||||
frm.trigger("refresh");
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ def build_response_as_excel(writer):
|
||||
reader = csv.reader(f)
|
||||
|
||||
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()
|
||||
os.remove(filename)
|
||||
|
@ -28,22 +28,22 @@ def test_create_test_data():
|
||||
"item_group": "_Test Item Group",
|
||||
"item_name": "_Test Tesla Car",
|
||||
"apply_warehouse_wise_reorder_level": 0,
|
||||
"warehouse":"Stores - TCP1",
|
||||
"warehouse":"Stores - _TC",
|
||||
"gst_hsn_code": "999800",
|
||||
"valuation_rate": 5000,
|
||||
"standard_rate":5000,
|
||||
"item_defaults": [{
|
||||
"company": "_Test Company with perpetual inventory",
|
||||
"default_warehouse": "Stores - TCP1",
|
||||
"company": "_Test Company",
|
||||
"default_warehouse": "Stores - _TC",
|
||||
"default_price_list":"_Test Price List",
|
||||
"expense_account": "Cost of Goods Sold - TCP1",
|
||||
"buying_cost_center": "Main - TCP1",
|
||||
"selling_cost_center": "Main - TCP1",
|
||||
"income_account": "Sales - TCP1"
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"buying_cost_center": "Main - _TC",
|
||||
"selling_cost_center": "Main - _TC",
|
||||
"income_account": "Sales - _TC"
|
||||
}],
|
||||
"show_in_website": 1,
|
||||
"route":"-test-tesla-car",
|
||||
"website_warehouse": "Stores - TCP1"
|
||||
"website_warehouse": "Stores - _TC"
|
||||
})
|
||||
item.insert()
|
||||
# create test item price
|
||||
@ -65,12 +65,12 @@ def test_create_test_data():
|
||||
"items": [{
|
||||
"item_code": "_Test Tesla Car"
|
||||
}],
|
||||
"warehouse":"Stores - TCP1",
|
||||
"warehouse":"Stores - _TC",
|
||||
"coupon_code_based":1,
|
||||
"selling": 1,
|
||||
"rate_or_discount": "Discount Percentage",
|
||||
"discount_percentage": 30,
|
||||
"company": "_Test Company with perpetual inventory",
|
||||
"company": "_Test Company",
|
||||
"currency":"INR",
|
||||
"for_price_list":"_Test Price List"
|
||||
})
|
||||
@ -85,7 +85,7 @@ def test_create_test_data():
|
||||
})
|
||||
sales_partner.insert()
|
||||
# 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({
|
||||
"doctype": "Coupon Code",
|
||||
"coupon_name":"SAVE30",
|
||||
@ -104,33 +104,25 @@ class TestCouponCode(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_1_check_coupon_code_used_before_so(self):
|
||||
coupon_code = frappe.get_doc("Coupon Code", frappe.db.get_value("Coupon Code", {"coupon_name":"SAVE30"}))
|
||||
# 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_sales_order_with_coupon_code(self):
|
||||
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
|
||||
|
||||
def test_2_sales_order_with_coupon_code(self):
|
||||
so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
|
||||
customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1,
|
||||
so = make_sales_order(company='_Test Company', warehouse='Stores - _TC',
|
||||
customer="_Test Customer", selling_price_list="_Test Price List",
|
||||
item_code="_Test Tesla Car", rate=5000, qty=1,
|
||||
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)
|
||||
|
||||
so.coupon_code='SAVE30'
|
||||
so.sales_partner='_Test Coupon Partner'
|
||||
so.save()
|
||||
|
||||
# check item price after coupon code is applied
|
||||
self.assertEqual(so.items[0].rate, 3500)
|
||||
|
||||
so.submit()
|
||||
|
||||
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)
|
||||
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)
|
||||
|
||||
|
||||
|
||||
|
@ -9,11 +9,7 @@ frappe.ui.form.on('Fiscal Year', {
|
||||
}
|
||||
},
|
||||
refresh: function (frm) {
|
||||
let doc = frm.doc;
|
||||
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)) {
|
||||
if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) {
|
||||
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'"));
|
||||
} else {
|
||||
@ -24,8 +20,10 @@ frappe.ui.form.on('Fiscal Year', {
|
||||
return frm.call('set_as_default');
|
||||
},
|
||||
year_start_date: function(frm) {
|
||||
let 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);
|
||||
if (!frm.doc.is_short_year) {
|
||||
let 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,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:year",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-22 16:50:25",
|
||||
"custom": 0,
|
||||
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"year",
|
||||
"disabled",
|
||||
"is_short_year",
|
||||
"year_start_date",
|
||||
"year_end_date",
|
||||
"companies",
|
||||
"auto_created"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "For e.g. 2012, 2012-13",
|
||||
"fieldname": "year",
|
||||
"fieldtype": "Data",
|
||||
"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 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
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "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
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "year_start_date",
|
||||
"fieldtype": "Date",
|
||||
"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 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
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "year_end_date",
|
||||
"fieldtype": "Date",
|
||||
"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
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"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
|
||||
"options": "Fiscal Year Company"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "auto_created",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Auto Created",
|
||||
"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
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Less than 12 months.",
|
||||
"fieldname": "is_short_year",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Short Year",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-04-25 14:21:41.273354",
|
||||
"links": [],
|
||||
"modified": "2020-11-05 12:16:53.081573",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"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
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"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
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"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
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"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
|
||||
"role": "Stock User"
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"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
|
||||
"role": "Employee"
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -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."))
|
||||
|
||||
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):
|
||||
frappe.throw(_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
|
||||
FiscalYearIncorrectDate)
|
||||
@ -116,12 +121,8 @@ def auto_create_fiscal_year():
|
||||
pass
|
||||
|
||||
def get_from_and_to_date(fiscal_year):
|
||||
from_and_to_date_tuple = frappe.db.sql("""select year_start_date, year_end_date
|
||||
from `tabFiscal Year` where name=%s""", (fiscal_year))[0]
|
||||
|
||||
from_and_to_date = {
|
||||
"from_date": from_and_to_date_tuple[0],
|
||||
"to_date": from_and_to_date_tuple[1]
|
||||
}
|
||||
|
||||
return from_and_to_date
|
||||
fields = [
|
||||
"year_start_date as from_date",
|
||||
"year_end_date as to_date"
|
||||
]
|
||||
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)
|
||||
|
@ -11,6 +11,7 @@ test_records = frappe.get_test_records('Fiscal Year')
|
||||
test_ignore = ["Company"]
|
||||
|
||||
class TestFiscalYear(unittest.TestCase):
|
||||
|
||||
def test_extra_year(self):
|
||||
if frappe.db.exists("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",
|
||||
"year": "_Test Fiscal Year 2012",
|
||||
|
@ -30,20 +30,22 @@ class GLEntry(Document):
|
||||
self.pl_must_have_cost_center()
|
||||
self.validate_cost_center()
|
||||
|
||||
self.check_pl_account()
|
||||
self.validate_party()
|
||||
self.validate_currency()
|
||||
if not self.flags.from_repost:
|
||||
self.check_pl_account()
|
||||
self.validate_party()
|
||||
self.validate_currency()
|
||||
|
||||
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes'):
|
||||
self.validate_account_details(adv_adj)
|
||||
self.validate_dimensions_for_pl_and_bs()
|
||||
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
|
||||
if not from_repost:
|
||||
self.validate_account_details(adv_adj)
|
||||
self.validate_dimensions_for_pl_and_bs()
|
||||
|
||||
validate_frozen_account(self.account, adv_adj)
|
||||
validate_balance_type(self.account, adv_adj)
|
||||
|
||||
# Update outstanding amt on against voucher
|
||||
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,
|
||||
self.against_voucher)
|
||||
|
||||
@ -106,8 +108,8 @@ class GLEntry(Document):
|
||||
from tabAccount where name=%s""", self.account, as_dict=1)[0]
|
||||
|
||||
if ret.is_group==1:
|
||||
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
|
||||
transactions''').format(self.voucher_type, self.voucher_no, self.account))
|
||||
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions''')
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
|
||||
if ret.docstatus==2:
|
||||
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))
|
||||
|
||||
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
|
||||
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
|
||||
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||
|
||||
def validate_party(self):
|
||||
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)
|
||||
})
|
||||
|
||||
je.append("accounts", {
|
||||
"account": self.bank_charges_account,
|
||||
"debit_in_account_currency": flt(self.bank_charges),
|
||||
"cost_center": erpnext.get_default_cost_center(self.company)
|
||||
})
|
||||
if self.bank_charges:
|
||||
je.append("accounts", {
|
||||
"account": self.bank_charges_account,
|
||||
"debit_in_account_currency": flt(self.bank_charges),
|
||||
"cost_center": erpnext.get_default_cost_center(self.company)
|
||||
})
|
||||
|
||||
je.append("accounts", {
|
||||
"account": self.short_term_loan,
|
||||
|
@ -80,6 +80,7 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
||||
short_term_loan=self.short_term_loan,
|
||||
bank_charges_account=self.bank_charges_account,
|
||||
bank_account=self.bank_account,
|
||||
bank_charges=100
|
||||
)
|
||||
|
||||
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.loan_start_date = args.start or nowdate()
|
||||
inv_disc.loan_period = args.period or 30
|
||||
inv_disc.bank_charges = flt(args.bank_charges)
|
||||
|
||||
for d in invoices:
|
||||
inv_disc.append("invoices", {
|
||||
|
@ -38,8 +38,8 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"modified": "2020-06-18 20:27:42.615842",
|
||||
"modified_by": "ahmad@havenir.com",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Item Tax Template",
|
||||
"owner": "Administrator",
|
||||
|
@ -20,7 +20,8 @@ def get_data():
|
||||
'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) {
|
||||
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());
|
||||
}
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-03-25 10:53:52",
|
||||
@ -503,7 +504,7 @@
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-02 18:15:46.955697",
|
||||
"modified": "2020-10-30 13:56:01.121995",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"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 import msgprint, _, scrub
|
||||
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.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 six import string_types, iteritems
|
||||
|
||||
class StockAccountInvalidTransaction(frappe.ValidationError): pass
|
||||
|
||||
class JournalEntry(AccountsController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JournalEntry, self).__init__(*args, **kwargs)
|
||||
@ -22,14 +26,19 @@ class JournalEntry(AccountsController):
|
||||
return self.voucher_type
|
||||
|
||||
def validate(self):
|
||||
if self.voucher_type == 'Opening Entry':
|
||||
self.is_opening = 'Yes'
|
||||
|
||||
if not self.is_opening:
|
||||
self.is_opening='No'
|
||||
|
||||
self.clearance_date = None
|
||||
|
||||
self.validate_party()
|
||||
self.validate_entries_for_advance()
|
||||
self.validate_multi_currency()
|
||||
self.set_amounts_in_company_currency()
|
||||
self.validate_debit_credit_amount()
|
||||
self.validate_total_debit_and_credit()
|
||||
self.validate_against_jv()
|
||||
self.validate_reference_doc()
|
||||
@ -41,6 +50,7 @@ class JournalEntry(AccountsController):
|
||||
self.validate_empty_accounts_table()
|
||||
self.set_account_and_party_balance()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_stock_accounts()
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
@ -52,6 +62,8 @@ class JournalEntry(AccountsController):
|
||||
self.update_expense_claim()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
check_if_stock_and_account_balance_synced(self.posting_date,
|
||||
self.company, self.doctype, self.name)
|
||||
|
||||
def on_cancel(self):
|
||||
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
||||
@ -91,6 +103,16 @@ class JournalEntry(AccountsController):
|
||||
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"))
|
||||
|
||||
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):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\
|
||||
@ -335,8 +357,7 @@ class JournalEntry(AccountsController):
|
||||
currency=account_currency)
|
||||
|
||||
if flt(voucher_total) < (flt(order.advance_paid) + total):
|
||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
|
||||
than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
|
||||
frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
|
||||
|
||||
def validate_invoices(self):
|
||||
"""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.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):
|
||||
self.set_total_debit_credit()
|
||||
if self.difference:
|
||||
@ -1023,7 +1049,7 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
return journal_entry.as_dict()
|
||||
|
||||
@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
|
||||
|
||||
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,
|
||||
},
|
||||
}, target_doc, ignore_permissions=ignore_permissions)
|
||||
}, target_doc)
|
||||
|
||||
return doclist
|
@ -6,7 +6,7 @@ import unittest, frappe
|
||||
from frappe.utils import flt, nowdate
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
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):
|
||||
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"]:
|
||||
# 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)
|
||||
self.assertRaises(frappe.LinkExistsError, submitted_voucher.cancel)
|
||||
|
||||
def test_jv_against_stock_account(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
set_perpetual_inventory()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
stock_account = get_inventory_account(company)
|
||||
|
||||
jv = frappe.copy_doc({
|
||||
"cheque_date": nowdate(),
|
||||
"cheque_no": "33",
|
||||
"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"
|
||||
})
|
||||
from erpnext.accounts.utils import get_stock_and_account_balance
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
|
||||
diff = flt(account_bal) - flt(stock_bal)
|
||||
|
||||
jv.get("accounts")[0].update({
|
||||
"account": get_inventory_account('_Test Company with perpetual inventory'),
|
||||
"company": "_Test Company with perpetual inventory",
|
||||
"party_type": None,
|
||||
"party": None
|
||||
if not diff:
|
||||
diff = 100
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
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
|
||||
})
|
||||
|
||||
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
|
||||
jv.cancel()
|
||||
set_perpetual_inventory(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()
|
||||
|
||||
if account_bal == stock_bal:
|
||||
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
|
||||
frappe.db.rollback()
|
||||
else:
|
||||
jv.submit()
|
||||
jv.cancel()
|
||||
|
||||
def test_multi_currency(self):
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
|
@ -8,12 +8,10 @@ import unittest
|
||||
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.party import get_dashboard_info
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
|
||||
class TestLoyaltyProgram(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
set_perpetual_inventory(0)
|
||||
# create relevant item, customer, loyalty program, etc
|
||||
create_records()
|
||||
|
||||
@ -195,88 +193,91 @@ def create_sales_invoice_record(qty=1):
|
||||
|
||||
def create_records():
|
||||
# create a new loyalty Account
|
||||
if frappe.db.exists("Account", "Loyalty - _TC"):
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"account_name": "Loyalty",
|
||||
"parent_account": "Direct Expenses - _TC",
|
||||
"company": "_Test Company",
|
||||
"is_group": 0,
|
||||
"account_type": "Expense Account",
|
||||
}).insert()
|
||||
if not frappe.db.exists("Account", "Loyalty - _TC"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"account_name": "Loyalty",
|
||||
"parent_account": "Direct Expenses - _TC",
|
||||
"company": "_Test Company",
|
||||
"is_group": 0,
|
||||
"account_type": "Expense Account",
|
||||
}).insert()
|
||||
|
||||
# create a new loyalty program Single tier
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Single Loyalty",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Single 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': 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": [
|
||||
{
|
||||
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Single Loyalty",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Single 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()
|
||||
'min_spent': 1000
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
# create a new customer
|
||||
if not frappe.db.exists("Customer","Test Loyalty 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
|
||||
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
|
||||
item = frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": "Loyal Item",
|
||||
"item_name": "Loyal Item",
|
||||
"item_group": "All Item Groups",
|
||||
"company": "_Test Company",
|
||||
"is_stock_item": 1,
|
||||
"opening_stock": 100,
|
||||
"valuation_rate": 10000,
|
||||
}).insert()
|
||||
if not frappe.db.exists("Item", "Loyal Item"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": "Loyal Item",
|
||||
"item_name": "Loyal Item",
|
||||
"item_group": "All Item Groups",
|
||||
"company": "_Test Company",
|
||||
"is_stock_item": 1,
|
||||
"opening_stock": 100,
|
||||
"valuation_rate": 10000,
|
||||
}).insert()
|
||||
|
||||
# create item price
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"price_list": "Standard Selling",
|
||||
"item_code": item.item_code,
|
||||
"price_list_rate": 10000
|
||||
}).insert()
|
||||
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"price_list": "Standard Selling",
|
||||
"item_code": "Loyal Item",
|
||||
"price_list_rate": 10000
|
||||
}).insert()
|
||||
|
@ -1,13 +1,17 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
return{
|
||||
filters: [
|
||||
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'company', '=', d.company]
|
||||
]
|
||||
}
|
||||
frappe.ui.form.on('Mode of Payment', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: [
|
||||
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'company', '=', d.company]
|
||||
]
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:mode_of_payment",
|
||||
@ -28,7 +29,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Type",
|
||||
"options": "Cash\nBank\nGeneral"
|
||||
"options": "Cash\nBank\nGeneral\nPhone"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts",
|
||||
@ -45,11 +46,13 @@
|
||||
],
|
||||
"icon": "fa fa-credit-card",
|
||||
"idx": 1,
|
||||
"modified": "2019-08-14 14:58:42.079115",
|
||||
"modified_by": "sammish.thundiyil@gmail.com",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 17:57:23.835236",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Mode of Payment",
|
||||
"owner": "harshada@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
|
@ -6,7 +6,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
||||
frm.set_query('party_type', 'invoices', function(doc, cdt, cdn) {
|
||||
return {
|
||||
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) {
|
||||
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) {
|
||||
frm.disable_save();
|
||||
frm.trigger("make_dashboard");
|
||||
!frm.doc.import_in_progress && frm.trigger("make_dashboard");
|
||||
frm.page.set_primary_action(__('Create Invoices'), () => {
|
||||
let btn_primary = frm.page.btn_primary.get(0);
|
||||
return frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
btn: $(btn_primary),
|
||||
method: "make_invoices",
|
||||
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();
|
||||
}
|
||||
}
|
||||
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
|
||||
});
|
||||
});
|
||||
|
||||
if (frm.doc.create_missing_party) {
|
||||
frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
|
||||
}
|
||||
},
|
||||
|
||||
setup_company_filters: function(frm) {
|
||||
|
@ -4,9 +4,12 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import traceback
|
||||
from json import dumps
|
||||
from frappe import _, scrub
|
||||
from frappe.utils import flt, nowdate
|
||||
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
|
||||
|
||||
|
||||
@ -62,66 +65,47 @@ class OpeningInvoiceCreationTool(Document):
|
||||
|
||||
return invoices_summary, max_count
|
||||
|
||||
def make_invoices(self):
|
||||
names = []
|
||||
mandatory_error_msg = _("Row {0}: {1} is required to create the Opening {2} Invoices")
|
||||
def validate_company(self):
|
||||
if not self.company:
|
||||
frappe.throw(_("Please select the Company"))
|
||||
|
||||
company_details = frappe.get_cached_value('Company', self.company,
|
||||
["default_currency", "default_letter_head"], as_dict=1) or {}
|
||||
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()
|
||||
|
||||
def validate_mandatory_invoice_fields(self, row):
|
||||
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:
|
||||
if not row.qty:
|
||||
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:
|
||||
if not row:
|
||||
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:
|
||||
args.update({
|
||||
invoice.update({
|
||||
"currency": company_details.get("default_currency"),
|
||||
"letter_head": company_details.get("default_letter_head")
|
||||
})
|
||||
invoices.append(invoice)
|
||||
|
||||
doc = frappe.get_doc(args).insert()
|
||||
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
|
||||
return invoices
|
||||
|
||||
def add_party(self, party_type, party):
|
||||
party_doc = frappe.new_doc(party_type)
|
||||
@ -140,14 +124,12 @@ class OpeningInvoiceCreationTool(Document):
|
||||
|
||||
def get_invoice_dict(self, row=None):
|
||||
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:
|
||||
frappe.throw(
|
||||
_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
|
||||
)
|
||||
frappe.throw(_("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)
|
||||
|
||||
return frappe._dict({
|
||||
@ -161,18 +143,9 @@ class OpeningInvoiceCreationTool(Document):
|
||||
"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()
|
||||
|
||||
args = frappe._dict({
|
||||
invoice = frappe._dict({
|
||||
"items": [item],
|
||||
"is_opening": "Yes",
|
||||
"set_posting_time": 1,
|
||||
@ -180,21 +153,77 @@ class OpeningInvoiceCreationTool(Document):
|
||||
"cost_center": self.cost_center,
|
||||
"due_date": row.due_date,
|
||||
"posting_date": row.posting_date,
|
||||
frappe.scrub(party_type): row.party,
|
||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
|
||||
frappe.scrub(row.party_type): row.party,
|
||||
"is_pos": 0,
|
||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||
"update_stock": 0
|
||||
})
|
||||
|
||||
accounting_dimension = get_accounting_dimensions()
|
||||
|
||||
for dimension in accounting_dimension:
|
||||
args.update({
|
||||
invoice.update({
|
||||
dimension: item.get(dimension)
|
||||
})
|
||||
|
||||
if self.invoice_type == "Sales":
|
||||
args["is_pos"] = 0
|
||||
return invoice
|
||||
|
||||
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()
|
||||
def get_temporary_opening_account(company=None):
|
||||
|
@ -7,17 +7,24 @@ import frappe
|
||||
import unittest
|
||||
|
||||
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
|
||||
|
||||
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")
|
||||
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)
|
||||
return doc.make_invoices()
|
||||
|
||||
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)
|
||||
expected_value = {
|
||||
@ -27,6 +34,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
}
|
||||
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"):
|
||||
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])
|
||||
|
||||
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)
|
||||
expected_value = {
|
||||
@ -44,7 +58,33 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||
0: ["_Test Supplier", 300, "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):
|
||||
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,
|
||||
"outstanding_amount": 300,
|
||||
"party": "_Test {0}".format(party),
|
||||
"party": args.get("party_1") or "_Test {0}".format(party),
|
||||
"item_name": "Opening Item",
|
||||
"due_date": "2016-09-10",
|
||||
"posting_date": "2016-09-05",
|
||||
@ -66,7 +106,7 @@ def get_opening_invoice_creation_dict(**args):
|
||||
{
|
||||
"qty": 2.0,
|
||||
"outstanding_amount": 250,
|
||||
"party": "_Test {0} 1".format(party),
|
||||
"party": args.get("party_2") or "_Test {0} 1".format(party),
|
||||
"item_name": "Opening Item",
|
||||
"due_date": "2016-09-10",
|
||||
"posting_date": "2016-09-05",
|
||||
@ -77,3 +117,30 @@ def get_opening_invoice_creation_dict(**args):
|
||||
|
||||
invoice_dict.update(args)
|
||||
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) {
|
||||
frm.set_query("paid_from", function() {
|
||||
frm.events.validate_company(frm);
|
||||
|
||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
@ -23,13 +24,16 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("party_type", function() {
|
||||
frm.events.validate_company(frm);
|
||||
return{
|
||||
filters: {
|
||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("party_bank_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -39,6 +43,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("bank_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -47,6 +52,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("contact_person", function() {
|
||||
if (frm.doc.party) {
|
||||
return {
|
||||
@ -58,10 +64,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("paid_to", function() {
|
||||
frm.events.validate_company(frm);
|
||||
|
||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ["in", account_types],
|
||||
@ -150,6 +158,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
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) {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
|
@ -1,4 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2016-06-01 14:38:51.012597",
|
||||
@ -63,6 +65,7 @@
|
||||
"cost_center",
|
||||
"section_break_12",
|
||||
"status",
|
||||
"custom_remarks",
|
||||
"remarks",
|
||||
"column_break_16",
|
||||
"letter_head",
|
||||
@ -462,7 +465,8 @@
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Remarks",
|
||||
"no_copy": 1
|
||||
"no_copy": 1,
|
||||
"read_only_depends_on": "eval:doc.custom_remarks == 0"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
@ -573,10 +577,18 @@
|
||||
"label": "Status",
|
||||
"options": "\nDraft\nSubmitted\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom_remarks",
|
||||
"fieldtype": "Check",
|
||||
"label": "Custom Remarks"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-12-08 13:02:30.016610",
|
||||
"links": [],
|
||||
"modified": "2020-10-30 13:56:20.007336",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
@ -202,17 +202,32 @@ class PaymentEntry(AccountsController):
|
||||
# if account_type not in 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_account_currency == self.company_currency:
|
||||
self.source_exchange_rate = 1
|
||||
else:
|
||||
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
|
||||
self.company_currency, self.posting_date)
|
||||
if ref_doc:
|
||||
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:
|
||||
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
|
||||
self.company_currency, self.posting_date)
|
||||
if ref_doc:
|
||||
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):
|
||||
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)
|
||||
|
||||
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>\
|
||||
If this is undesirable please cancel the corresponding Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
|
||||
frappe.msgprint(
|
||||
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
|
||||
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||
title=_("Warning"), indicator="orange")
|
||||
|
||||
|
||||
@ -453,7 +469,7 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
|
||||
|
||||
def set_remarks(self):
|
||||
if self.remarks: return
|
||||
if self.custom_remarks: return
|
||||
|
||||
if self.payment_type=="Internal Transfer":
|
||||
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
|
||||
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
||||
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)
|
||||
elif ref_doc.doctype == "Employee Advance":
|
||||
total_amount = ref_doc.advance_amount
|
||||
else:
|
||||
elif ref_doc.doctype == "Employee Advance":
|
||||
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)
|
||||
if not total_amount:
|
||||
if party_account_currency == company_currency:
|
||||
total_amount = ref_doc.base_grand_total
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.grand_total
|
||||
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.grand_total
|
||||
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
|
||||
# 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)
|
||||
|
||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
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"))\
|
||||
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
|
||||
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:
|
||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||
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,
|
||||
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
|
||||
})
|
||||
|
||||
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()
|
||||
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
||||
reference_doc = None
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
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))
|
||||
|
||||
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"
|
||||
|
||||
# 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)
|
||||
party_type = set_party_type(dt)
|
||||
party_account = set_party_account(dt, dn, doc, party_type)
|
||||
party_account_currency = set_party_account_currency(dt, party_account, doc)
|
||||
payment_type = set_payment_type(dt, doc)
|
||||
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
|
||||
|
||||
# bank or cash
|
||||
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account)
|
||||
bank = get_bank_cash_account(doc, 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)
|
||||
|
||||
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)
|
||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
|
||||
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
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.set_missing_values()
|
||||
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()
|
||||
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):
|
||||
references = []
|
||||
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
|
||||
def set_missing_values(source, target):
|
||||
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):
|
||||
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, {
|
||||
doclist = get_mapped_doc("Payment Entry", source_name, {
|
||||
"Payment Entry": {
|
||||
"doctype": "Payment Order",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Payment Entry Reference": {
|
||||
"doctype": "Payment Order Reference",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
},
|
||||
"postprocess": update_item
|
||||
},
|
||||
}
|
||||
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
frappe.listview_settings['Payment Entry'] = {
|
||||
|
||||
onload: function(listview) {
|
||||
listview.page.fields_dict.party_type.get_query = function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||
}
|
||||
if (listview.page.fields_dict.party_type) {
|
||||
listview.page.fields_dict.party_type.get_query = function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
@ -1,313 +1,98 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2015-12-23 21:31:52.699821",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"field_order": [
|
||||
"payment_gateway",
|
||||
"payment_channel",
|
||||
"is_default",
|
||||
"column_break_4",
|
||||
"payment_account",
|
||||
"currency",
|
||||
"payment_request_message",
|
||||
"message",
|
||||
"message_examples"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"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_standard_filter": 0,
|
||||
"label": "Payment Gateway",
|
||||
"length": 0,
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "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
|
||||
"label": "Is Default"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"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_standard_filter": 0,
|
||||
"label": "Payment Account",
|
||||
"length": 0,
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "payment_account.account_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Read Only",
|
||||
"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": "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
|
||||
"label": "Currency"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.payment_channel !== \"Phone\"",
|
||||
"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
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"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
|
||||
"label": "Default Payment Request Message"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "message_examples",
|
||||
"fieldtype": "HTML",
|
||||
"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": "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
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"default": "Email",
|
||||
"fieldname": "payment_channel",
|
||||
"fieldtype": "Select",
|
||||
"label": "Payment Channel",
|
||||
"options": "\nEmail\nPhone"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-05-16 22:43:34.970491",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-20 13:30:27.722852",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Gateway Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -21,10 +21,15 @@ class PaymentOrder(Document):
|
||||
if cancel:
|
||||
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:
|
||||
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.validate_and_sanitize_search_inputs
|
||||
|
@ -5,6 +5,45 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
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):
|
||||
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",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -10,7 +11,6 @@
|
||||
"column_break_4",
|
||||
"supplier",
|
||||
"payment_request",
|
||||
"payment_entry",
|
||||
"mode_of_payment",
|
||||
"bank_account_details",
|
||||
"bank_account",
|
||||
@ -103,17 +103,12 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 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,
|
||||
"modified": "2019-05-08 13:56:25.724557",
|
||||
"links": [],
|
||||
"modified": "2020-09-04 08:29:51.014390",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Order Reference",
|
||||
|
@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", {
|
||||
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
|
||||
onload: function() {
|
||||
var me = this;
|
||||
|
||||
this.frm.set_query("party", function() {
|
||||
check_mandatory(me.frm);
|
||||
});
|
||||
|
||||
this.frm.set_query("party_type", function() {
|
||||
return {
|
||||
"filters": {
|
||||
@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
});
|
||||
|
||||
this.frm.set_query('receivable_payable_account', function() {
|
||||
if(!me.frm.doc.company || !me.frm.doc.party_type) {
|
||||
frappe.msgprint(__("Please select Company and Party Type first"));
|
||||
} else {
|
||||
return{
|
||||
filters: {
|
||||
"company": me.frm.doc.company,
|
||||
"is_group": 0,
|
||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check_mandatory(me.frm);
|
||||
return {
|
||||
filters: {
|
||||
"company": me.frm.doc.company,
|
||||
"is_group": 0,
|
||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_query('bank_cash_account', function() {
|
||||
if(!me.frm.doc.company) {
|
||||
frappe.msgprint(__("Please select Company first"));
|
||||
} else {
|
||||
return{
|
||||
filters:[
|
||||
['Account', 'company', '=', me.frm.doc.company],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
||||
]
|
||||
};
|
||||
}
|
||||
check_mandatory(me.frm, true);
|
||||
return {
|
||||
filters:[
|
||||
['Account', 'company', '=', me.frm.doc.company],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_value('party_type', '');
|
||||
this.frm.set_value('party', '');
|
||||
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() {
|
||||
@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
|
||||
party: function() {
|
||||
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({
|
||||
method: "erpnext.accounts.party.get_party_account",
|
||||
args: {
|
||||
@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
party: me.frm.doc.party
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc && r.message) {
|
||||
if (!r.exc && 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 `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`.is_cancelled = 0
|
||||
GROUP BY `tab{doc}`.name
|
||||
Having
|
||||
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) {
|
||||
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){
|
||||
frm.add_custom_button(__('Resend Payment Email'), function(){
|
||||
frappe.call({
|
||||
|
@ -48,6 +48,7 @@
|
||||
"section_break_7",
|
||||
"payment_gateway",
|
||||
"payment_account",
|
||||
"payment_channel",
|
||||
"payment_order",
|
||||
"amended_from"
|
||||
],
|
||||
@ -230,6 +231,7 @@
|
||||
"label": "Recipient Message And Payment Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "print_format",
|
||||
"fieldtype": "Select",
|
||||
"label": "Print Format"
|
||||
@ -241,6 +243,7 @@
|
||||
"label": "To"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
@ -277,16 +280,18 @@
|
||||
"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",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text",
|
||||
"label": "Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||
"fieldname": "message_examples",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Message Examples",
|
||||
@ -347,12 +352,21 @@
|
||||
"options": "Payment Request",
|
||||
"print_hide": 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,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-17 14:06:42.185763",
|
||||
"modified": "2020-09-18 12:24:14.178853",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
@ -36,7 +36,7 @@ class PaymentRequest(Document):
|
||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||
if (hasattr(ref_doc, "order_type") \
|
||||
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:
|
||||
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
|
||||
@ -76,11 +76,25 @@ class PaymentRequest(Document):
|
||||
or self.flags.mute_email:
|
||||
send_mail = False
|
||||
|
||||
if send_mail:
|
||||
if send_mail and self.payment_channel != "Phone":
|
||||
self.set_payment_request_url()
|
||||
self.send_email()
|
||||
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):
|
||||
self.check_if_payment_entry_exists()
|
||||
self.set_as_cancelled()
|
||||
@ -105,13 +119,14 @@ class PaymentRequest(Document):
|
||||
return False
|
||||
|
||||
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()
|
||||
|
||||
if 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')
|
||||
|
||||
def get_payment_url(self):
|
||||
@ -140,10 +155,14 @@ class PaymentRequest(Document):
|
||||
})
|
||||
|
||||
def set_as_paid(self):
|
||||
payment_entry = self.create_payment_entry()
|
||||
self.make_invoice()
|
||||
if self.payment_channel == "Phone":
|
||||
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):
|
||||
"""create entry"""
|
||||
@ -151,7 +170,7 @@ class PaymentRequest(Document):
|
||||
|
||||
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
|
||||
elif self.reference_doctype == "Purchase Invoice":
|
||||
party_account = ref_doc.credit_to
|
||||
@ -166,8 +185,8 @@ class PaymentRequest(Document):
|
||||
else:
|
||||
party_amount = self.grand_total
|
||||
|
||||
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name,
|
||||
party_amount=party_amount, bank_account=self.payment_account, bank_amount=bank_amount)
|
||||
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name, party_amount=party_amount,
|
||||
bank_account=self.payment_account, bank_amount=bank_amount)
|
||||
|
||||
payment_entry.update({
|
||||
"reference_no": self.name,
|
||||
@ -255,7 +274,7 @@ class PaymentRequest(Document):
|
||||
|
||||
# if shopping cart enabled and in 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
|
||||
if success_url:
|
||||
@ -280,7 +299,9 @@ def make_payment_request(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
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":
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_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)
|
||||
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'))
|
||||
if args.get('party_type') else '')
|
||||
|
||||
@ -314,9 +333,11 @@ def make_payment_request(**args):
|
||||
"payment_gateway_account": gateway_account.get("name"),
|
||||
"payment_gateway": gateway_account.get("payment_gateway"),
|
||||
"payment_account": gateway_account.get("payment_account"),
|
||||
"payment_channel": gateway_account.get("payment_channel"),
|
||||
"payment_request_type": args.get("payment_request_type"),
|
||||
"currency": ref_doc.currency,
|
||||
"grand_total": grand_total,
|
||||
"mode_of_payment": args.mode_of_payment,
|
||||
"email_to": args.recipient_id or ref_doc.owner,
|
||||
"subject": _("Payment Request for {0}").format(args.dn),
|
||||
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
||||
@ -344,7 +365,7 @@ def make_payment_request(**args):
|
||||
|
||||
return pr.as_dict()
|
||||
|
||||
def get_amount(ref_doc):
|
||||
def get_amount(ref_doc, payment_account=None):
|
||||
"""get amount based on doctype"""
|
||||
dt = ref_doc.doctype
|
||||
if dt in ["Sales Order", "Purchase Order"]:
|
||||
@ -356,6 +377,12 @@ def get_amount(ref_doc):
|
||||
else:
|
||||
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":
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
@ -366,6 +393,10 @@ def get_amount(ref_doc):
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
|
||||
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("""
|
||||
select sum(grand_total)
|
||||
from `tabPayment Request`
|
||||
@ -373,7 +404,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||
reference_doctype = %s
|
||||
and reference_name = %s
|
||||
and docstatus = 1
|
||||
and status != 'Paid'
|
||||
and (status != 'Paid'
|
||||
or (payment_channel = 'Phone'
|
||||
and status = 'Paid'))
|
||||
""", (ref_dt, ref_dn))
|
||||
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
||||
|
||||
|
@ -45,6 +45,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"description": "Provide the invoice portion in percent",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
@ -170,6 +171,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"description": "Give number of days according to prior selection",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
@ -305,7 +307,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-03-08 10:47:32.830478",
|
||||
"modified": "2020-10-14 10:47:32.830478",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
|
@ -291,11 +291,11 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:49.089450",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Period Closing Voucher",
|
||||
"owner": "jai@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
|
@ -51,18 +51,53 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
args: {
|
||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||
pos_profile: frm.doc.pos_profile,
|
||||
user: frm.doc.user
|
||||
},
|
||||
callback: (r) => {
|
||||
let pos_docs = r.message;
|
||||
set_form_data(pos_docs, frm)
|
||||
refresh_fields(frm)
|
||||
set_html_data(frm)
|
||||
set_form_data(pos_docs, frm);
|
||||
refresh_fields(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', {
|
||||
closing_amount: (frm, 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.net_total += flt(d.net_total);
|
||||
frm.doc.total_quantity += flt(d.total_qty);
|
||||
add_to_payments(d, frm);
|
||||
add_to_taxes(d, frm);
|
||||
refresh_payments(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 => {
|
||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_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 {
|
||||
frm.add_child("payment_reconciliation", {
|
||||
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 => {
|
||||
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
||||
if (tax) {
|
||||
tax.amount += flt(t.tax_amount);
|
||||
if (!remove) tax.amount += flt(t.tax_amount);
|
||||
else tax.amount -= flt(t.tax_amount);
|
||||
} else {
|
||||
frm.add_child("taxes", {
|
||||
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):
|
||||
def validate(self):
|
||||
user = frappe.get_all('POS Closing Entry',
|
||||
filters = { 'user': self.user, 'docstatus': 1 },
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
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 = {
|
||||
'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_start_date": ("between", [self.period_start_date, self.period_end_date]),
|
||||
"period_end_date": ("between", [self.period_start_date, self.period_end_date])
|
||||
})
|
||||
|
||||
if user:
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period"
|
||||
.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
|
||||
bold_already_exists = frappe.bold(_("already exists"))
|
||||
bold_user = frappe.bold(self.user)
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
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 invalid_row.get('msg'):
|
||||
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):
|
||||
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]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pos_invoices(start, end, user):
|
||||
def get_pos_invoices(start, end, pos_profile, user):
|
||||
data = frappe.db.sql("""
|
||||
select
|
||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||
from
|
||||
`tabPOS Invoice`
|
||||
where
|
||||
owner = %s and docstatus = 1 and
|
||||
(consolidated_invoice is NULL or consolidated_invoice = '')
|
||||
""", (user), as_dict=1)
|
||||
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||
""", (user, pos_profile), as_dict=1)
|
||||
|
||||
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
|
||||
@ -76,7 +107,8 @@ def make_closing_entry_from_opening(opening_entry):
|
||||
closing_entry.net_total = 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 = []
|
||||
taxes = []
|
||||
|
@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def init_user_and_profile():
|
||||
def init_user_and_profile(**args):
|
||||
user = 'test@example.com'
|
||||
test_user = frappe.get_doc('User', user)
|
||||
|
||||
@ -53,7 +53,7 @@ def init_user_and_profile():
|
||||
test_user.add_roles(*roles)
|
||||
frappe.set_user(user)
|
||||
|
||||
pos_profile = make_pos_profile()
|
||||
pos_profile = make_pos_profile(**args)
|
||||
pos_profile.append('applicable_for_users', {
|
||||
'default': 1,
|
||||
'user': user
|
||||
|
@ -7,8 +7,8 @@
|
||||
"field_order": [
|
||||
"mode_of_payment",
|
||||
"opening_amount",
|
||||
"closing_amount",
|
||||
"expected_amount",
|
||||
"closing_amount",
|
||||
"difference"
|
||||
],
|
||||
"fields": [
|
||||
@ -26,8 +26,7 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Expected Amount",
|
||||
"options": "company:company_currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
@ -55,9 +54,10 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:03:34.533607",
|
||||
"modified": "2020-10-23 16:45:43.662034",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry Detail",
|
||||
|
@ -9,80 +9,63 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
||||
this._super(doc);
|
||||
},
|
||||
|
||||
onload() {
|
||||
onload(doc) {
|
||||
this._super();
|
||||
if(this.frm.doc.__islocal && this.frm.doc.is_pos) {
|
||||
//Load pos profile data on the invoice if the default value of Is POS is 1
|
||||
|
||||
me.frm.script_manager.trigger("is_pos");
|
||||
me.frm.refresh_fields();
|
||||
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||
this.frm.script_manager.trigger("is_pos");
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
},
|
||||
|
||||
refresh(doc) {
|
||||
this._super();
|
||||
if (doc.docstatus == 1 && !doc.is_return) {
|
||||
if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
|
||||
cur_frm.add_custom_button(__('Return'),
|
||||
this.make_sales_return, __('Create'));
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
|
||||
this.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";
|
||||
cur_frm.set_value('consolidated_invoice', '');
|
||||
this.frm.set_value('consolidated_invoice', '');
|
||||
}
|
||||
},
|
||||
|
||||
is_pos: function(frm){
|
||||
is_pos: function() {
|
||||
this.set_pos_data();
|
||||
},
|
||||
|
||||
set_pos_data: function() {
|
||||
set_pos_data: async function() {
|
||||
if(this.frm.doc.is_pos) {
|
||||
this.frm.set_value("allocate_advances_automatically", 0);
|
||||
if(!this.frm.doc.company) {
|
||||
this.frm.set_value("is_pos", 0);
|
||||
frappe.msgprint(__("Please specify Company to proceed"));
|
||||
} else {
|
||||
var me = this;
|
||||
return this.frm.call({
|
||||
doc: me.frm.doc,
|
||||
const r = await this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "set_missing_values",
|
||||
callback: function(r) {
|
||||
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();
|
||||
|
||||
}
|
||||
}
|
||||
freeze: true
|
||||
});
|
||||
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() {
|
||||
if (!this.frm.doc.customer) return
|
||||
|
||||
if (this.frm.doc.is_pos){
|
||||
var pos_profile = this.frm.doc.pos_profile;
|
||||
}
|
||||
var me = this;
|
||||
const pos_profile = this.frm.doc.pos_profile;
|
||||
if(this.frm.updating_party_details) return;
|
||||
erpnext.utils.get_party_details(this.frm,
|
||||
"erpnext.accounts.party.get_party_details", {
|
||||
@ -92,8 +75,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
||||
account: this.frm.doc.debit_to,
|
||||
price_list: this.frm.doc.selling_price_list,
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
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": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2020-01-24 15:29:29.933693",
|
||||
@ -460,7 +461,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "contact_mobile",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Mobile No",
|
||||
"read_only": 1
|
||||
@ -1580,7 +1581,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:08:39.337385",
|
||||
"modified": "2020-10-30 13:56:51.056083",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"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 erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.party import get_party_account, get_due_date
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
||||
get_loyalty_program_details_with_points, validate_loyalty_points
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
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, get_mode_of_payment_info
|
||||
|
||||
from six import iteritems
|
||||
|
||||
@ -29,8 +28,7 @@ class POSInvoice(SalesInvoice):
|
||||
# run on validate method of selling controller
|
||||
super(SalesInvoice, self).validate()
|
||||
self.validate_auto_set_posting_time()
|
||||
self.validate_pos_paid_amount()
|
||||
self.validate_pos_return()
|
||||
self.validate_mode_of_payment()
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_debit_to_acc()
|
||||
@ -40,11 +38,12 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_item_cost_centers()
|
||||
self.validate_serialised_or_batched_item()
|
||||
self.validate_stock_availablility()
|
||||
self.validate_return_items()
|
||||
self.validate_return_items_qty()
|
||||
self.validate_non_stock_items()
|
||||
self.set_status()
|
||||
self.set_account_for_mode_of_payment()
|
||||
self.validate_pos()
|
||||
self.verify_payment_amount()
|
||||
self.validate_payment_amount()
|
||||
self.validate_loyalty_transaction()
|
||||
|
||||
def on_submit(self):
|
||||
@ -57,6 +56,7 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
if self.redeem_loyalty_points and self.loyalty_points:
|
||||
self.apply_loyalty_points()
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
|
||||
def on_cancel(self):
|
||||
@ -69,77 +69,128 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc.delete_loyalty_point_entry()
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
def check_phone_payments(self):
|
||||
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'):
|
||||
msg = ""
|
||||
if d.serial_no:
|
||||
filters = {
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"delivery_document_no": "",
|
||||
"sales_invoice": ""
|
||||
}
|
||||
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||
if 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):
|
||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
|
||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
||||
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
|
||||
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:
|
||||
if allow_negative_stock:
|
||||
return
|
||||
|
||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||
if not (flt(available_stock) > 0):
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
|
||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||
if flt(available_stock) <= 0:
|
||||
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
||||
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||
.format(d.idx, item_code, warehouse, qty))
|
||||
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):
|
||||
error_msg = []
|
||||
for d in self.get("items"):
|
||||
serialized = d.get("has_serial_no")
|
||||
batched = d.get("has_batch_no")
|
||||
no_serial_selected = not d.get("serial_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):
|
||||
frappe.throw(_('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"))
|
||||
if serialized and no_serial_selected:
|
||||
frappe.throw(_('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"))
|
||||
if batched and no_batch_selected:
|
||||
frappe.throw(_('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"))
|
||||
msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.')
|
||||
.format(d.idx, item_code))
|
||||
elif serialized and no_serial_selected:
|
||||
msg = (_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.')
|
||||
.format(d.idx, item_code))
|
||||
elif batched and no_batch_selected:
|
||||
msg = (_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.')
|
||||
.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
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.get("qty") > 0:
|
||||
frappe.throw(_("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"))
|
||||
frappe.throw(
|
||||
_("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_pos_paid_amount(self):
|
||||
if len(self.payments) == 0 and self.is_pos:
|
||||
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_mode_of_payment(self):
|
||||
if len(self.payments) == 0:
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
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:
|
||||
total_amount_in_payments += entry.amount
|
||||
if not self.is_return and entry.amount < 0:
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||
if self.is_return and entry.amount > 0:
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||
|
||||
def validate_pos_return(self):
|
||||
if self.is_pos and self.is_return:
|
||||
total_amount_in_payments = 0
|
||||
for payment in self.payments:
|
||||
total_amount_in_payments += payment.amount
|
||||
if self.is_return:
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if total_amount_in_payments < invoice_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-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))
|
||||
|
||||
def validate_loyalty_transaction(self):
|
||||
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
|
||||
if not self.pos_profile:
|
||||
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')
|
||||
|
||||
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:
|
||||
update_multi_mode_option(self, pos)
|
||||
update_multi_mode_option(self, profile)
|
||||
|
||||
if not self.account_for_change_amount:
|
||||
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
||||
|
||||
if pos:
|
||||
if not for_validate:
|
||||
self.tax_category = pos.get("tax_category")
|
||||
if self.is_return and not for_validate:
|
||||
add_return_modes(self, profile)
|
||||
|
||||
if profile:
|
||||
if not for_validate and not self.customer:
|
||||
self.customer = pos.customer
|
||||
self.customer = profile.customer
|
||||
|
||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
||||
if pos.get('account_for_change_amount'):
|
||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||
if pos.get('warehouse'):
|
||||
self.set_warehouse = pos.get('warehouse')
|
||||
self.ignore_pricing_rule = profile.ignore_pricing_rule
|
||||
self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
|
||||
self.set_warehouse = profile.get('warehouse') or self.set_warehouse
|
||||
|
||||
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',
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||
self.set(fieldname, pos.get(fieldname))
|
||||
|
||||
if pos.get("company_address"):
|
||||
self.company_address = pos.get("company_address")
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center', 'tax_category',
|
||||
'ignore_pricing_rule', 'company_address', 'update_stock'):
|
||||
if not for_validate:
|
||||
self.set(fieldname, profile.get(fieldname))
|
||||
|
||||
if self.customer:
|
||||
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')
|
||||
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:
|
||||
selling_price_list = pos.get('selling_price_list')
|
||||
selling_price_list = profile.get('selling_price_list')
|
||||
|
||||
if 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
|
||||
for item in self.get("items"):
|
||||
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):
|
||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||
item.set(fname, val)
|
||||
@ -281,10 +322,13 @@ class POSInvoice(SalesInvoice):
|
||||
if self.taxes_and_charges and not len(self.get("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):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
profile = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
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)
|
||||
|
||||
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')):
|
||||
print_format = 'POS Invoice'
|
||||
|
||||
if pos:
|
||||
if profile:
|
||||
return {
|
||||
"print_format": print_format,
|
||||
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
|
||||
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
|
||||
"campaign": pos.get("campaign"),
|
||||
"allow_print_before_pay": pos.get("allow_print_before_pay")
|
||||
"campaign": profile.get("campaign"),
|
||||
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||
}
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
@ -313,6 +355,32 @@ class POSInvoice(SalesInvoice):
|
||||
if not pay.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()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
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
|
||||
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
|
||||
else:
|
||||
# when sle_qty is 0
|
||||
# when sle_qty > 0 and pos_sales_qty is 0
|
||||
return sle_qty
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -372,3 +438,18 @@ def make_merge_log(invoices):
|
||||
|
||||
if merge_log.get('pos_invoices'):
|
||||
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
|
||||
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.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):
|
||||
def test_timestamp_change(self):
|
||||
@ -182,8 +184,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
def test_pos_returns_with_repayment(self):
|
||||
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': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
|
||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500, 'default': 1})
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
|
||||
@ -200,8 +203,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
||||
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': 'Cash', 'account': 'Cash - _TC', 'amount': 60})
|
||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60, 'default': 1})
|
||||
|
||||
pos.insert()
|
||||
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.serial_no.serial_no import get_serial_nos
|
||||
|
||||
se = make_serialized_item(company='_Test Company with perpetual inventory',
|
||||
target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1')
|
||||
se = make_serialized_item(company='_Test Company',
|
||||
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)
|
||||
|
||||
pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
||||
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
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.submit()
|
||||
|
||||
pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
||||
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
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)
|
||||
|
||||
@ -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)
|
||||
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):
|
||||
args = frappe._dict(args)
|
||||
pos_profile = None
|
||||
@ -292,12 +409,11 @@ def create_pos_invoice(**args):
|
||||
pos_profile.save()
|
||||
|
||||
pos_inv = frappe.new_doc("POS Invoice")
|
||||
pos_inv.update(args)
|
||||
pos_inv.update_stock = 1
|
||||
pos_inv.is_pos = 1
|
||||
pos_inv.pos_profile = args.pos_profile or pos_profile.name
|
||||
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
if args.posting_date:
|
||||
pos_inv.set_posting_time = 1
|
||||
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.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
|
||||
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
pos_inv.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
|
@ -24,11 +24,27 @@ class POSInvoiceMergeLog(Document):
|
||||
|
||||
def validate_pos_invoice_status(self):
|
||||
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:
|
||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
||||
if status in ['Consolidated']:
|
||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice))
|
||||
if status == "Consolidated":
|
||||
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):
|
||||
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]
|
||||
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)
|
||||
else:
|
||||
credit_note = ""
|
||||
|
||||
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
|
||||
|
||||
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'):
|
||||
found = False
|
||||
for t in taxes:
|
||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate:
|
||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount)
|
||||
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_after_discount_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||
found = True
|
||||
if not found:
|
||||
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)
|
||||
|
||||
for payment in doc.get('payments'):
|
||||
@ -118,6 +145,8 @@ class POSInvoiceMergeLog(Document):
|
||||
invoice.set('items', items)
|
||||
invoice.set('payments', payments)
|
||||
invoice.set('taxes', taxes)
|
||||
invoice.additional_discount_percentage = 0
|
||||
invoice.discount_amount = 0.0
|
||||
|
||||
return invoice
|
||||
|
||||
|
@ -5,21 +5,37 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint
|
||||
from frappe.utils import cint, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class POSOpeningEntry(StatusUpdater):
|
||||
def validate(self):
|
||||
self.validate_pos_profile_and_cashier()
|
||||
self.validate_payment_method_account()
|
||||
self.set_status()
|
||||
|
||||
def validate_pos_profile_and_cashier(self):
|
||||
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")):
|
||||
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):
|
||||
self.set_status(update=True)
|
@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"default",
|
||||
"allow_in_returns",
|
||||
"mode_of_payment"
|
||||
],
|
||||
"fields": [
|
||||
@ -24,11 +25,19 @@
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2020-05-29 15:08:41.704844",
|
||||
"modified": "2020-10-20 12:58:46.114456",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Payment Method",
|
||||
|
@ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
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', {
|
||||
@ -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) {
|
||||
if(!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
|
@ -8,7 +8,6 @@
|
||||
"field_order": [
|
||||
"disabled",
|
||||
"section_break_2",
|
||||
"naming_series",
|
||||
"customer",
|
||||
"company",
|
||||
"country",
|
||||
@ -23,6 +22,9 @@
|
||||
"section_break_11",
|
||||
"payments",
|
||||
"section_break_14",
|
||||
"hide_images",
|
||||
"hide_unavailable_items",
|
||||
"auto_add_item_to_cart",
|
||||
"item_groups",
|
||||
"column_break_16",
|
||||
"customer_groups",
|
||||
@ -59,17 +61,6 @@
|
||||
"fieldname": "section_break_2",
|
||||
"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",
|
||||
"fieldtype": "Link",
|
||||
@ -135,7 +126,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Configuration"
|
||||
},
|
||||
{
|
||||
"description": "Only show Items from these Item Groups",
|
||||
@ -302,28 +294,48 @@
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
"mandatory_depends_on": "update_stock",
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Stock"
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_pricing_rule",
|
||||
"fieldtype": "Check",
|
||||
"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",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-29 12:20:30.977272",
|
||||
"modified": "2020-12-10 13:59:28.877572",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
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 frappe.model.document import Document
|
||||
|
||||
@ -13,7 +13,7 @@ class POSProfile(Document):
|
||||
self.validate_default_profile()
|
||||
self.validate_all_link_fields()
|
||||
self.validate_duplicate_groups()
|
||||
self.check_default_payment()
|
||||
self.validate_payment_methods()
|
||||
|
||||
def validate_default_profile(self):
|
||||
for row in self.applicable_for_users:
|
||||
@ -52,14 +52,33 @@ class POSProfile(Document):
|
||||
if len(customer_groups) != len(set(customer_groups)):
|
||||
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
|
||||
|
||||
def check_default_payment(self):
|
||||
if self.payments:
|
||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
||||
if not default_mode_of_payment:
|
||||
frappe.throw(_("Set default mode of payment"))
|
||||
def validate_payment_methods(self):
|
||||
if not self.payments:
|
||||
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||
|
||||
if len(default_mode_of_payment) > 1:
|
||||
frappe.throw(_("Multiple default mode of payment is not allowed"))
|
||||
default_mode = [d.default for d in self.payments if d.default]
|
||||
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):
|
||||
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
|
||||
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.validate_and_sanitize_search_inputs
|
||||
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)
|
||||
|
||||
def make_pos_profile(**args):
|
||||
frappe.db.sql("delete from `tabPOS Payment Method`")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
@ -7,10 +7,9 @@ frappe.ui.form.on('POS Settings', {
|
||||
},
|
||||
|
||||
get_invoice_fields: function(frm) {
|
||||
frappe.model.with_doctype("Sales Invoice", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
||||
d.fieldtype === 'Table') {
|
||||
frappe.model.with_doctype("POS Invoice", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || ['Button'].includes(d.fieldtype)) {
|
||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||
} else {
|
||||
return null;
|
||||
@ -25,7 +24,7 @@ frappe.ui.form.on('POS Settings', {
|
||||
frappe.ui.form.on("POS Field", {
|
||||
fieldname: function(frm, 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;
|
||||
})[0];
|
||||
|
||||
|
@ -42,56 +42,56 @@ frappe.ui.form.on('Pricing Rule', {
|
||||
<tr><td>
|
||||
<h4>
|
||||
<i class="fa fa-hand-right"></i>
|
||||
${__('Notes')}
|
||||
{{__('Notes')}}
|
||||
</h4>
|
||||
<ul>
|
||||
<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>
|
||||
${__("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>
|
||||
${__('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>
|
||||
${__('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>
|
||||
</ul>
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
<h4><i class="fa fa-question-sign"></i>
|
||||
${__('How Pricing Rule is applied?')}
|
||||
{{__('How Pricing Rule is applied?')}}
|
||||
</h4>
|
||||
<ol>
|
||||
<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>
|
||||
${__("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>
|
||||
${__('Pricing Rules are further filtered based on quantity.')}
|
||||
{{__('Pricing Rules are further filtered based on quantity.')}}
|
||||
</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>
|
||||
${__('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>
|
||||
<li>
|
||||
${__('Item Code > Item Group > Brand')}
|
||||
{{__('Item Code > Item Group > Brand')}}
|
||||
</li>
|
||||
<li>
|
||||
${__('Customer > Customer Group > Territory')}
|
||||
{{__('Customer > Customer Group > Territory')}}
|
||||
</li>
|
||||
<li>
|
||||
${__('Supplier > Supplier Type')}
|
||||
{{__('Supplier > Supplier Type')}}
|
||||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
</ol>
|
||||
</td></tr>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
@ -71,6 +72,7 @@
|
||||
"section_break_13",
|
||||
"threshold_percentage",
|
||||
"priority",
|
||||
"condition",
|
||||
"column_break_66",
|
||||
"apply_multiple_pricing_rules",
|
||||
"apply_discount_on_rate",
|
||||
@ -404,6 +406,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
@ -467,6 +470,7 @@
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
||||
"fieldname": "free_item_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate"
|
||||
@ -502,10 +506,10 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Discount on Rate"
|
||||
"label": "Apply Discount on Discounted Rate"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -550,11 +554,18 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Promotional Scheme",
|
||||
"options": "Promotional Scheme"
|
||||
},
|
||||
{
|
||||
"description": "Simple Python Expression, Example: territory != 'All Territories'",
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Condition"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"modified": "2019-12-18 17:29:22.957077",
|
||||
"links": [],
|
||||
"modified": "2020-12-04 00:36:24.698219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -6,9 +6,10 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
import copy
|
||||
import re
|
||||
|
||||
from frappe import throw, _
|
||||
from frappe.utils import flt, cint, getdate
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
from six import string_types
|
||||
@ -30,6 +31,7 @@ class PricingRule(Document):
|
||||
self.validate_max_discount()
|
||||
self.validate_price_list_with_currency()
|
||||
self.validate_dates()
|
||||
self.validate_condition()
|
||||
|
||||
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:
|
||||
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):
|
||||
if not self.selling and not self.buying:
|
||||
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):
|
||||
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()
|
||||
@ -220,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
|
||||
item_details = frappe._dict({
|
||||
"doctype": args.doctype,
|
||||
"has_margin": False,
|
||||
"name": args.name,
|
||||
"parent": args.parent,
|
||||
"parenttype": args.parenttype,
|
||||
"child_docname": args.get('child_docname'),
|
||||
"discount_percentage_on_rate": [],
|
||||
"discount_amount_on_rate": []
|
||||
"child_docname": args.get('child_docname')
|
||||
})
|
||||
|
||||
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:
|
||||
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.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):
|
||||
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')):
|
||||
item_details.margin_type = pricing_rule.margin_type
|
||||
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||
else:
|
||||
item_details.margin_type = None
|
||||
item_details.margin_rate_or_amount = 0.0
|
||||
item_details.has_margin = True
|
||||
|
||||
if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None:
|
||||
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':
|
||||
pricing_rule_rate = 0.0
|
||||
if pricing_rule.currency == args.currency:
|
||||
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({
|
||||
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
|
||||
"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
|
||||
|
||||
field = frappe.scrub(apply_on)
|
||||
if pricing_rule.apply_discount_on_rate:
|
||||
discount_field = "{0}_on_rate".format(field)
|
||||
item_details[discount_field].append(pricing_rule.get(field, 0))
|
||||
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
||||
# Apply discount on discounted rate
|
||||
item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
|
||||
else:
|
||||
if field not in item_details:
|
||||
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)
|
||||
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):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
||||
get_pricing_rule_items)
|
||||
|
@ -430,6 +430,113 @@ class TestPricingRule(unittest.TestCase):
|
||||
|
||||
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):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@ -441,21 +548,30 @@ def make_pricing_rule(**args):
|
||||
"applicable_for": args.applicable_for,
|
||||
"selling": args.selling or 0,
|
||||
"currency": "USD",
|
||||
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
|
||||
"buying": args.buying or 0,
|
||||
"min_qty": args.min_qty or 0.0,
|
||||
"max_qty": args.max_qty or 0.0,
|
||||
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
|
||||
"discount_percentage": args.discount_percentage 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()
|
||||
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)
|
||||
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.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from frappe import _, throw
|
||||
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
|
||||
|
||||
from frappe import _, bold
|
||||
from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
|
||||
|
||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||
|
||||
@ -37,9 +36,12 @@ def get_pricing_rules(args, doc=None):
|
||||
|
||||
rules = []
|
||||
|
||||
pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc)
|
||||
|
||||
if not pricing_rules: return []
|
||||
|
||||
if apply_multiple_pricing_rules(pricing_rules):
|
||||
pricing_rules = sorted_by_priority(pricing_rules)
|
||||
for pricing_rule in pricing_rules:
|
||||
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
||||
if pricing_rule:
|
||||
@ -51,6 +53,37 @@ def get_pricing_rules(args, doc=None):
|
||||
|
||||
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):
|
||||
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)))
|
||||
|
||||
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 allow_blank: parent_groups.append('')
|
||||
@ -265,12 +306,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
|
||||
fieldname = field
|
||||
|
||||
if fieldname:
|
||||
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
|
||||
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
|
||||
msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
|
||||
.format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
|
||||
|
||||
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.
|
||||
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
|
||||
msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
|
||||
.format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
|
||||
bold(item_code), bold(args.rule_description)))
|
||||
|
||||
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,
|
||||
doc.total, pricing_rules)
|
||||
|
||||
if not pricing_rules:
|
||||
remove_free_item(doc)
|
||||
|
||||
for d in pricing_rules:
|
||||
if d.price_or_product_discount == 'Price':
|
||||
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)
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
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):
|
||||
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):
|
||||
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
|
||||
|
||||
if not free_item:
|
||||
|
@ -21,7 +21,7 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
item.no_of_months = 12
|
||||
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].service_start_date = "2019-01-10"
|
||||
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";
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.set_query("unrealized_profit_loss_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
is_group: 0,
|
||||
root_type: "Liability",
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
onload: function() {
|
||||
this._super();
|
||||
@ -25,6 +35,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
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) {
|
||||
@ -93,6 +109,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
target: me.frm,
|
||||
setters: {
|
||||
supplier: me.frm.doc.supplier || undefined,
|
||||
schedule_date: undefined
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
@ -101,16 +118,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}, __("Get Items From"));
|
||||
|
||||
this.frm.add_custom_button(__('Purchase Receipt'), function() {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
|
||||
source_doctype: "Purchase Receipt",
|
||||
target: me.frm,
|
||||
date_field: "posting_date",
|
||||
setters: {
|
||||
supplier: me.frm.doc.supplier || undefined,
|
||||
posting_date: undefined
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
@ -119,7 +136,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
is_return: 0
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}, __("Get Items From"));
|
||||
}
|
||||
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() {
|
||||
|
@ -126,6 +126,7 @@
|
||||
"write_off_cost_center",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"adjust_advance_taxes",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"payment_schedule_section",
|
||||
@ -151,9 +152,11 @@
|
||||
"is_opening",
|
||||
"against_expense_account",
|
||||
"column_break_63",
|
||||
"unrealized_profit_loss_account",
|
||||
"status",
|
||||
"inter_company_invoice_reference",
|
||||
"is_internal_supplier",
|
||||
"represents_company",
|
||||
"remarks",
|
||||
"subscription_section",
|
||||
"from_date",
|
||||
@ -361,6 +364,7 @@
|
||||
"fieldname": "bill_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Supplier Invoice Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "bill_date",
|
||||
"oldfieldtype": "Date",
|
||||
"print_hide": 1
|
||||
@ -1221,7 +1225,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_standard_filter": 1,
|
||||
"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
|
||||
},
|
||||
{
|
||||
@ -1328,13 +1332,37 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "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",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 23:20:04.466153",
|
||||
"modified": "2020-12-11 12:46:12.796378",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -132,6 +132,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if not self.due_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)
|
||||
|
||||
def check_conversion_rate(self):
|
||||
@ -142,18 +147,25 @@ class PurchaseInvoice(BuyingController):
|
||||
throw(_("Conversion rate cannot be 0 or 1"))
|
||||
|
||||
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_type", "report_type", "account_currency"], as_dict=True)
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
frappe.throw(_("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"))
|
||||
frappe.throw(
|
||||
_("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")
|
||||
)
|
||||
|
||||
if self.supplier and account.account_type != "Payable":
|
||||
frappe.throw(_("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"))
|
||||
frappe.throw(
|
||||
_("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")
|
||||
)
|
||||
|
||||
self.party_account_currency = account.account_currency
|
||||
|
||||
@ -194,8 +206,8 @@ class PurchaseInvoice(BuyingController):
|
||||
["Purchase Receipt", "purchase_receipt", "pr_detail"]
|
||||
])
|
||||
|
||||
def validate_warehouse(self):
|
||||
if self.update_stock:
|
||||
def validate_warehouse(self, for_validate=True):
|
||||
if self.update_stock and for_validate:
|
||||
for d in self.get('items'):
|
||||
if not d.warehouse:
|
||||
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:
|
||||
self.validate_item_code()
|
||||
self.validate_warehouse()
|
||||
self.validate_warehouse(for_validate)
|
||||
if auto_accounting_for_stock:
|
||||
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 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}
|
||||
is not linked to warehouse {3} or it is not the default inventory account'''.format(
|
||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]),
|
||||
frappe.bold(item.expense_account), frappe.bold(item.warehouse))))
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
||||
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||
msg += _("or it is not the default inventory account")
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||
else:
|
||||
@ -254,19 +266,19 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if negative_expense_booked_in_pr:
|
||||
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
|
||||
expense is booked against this account in Purchase Receipt {2}'''.format(
|
||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))))
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
else:
|
||||
# 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
|
||||
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
|
||||
Receipt is created against Item {2}. This is done to handle accounting for cases
|
||||
when Purchase Receipt is created after Purchase Invoice'''.format(
|
||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))))
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
||||
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
|
||||
@ -294,10 +306,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
for d in self.get('items'):
|
||||
if not d.purchase_order:
|
||||
throw(_("""Purchase Order Required for item {0}
|
||||
To submit the invoice without purchase order please set
|
||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Order Required')),
|
||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
||||
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||
msg += "<br><br>"
|
||||
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
||||
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):
|
||||
stock_items = self.get_stock_items()
|
||||
@ -308,10 +321,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
for d in self.get('items'):
|
||||
if not d.purchase_receipt and d.item_code in stock_items:
|
||||
throw(_("""Purchase Receipt Required for item {0}
|
||||
To submit the invoice without purchase receipt please set
|
||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Receipt Required')),
|
||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
||||
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||
msg += "<br><br>"
|
||||
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
||||
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):
|
||||
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
|
||||
self.make_gl_entries()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.update_project()
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
|
||||
def make_gl_entries(self, gl_entries=None):
|
||||
if not self.grand_total:
|
||||
return
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
if not 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"
|
||||
|
||||
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:
|
||||
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)
|
||||
if self.auto_accounting_for_stock:
|
||||
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:
|
||||
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
|
||||
gl_entries = []
|
||||
|
||||
@ -437,6 +454,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_asset_gl_entry(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)
|
||||
|
||||
@ -445,7 +463,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_payment_gl_entries(gl_entries)
|
||||
self.make_write_off_gl_entry(gl_entries)
|
||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||
|
||||
return gl_entries
|
||||
|
||||
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
|
||||
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||
|
||||
if grand_total:
|
||||
# Didnot use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.credit_to,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"due_date": self.due_date,
|
||||
"against": self.against_expense_account,
|
||||
"credit": grand_total_in_company_currency,
|
||||
"credit_in_account_currency": grand_total_in_company_currency \
|
||||
if self.party_account_currency==self.company_currency else grand_total,
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"project": self.project,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
# Didnot use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.credit_to,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"due_date": self.due_date,
|
||||
"against": self.against_expense_account,
|
||||
"credit": grand_total_in_company_currency,
|
||||
"credit_in_account_currency": grand_total_in_company_currency \
|
||||
if self.party_account_currency==self.company_currency else grand_total,
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"project": self.project,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
def make_item_gl_entries(self, gl_entries):
|
||||
# item gl entries
|
||||
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:
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
@ -514,7 +530,6 @@ class PurchaseInvoice(BuyingController):
|
||||
item, voucher_wise_stock_value, account_currency)
|
||||
|
||||
if item.from_warehouse:
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": warehouse_account[item.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")),
|
||||
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
# Do not book expense for transfer within same company transfer
|
||||
if not self.is_internal_transfer():
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
else:
|
||||
gl_entries.append(
|
||||
@ -708,7 +725,8 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount / self.conversion_rate)
|
||||
}, item=item))
|
||||
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)
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
@ -819,7 +837,8 @@ class PurchaseInvoice(BuyingController):
|
||||
}, account_currency, item=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:
|
||||
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)
|
||||
@ -863,8 +882,19 @@ class PurchaseInvoice(BuyingController):
|
||||
"against": self.supplier,
|
||||
"credit": valuation_tax[tax.name],
|
||||
"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):
|
||||
# Make Cash GL Entries
|
||||
@ -969,11 +999,15 @@ class PurchaseInvoice(BuyingController):
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.update_project()
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
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):
|
||||
project_list = []
|
||||
@ -1024,7 +1058,9 @@ class PurchaseInvoice(BuyingController):
|
||||
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
|
||||
|
||||
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):
|
||||
self.due_date = None
|
||||
@ -1080,7 +1116,9 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.docstatus == 2:
|
||||
status = "Cancelled"
|
||||
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"
|
||||
elif outstanding_amount > 0 and due_date >= nowdate:
|
||||
self.status = "Unpaid"
|
||||
|
@ -4,23 +4,25 @@
|
||||
// render
|
||||
frappe.listview_settings['Purchase Invoice'] = {
|
||||
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) {
|
||||
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"];
|
||||
} 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) {
|
||||
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"];
|
||||
} 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"];
|
||||
} else {
|
||||
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"];
|
||||
} 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"];
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,7 @@ import frappe.model
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from frappe.utils import cint, flt, today, nowdate, add_days, getdate
|
||||
import frappe.defaults
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
|
||||
test_records as pr_test_records, make_purchase_receipt, get_taxes
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt, get_taxes
|
||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
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):
|
||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||
wrapper = frappe.copy_doc(test_records[0])
|
||||
set_perpetual_inventory(0, wrapper.company)
|
||||
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(wrapper.company)))
|
||||
wrapper.insert()
|
||||
wrapper.submit()
|
||||
wrapper.load_from_db()
|
||||
dl = wrapper
|
||||
pi = frappe.copy_doc(test_records[0])
|
||||
self.assertTrue(not cint(erpnext.is_perpetual_inventory_enabled(pi.company)))
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
expected_gl_entries = {
|
||||
"_Test Payable - _TC": [0, 1512.0],
|
||||
@ -54,12 +50,16 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
"Round Off - _TC": [0, 0.3]
|
||||
}
|
||||
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:
|
||||
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
|
||||
|
||||
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.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,)
|
||||
|
||||
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")
|
||||
|
||||
for d in pi.items:
|
||||
@ -247,17 +245,11 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
|
||||
|
||||
def test_gl_entries_with_aia_for_non_stock_items(self):
|
||||
pi = frappe.copy_doc(test_records[1])
|
||||
set_perpetual_inventory(1, pi.company)
|
||||
self.assertTrue(cint(erpnext.is_perpetual_inventory_enabled(pi.company)), 1)
|
||||
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()
|
||||
def test_gl_entries_for_non_stock_items_with_perpetual_inventory(self):
|
||||
pi = make_purchase_invoice(item_code = "_Test Non Stock Item",
|
||||
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||
|
||||
self.assertTrue(pi.status, "Unpaid")
|
||||
|
||||
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)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = sorted([
|
||||
["_Test Payable - _TC", 0, 620],
|
||||
["_Test Account Cost for Goods Sold - _TC", 500.0, 0],
|
||||
["_Test Account VAT - _TC", 120.0, 0],
|
||||
])
|
||||
expected_values = [
|
||||
["_Test Account Cost for Goods Sold - TCP1", 250.0, 0],
|
||||
["Creditors - TCP1", 0, 250]
|
||||
]
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[i][0], gle.account)
|
||||
self.assertEqual(expected_values[i][1], gle.debit)
|
||||
self.assertEqual(expected_values[i][2], gle.credit)
|
||||
set_perpetual_inventory(0, pi.company)
|
||||
|
||||
def test_purchase_invoice_calculation(self):
|
||||
pi = frappe.copy_doc(test_records[0])
|
||||
@ -457,12 +447,13 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
|
||||
|
||||
def test_return_purchase_invoice(self):
|
||||
set_perpetual_inventory()
|
||||
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
||||
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)
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
|
||||
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||
|
||||
|
||||
# check gl entries for return
|
||||
@ -473,19 +464,15 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = {
|
||||
"Creditors - _TC": [100.0, 0.0],
|
||||
"Stock Received But Not Billed - _TC": [0.0, 100.0],
|
||||
"Creditors - TCP1": [100.0, 0.0],
|
||||
"Stock Received But Not Billed - TCP1": [0.0, 100.0],
|
||||
}
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_multi_currency_gle(self):
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", credit_to="_Test Payable USD - _TC",
|
||||
currency="USD", conversion_rate=50)
|
||||
|
||||
@ -640,10 +627,9 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(len(pi.get("supplied_items")), 2)
|
||||
|
||||
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):
|
||||
set_perpetual_inventory(0)
|
||||
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_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',
|
||||
"conversion_factor": 1.0,
|
||||
"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",
|
||||
"project": args.project,
|
||||
"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:
|
||||
@ -1039,7 +1026,8 @@ def make_purchase_invoice_against_cost_center(**args):
|
||||
pi.is_return = args.is_return
|
||||
pi.credit_to = args.return_against or "Creditors - _TC"
|
||||
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", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
|
@ -210,7 +210,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-12 14:53:47.679439",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges",
|
||||
|
@ -78,7 +78,7 @@
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges Template",
|
||||
"owner": "wasim@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
|
@ -1,92 +1,38 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2016-07-27 17:24:24.956896",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "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
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
|
||||
"fieldname": "default_account",
|
||||
"fieldname": "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
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-09-02 07:49:06.567389",
|
||||
"links": [],
|
||||
"modified": "2020-10-18 17:57:57.110257",
|
||||
"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
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
{% include "erpnext/regional/india/taxes.js" %}
|
||||
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||
|
||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||
|
||||
frappe.ui.form.on("Sales Invoice", {
|
||||
setup: function(frm) {
|
||||
|
@ -199,7 +199,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}, __("Get Items From"));
|
||||
},
|
||||
|
||||
quotation_btn: function() {
|
||||
@ -223,7 +223,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}, __("Get Items From"));
|
||||
},
|
||||
|
||||
delivery_note_btn: function() {
|
||||
@ -251,7 +251,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
};
|
||||
}
|
||||
});
|
||||
}, __("Get items from"));
|
||||
}, __("Get Items From"));
|
||||
},
|
||||
|
||||
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 = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'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) {
|
||||
frm.add_custom_button(__('Healthcare Services'), function() {
|
||||
get_healthcare_services_to_invoice(frm);
|
||||
},"Get items from");
|
||||
},"Get Items From");
|
||||
frm.add_custom_button(__('Prescriptions'), function() {
|
||||
get_drugs_to_invoice(frm);
|
||||
},"Get items from");
|
||||
},"Get Items From");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -19,6 +19,7 @@
|
||||
"is_return",
|
||||
"column_break1",
|
||||
"company",
|
||||
"company_tax_id",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"set_posting_time",
|
||||
@ -156,6 +157,7 @@
|
||||
"more_information",
|
||||
"inter_company_invoice_reference",
|
||||
"is_internal_customer",
|
||||
"represents_company",
|
||||
"customer_group",
|
||||
"campaign",
|
||||
"is_discounted",
|
||||
@ -169,6 +171,7 @@
|
||||
"c_form_applicable",
|
||||
"c_form_no",
|
||||
"column_break8",
|
||||
"unrealized_profit_loss_account",
|
||||
"remarks",
|
||||
"sales_team_section_break",
|
||||
"sales_partner",
|
||||
@ -1653,7 +1656,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"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,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -1825,7 +1828,7 @@
|
||||
"fieldtype": "Table",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Sales Team1",
|
||||
"label": "Sales Contributions and Incentives",
|
||||
"oldfieldname": "sales_team",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Sales Team",
|
||||
@ -1926,6 +1929,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.is_pos && doc.is_consolidated)",
|
||||
"fieldname": "is_consolidated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Consolidated",
|
||||
@ -1940,13 +1944,38 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Is Internal Customer",
|
||||
"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",
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-27 01:56:28.532140",
|
||||
"modified": "2020-12-11 12:48:31.769958",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
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 erpnext.accounts.party import get_party_account, get_due_date
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@ -180,6 +180,9 @@ class SalesInvoice(SellingController):
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
if not self.is_return:
|
||||
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
@ -229,9 +232,9 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
||||
def before_cancel(self):
|
||||
super(SalesInvoice, self).before_cancel()
|
||||
self.update_time_sheet(None)
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
super(SalesInvoice, self).on_cancel()
|
||||
|
||||
@ -258,6 +261,10 @@ class SalesInvoice(SellingController):
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
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:
|
||||
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):
|
||||
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
|
||||
if not self.pos_profile:
|
||||
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')
|
||||
|
||||
pos = {}
|
||||
@ -428,7 +437,7 @@ class SalesInvoice(SellingController):
|
||||
if 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',
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||
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]
|
||||
|
||||
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_type", "report_type", "account_currency"], as_dict=True)
|
||||
|
||||
@ -479,14 +493,14 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
frappe.throw(_("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("Debit To")), title=_("Invalid Account"))
|
||||
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
if self.customer and account.account_type != "Receivable":
|
||||
frappe.throw(_("Please ensure {} account is a Receivable account. \
|
||||
Change the account type to Receivable or select a different account.")
|
||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
||||
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||
msg += _("Change the account type to Receivable or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
self.party_account_currency = account.account_currency
|
||||
|
||||
@ -535,7 +549,12 @@ class SalesInvoice(SellingController):
|
||||
self.against_income_account = ','.join(against_acc)
|
||||
|
||||
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):
|
||||
# Don't auto set the posting date and time if invoice is amended
|
||||
@ -572,7 +591,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def validate_pos(self):
|
||||
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)):
|
||||
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:
|
||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
||||
|
||||
def make_gl_entries(self, gl_entries=None):
|
||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||
|
||||
auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||
if not gl_entries:
|
||||
gl_entries = self.get_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
|
||||
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account or
|
||||
cint(self.redeem_loyalty_points)) else "Yes"
|
||||
|
||||
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:
|
||||
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_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_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
|
||||
# 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
|
||||
if grand_total:
|
||||
if grand_total and not self.is_internal_transfer():
|
||||
# Didnot use base_grand_total to book rounding loss gle
|
||||
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
|
||||
self.precision("grand_total"))
|
||||
@ -808,6 +827,18 @@ class SalesInvoice(SellingController):
|
||||
}, 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):
|
||||
# income account gl entries
|
||||
for item in self.get("items"):
|
||||
@ -830,22 +861,24 @@ class SalesInvoice(SellingController):
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||
else:
|
||||
income_account = (item.income_account
|
||||
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
|
||||
# Do not book income for transfer within same company
|
||||
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)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": income_account,
|
||||
"against": self.customer,
|
||||
"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"))
|
||||
if account_currency==self.company_currency
|
||||
else flt(item.net_amount, item.precision("net_amount"))),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
account_currency = get_account_currency(income_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": income_account,
|
||||
"against": self.customer,
|
||||
"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"))
|
||||
if account_currency==self.company_currency
|
||||
else flt(item.net_amount, item.precision("net_amount"))),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
# expense account gl entries
|
||||
if cint(self.update_stock) and \
|
||||
@ -1140,8 +1173,10 @@ class SalesInvoice(SellingController):
|
||||
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
||||
if 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.
|
||||
First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list))
|
||||
frappe.throw(
|
||||
_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}''')
|
||||
.format(self.doctype, self.doctype, invoice_list)
|
||||
)
|
||||
else:
|
||||
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
||||
# Set loyalty program
|
||||
@ -1255,7 +1290,9 @@ class SalesInvoice(SellingController):
|
||||
if self.docstatus == 2:
|
||||
status = "Cancelled"
|
||||
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"
|
||||
elif outstanding_amount > 0 and due_date < nowdate:
|
||||
self.status = "Overdue"
|
||||
@ -1372,7 +1409,7 @@ def get_bank_cash_account(mode_of_payment, company):
|
||||
{"parent": mode_of_payment, "company": company}, "default_account")
|
||||
if not account:
|
||||
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 {
|
||||
"account": account
|
||||
}
|
||||
@ -1398,6 +1435,7 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
def set_missing_values(source, target):
|
||||
target.ignore_pricing_rule = 1
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("set_po_nos")
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
|
||||
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"]:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
|
||||
source_document_warehouse_field = 'target_warehouse'
|
||||
target_document_warehouse_field = 'from_warehouse'
|
||||
else:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
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)
|
||||
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:
|
||||
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, {
|
||||
doctype: {
|
||||
"doctype": target_doctype,
|
||||
@ -1556,15 +1618,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
"taxes_and_charges"
|
||||
]
|
||||
},
|
||||
doctype +" Item": {
|
||||
"doctype": target_doctype + " Item",
|
||||
"field_no_map": [
|
||||
"income_account",
|
||||
"expense_account",
|
||||
"cost_center",
|
||||
"warehouse"
|
||||
]
|
||||
}
|
||||
doctype +" Item": item_field_map
|
||||
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
@ -1612,18 +1666,24 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
payment.type = payment_mode.type
|
||||
|
||||
doc.set('payments', [])
|
||||
if not pos_profile or not pos_profile.get('payments'):
|
||||
for payment_mode in get_all_mode_of_payments(doc):
|
||||
append_payment(payment_mode)
|
||||
return
|
||||
|
||||
invalid_modes = []
|
||||
for pos_payment_method in pos_profile.get('payments'):
|
||||
pos_payment_method = pos_payment_method.as_dict()
|
||||
|
||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||
if payment_mode:
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
if not payment_mode:
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
|
||||
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):
|
||||
return frappe.db.sql("""
|
||||
|
@ -13,8 +13,7 @@ def get_data():
|
||||
'Auto Repeat': 'reference_document',
|
||||
},
|
||||
'internal_links': {
|
||||
'Sales Order': ['items', 'sales_order'],
|
||||
'Delivery Note': ['items', 'delivery_note']
|
||||
'Sales Order': ['items', 'sales_order']
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
|
@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = {
|
||||
"Credit Note Issued": "darkgrey",
|
||||
"Unpaid and Discounted": "orange",
|
||||
"Overdue and Discounted": "red",
|
||||
"Overdue": "red"
|
||||
|
||||
"Overdue": "red",
|
||||
"Internal Transfer": "darkgrey"
|
||||
};
|
||||
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…
Reference in New Issue
Block a user