Merge branch 'develop' into naming-series-proj
This commit is contained in:
commit
f30c389e4d
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":
|
||||||
|
_, org, repo, _type, ref = parsed_url.path.split('/')
|
||||||
|
if org == "frappe" and repo 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
|
@ -16,7 +16,7 @@
|
|||||||
ERPNext as a monolith includes the following areas for managing businesses:
|
ERPNext as a monolith includes the following areas for managing businesses:
|
||||||
|
|
||||||
1. [Accounting](https://erpnext.com/open-source-accounting)
|
1. [Accounting](https://erpnext.com/open-source-accounting)
|
||||||
1. [Inventory](https://erpnext.com/distribution/inventory-management-system)
|
1. [Warehouse Management](https://erpnext.com/distribution/warehouse-management-system)
|
||||||
1. [CRM](https://erpnext.com/open-source-crm)
|
1. [CRM](https://erpnext.com/open-source-crm)
|
||||||
1. [Sales](https://erpnext.com/open-source-sales-purchase)
|
1. [Sales](https://erpnext.com/open-source-sales-purchase)
|
||||||
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
|
1. [Purchase](https://erpnext.com/open-source-sales-purchase)
|
||||||
|
@ -1,58 +1,126 @@
|
|||||||
{
|
{
|
||||||
"custom_fields": [
|
"custom_fields": [
|
||||||
{
|
{
|
||||||
"_assign": null,
|
"_assign": null,
|
||||||
"_comments": null,
|
"_comments": null,
|
||||||
"_liked_by": null,
|
"_liked_by": null,
|
||||||
"_user_tags": null,
|
"_user_tags": null,
|
||||||
"allow_on_submit": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"bold": 0,
|
"allow_on_submit": 0,
|
||||||
"collapsible": 0,
|
"bold": 0,
|
||||||
"collapsible_depends_on": null,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"collapsible_depends_on": null,
|
||||||
"creation": "2018-12-28 22:29:21.828090",
|
"columns": 0,
|
||||||
"default": null,
|
"creation": "2018-12-28 22:29:21.828090",
|
||||||
"depends_on": null,
|
"default": null,
|
||||||
"description": null,
|
"depends_on": null,
|
||||||
"docstatus": 0,
|
"description": null,
|
||||||
"dt": "Address",
|
"docstatus": 0,
|
||||||
"fetch_from": null,
|
"dt": "Address",
|
||||||
"fieldname": "tax_category",
|
"fetch_from": null,
|
||||||
"fieldtype": "Link",
|
"fetch_if_empty": 0,
|
||||||
"hidden": 0,
|
"fieldname": "tax_category",
|
||||||
"idx": 14,
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 0,
|
"hidden": 0,
|
||||||
"ignore_xss_filter": 0,
|
"hide_border": 0,
|
||||||
"in_global_search": 0,
|
"hide_days": 0,
|
||||||
"in_list_view": 0,
|
"hide_seconds": 0,
|
||||||
"in_standard_filter": 0,
|
"idx": 15,
|
||||||
"insert_after": "fax",
|
"ignore_user_permissions": 0,
|
||||||
"label": "Tax Category",
|
"ignore_xss_filter": 0,
|
||||||
"modified": "2018-12-28 22:29:21.828090",
|
"in_global_search": 0,
|
||||||
"modified_by": "Administrator",
|
"in_list_view": 0,
|
||||||
"name": "Address-tax_category",
|
"in_preview": 0,
|
||||||
"no_copy": 0,
|
"in_standard_filter": 0,
|
||||||
"options": "Tax Category",
|
"insert_after": "fax",
|
||||||
"owner": "Administrator",
|
"label": "Tax Category",
|
||||||
"parent": null,
|
"length": 0,
|
||||||
"parentfield": null,
|
"mandatory_depends_on": null,
|
||||||
"parenttype": null,
|
"modified": "2018-12-28 22:29:21.828090",
|
||||||
"permlevel": 0,
|
"modified_by": "Administrator",
|
||||||
"precision": "",
|
"name": "Address-tax_category",
|
||||||
"print_hide": 0,
|
"no_copy": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"options": "Tax Category",
|
||||||
"print_width": null,
|
"owner": "Administrator",
|
||||||
"read_only": 0,
|
"parent": null,
|
||||||
"report_hide": 0,
|
"parentfield": null,
|
||||||
"reqd": 0,
|
"parenttype": null,
|
||||||
"search_index": 0,
|
"permlevel": 0,
|
||||||
"translatable": 0,
|
"precision": "",
|
||||||
"unique": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"print_width": null,
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_depends_on": null,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0,
|
||||||
|
"width": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_assign": null,
|
||||||
|
"_comments": null,
|
||||||
|
"_liked_by": null,
|
||||||
|
"_user_tags": null,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"collapsible_depends_on": null,
|
||||||
|
"columns": 0,
|
||||||
|
"creation": "2020-10-14 17:41:40.878179",
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": null,
|
||||||
|
"description": null,
|
||||||
|
"docstatus": 0,
|
||||||
|
"dt": "Address",
|
||||||
|
"fetch_from": null,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
|
"fieldname": "is_your_company_address",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"hide_border": 0,
|
||||||
|
"hide_days": 0,
|
||||||
|
"hide_seconds": 0,
|
||||||
|
"idx": 20,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_preview": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"insert_after": "linked_with",
|
||||||
|
"label": "Is Your Company Address",
|
||||||
|
"length": 0,
|
||||||
|
"mandatory_depends_on": null,
|
||||||
|
"modified": "2020-10-14 17:41:40.878179",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"name": "Address-is_your_company_address",
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": null,
|
||||||
|
"owner": "Administrator",
|
||||||
|
"parent": null,
|
||||||
|
"parentfield": null,
|
||||||
|
"parenttype": null,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"print_width": null,
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_depends_on": null,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0,
|
||||||
"width": null
|
"width": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"custom_perms": [],
|
"custom_perms": [],
|
||||||
"doctype": "Address",
|
"doctype": "Address",
|
||||||
"property_setters": [],
|
"property_setters": [],
|
||||||
"sync_on_migrate": 1
|
"sync_on_migrate": 1
|
||||||
}
|
}
|
42
erpnext/accounts/custom/address.py
Normal file
42
erpnext/accounts/custom/address.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.contacts.doctype.address.address import Address
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_templates
|
||||||
|
|
||||||
|
class ERPNextAddress(Address):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_reference()
|
||||||
|
super(ERPNextAddress, self).validate()
|
||||||
|
|
||||||
|
def link_address(self):
|
||||||
|
"""Link address based on owner"""
|
||||||
|
if self.is_your_company_address:
|
||||||
|
return
|
||||||
|
|
||||||
|
return super(ERPNextAddress, self).link_address()
|
||||||
|
|
||||||
|
def validate_reference(self):
|
||||||
|
if self.is_your_company_address and not [
|
||||||
|
row for row in self.links if row.link_doctype == "Company"
|
||||||
|
]:
|
||||||
|
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
||||||
|
title=_("Company Not Linked"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_shipping_address(company, address = None):
|
||||||
|
filters = [
|
||||||
|
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||||
|
["Dynamic Link", "link_name", "=", company],
|
||||||
|
["Address", "is_your_company_address", "=", 1]
|
||||||
|
]
|
||||||
|
fields = ["*"]
|
||||||
|
if address and frappe.db.get_value('Dynamic Link',
|
||||||
|
{'parent': address, 'link_name': company}):
|
||||||
|
filters.append(["Address", "name", "=", address])
|
||||||
|
|
||||||
|
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
||||||
|
|
||||||
|
if address:
|
||||||
|
address_as_dict = address[0]
|
||||||
|
name, address_template = get_address_templates(address_as_dict)
|
||||||
|
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)
|
@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Bank Statement",
|
"label": "Bank Statement",
|
||||||
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank 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]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -53,7 +53,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Goods and Services Tax (GST India)",
|
"label": "Goods and Services Tax (GST India)",
|
||||||
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"India\",\n \"label\": \"Lower Deduction Certificate\",\n \"name\": \"Lower Deduction Certificate\",\n \"type\": \"doctype\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -98,7 +98,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Accounting",
|
"label": "Accounting",
|
||||||
"modified": "2020-09-03 10:37:07.865801",
|
"modified": "2020-10-08 20:31:46.022470",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
@ -108,7 +108,7 @@
|
|||||||
"pin_to_top": 0,
|
"pin_to_top": 0,
|
||||||
"shortcuts": [
|
"shortcuts": [
|
||||||
{
|
{
|
||||||
"label": "Chart Of Accounts",
|
"label": "Chart of Accounts",
|
||||||
"link_to": "Account",
|
"link_to": "Account",
|
||||||
"type": "DocType"
|
"type": "DocType"
|
||||||
},
|
},
|
||||||
@ -147,11 +147,6 @@
|
|||||||
"link_to": "Trial Balance",
|
"link_to": "Trial Balance",
|
||||||
"type": "Report"
|
"type": "Report"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Point of Sale",
|
|
||||||
"link_to": "point-of-sale",
|
|
||||||
"type": "Page"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Dashboard",
|
"label": "Dashboard",
|
||||||
"link_to": "Accounts",
|
"link_to": "Accounts",
|
||||||
|
@ -117,7 +117,9 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||||
parent_acc_name_map[d["company"]] = d["name"]
|
parent_acc_name_map[d["company"]] = d["name"]
|
||||||
|
|
||||||
if not parent_acc_name_map: return
|
if not parent_acc_name_map: return
|
||||||
|
|
||||||
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
||||||
|
|
||||||
def validate_group_or_ledger(self):
|
def validate_group_or_ledger(self):
|
||||||
@ -289,10 +291,30 @@ def validate_account_number(name, account_number, company):
|
|||||||
.format(account_number, account_with_same_number))
|
.format(account_number, account_with_same_number))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_account_number(name, account_name, account_number=None):
|
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||||
|
|
||||||
account = frappe.db.get_value("Account", name, "company", as_dict=True)
|
account = frappe.db.get_value("Account", name, "company", as_dict=True)
|
||||||
if not account: return
|
if not account: return
|
||||||
|
|
||||||
|
old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
|
||||||
|
["account_name", "account_number"])
|
||||||
|
|
||||||
|
# check if account exists in parent company
|
||||||
|
ancestors = get_ancestors_of("Company", account.company)
|
||||||
|
allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
|
||||||
|
|
||||||
|
if ancestors and not allow_independent_account_creation:
|
||||||
|
for ancestor in ancestors:
|
||||||
|
if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
|
||||||
|
# same account in parent company exists
|
||||||
|
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
||||||
|
|
||||||
|
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
|
||||||
|
message += "<br>" + _("Renaming it is only allowed via parent company {0}, \
|
||||||
|
to avoid mismatch.").format(frappe.bold(ancestor)) + "<br><br>"
|
||||||
|
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
|
||||||
|
|
||||||
|
frappe.throw(message, title=_("Rename Not Allowed"))
|
||||||
|
|
||||||
validate_account_number(name, account_number, account.company)
|
validate_account_number(name, account_number, account.company)
|
||||||
if account_number:
|
if account_number:
|
||||||
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
||||||
@ -300,6 +322,12 @@ def update_account_number(name, account_name, account_number=None):
|
|||||||
frappe.db.set_value("Account", name, "account_number", "")
|
frappe.db.set_value("Account", name, "account_number", "")
|
||||||
frappe.db.set_value("Account", name, "account_name", account_name.strip())
|
frappe.db.set_value("Account", name, "account_name", account_name.strip())
|
||||||
|
|
||||||
|
if not from_descendant:
|
||||||
|
# Update and rename in child company accounts as well
|
||||||
|
descendants = get_descendants_of('Company', account.company)
|
||||||
|
if descendants:
|
||||||
|
sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
|
||||||
|
|
||||||
new_name = get_account_autoname(account_number, account_name, account.company)
|
new_name = get_account_autoname(account_number, account_name, account.company)
|
||||||
if name != new_name:
|
if name != new_name:
|
||||||
frappe.rename_doc("Account", name, new_name, force=1)
|
frappe.rename_doc("Account", name, new_name, force=1)
|
||||||
@ -330,3 +358,14 @@ def get_root_company(company):
|
|||||||
# return the topmost company in the hierarchy
|
# return the topmost company in the hierarchy
|
||||||
ancestors = get_ancestors_of('Company', company, "lft asc")
|
ancestors = get_ancestors_of('Company', company, "lft asc")
|
||||||
return [ancestors[0]] if ancestors else []
|
return [ancestors[0]] if ancestors else []
|
||||||
|
|
||||||
|
def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
|
||||||
|
filters = {
|
||||||
|
"company": ["in", descendants],
|
||||||
|
"account_name": old_acc_name,
|
||||||
|
}
|
||||||
|
if old_acc_number:
|
||||||
|
filters["account_number"] = old_acc_number
|
||||||
|
|
||||||
|
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||||
|
update_account_number(d["name"], account_name, account_number, from_descendant=True)
|
||||||
|
@ -2,7 +2,7 @@ frappe.provide("frappe.treeview_settings")
|
|||||||
|
|
||||||
frappe.treeview_settings["Account"] = {
|
frappe.treeview_settings["Account"] = {
|
||||||
breadcrumb: "Accounts",
|
breadcrumb: "Accounts",
|
||||||
title: __("Chart Of Accounts"),
|
title: __("Chart of Accounts"),
|
||||||
get_tree_root: false,
|
get_tree_root: false,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
@ -97,7 +97,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
treeview.page.add_inner_button(__("Journal Entry"), function() {
|
treeview.page.add_inner_button(__("Journal Entry"), function() {
|
||||||
frappe.new_doc('Journal Entry', {company: get_company()});
|
frappe.new_doc('Journal Entry', {company: get_company()});
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
treeview.page.add_inner_button(__("New Company"), function() {
|
treeview.page.add_inner_button(__("Company"), function() {
|
||||||
frappe.new_doc('Company');
|
frappe.new_doc('Company');
|
||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import unittest
|
import unittest
|
||||||
import frappe
|
import frappe
|
||||||
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
|
from erpnext.stock import get_warehouse_account, get_company_default_inventory_account
|
||||||
from erpnext.accounts.doctype.account.account import update_account_number
|
from erpnext.accounts.doctype.account.account import update_account_number, merge_account
|
||||||
from erpnext.accounts.doctype.account.account import merge_account
|
|
||||||
|
|
||||||
class TestAccount(unittest.TestCase):
|
class TestAccount(unittest.TestCase):
|
||||||
def test_rename_account(self):
|
def test_rename_account(self):
|
||||||
@ -99,7 +98,8 @@ class TestAccount(unittest.TestCase):
|
|||||||
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
|
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
|
||||||
|
|
||||||
def test_account_sync(self):
|
def test_account_sync(self):
|
||||||
del frappe.local.flags["ignore_root_company_validation"]
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
acc = frappe.new_doc("Account")
|
acc = frappe.new_doc("Account")
|
||||||
acc.account_name = "Test Sync Account"
|
acc.account_name = "Test Sync Account"
|
||||||
acc.parent_account = "Temporary Accounts - _TC3"
|
acc.parent_account = "Temporary Accounts - _TC3"
|
||||||
@ -111,6 +111,55 @@ class TestAccount(unittest.TestCase):
|
|||||||
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
||||||
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
||||||
|
|
||||||
|
def 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):
|
def _make_test_records(verbose):
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.test_runner import make_test_objects
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Unlink Advance Payment on Cancelation of Order"
|
"label": "Unlink Advance Payment on Cancellation of Order"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
@ -223,9 +223,10 @@
|
|||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-03 20:13:26.043092",
|
"modified": "2020-10-07 14:58:50.325577",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
|
|||||||
|
|
||||||
def populate_payment_entries(self):
|
def populate_payment_entries(self):
|
||||||
if self.bank_statement is None: return
|
if self.bank_statement is None: return
|
||||||
filename = self.bank_statement.split("/")[-1]
|
file_url = self.bank_statement
|
||||||
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
|
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
|
||||||
frappe.throw(_("Transactions already retreived from the statement"))
|
frappe.throw(_("Transactions already retreived from the statement"))
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
|
|||||||
if self.bank_settings:
|
if self.bank_settings:
|
||||||
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
|
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
|
||||||
statement_headers = self.get_statement_headers()
|
statement_headers = self.get_statement_headers()
|
||||||
transactions = get_transaction_entries(filename, statement_headers)
|
transactions = get_transaction_entries(file_url, statement_headers)
|
||||||
for entry in transactions:
|
for entry in transactions:
|
||||||
date = entry[statement_headers["Date"]].strip()
|
date = entry[statement_headers["Date"]].strip()
|
||||||
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
|
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
|
||||||
@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
|
|||||||
transaction[header] = ""
|
transaction[header] = ""
|
||||||
return transaction
|
return transaction
|
||||||
|
|
||||||
def get_transaction_entries(filename, headers):
|
def get_transaction_entries(file_url, headers):
|
||||||
header_index = {}
|
header_index = {}
|
||||||
rows, transactions = [], []
|
rows, transactions = [], []
|
||||||
|
|
||||||
if (filename.lower().endswith("xlsx")):
|
if (file_url.lower().endswith("xlsx")):
|
||||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
||||||
rows = read_xlsx_file_from_attached_file(file_id=filename)
|
rows = read_xlsx_file_from_attached_file(file_url=file_url)
|
||||||
elif (filename.lower().endswith("csv")):
|
elif (file_url.lower().endswith("csv")):
|
||||||
from frappe.utils.csvutils import read_csv_content
|
from frappe.utils.csvutils import read_csv_content
|
||||||
_file = frappe.get_doc("File", {"file_name": filename})
|
_file = frappe.get_doc("File", {"file_url": file_url})
|
||||||
filepath = _file.get_full_path()
|
filepath = _file.get_full_path()
|
||||||
with open(filepath,'rb') as csvfile:
|
with open(filepath,'rb') as csvfile:
|
||||||
rows = read_csv_content(csvfile.read())
|
rows = read_csv_content(csvfile.read())
|
||||||
elif (filename.lower().endswith("xls")):
|
elif (file_url.lower().endswith("xls")):
|
||||||
|
filename = file_url.split("/")[-1]
|
||||||
rows = get_rows_from_xls_file(filename)
|
rows = get_rows_from_xls_file(filename)
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Only .csv and .xlsx files are supported currently"))
|
frappe.throw(_("Only .csv and .xlsx files are supported currently"))
|
||||||
|
@ -91,15 +91,11 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
|
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
|
||||||
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
|
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
|
||||||
|
|
||||||
def add_transactions():
|
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||||
if frappe.flags.test_bank_transactions_created:
|
|
||||||
return
|
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
try:
|
try:
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Bank",
|
"doctype": "Bank",
|
||||||
"bank_name":"Citi Bank",
|
"bank_name":bank_name,
|
||||||
}).insert()
|
}).insert()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
@ -108,12 +104,19 @@ def add_transactions():
|
|||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Bank Account",
|
"doctype": "Bank Account",
|
||||||
"account_name":"Checking Account",
|
"account_name":"Checking Account",
|
||||||
"bank": "Citi Bank",
|
"bank": bank_name,
|
||||||
"account": "_Test Bank - _TC"
|
"account": account_name
|
||||||
}).insert()
|
}).insert()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def add_transactions():
|
||||||
|
if frappe.flags.test_bank_transactions_created:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
create_bank_account()
|
||||||
|
|
||||||
doc = frappe.get_doc({
|
doc = frappe.get_doc({
|
||||||
"doctype": "Bank Transaction",
|
"doctype": "Bank Transaction",
|
||||||
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -195,7 +195,7 @@ def build_response_as_excel(writer):
|
|||||||
reader = csv.reader(f)
|
reader = csv.reader(f)
|
||||||
|
|
||||||
from frappe.utils.xlsxutils import make_xlsx
|
from frappe.utils.xlsxutils import make_xlsx
|
||||||
xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
|
xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template")
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
|
@ -38,8 +38,8 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2020-06-18 20:27:42.615842",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "ahmad@havenir.com",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Item Tax Template",
|
"name": "Item Tax Template",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -210,7 +210,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
$.each(this.frm.doc.accounts || [], function(i, jvd) {
|
$.each(this.frm.doc.accounts || [], function(i, jvd) {
|
||||||
frappe.model.set_default_values(jvd);
|
frappe.model.set_default_values(jvd);
|
||||||
});
|
});
|
||||||
var posting_date = this.frm.posting_date;
|
var posting_date = this.frm.doc.posting_date;
|
||||||
if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
|
if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -22,8 +22,12 @@ class JournalEntry(AccountsController):
|
|||||||
return self.voucher_type
|
return self.voucher_type
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
if self.voucher_type == 'Opening Entry':
|
||||||
|
self.is_opening = 'Yes'
|
||||||
|
|
||||||
if not self.is_opening:
|
if not self.is_opening:
|
||||||
self.is_opening='No'
|
self.is_opening='No'
|
||||||
|
|
||||||
self.clearance_date = None
|
self.clearance_date = None
|
||||||
|
|
||||||
self.validate_party()
|
self.validate_party()
|
||||||
@ -1023,7 +1027,7 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
|||||||
return journal_entry.as_dict()
|
return journal_entry.as_dict()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
|
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
def update_accounts(source, target, source_parent):
|
def update_accounts(source, target, source_parent):
|
||||||
@ -1049,6 +1053,6 @@ def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=
|
|||||||
},
|
},
|
||||||
"postprocess": update_accounts,
|
"postprocess": update_accounts,
|
||||||
},
|
},
|
||||||
}, target_doc, ignore_permissions=ignore_permissions)
|
}, target_doc)
|
||||||
|
|
||||||
return doclist
|
return doclist
|
@ -195,88 +195,91 @@ def create_sales_invoice_record(qty=1):
|
|||||||
|
|
||||||
def create_records():
|
def create_records():
|
||||||
# create a new loyalty Account
|
# create a new loyalty Account
|
||||||
if frappe.db.exists("Account", "Loyalty - _TC"):
|
if not frappe.db.exists("Account", "Loyalty - _TC"):
|
||||||
return
|
frappe.get_doc({
|
||||||
|
"doctype": "Account",
|
||||||
frappe.get_doc({
|
"account_name": "Loyalty",
|
||||||
"doctype": "Account",
|
"parent_account": "Direct Expenses - _TC",
|
||||||
"account_name": "Loyalty",
|
"company": "_Test Company",
|
||||||
"parent_account": "Direct Expenses - _TC",
|
"is_group": 0,
|
||||||
"company": "_Test Company",
|
"account_type": "Expense Account",
|
||||||
"is_group": 0,
|
}).insert()
|
||||||
"account_type": "Expense Account",
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
# create a new loyalty program Single tier
|
# create a new loyalty program Single tier
|
||||||
frappe.get_doc({
|
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
|
||||||
"doctype": "Loyalty Program",
|
frappe.get_doc({
|
||||||
"loyalty_program_name": "Test Single Loyalty",
|
"doctype": "Loyalty Program",
|
||||||
"auto_opt_in": 1,
|
"loyalty_program_name": "Test Single Loyalty",
|
||||||
"from_date": today(),
|
"auto_opt_in": 1,
|
||||||
"loyalty_program_type": "Single Tier Program",
|
"from_date": today(),
|
||||||
"conversion_factor": 1,
|
"loyalty_program_type": "Single Tier Program",
|
||||||
"expiry_duration": 10,
|
"conversion_factor": 1,
|
||||||
"company": "_Test Company",
|
"expiry_duration": 10,
|
||||||
"cost_center": "Main - _TC",
|
"company": "_Test Company",
|
||||||
"expense_account": "Loyalty - _TC",
|
"cost_center": "Main - _TC",
|
||||||
"collection_rules": [{
|
"expense_account": "Loyalty - _TC",
|
||||||
'tier_name': 'Silver',
|
"collection_rules": [{
|
||||||
'collection_factor': 1000,
|
|
||||||
'min_spent': 1000
|
|
||||||
}]
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
# create a new customer
|
|
||||||
frappe.get_doc({
|
|
||||||
"customer_group": "_Test Customer Group",
|
|
||||||
"customer_name": "Test Loyalty Customer",
|
|
||||||
"customer_type": "Individual",
|
|
||||||
"doctype": "Customer",
|
|
||||||
"territory": "_Test Territory"
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
# create a new loyalty program Multiple tier
|
|
||||||
frappe.get_doc({
|
|
||||||
"doctype": "Loyalty Program",
|
|
||||||
"loyalty_program_name": "Test Multiple Loyalty",
|
|
||||||
"auto_opt_in": 1,
|
|
||||||
"from_date": today(),
|
|
||||||
"loyalty_program_type": "Multiple Tier Program",
|
|
||||||
"conversion_factor": 1,
|
|
||||||
"expiry_duration": 10,
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cost_center": "Main - _TC",
|
|
||||||
"expense_account": "Loyalty - _TC",
|
|
||||||
"collection_rules": [
|
|
||||||
{
|
|
||||||
'tier_name': 'Silver',
|
'tier_name': 'Silver',
|
||||||
'collection_factor': 1000,
|
'collection_factor': 1000,
|
||||||
'min_spent': 10000
|
'min_spent': 1000
|
||||||
},
|
}]
|
||||||
{
|
}).insert()
|
||||||
'tier_name': 'Gold',
|
|
||||||
'collection_factor': 1000,
|
# create a new customer
|
||||||
'min_spent': 19000
|
if not frappe.db.exists("Customer","Test Loyalty Customer"):
|
||||||
}
|
frappe.get_doc({
|
||||||
]
|
"customer_group": "_Test Customer Group",
|
||||||
}).insert()
|
"customer_name": "Test Loyalty Customer",
|
||||||
|
"customer_type": "Individual",
|
||||||
|
"doctype": "Customer",
|
||||||
|
"territory": "_Test Territory"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
# create a new loyalty program Multiple tier
|
||||||
|
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Loyalty Program",
|
||||||
|
"loyalty_program_name": "Test Multiple Loyalty",
|
||||||
|
"auto_opt_in": 1,
|
||||||
|
"from_date": today(),
|
||||||
|
"loyalty_program_type": "Multiple Tier Program",
|
||||||
|
"conversion_factor": 1,
|
||||||
|
"expiry_duration": 10,
|
||||||
|
"company": "_Test Company",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"expense_account": "Loyalty - _TC",
|
||||||
|
"collection_rules": [
|
||||||
|
{
|
||||||
|
'tier_name': 'Silver',
|
||||||
|
'collection_factor': 1000,
|
||||||
|
'min_spent': 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tier_name': 'Gold',
|
||||||
|
'collection_factor': 1000,
|
||||||
|
'min_spent': 19000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).insert()
|
||||||
|
|
||||||
# create an item
|
# create an item
|
||||||
item = frappe.get_doc({
|
if not frappe.db.exists("Item", "Loyal Item"):
|
||||||
"doctype": "Item",
|
frappe.get_doc({
|
||||||
"item_code": "Loyal Item",
|
"doctype": "Item",
|
||||||
"item_name": "Loyal Item",
|
"item_code": "Loyal Item",
|
||||||
"item_group": "All Item Groups",
|
"item_name": "Loyal Item",
|
||||||
"company": "_Test Company",
|
"item_group": "All Item Groups",
|
||||||
"is_stock_item": 1,
|
"company": "_Test Company",
|
||||||
"opening_stock": 100,
|
"is_stock_item": 1,
|
||||||
"valuation_rate": 10000,
|
"opening_stock": 100,
|
||||||
}).insert()
|
"valuation_rate": 10000,
|
||||||
|
}).insert()
|
||||||
|
|
||||||
# create item price
|
# create item price
|
||||||
frappe.get_doc({
|
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
|
||||||
"doctype": "Item Price",
|
frappe.get_doc({
|
||||||
"price_list": "Standard Selling",
|
"doctype": "Item Price",
|
||||||
"item_code": item.item_code,
|
"price_list": "Standard Selling",
|
||||||
"price_list_rate": 10000
|
"item_code": "Loyal Item",
|
||||||
}).insert()
|
"price_list_rate": 10000
|
||||||
|
}).insert()
|
||||||
|
@ -45,11 +45,11 @@
|
|||||||
],
|
],
|
||||||
"icon": "fa fa-credit-card",
|
"icon": "fa fa-credit-card",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-08-14 14:58:42.079115",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "sammish.thundiyil@gmail.com",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Mode of Payment",
|
"name": "Mode of Payment",
|
||||||
"owner": "harshada@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
@ -12,9 +12,10 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.set_query("paid_from", function() {
|
frm.set_query("paid_from", function() {
|
||||||
|
frm.events.validate_company(frm);
|
||||||
|
|
||||||
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type) ?
|
||||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"account_type": ["in", account_types],
|
"account_type": ["in", account_types],
|
||||||
@ -23,13 +24,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("party_type", function() {
|
frm.set_query("party_type", function() {
|
||||||
|
frm.events.validate_company(frm);
|
||||||
return{
|
return{
|
||||||
filters: {
|
filters: {
|
||||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("party_bank_account", function() {
|
frm.set_query("party_bank_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -39,6 +43,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("bank_account", function() {
|
frm.set_query("bank_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -47,6 +52,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("contact_person", function() {
|
frm.set_query("contact_person", function() {
|
||||||
if (frm.doc.party) {
|
if (frm.doc.party) {
|
||||||
return {
|
return {
|
||||||
@ -58,10 +64,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("paid_to", function() {
|
frm.set_query("paid_to", function() {
|
||||||
|
frm.events.validate_company(frm);
|
||||||
|
|
||||||
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
|
||||||
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"account_type": ["in", account_types],
|
"account_type": ["in", account_types],
|
||||||
@ -150,6 +158,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.events.show_general_ledger(frm);
|
frm.events.show_general_ledger(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validate_company: (frm) => {
|
||||||
|
if (!frm.doc.company){
|
||||||
|
frappe.throw({message:__("Please select a Company first."), title: __("Mandatory")});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2016-06-01 14:38:51.012597",
|
"creation": "2016-06-01 14:38:51.012597",
|
||||||
@ -63,6 +64,7 @@
|
|||||||
"cost_center",
|
"cost_center",
|
||||||
"section_break_12",
|
"section_break_12",
|
||||||
"status",
|
"status",
|
||||||
|
"custom_remarks",
|
||||||
"remarks",
|
"remarks",
|
||||||
"column_break_16",
|
"column_break_16",
|
||||||
"letter_head",
|
"letter_head",
|
||||||
@ -462,7 +464,8 @@
|
|||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"no_copy": 1
|
"no_copy": 1,
|
||||||
|
"read_only_depends_on": "eval:doc.custom_remarks == 0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_16",
|
"fieldname": "column_break_16",
|
||||||
@ -573,10 +576,18 @@
|
|||||||
"label": "Status",
|
"label": "Status",
|
||||||
"options": "\nDraft\nSubmitted\nCancelled",
|
"options": "\nDraft\nSubmitted\nCancelled",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "custom_remarks",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Custom Remarks"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2019-12-08 13:02:30.016610",
|
"links": [],
|
||||||
|
"modified": "2020-09-02 13:39:43.383705",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
@ -453,7 +453,7 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
|
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
|
||||||
|
|
||||||
def set_remarks(self):
|
def set_remarks(self):
|
||||||
if self.remarks: return
|
if self.custom_remarks: return
|
||||||
|
|
||||||
if self.payment_type=="Internal Transfer":
|
if self.payment_type=="Internal Transfer":
|
||||||
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
|
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
|
||||||
@ -1172,30 +1172,23 @@ def make_payment_order(source_name, target_doc=None):
|
|||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.payment_order_type = "Payment Entry"
|
target.payment_order_type = "Payment Entry"
|
||||||
|
target.append('references', dict(
|
||||||
|
reference_doctype="Payment Entry",
|
||||||
|
reference_name=source.name,
|
||||||
|
bank_account=source.party_bank_account,
|
||||||
|
amount=source.paid_amount,
|
||||||
|
account=source.paid_to,
|
||||||
|
supplier=source.party,
|
||||||
|
mode_of_payment=source.mode_of_payment,
|
||||||
|
))
|
||||||
|
|
||||||
def update_item(source_doc, target_doc, source_parent):
|
doclist = get_mapped_doc("Payment Entry", source_name, {
|
||||||
target_doc.bank_account = source_parent.party_bank_account
|
|
||||||
target_doc.amount = source_doc.allocated_amount
|
|
||||||
target_doc.account = source_parent.paid_to
|
|
||||||
target_doc.payment_entry = source_parent.name
|
|
||||||
target_doc.supplier = source_parent.party
|
|
||||||
target_doc.mode_of_payment = source_parent.mode_of_payment
|
|
||||||
|
|
||||||
|
|
||||||
doclist = get_mapped_doc("Payment Entry", source_name, {
|
|
||||||
"Payment Entry": {
|
"Payment Entry": {
|
||||||
"doctype": "Payment Order",
|
"doctype": "Payment Order",
|
||||||
"validation": {
|
|
||||||
"docstatus": ["=", 1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Payment Entry Reference": {
|
|
||||||
"doctype": "Payment Order Reference",
|
|
||||||
"validation": {
|
"validation": {
|
||||||
"docstatus": ["=", 1]
|
"docstatus": ["=", 1]
|
||||||
},
|
},
|
||||||
"postprocess": update_item
|
}
|
||||||
},
|
|
||||||
|
|
||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
frappe.listview_settings['Payment Entry'] = {
|
frappe.listview_settings['Payment Entry'] = {
|
||||||
|
|
||||||
onload: function(listview) {
|
onload: function(listview) {
|
||||||
listview.page.fields_dict.party_type.get_query = function() {
|
if (listview.page.fields_dict.party_type) {
|
||||||
return {
|
listview.page.fields_dict.party_type.get_query = function() {
|
||||||
"filters": {
|
return {
|
||||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
"filters": {
|
||||||
}
|
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -21,10 +21,15 @@ class PaymentOrder(Document):
|
|||||||
if cancel:
|
if cancel:
|
||||||
status = 'Initiated'
|
status = 'Initiated'
|
||||||
|
|
||||||
ref_field = "status" if self.payment_order_type == "Payment Request" else "payment_order_status"
|
if self.payment_order_type == "Payment Request":
|
||||||
|
ref_field = "status"
|
||||||
|
ref_doc_field = frappe.scrub(self.payment_order_type)
|
||||||
|
else:
|
||||||
|
ref_field = "payment_order_status"
|
||||||
|
ref_doc_field = "reference_name"
|
||||||
|
|
||||||
for d in self.references:
|
for d in self.references:
|
||||||
frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status)
|
frappe.db.set_value(self.payment_order_type, d.get(ref_doc_field), ref_field, status)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
@ -5,6 +5,45 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
from frappe.utils import getdate
|
||||||
|
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry, make_payment_order
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
class TestPaymentOrder(unittest.TestCase):
|
class TestPaymentOrder(unittest.TestCase):
|
||||||
pass
|
def setUp(self):
|
||||||
|
create_bank_account()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for bt in frappe.get_all("Payment Order"):
|
||||||
|
doc = frappe.get_doc("Payment Order", bt.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
def test_payment_order_creation_against_payment_entry(self):
|
||||||
|
purchase_invoice = make_purchase_invoice()
|
||||||
|
payment_entry = get_payment_entry("Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC")
|
||||||
|
payment_entry.reference_no = "_Test_Payment_Order"
|
||||||
|
payment_entry.reference_date = getdate()
|
||||||
|
payment_entry.party_bank_account = "Checking Account - Citi Bank"
|
||||||
|
payment_entry.insert()
|
||||||
|
payment_entry.submit()
|
||||||
|
|
||||||
|
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
|
||||||
|
reference_doc = doc.get("references")[0]
|
||||||
|
self.assertEquals(reference_doc.reference_name, payment_entry.name)
|
||||||
|
self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
|
||||||
|
self.assertEquals(reference_doc.supplier, "_Test Supplier")
|
||||||
|
self.assertEquals(reference_doc.amount, 250)
|
||||||
|
|
||||||
|
def create_payment_order_against_payment_entry(ref_doc, order_type):
|
||||||
|
payment_order = frappe.get_doc(dict(
|
||||||
|
doctype="Payment Order",
|
||||||
|
company="_Test Company",
|
||||||
|
payment_order_type=order_type,
|
||||||
|
company_bank_account="Checking Account - Citi Bank"
|
||||||
|
))
|
||||||
|
doc = make_payment_order(ref_doc.name, payment_order)
|
||||||
|
doc.save()
|
||||||
|
doc.submit()
|
||||||
|
return doc
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2018-07-20 16:38:06.630813",
|
"creation": "2018-07-20 16:38:06.630813",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@ -10,7 +11,6 @@
|
|||||||
"column_break_4",
|
"column_break_4",
|
||||||
"supplier",
|
"supplier",
|
||||||
"payment_request",
|
"payment_request",
|
||||||
"payment_entry",
|
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"bank_account_details",
|
"bank_account_details",
|
||||||
"bank_account",
|
"bank_account",
|
||||||
@ -103,17 +103,12 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "payment_entry",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Payment Entry",
|
|
||||||
"options": "Payment Entry",
|
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-08 13:56:25.724557",
|
"links": [],
|
||||||
|
"modified": "2020-09-04 08:29:51.014390",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Order Reference",
|
"name": "Payment Order Reference",
|
||||||
|
@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", {
|
|||||||
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
|
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
|
||||||
onload: function() {
|
onload: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
|
this.frm.set_query("party", function() {
|
||||||
|
check_mandatory(me.frm);
|
||||||
|
});
|
||||||
|
|
||||||
this.frm.set_query("party_type", function() {
|
this.frm.set_query("party_type", function() {
|
||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.frm.set_query('receivable_payable_account', function() {
|
this.frm.set_query('receivable_payable_account', function() {
|
||||||
if(!me.frm.doc.company || !me.frm.doc.party_type) {
|
check_mandatory(me.frm);
|
||||||
frappe.msgprint(__("Please select Company and Party Type first"));
|
return {
|
||||||
} else {
|
filters: {
|
||||||
return{
|
"company": me.frm.doc.company,
|
||||||
filters: {
|
"is_group": 0,
|
||||||
"company": me.frm.doc.company,
|
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
||||||
"is_group": 0,
|
}
|
||||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.frm.set_query('bank_cash_account', function() {
|
this.frm.set_query('bank_cash_account', function() {
|
||||||
if(!me.frm.doc.company) {
|
check_mandatory(me.frm, true);
|
||||||
frappe.msgprint(__("Please select Company first"));
|
return {
|
||||||
} else {
|
filters:[
|
||||||
return{
|
['Account', 'company', '=', me.frm.doc.company],
|
||||||
filters:[
|
['Account', 'is_group', '=', 0],
|
||||||
['Account', 'company', '=', me.frm.doc.company],
|
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
||||||
['Account', 'is_group', '=', 0],
|
]
|
||||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
};
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.frm.set_value('party_type', '');
|
this.frm.set_value('party_type', '');
|
||||||
this.frm.set_value('party', '');
|
this.frm.set_value('party', '');
|
||||||
this.frm.set_value('receivable_payable_account', '');
|
this.frm.set_value('receivable_payable_account', '');
|
||||||
|
|
||||||
|
var check_mandatory = (frm, only_company=false) => {
|
||||||
|
var title = __("Mandatory");
|
||||||
|
if (only_company && !frm.doc.company) {
|
||||||
|
frappe.throw({message: __("Please Select a Company First"), title: title});
|
||||||
|
} else if (!frm.doc.company || !frm.doc.party_type) {
|
||||||
|
frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
|
||||||
|
}
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function() {
|
refresh: function() {
|
||||||
@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
|
|
||||||
party: function() {
|
party: function() {
|
||||||
var me = this
|
var me = this
|
||||||
if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
|
if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "erpnext.accounts.party.get_party_account",
|
method: "erpnext.accounts.party.get_party_account",
|
||||||
args: {
|
args: {
|
||||||
@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
party: me.frm.doc.party
|
party: me.frm.doc.party
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc && r.message) {
|
if (!r.exc && r.message) {
|
||||||
me.frm.set_value("receivable_payable_account", r.message);
|
me.frm.set_value("receivable_payable_account", r.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ class PaymentReconciliation(Document):
|
|||||||
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
||||||
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
||||||
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
||||||
|
and `tabGL Entry`.is_cancelled = 0
|
||||||
GROUP BY `tab{doc}`.name
|
GROUP BY `tab{doc}`.name
|
||||||
Having
|
Having
|
||||||
amount > 0
|
amount > 0
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Provide the invoice portion in percent",
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@ -170,6 +171,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Give number of days according to prior selection",
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@ -305,7 +307,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-03-08 10:47:32.830478",
|
"modified": "2020-10-14 10:47:32.830478",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Term",
|
"name": "Payment Term",
|
||||||
@ -381,4 +383,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0
|
||||||
}
|
}
|
||||||
|
@ -291,11 +291,11 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-08-21 16:15:49.089450",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
"owner": "jai@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 1,
|
"amend": 1,
|
||||||
|
@ -55,14 +55,48 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
let pos_docs = r.message;
|
let pos_docs = r.message;
|
||||||
set_form_data(pos_docs, frm)
|
set_form_data(pos_docs, frm);
|
||||||
refresh_fields(frm)
|
refresh_fields(frm);
|
||||||
set_html_data(frm)
|
set_html_data(frm);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
|
||||||
|
const removed_row = locals[cdt][cdn];
|
||||||
|
|
||||||
|
if (!removed_row.pos_invoice) return;
|
||||||
|
|
||||||
|
frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
|
||||||
|
cur_frm.doc.grand_total -= flt(doc.grand_total);
|
||||||
|
cur_frm.doc.net_total -= flt(doc.net_total);
|
||||||
|
cur_frm.doc.total_quantity -= flt(doc.total_qty);
|
||||||
|
refresh_payments(doc, cur_frm, 1);
|
||||||
|
refresh_taxes(doc, cur_frm, 1);
|
||||||
|
refresh_fields(cur_frm);
|
||||||
|
set_html_data(cur_frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.ui.form.on('POS Invoice Reference', {
|
||||||
|
pos_invoice(frm, cdt, cdn) {
|
||||||
|
const added_row = locals[cdt][cdn];
|
||||||
|
|
||||||
|
if (!added_row.pos_invoice) return;
|
||||||
|
|
||||||
|
frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
|
||||||
|
frm.doc.grand_total += flt(doc.grand_total);
|
||||||
|
frm.doc.net_total += flt(doc.net_total);
|
||||||
|
frm.doc.total_quantity += flt(doc.total_qty);
|
||||||
|
refresh_payments(doc, frm);
|
||||||
|
refresh_taxes(doc, frm);
|
||||||
|
refresh_fields(frm);
|
||||||
|
set_html_data(frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
frappe.ui.form.on('POS Closing Entry Detail', {
|
frappe.ui.form.on('POS Closing Entry Detail', {
|
||||||
closing_amount: (frm, cdt, cdn) => {
|
closing_amount: (frm, cdt, cdn) => {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
@ -76,8 +110,8 @@ function set_form_data(data, frm) {
|
|||||||
frm.doc.grand_total += flt(d.grand_total);
|
frm.doc.grand_total += flt(d.grand_total);
|
||||||
frm.doc.net_total += flt(d.net_total);
|
frm.doc.net_total += flt(d.net_total);
|
||||||
frm.doc.total_quantity += flt(d.total_qty);
|
frm.doc.total_quantity += flt(d.total_qty);
|
||||||
add_to_payments(d, frm);
|
refresh_payments(d, frm);
|
||||||
add_to_taxes(d, frm);
|
refresh_taxes(d, frm);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +124,12 @@ function add_to_pos_transaction(d, frm) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_to_payments(d, frm) {
|
function refresh_payments(d, frm, remove) {
|
||||||
d.payments.forEach(p => {
|
d.payments.forEach(p => {
|
||||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
||||||
if (payment) {
|
if (payment) {
|
||||||
payment.expected_amount += flt(p.amount);
|
if (!remove) payment.expected_amount += flt(p.amount);
|
||||||
|
else payment.expected_amount -= flt(p.amount);
|
||||||
} else {
|
} else {
|
||||||
frm.add_child("payment_reconciliation", {
|
frm.add_child("payment_reconciliation", {
|
||||||
mode_of_payment: p.mode_of_payment,
|
mode_of_payment: p.mode_of_payment,
|
||||||
@ -105,11 +140,12 @@ function add_to_payments(d, frm) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_to_taxes(d, frm) {
|
function refresh_taxes(d, frm, remove) {
|
||||||
d.taxes.forEach(t => {
|
d.taxes.forEach(t => {
|
||||||
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
||||||
if (tax) {
|
if (tax) {
|
||||||
tax.amount += flt(t.tax_amount);
|
if (!remove) tax.amount += flt(t.tax_amount);
|
||||||
|
else tax.amount -= flt(t.tax_amount);
|
||||||
} else {
|
} else {
|
||||||
frm.add_child("taxes", {
|
frm.add_child("taxes", {
|
||||||
account_head: t.account_head,
|
account_head: t.account_head,
|
||||||
|
@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
def init_user_and_profile():
|
def init_user_and_profile(**args):
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
test_user = frappe.get_doc('User', user)
|
test_user = frappe.get_doc('User', user)
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ def init_user_and_profile():
|
|||||||
test_user.add_roles(*roles)
|
test_user.add_roles(*roles)
|
||||||
frappe.set_user(user)
|
frappe.set_user(user)
|
||||||
|
|
||||||
pos_profile = make_pos_profile()
|
pos_profile = make_pos_profile(**args)
|
||||||
pos_profile.append('applicable_for_users', {
|
pos_profile.append('applicable_for_users', {
|
||||||
'default': 1,
|
'default': 1,
|
||||||
'user': user
|
'user': user
|
||||||
|
@ -279,7 +279,8 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Return (Credit Note)",
|
"label": "Is Return (Credit Note)",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break1",
|
"fieldname": "column_break1",
|
||||||
@ -1578,9 +1579,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:39.337385",
|
"modified": "2020-09-07 12:43:09.138720",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
@ -92,21 +92,19 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
if len(invalid_serial_nos):
|
if len(invalid_serial_nos):
|
||||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
||||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
|
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. Please select valid serial no.").format(
|
||||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
d.idx, multiple_nos, frappe.bold(', '.join(invalid_serial_nos))), title=_("Not Available"))
|
||||||
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
|
|
||||||
else:
|
else:
|
||||||
if allow_negative_stock:
|
if allow_negative_stock:
|
||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||||
if not (flt(available_stock) > 0):
|
if not (flt(available_stock) > 0):
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
|
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.').format(
|
||||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse)), title=_("Not Available"))
|
||||||
elif flt(available_stock) < flt(d.qty):
|
elif flt(available_stock) < flt(d.qty):
|
||||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.').format(
|
||||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)), title=_("Not Available"))
|
||||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
|
||||||
|
|
||||||
def validate_serialised_or_batched_item(self):
|
def validate_serialised_or_batched_item(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
@ -117,14 +115,14 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
|
|
||||||
if serialized and batched and (no_batch_selected or no_serial_selected):
|
if serialized and batched and (no_batch_selected or no_serial_selected):
|
||||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.'
|
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.').format(
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||||
if serialized and no_serial_selected:
|
if serialized and no_serial_selected:
|
||||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.'
|
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.').format(
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||||
if batched and no_batch_selected:
|
if batched and no_batch_selected:
|
||||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
|
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.').format(
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||||
|
|
||||||
def validate_return_items(self):
|
def validate_return_items(self):
|
||||||
if not self.get("is_return"): return
|
if not self.get("is_return"): return
|
||||||
@ -139,7 +137,8 @@ class POSInvoice(SalesInvoice):
|
|||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def validate_change_account(self):
|
def validate_change_account(self):
|
||||||
if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
if self.change_amount and self.account_for_change_amount and \
|
||||||
|
frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
||||||
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
||||||
|
|
||||||
def validate_change_amount(self):
|
def validate_change_amount(self):
|
||||||
@ -150,7 +149,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
||||||
|
|
||||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||||
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||||
|
|
||||||
def verify_payment_amount(self):
|
def verify_payment_amount(self):
|
||||||
for entry in self.payments:
|
for entry in self.payments:
|
||||||
@ -166,7 +165,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
total_amount_in_payments += payment.amount
|
total_amount_in_payments += payment.amount
|
||||||
invoice_total = self.rounded_total or self.grand_total
|
invoice_total = self.rounded_total or self.grand_total
|
||||||
if total_amount_in_payments < invoice_total:
|
if total_amount_in_payments < invoice_total:
|
||||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
|
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
||||||
|
|
||||||
def validate_loyalty_transaction(self):
|
def validate_loyalty_transaction(self):
|
||||||
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
||||||
|
@ -7,6 +7,8 @@ import frappe
|
|||||||
import unittest, copy, time
|
import unittest, copy, time
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|
||||||
class TestPOSInvoice(unittest.TestCase):
|
class TestPOSInvoice(unittest.TestCase):
|
||||||
def test_timestamp_change(self):
|
def test_timestamp_change(self):
|
||||||
@ -182,8 +184,9 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
def test_pos_returns_with_repayment(self):
|
def test_pos_returns_with_repayment(self):
|
||||||
pos = create_pos_invoice(qty = 10, do_not_save=True)
|
pos = create_pos_invoice(qty = 10, do_not_save=True)
|
||||||
|
|
||||||
|
pos.set('payments', [])
|
||||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
|
||||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500, 'default': 1})
|
||||||
pos.insert()
|
pos.insert()
|
||||||
pos.submit()
|
pos.submit()
|
||||||
|
|
||||||
@ -200,8 +203,9 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
|
||||||
cost_center = "Main - _TC", do_not_save=True)
|
cost_center = "Main - _TC", do_not_save=True)
|
||||||
|
|
||||||
|
pos.set('payments', [])
|
||||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
|
||||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60})
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60, 'default': 1})
|
||||||
|
|
||||||
pos.insert()
|
pos.insert()
|
||||||
pos.submit()
|
pos.submit()
|
||||||
@ -219,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
se = make_serialized_item(company='_Test Company with perpetual inventory',
|
se = make_serialized_item(company='_Test Company',
|
||||||
target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1')
|
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||||
|
|
||||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||||
|
|
||||||
pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
pos.get("items")[0].serial_no = serial_nos[0]
|
pos.get("items")[0].serial_no = serial_nos[0]
|
||||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||||
|
|
||||||
pos.insert()
|
pos.insert()
|
||||||
pos.submit()
|
pos.submit()
|
||||||
|
|
||||||
pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||||
|
|
||||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
|
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||||
|
|
||||||
@ -284,6 +288,119 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
|
after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program)
|
||||||
self.assertEqual(after_redeem_lp_details.loyalty_points, 9)
|
self.assertEqual(after_redeem_lp_details.loyalty_points, 9)
|
||||||
|
|
||||||
|
def test_merging_into_sales_invoice_with_discount(self):
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270
|
||||||
|
})
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
merge_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
|
self.assertEqual(rounded_total, 3470)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
|
})
|
||||||
|
pos_inv.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
|
||||||
|
pos_inv2.additional_discount_percentage = 10
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 540
|
||||||
|
})
|
||||||
|
pos_inv2.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
merge_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
|
self.assertEqual(rounded_total, 840)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_merging_with_validate_selling_price(self):
|
||||||
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||||
|
|
||||||
|
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||||
|
|
||||||
|
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
|
pos_inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
|
})
|
||||||
|
pos_inv.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||||
|
|
||||||
|
pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
|
||||||
|
pos_inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
|
||||||
|
})
|
||||||
|
pos_inv2.append('taxes', {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account Service Tax - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Service Tax",
|
||||||
|
"rate": 14,
|
||||||
|
'included_in_print_rate': 1
|
||||||
|
})
|
||||||
|
pos_inv2.submit()
|
||||||
|
|
||||||
|
merge_pos_invoices()
|
||||||
|
|
||||||
|
pos_inv2.load_from_db()
|
||||||
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||||
|
self.assertEqual(rounded_total, 400)
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
|
||||||
|
|
||||||
def create_pos_invoice(**args):
|
def create_pos_invoice(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
pos_profile = None
|
pos_profile = None
|
||||||
@ -292,12 +409,11 @@ def create_pos_invoice(**args):
|
|||||||
pos_profile.save()
|
pos_profile.save()
|
||||||
|
|
||||||
pos_inv = frappe.new_doc("POS Invoice")
|
pos_inv = frappe.new_doc("POS Invoice")
|
||||||
|
pos_inv.update(args)
|
||||||
pos_inv.update_stock = 1
|
pos_inv.update_stock = 1
|
||||||
pos_inv.is_pos = 1
|
pos_inv.is_pos = 1
|
||||||
pos_inv.pos_profile = args.pos_profile or pos_profile.name
|
pos_inv.pos_profile = args.pos_profile or pos_profile.name
|
||||||
|
|
||||||
pos_inv.set_missing_values()
|
|
||||||
|
|
||||||
if args.posting_date:
|
if args.posting_date:
|
||||||
pos_inv.set_posting_time = 1
|
pos_inv.set_posting_time = 1
|
||||||
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
|
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
|
||||||
@ -311,6 +427,8 @@ def create_pos_invoice(**args):
|
|||||||
pos_inv.conversion_rate = args.conversion_rate or 1
|
pos_inv.conversion_rate = args.conversion_rate or 1
|
||||||
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
|
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
|
||||||
|
|
||||||
|
pos_inv.set_missing_values()
|
||||||
|
|
||||||
pos_inv.append("items", {
|
pos_inv.append("items", {
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
|
@ -24,11 +24,20 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
def validate_pos_invoice_status(self):
|
def validate_pos_invoice_status(self):
|
||||||
for d in self.pos_invoices:
|
for d in self.pos_invoices:
|
||||||
status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus'])
|
status, docstatus, is_return, return_against = frappe.db.get_value(
|
||||||
|
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
||||||
|
|
||||||
if docstatus != 1:
|
if docstatus != 1:
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
||||||
if status in ['Consolidated']:
|
if status == "Consolidated":
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
||||||
|
if is_return and return_against not in [d.pos_invoice for d in self.pos_invoices] and status != "Consolidated":
|
||||||
|
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{}: Return Invoice {} cannot be made against unconsolidated invoice. \
|
||||||
|
You can add original invoice {} manually to proceed.")
|
||||||
|
.format(d.idx, frappe.bold(d.pos_invoice), frappe.bold(return_against))
|
||||||
|
)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
@ -36,12 +45,12 @@ class POSInvoiceMergeLog(Document):
|
|||||||
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
|
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
|
||||||
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
||||||
|
|
||||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
sales_invoice, credit_note = "", ""
|
||||||
|
if sales:
|
||||||
|
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||||
|
|
||||||
if len(returns):
|
if returns:
|
||||||
credit_note = self.process_merging_into_credit_note(returns)
|
credit_note = self.process_merging_into_credit_note(returns)
|
||||||
else:
|
|
||||||
credit_note = ""
|
|
||||||
|
|
||||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||||
|
|
||||||
@ -87,17 +96,28 @@ class POSInvoiceMergeLog(Document):
|
|||||||
loyalty_amount_sum += doc.loyalty_amount
|
loyalty_amount_sum += doc.loyalty_amount
|
||||||
|
|
||||||
for item in doc.get('items'):
|
for item in doc.get('items'):
|
||||||
items.append(item)
|
found = False
|
||||||
|
for i in items:
|
||||||
|
if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and
|
||||||
|
i.uom == item.uom and i.net_rate == item.net_rate):
|
||||||
|
found = True
|
||||||
|
i.qty = i.qty + item.qty
|
||||||
|
if not found:
|
||||||
|
item.rate = item.net_rate
|
||||||
|
items.append(item)
|
||||||
|
|
||||||
for tax in doc.get('taxes'):
|
for tax in doc.get('taxes'):
|
||||||
found = False
|
found = False
|
||||||
for t in taxes:
|
for t in taxes:
|
||||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center and t.rate == tax.rate:
|
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount)
|
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount)
|
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
tax.charge_type = 'Actual'
|
tax.charge_type = 'Actual'
|
||||||
|
tax.included_in_print_rate = 0
|
||||||
|
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||||
|
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||||
taxes.append(tax)
|
taxes.append(tax)
|
||||||
|
|
||||||
for payment in doc.get('payments'):
|
for payment in doc.get('payments'):
|
||||||
@ -118,6 +138,8 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.set('items', items)
|
invoice.set('items', items)
|
||||||
invoice.set('payments', payments)
|
invoice.set('payments', payments)
|
||||||
invoice.set('taxes', taxes)
|
invoice.set('taxes', taxes)
|
||||||
|
invoice.additional_discount_percentage = 0
|
||||||
|
invoice.discount_amount = 0.0
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint, get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.controllers.status_updater import StatusUpdater
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
|
|
||||||
class POSOpeningEntry(StatusUpdater):
|
class POSOpeningEntry(StatusUpdater):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_pos_profile_and_cashier()
|
self.validate_pos_profile_and_cashier()
|
||||||
|
self.validate_payment_method_account()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def validate_pos_profile_and_cashier(self):
|
def validate_pos_profile_and_cashier(self):
|
||||||
@ -20,6 +21,14 @@ class POSOpeningEntry(StatusUpdater):
|
|||||||
|
|
||||||
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
||||||
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
||||||
|
|
||||||
|
def validate_payment_method_account(self):
|
||||||
|
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:
|
||||||
|
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||||
|
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
@ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
|||||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||||
return erpnext.queries.warehouse(frm.doc);
|
return erpnext.queries.warehouse(frm.doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.call({
|
|
||||||
method: "erpnext.accounts.doctype.pos_profile.pos_profile.get_series",
|
|
||||||
callback: function(r) {
|
|
||||||
if(!r.exc) {
|
|
||||||
set_field_options("naming_series", r.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('POS Profile', {
|
frappe.ui.form.on('POS Profile', {
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"disabled",
|
"disabled",
|
||||||
"section_break_2",
|
"section_break_2",
|
||||||
"naming_series",
|
|
||||||
"customer",
|
"customer",
|
||||||
"company",
|
"company",
|
||||||
"country",
|
"country",
|
||||||
@ -59,17 +58,6 @@
|
|||||||
"fieldname": "section_break_2",
|
"fieldname": "section_break_2",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "naming_series",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Series",
|
|
||||||
"no_copy": 1,
|
|
||||||
"oldfieldname": "naming_series",
|
|
||||||
"oldfieldtype": "Select",
|
|
||||||
"options": "[Select]",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "customer",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -323,7 +311,7 @@
|
|||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-29 12:20:30.977272",
|
"modified": "2020-10-01 17:29:27.759088",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
@ -350,4 +338,4 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC"
|
||||||
}
|
}
|
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import msgprint, _
|
from frappe import msgprint, _
|
||||||
from frappe.utils import cint, now
|
from frappe.utils import cint, now, get_link_to_form
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ class POSProfile(Document):
|
|||||||
self.validate_default_profile()
|
self.validate_default_profile()
|
||||||
self.validate_all_link_fields()
|
self.validate_all_link_fields()
|
||||||
self.validate_duplicate_groups()
|
self.validate_duplicate_groups()
|
||||||
self.check_default_payment()
|
self.validate_payment_methods()
|
||||||
|
|
||||||
def validate_default_profile(self):
|
def validate_default_profile(self):
|
||||||
for row in self.applicable_for_users:
|
for row in self.applicable_for_users:
|
||||||
@ -52,14 +52,23 @@ class POSProfile(Document):
|
|||||||
if len(customer_groups) != len(set(customer_groups)):
|
if len(customer_groups) != len(set(customer_groups)):
|
||||||
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
|
frappe.throw(_("Duplicate customer group found in the cutomer group table"), title = "Duplicate Customer Group")
|
||||||
|
|
||||||
def check_default_payment(self):
|
def validate_payment_methods(self):
|
||||||
if self.payments:
|
if not self.payments:
|
||||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||||
if not default_mode_of_payment:
|
|
||||||
frappe.throw(_("Set default mode of payment"))
|
|
||||||
|
|
||||||
if len(default_mode_of_payment) > 1:
|
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
||||||
frappe.throw(_("Multiple default mode of payment is not allowed"))
|
if not default_mode_of_payment:
|
||||||
|
frappe.throw(_("Please select a default mode of payment"))
|
||||||
|
|
||||||
|
if len(default_mode_of_payment) > 1:
|
||||||
|
frappe.throw(_("You can only select one mode of payment as default"))
|
||||||
|
|
||||||
|
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:
|
||||||
|
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||||
|
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
@ -100,10 +109,6 @@ def get_child_nodes(group_type, root):
|
|||||||
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
|
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
|
||||||
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
|
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_series():
|
|
||||||
return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s"
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
@ -7,10 +7,10 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get_invoice_fields: function(frm) {
|
get_invoice_fields: function(frm) {
|
||||||
frappe.model.with_doctype("Sales Invoice", () => {
|
frappe.model.with_doctype("POS Invoice", () => {
|
||||||
var fields = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
||||||
d.fieldtype === 'Table') {
|
['Table', 'Button'].includes(d.fieldtype)) {
|
||||||
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -25,7 +25,7 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
frappe.ui.form.on("POS Field", {
|
frappe.ui.form.on("POS Field", {
|
||||||
fieldname: function(frm, doctype, name) {
|
fieldname: function(frm, doctype, name) {
|
||||||
var doc = frappe.get_doc(doctype, name);
|
var doc = frappe.get_doc(doctype, name);
|
||||||
var df = $.map(frappe.get_doc("DocType", "Sales Invoice").fields, function(d) {
|
var df = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||||
return doc.fieldname == d.fieldname ? d : null;
|
return doc.fieldname == d.fieldname ? d : null;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:title",
|
"autoname": "field:title",
|
||||||
@ -71,6 +72,7 @@
|
|||||||
"section_break_13",
|
"section_break_13",
|
||||||
"threshold_percentage",
|
"threshold_percentage",
|
||||||
"priority",
|
"priority",
|
||||||
|
"condition",
|
||||||
"column_break_66",
|
"column_break_66",
|
||||||
"apply_multiple_pricing_rules",
|
"apply_multiple_pricing_rules",
|
||||||
"apply_discount_on_rate",
|
"apply_discount_on_rate",
|
||||||
@ -550,11 +552,18 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Promotional Scheme",
|
"label": "Promotional Scheme",
|
||||||
"options": "Promotional Scheme"
|
"options": "Promotional Scheme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Simple Python Expression, Example: territory != 'All Territories'",
|
||||||
|
"fieldname": "condition",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Condition"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-12-18 17:29:22.957077",
|
"links": [],
|
||||||
|
"modified": "2020-08-26 12:24:44.740734",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
@ -6,9 +6,10 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import json
|
import json
|
||||||
import copy
|
import copy
|
||||||
|
import re
|
||||||
|
|
||||||
from frappe import throw, _
|
from frappe import throw, _
|
||||||
from frappe.utils import flt, cint, getdate
|
from frappe.utils import flt, cint, getdate
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
@ -30,6 +31,7 @@ class PricingRule(Document):
|
|||||||
self.validate_max_discount()
|
self.validate_max_discount()
|
||||||
self.validate_price_list_with_currency()
|
self.validate_price_list_with_currency()
|
||||||
self.validate_dates()
|
self.validate_dates()
|
||||||
|
self.validate_condition()
|
||||||
|
|
||||||
if not self.margin_type: self.margin_rate_or_amount = 0.0
|
if not self.margin_type: self.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
@ -140,6 +142,10 @@ class PricingRule(Document):
|
|||||||
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
|
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto):
|
||||||
frappe.throw(_("Valid from date must be less than valid upto date"))
|
frappe.throw(_("Valid from date must be less than valid upto date"))
|
||||||
|
|
||||||
|
def validate_condition(self):
|
||||||
|
if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition):
|
||||||
|
frappe.throw(_("Invalid condition expression"))
|
||||||
|
|
||||||
#--------------------------------------------------------------------------------
|
#--------------------------------------------------------------------------------
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -429,7 +429,34 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
details = get_item_details(args)
|
details = get_item_details(args)
|
||||||
|
|
||||||
self.assertTrue(details)
|
self.assertTrue(details)
|
||||||
|
|
||||||
|
def test_pricing_rule_for_condition(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
|
||||||
|
make_pricing_rule(selling=1, margin_type="Percentage", \
|
||||||
|
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
|
||||||
|
|
||||||
|
# Incorrect Customer and Correct is_return value
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
|
||||||
|
si.items[0].price_list_rate = 1000
|
||||||
|
si.submit()
|
||||||
|
item = si.items[0]
|
||||||
|
self.assertEquals(item.rate, 100)
|
||||||
|
|
||||||
|
# Correct Customer and Incorrect is_return value
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
|
||||||
|
si.items[0].price_list_rate = 1000
|
||||||
|
si.submit()
|
||||||
|
item = si.items[0]
|
||||||
|
self.assertEquals(item.rate, 100)
|
||||||
|
|
||||||
|
# Correct Customer and correct is_return value
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
|
||||||
|
si.items[0].price_list_rate = 1000
|
||||||
|
si.submit()
|
||||||
|
item = si.items[0]
|
||||||
|
self.assertEquals(item.rate, 900)
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@ -448,7 +475,8 @@ def make_pricing_rule(**args):
|
|||||||
"discount_percentage": args.discount_percentage or 0.0,
|
"discount_percentage": args.discount_percentage or 0.0,
|
||||||
"rate": args.rate or 0.0,
|
"rate": args.rate or 0.0,
|
||||||
"margin_type": args.margin_type,
|
"margin_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_on = doc.apply_on.replace(' ', '_').lower()
|
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||||
|
@ -37,6 +37,8 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
rules = []
|
rules = []
|
||||||
|
|
||||||
|
pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc)
|
||||||
|
|
||||||
if not pricing_rules: return []
|
if not pricing_rules: return []
|
||||||
|
|
||||||
if apply_multiple_pricing_rules(pricing_rules):
|
if apply_multiple_pricing_rules(pricing_rules):
|
||||||
@ -51,6 +53,23 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
||||||
|
filtered_pricing_rules = []
|
||||||
|
if doc:
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
if pricing_rule.condition:
|
||||||
|
try:
|
||||||
|
if frappe.safe_eval(pricing_rule.condition, None, doc.as_dict()):
|
||||||
|
filtered_pricing_rules.append(pricing_rule)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
filtered_pricing_rules.append(pricing_rule)
|
||||||
|
else:
|
||||||
|
filtered_pricing_rules = pricing_rules
|
||||||
|
|
||||||
|
return filtered_pricing_rules
|
||||||
|
|
||||||
def _get_pricing_rules(apply_on, args, values):
|
def _get_pricing_rules(apply_on, args, values):
|
||||||
apply_on_field = frappe.scrub(apply_on)
|
apply_on_field = frappe.scrub(apply_on)
|
||||||
|
|
||||||
|
@ -10,13 +10,15 @@ frappe.ui.form.on('Process Deferred Accounting', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
if (frm.doc.company) {
|
type: function(frm) {
|
||||||
|
if (frm.doc.company && frm.doc.type) {
|
||||||
frm.set_query("account", function() {
|
frm.set_query("account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
'company': frm.doc.company,
|
'company': frm.doc.company,
|
||||||
'root_type': 'Liability',
|
'root_type': frm.doc.type === 'Income' ? 'Liability' : 'Asset',
|
||||||
'is_group': 0
|
'is_group': 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.type",
|
||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Account",
|
"label": "Account",
|
||||||
@ -73,9 +74,10 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-06 18:18:09.852844",
|
"modified": "2020-09-03 18:07:02.463754",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Deferred Accounting",
|
"name": "Process Deferred Accounting",
|
||||||
|
@ -25,6 +25,12 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
this.frm.set_df_property("credit_to", "print_hide", 0);
|
this.frm.set_df_property("credit_to", "print_hide", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger supplier event on load if supplier is available
|
||||||
|
// The reason for this is PI can be created from PR or PO and supplier is pre populated
|
||||||
|
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||||
|
this.frm.trigger('supplier');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc) {
|
refresh: function(doc) {
|
||||||
@ -135,6 +141,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
unblock_invoice: function() {
|
unblock_invoice: function() {
|
||||||
|
@ -361,6 +361,7 @@
|
|||||||
"fieldname": "bill_date",
|
"fieldname": "bill_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Supplier Invoice Date",
|
"label": "Supplier Invoice Date",
|
||||||
|
"no_copy": 1,
|
||||||
"oldfieldname": "bill_date",
|
"oldfieldname": "bill_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
@ -1333,8 +1334,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"modified": "2020-09-21 12:22:09.164068",
|
||||||
"modified": "2020-08-03 23:20:04.466153",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -132,6 +132,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not self.due_date:
|
if not self.due_date:
|
||||||
self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date)
|
self.due_date = get_due_date(self.posting_date, "Supplier", self.supplier, self.company, self.bill_date)
|
||||||
|
|
||||||
|
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||||
|
if tds_category and not for_validate:
|
||||||
|
self.apply_tds = 1
|
||||||
|
self.tax_withholding_category = tds_category
|
||||||
|
|
||||||
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
def check_conversion_rate(self):
|
def check_conversion_rate(self):
|
||||||
@ -400,8 +405,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None):
|
def make_gl_entries(self, gl_entries=None):
|
||||||
if not self.grand_total:
|
|
||||||
return
|
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
|
|
||||||
@ -708,7 +711,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item.item_tax_amount / self.conversion_rate)
|
item.item_tax_amount / self.conversion_rate)
|
||||||
}, item=item))
|
}, item=item))
|
||||||
else:
|
else:
|
||||||
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
|
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||||
|
asset_category=item.asset_category,company=self.company)
|
||||||
|
|
||||||
cwip_account_currency = get_account_currency(cwip_account)
|
cwip_account_currency = get_account_currency(cwip_account)
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
|
@ -1002,7 +1002,8 @@ def make_purchase_invoice(**args):
|
|||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"project": args.project,
|
"project": args.project,
|
||||||
"rejected_warehouse": args.rejected_warehouse or "",
|
"rejected_warehouse": args.rejected_warehouse or "",
|
||||||
"rejected_serial_no": args.rejected_serial_no or ""
|
"rejected_serial_no": args.rejected_serial_no or "",
|
||||||
|
"asset_location": args.location or ""
|
||||||
})
|
})
|
||||||
|
|
||||||
if args.get_taxes_and_charges:
|
if args.get_taxes_and_charges:
|
||||||
|
@ -210,7 +210,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-12 14:53:47.679439",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges",
|
"name": "Purchase Taxes and Charges",
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges Template",
|
"name": "Purchase Taxes and Charges Template",
|
||||||
"owner": "wasim@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"is_return",
|
"is_return",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
|
"company_tax_id",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"posting_time",
|
"posting_time",
|
||||||
"set_posting_time",
|
"set_posting_time",
|
||||||
@ -1825,7 +1826,7 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Sales Team1",
|
"label": "Sales Contributions and Incentives",
|
||||||
"oldfieldname": "sales_team",
|
"oldfieldname": "sales_team",
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"options": "Sales Team",
|
"options": "Sales Team",
|
||||||
@ -1926,6 +1927,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:(doc.is_pos && doc.is_consolidated)",
|
||||||
"fieldname": "is_consolidated",
|
"fieldname": "is_consolidated",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Consolidated",
|
"label": "Is Consolidated",
|
||||||
@ -1940,13 +1942,20 @@
|
|||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Is Internal Customer",
|
"label": "Is Internal Customer",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "company.tax_id",
|
||||||
|
"fieldname": "company_tax_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Company Tax ID",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 181,
|
"idx": 181,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-27 01:56:28.532140",
|
"modified": "2020-10-09 15:59:57.544736",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
|
||||||
from frappe import _, msgprint, throw
|
from frappe import _, msgprint, throw
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
@ -428,7 +428,7 @@ class SalesInvoice(SellingController):
|
|||||||
if pos.get('account_for_change_amount'):
|
if pos.get('account_for_change_amount'):
|
||||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||||
|
|
||||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||||
@ -572,7 +572,8 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def validate_pos(self):
|
def validate_pos(self):
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \
|
invoice_total = self.rounded_total or self.grand_total
|
||||||
|
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \
|
||||||
1.0/(10.0**(self.precision("grand_total") + 1.0)):
|
1.0/(10.0**(self.precision("grand_total") + 1.0)):
|
||||||
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
||||||
|
|
||||||
@ -1372,7 +1373,7 @@ def get_bank_cash_account(mode_of_payment, company):
|
|||||||
{"parent": mode_of_payment, "company": company}, "default_account")
|
{"parent": mode_of_payment, "company": company}, "default_account")
|
||||||
if not account:
|
if not account:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||||
.format(mode_of_payment))
|
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
||||||
return {
|
return {
|
||||||
"account": account
|
"account": account
|
||||||
}
|
}
|
||||||
@ -1612,18 +1613,16 @@ def update_multi_mode_option(doc, pos_profile):
|
|||||||
payment.type = payment_mode.type
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
doc.set('payments', [])
|
doc.set('payments', [])
|
||||||
if not pos_profile or not pos_profile.get('payments'):
|
|
||||||
for payment_mode in get_all_mode_of_payments(doc):
|
|
||||||
append_payment(payment_mode)
|
|
||||||
return
|
|
||||||
|
|
||||||
for pos_payment_method in pos_profile.get('payments'):
|
for pos_payment_method in pos_profile.get('payments'):
|
||||||
pos_payment_method = pos_payment_method.as_dict()
|
pos_payment_method = pos_payment_method.as_dict()
|
||||||
|
|
||||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||||
if payment_mode:
|
if not payment_mode:
|
||||||
payment_mode[0].default = pos_payment_method.default
|
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
||||||
append_payment(payment_mode[0])
|
.format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
|
||||||
|
|
||||||
|
payment_mode[0].default = pos_payment_method.default
|
||||||
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
def get_all_mode_of_payments(doc):
|
def get_all_mode_of_payments(doc):
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
|
@ -13,8 +13,7 @@ def get_data():
|
|||||||
'Auto Repeat': 'reference_document',
|
'Auto Repeat': 'reference_document',
|
||||||
},
|
},
|
||||||
'internal_links': {
|
'internal_links': {
|
||||||
'Sales Order': ['items', 'sales_order'],
|
'Sales Order': ['items', 'sales_order']
|
||||||
'Delivery Note': ['items', 'delivery_note']
|
|
||||||
},
|
},
|
||||||
'transactions': [
|
'transactions': [
|
||||||
{
|
{
|
||||||
|
@ -345,13 +345,14 @@ class Subscription(Document):
|
|||||||
invoice.set_taxes()
|
invoice.set_taxes()
|
||||||
|
|
||||||
# Due date
|
# Due date
|
||||||
invoice.append(
|
if self.days_until_due:
|
||||||
'payment_schedule',
|
invoice.append(
|
||||||
{
|
'payment_schedule',
|
||||||
'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
|
{
|
||||||
'invoice_portion': 100
|
'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
|
||||||
}
|
'invoice_portion': 100
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Discounts
|
# Discounts
|
||||||
if self.additional_discount_percentage:
|
if self.additional_discount_percentage:
|
||||||
|
@ -6,6 +6,8 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from erpnext.accounts.doctype.tax_rule.tax_rule import IncorrectCustomerGroup, IncorrectSupplierType, ConflictingTaxRule, get_tax_template
|
from erpnext.accounts.doctype.tax_rule.tax_rule import IncorrectCustomerGroup, IncorrectSupplierType, ConflictingTaxRule, get_tax_template
|
||||||
|
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
|
||||||
|
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
|
||||||
|
|
||||||
test_records = frappe.get_test_records('Tax Rule')
|
test_records = frappe.get_test_records('Tax Rule')
|
||||||
|
|
||||||
@ -144,6 +146,23 @@ class TestTaxRule(unittest.TestCase):
|
|||||||
self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
|
self.assertEqual(get_tax_template("2015-01-01", {"customer":"_Test Customer", "billing_city": "Test City 1"}),
|
||||||
"_Test Sales Taxes and Charges Template 1 - _TC")
|
"_Test Sales Taxes and Charges Template 1 - _TC")
|
||||||
|
|
||||||
|
def test_taxes_fetch_via_tax_rule(self):
|
||||||
|
make_tax_rule(customer= "_Test Customer", billing_city = "_Test City",
|
||||||
|
sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", save=1)
|
||||||
|
|
||||||
|
# create opportunity for customer
|
||||||
|
opportunity = make_opportunity(with_items=1)
|
||||||
|
|
||||||
|
# make quotation from opportunity
|
||||||
|
quotation = make_quotation(opportunity.name)
|
||||||
|
quotation.save()
|
||||||
|
|
||||||
|
self.assertEqual(quotation.taxes_and_charges, "_Test Sales Taxes and Charges Template - _TC")
|
||||||
|
|
||||||
|
# Check if accounts heads and rate fetched are also fetched from tax template or not
|
||||||
|
self.assertTrue(len(quotation.taxes) > 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def make_tax_rule(**args):
|
def make_tax_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
@ -106,6 +106,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
|
|||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where company = %s and
|
where company = %s and
|
||||||
party in %s and fiscal_year=%s and credit > 0
|
party in %s and fiscal_year=%s and credit > 0
|
||||||
|
and is_opening = 'No'
|
||||||
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
|
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
|
||||||
|
|
||||||
vouchers = [d.voucher_no for d in entries]
|
vouchers = [d.voucher_no for d in entries]
|
||||||
@ -192,6 +193,7 @@ def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=No
|
|||||||
select distinct voucher_no
|
select distinct voucher_no
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where party in %s and %s and debit > 0
|
where party in %s and %s and debit > 0
|
||||||
|
and is_opening = 'No'
|
||||||
""", (tuple(suppliers), condition)) or []
|
""", (tuple(suppliers), condition)) or []
|
||||||
|
|
||||||
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
|
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
|
||||||
|
@ -171,7 +171,7 @@ def validate_account_for_perpetual_inventory(gl_map):
|
|||||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||||
.format(account), StockAccountInvalidTransaction)
|
.format(account), StockAccountInvalidTransaction)
|
||||||
|
|
||||||
elif account_bal != stock_bal:
|
elif abs(account_bal - stock_bal) > 0.1:
|
||||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||||
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
||||||
|
|
||||||
@ -320,7 +320,6 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
|||||||
|
|
||||||
entry['remarks'] = "On cancellation of " + entry['voucher_no']
|
entry['remarks'] = "On cancellation of " + entry['voucher_no']
|
||||||
entry['is_cancelled'] = 1
|
entry['is_cancelled'] = 1
|
||||||
entry['posting_date'] = today()
|
|
||||||
|
|
||||||
if entry['debit'] or entry['credit']:
|
if entry['debit'] or entry['credit']:
|
||||||
make_entry(entry, adv_adj, "Yes")
|
make_entry(entry, adv_adj, "Yes")
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"step": "Chart Of Accounts"
|
"step": "Chart of Accounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step": "Setup Taxes"
|
"step": "Setup Taxes"
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2020-05-14 17:40:28.410447",
|
"modified": "2020-05-14 17:40:28.410447",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Chart Of Accounts",
|
"name": "Chart of Accounts",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"path": "Tree/Account",
|
"path": "Tree/Account",
|
||||||
"reference_document": "Account",
|
"reference_document": "Account",
|
||||||
"show_full_form": 0,
|
"show_full_form": 0,
|
||||||
"title": "Review Chart Of Accounts",
|
"title": "Review Chart of Accounts",
|
||||||
"validate_action": 0
|
"validate_action": 0
|
||||||
}
|
}
|
@ -72,7 +72,7 @@ erpnext.accounts.bankReconciliation = class BankReconciliation {
|
|||||||
check_plaid_status() {
|
check_plaid_status() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
|
||||||
if (r && r.enabled == "1") {
|
if (r && r.enabled === "1") {
|
||||||
me.plaid_status = "active"
|
me.plaid_status = "active"
|
||||||
} else {
|
} else {
|
||||||
me.plaid_status = "inactive"
|
me.plaid_status = "inactive"
|
||||||
@ -139,7 +139,7 @@ erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make() {
|
make() {
|
||||||
const me = this;
|
const me = this;
|
||||||
new frappe.ui.FileUploader({
|
new frappe.ui.FileUploader({
|
||||||
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
|
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
|
||||||
allow_multiple: 0,
|
allow_multiple: 0,
|
||||||
@ -214,31 +214,35 @@ erpnext.accounts.bankTransactionSync = class bankTransactionSync {
|
|||||||
|
|
||||||
init_config() {
|
init_config() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration')
|
||||||
.then(result => {
|
.then(result => {
|
||||||
me.plaid_env = result.plaid_env;
|
me.plaid_env = result.plaid_env;
|
||||||
me.plaid_public_key = result.plaid_public_key;
|
me.client_name = result.client_name;
|
||||||
me.client_name = result.client_name;
|
me.link_token = result.link_token;
|
||||||
me.sync_transactions()
|
me.sync_transactions();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sync_transactions() {
|
sync_transactions() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
|
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => {
|
||||||
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
|
||||||
bank: v['bank'],
|
bank: r.bank,
|
||||||
bank_account: me.parent.bank_account,
|
bank_account: me.parent.bank_account,
|
||||||
freeze: true
|
freeze: true
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
|
let result_title = (result && result.length > 0)
|
||||||
|
? __("{0} bank transaction(s) created", [result.length])
|
||||||
|
: __("This bank account is already synchronized");
|
||||||
|
|
||||||
let result_msg = `
|
let result_msg = `
|
||||||
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
|
||||||
<h5 class="text-muted">${result_title}</h5>
|
<h5 class="text-muted">${result_title}</h5>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
this.parent.$main_section.append(result_msg)
|
this.parent.$main_section.append(result_msg)
|
||||||
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
|
frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' });
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -384,7 +388,7 @@ erpnext.accounts.ReconciliationRow = class ReconciliationRow {
|
|||||||
})
|
})
|
||||||
|
|
||||||
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
|
||||||
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
|
{ bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") }
|
||||||
).then((result) => {
|
).then((result) => {
|
||||||
me.make_dialog(result)
|
me.make_dialog(result)
|
||||||
})
|
})
|
||||||
|
@ -19,8 +19,7 @@ def reconcile(bank_transaction, payment_doctype, payment_name):
|
|||||||
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
|
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
|
||||||
|
|
||||||
if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
|
if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
|
||||||
frappe.throw(_("The unallocated amount of Payment Entry {0} \
|
frappe.throw(_("The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount").format(payment_name))
|
||||||
is greater than the Bank Transaction's unallocated amount").format(payment_name))
|
|
||||||
|
|
||||||
if transaction.unallocated_amount == 0:
|
if transaction.unallocated_amount == 0:
|
||||||
frappe.throw(_("This bank transaction is already fully reconciled"))
|
frappe.throw(_("This bank transaction is already fully reconciled"))
|
||||||
@ -83,50 +82,30 @@ def check_matching_amount(bank_account, company, transaction):
|
|||||||
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
|
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
|
||||||
["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
|
["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
|
||||||
|
|
||||||
if transaction.credit > 0:
|
jea_side = "debit" if transaction.credit > 0 else "credit"
|
||||||
journal_entries = frappe.db.sql("""
|
journal_entries = frappe.db.sql(f"""
|
||||||
SELECT
|
SELECT
|
||||||
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
|
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
|
||||||
je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.debit_in_account_currency as paid_amount
|
jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
|
||||||
FROM
|
jea.{jea_side}_in_account_currency as paid_amount
|
||||||
`tabJournal Entry Account` as jea
|
FROM
|
||||||
JOIN
|
`tabJournal Entry Account` as jea
|
||||||
`tabJournal Entry` as je
|
JOIN
|
||||||
ON
|
`tabJournal Entry` as je
|
||||||
jea.parent = je.name
|
ON
|
||||||
WHERE
|
jea.parent = je.name
|
||||||
(je.clearance_date is null or je.clearance_date='0000-00-00')
|
WHERE
|
||||||
AND
|
(je.clearance_date is null or je.clearance_date='0000-00-00')
|
||||||
jea.account = %s
|
AND
|
||||||
AND
|
jea.account = %(bank_account)s
|
||||||
jea.debit_in_account_currency like %s
|
AND
|
||||||
AND
|
jea.{jea_side}_in_account_currency like %(txt)s
|
||||||
je.docstatus = 1
|
AND
|
||||||
""", (bank_account, amount), as_dict=True)
|
je.docstatus = 1
|
||||||
else:
|
""", {
|
||||||
journal_entries = frappe.db.sql("""
|
'bank_account': bank_account,
|
||||||
SELECT
|
'txt': '%%%s%%' % amount
|
||||||
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
|
}, as_dict=True)
|
||||||
jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
|
|
||||||
jea.credit_in_account_currency as paid_amount
|
|
||||||
FROM
|
|
||||||
`tabJournal Entry Account` as jea
|
|
||||||
JOIN
|
|
||||||
`tabJournal Entry` as je
|
|
||||||
ON
|
|
||||||
jea.parent = je.name
|
|
||||||
WHERE
|
|
||||||
(je.clearance_date is null or je.clearance_date='0000-00-00')
|
|
||||||
AND
|
|
||||||
jea.account = %(bank_account)s
|
|
||||||
AND
|
|
||||||
jea.credit_in_account_currency like %(txt)s
|
|
||||||
AND
|
|
||||||
je.docstatus = 1
|
|
||||||
""", {
|
|
||||||
'bank_account': bank_account,
|
|
||||||
'txt': '%%%s%%' % amount
|
|
||||||
}, as_dict=True)
|
|
||||||
|
|
||||||
if transaction.credit > 0:
|
if transaction.credit > 0:
|
||||||
sales_invoices = frappe.db.sql("""
|
sales_invoices = frappe.db.sql("""
|
||||||
|
@ -203,7 +203,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_party_account(party_type, party, company):
|
def get_party_account(party_type, party, company=None):
|
||||||
"""Returns the account for the given `party`.
|
"""Returns the account for the given `party`.
|
||||||
Will first search in party (Customer / Supplier) record, if not found,
|
Will first search in party (Customer / Supplier) record, if not found,
|
||||||
will search in group (Customer Group / Supplier Group),
|
will search in group (Customer Group / Supplier Group),
|
||||||
|
@ -34,7 +34,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
filters: {
|
filters: {
|
||||||
'company': company
|
'company': company
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -617,9 +617,19 @@ class ReceivablePayableReport(object):
|
|||||||
elif party_type_field=="supplier":
|
elif party_type_field=="supplier":
|
||||||
self.add_supplier_filters(conditions, values)
|
self.add_supplier_filters(conditions, values)
|
||||||
|
|
||||||
|
if self.filters.cost_center:
|
||||||
|
self.get_cost_center_conditions(conditions)
|
||||||
|
|
||||||
self.add_accounting_dimensions_filters(conditions, values)
|
self.add_accounting_dimensions_filters(conditions, values)
|
||||||
return " and ".join(conditions), values
|
return " and ".join(conditions), values
|
||||||
|
|
||||||
|
def get_cost_center_conditions(self, conditions):
|
||||||
|
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
||||||
|
cost_center_list = [center.name for center in frappe.get_list("Cost Center", filters = {'lft': (">=", lft), 'rgt': ("<=", rgt)})]
|
||||||
|
|
||||||
|
cost_center_string = '", "'.join(cost_center_list)
|
||||||
|
conditions.append('cost_center in ("{0}")'.format(cost_center_string))
|
||||||
|
|
||||||
def get_order_by_condition(self):
|
def get_order_by_condition(self):
|
||||||
if self.filters.get('group_by_party'):
|
if self.filters.get('group_by_party'):
|
||||||
return "order by party, posting_date"
|
return "order by party, posting_date"
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
frappe.query_reports["Bank Reconciliation Statement"] = {
|
frappe.query_reports["Bank Reconciliation Statement"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.defaults.get_user_default("Company")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"account",
|
"fieldname":"account",
|
||||||
"label": __("Bank Account"),
|
"label": __("Bank Account"),
|
||||||
@ -12,11 +20,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
|||||||
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
|
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"get_query": function() {
|
"get_query": function() {
|
||||||
|
var company = frappe.query_report.get_filter_value('company')
|
||||||
return {
|
return {
|
||||||
"query": "erpnext.controllers.queries.get_account_list",
|
"query": "erpnext.controllers.queries.get_account_list",
|
||||||
"filters": [
|
"filters": [
|
||||||
['Account', 'account_type', 'in', 'Bank, Cash'],
|
['Account', 'account_type', 'in', 'Bank, Cash'],
|
||||||
['Account', 'is_group', '=', 0],
|
['Account', 'is_group', '=', 0],
|
||||||
|
['Account', 'disabled', '=', 0],
|
||||||
|
['Account', 'company', '=', company],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,4 +45,4 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
|||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
|
|||||||
"""accumulate children's values in parent accounts"""
|
"""accumulate children's values in parent accounts"""
|
||||||
for d in reversed(accounts):
|
for d in reversed(accounts):
|
||||||
if d.parent_account:
|
if d.parent_account:
|
||||||
account = d.parent_account.split('-')[0].strip()
|
account = d.parent_account.split(' - ')[0].strip()
|
||||||
if not accounts_by_name.get(account):
|
if not accounts_by_name.get(account):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -268,9 +268,9 @@ class GrossProfitGenerator(object):
|
|||||||
def get_last_purchase_rate(self, item_code, row):
|
def get_last_purchase_rate(self, item_code, row):
|
||||||
condition = ''
|
condition = ''
|
||||||
if row.project:
|
if row.project:
|
||||||
condition += " AND a.project='%s'" % (row.project)
|
condition += " AND a.project=%s" % (frappe.db.escape(row.project))
|
||||||
elif row.cost_center:
|
elif row.cost_center:
|
||||||
condition += " AND a.cost_center='%s'" % (row.cost_center)
|
condition += " AND a.cost_center=%s" % (frappe.db.escape(row.cost_center))
|
||||||
if self.filters.to_date:
|
if self.filters.to_date:
|
||||||
condition += " AND modified='%s'" % (self.filters.to_date)
|
condition += " AND modified='%s'" % (self.filters.to_date)
|
||||||
|
|
||||||
|
76
erpnext/accounts/report/pos_register/pos_register.js
Normal file
76
erpnext/accounts/report/pos_register/pos_register.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports["POS Register"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"from_date",
|
||||||
|
"label": __("From Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "60px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"to_date",
|
||||||
|
"label": __("To Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"default": frappe.datetime.get_today(),
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "60px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"pos_profile",
|
||||||
|
"label": __("POS Profile"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "POS Profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"cashier",
|
||||||
|
"label": __("Cashier"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"customer",
|
||||||
|
"label": __("Customer"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Customer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"mode_of_payment",
|
||||||
|
"label": __("Payment Method"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Mode of Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"group_by",
|
||||||
|
"label": __("Group by"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["", "POS Profile", "Cashier", "Payment Method", "Customer"],
|
||||||
|
"default": "POS Profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"is_return",
|
||||||
|
"label": __("Is Return"),
|
||||||
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
if (data && data.bold) {
|
||||||
|
value = value.bold();
|
||||||
|
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
30
erpnext/accounts/report/pos_register/pos_register.json
Normal file
30
erpnext/accounts/report/pos_register/pos_register.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2020-09-10 19:25:03.766871",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"json": "{}",
|
||||||
|
"modified": "2020-09-10 19:25:15.851331",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "POS Register",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "POS Invoice",
|
||||||
|
"report_name": "POS Register",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
222
erpnext/accounts/report/pos_register/pos_register.py
Normal file
222
erpnext/accounts/report/pos_register/pos_register.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _, _dict
|
||||||
|
from erpnext import get_company_currency, get_default_company
|
||||||
|
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
if not filters:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
validate_filters(filters)
|
||||||
|
|
||||||
|
columns = get_columns(filters)
|
||||||
|
|
||||||
|
group_by_field = get_group_by_field(filters.get("group_by"))
|
||||||
|
|
||||||
|
pos_entries = get_pos_entries(filters, group_by_field)
|
||||||
|
if group_by_field != "mode_of_payment":
|
||||||
|
concat_mode_of_payments(pos_entries)
|
||||||
|
|
||||||
|
# return only entries if group by is unselected
|
||||||
|
if not group_by_field:
|
||||||
|
return columns, pos_entries
|
||||||
|
|
||||||
|
# handle grouping
|
||||||
|
invoice_map, grouped_data = {}, []
|
||||||
|
for d in pos_entries:
|
||||||
|
invoice_map.setdefault(d[group_by_field], []).append(d)
|
||||||
|
|
||||||
|
for key in invoice_map:
|
||||||
|
invoices = invoice_map[key]
|
||||||
|
grouped_data += invoices
|
||||||
|
add_subtotal_row(grouped_data, invoices, group_by_field, key)
|
||||||
|
|
||||||
|
# move group by column to first position
|
||||||
|
column_index = next((index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None)
|
||||||
|
columns.insert(0, columns.pop(column_index))
|
||||||
|
|
||||||
|
return columns, grouped_data
|
||||||
|
|
||||||
|
def get_pos_entries(filters, group_by_field):
|
||||||
|
conditions = get_conditions(filters)
|
||||||
|
order_by = "p.posting_date"
|
||||||
|
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
||||||
|
if group_by_field == "mode_of_payment":
|
||||||
|
select_mop_field = ", sip.mode_of_payment"
|
||||||
|
from_sales_invoice_payment = ", `tabSales Invoice Payment` sip"
|
||||||
|
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND"
|
||||||
|
order_by += ", sip.mode_of_payment"
|
||||||
|
|
||||||
|
elif group_by_field:
|
||||||
|
order_by += ", p.{}".format(group_by_field)
|
||||||
|
|
||||||
|
return frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
||||||
|
p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount,
|
||||||
|
p.customer, p.is_return {select_mop_field}
|
||||||
|
FROM
|
||||||
|
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||||
|
WHERE
|
||||||
|
{group_by_mop_condition}
|
||||||
|
{conditions}
|
||||||
|
ORDER BY
|
||||||
|
{order_by}
|
||||||
|
""".format(
|
||||||
|
select_mop_field=select_mop_field,
|
||||||
|
from_sales_invoice_payment=from_sales_invoice_payment,
|
||||||
|
group_by_mop_condition=group_by_mop_condition,
|
||||||
|
conditions=conditions,
|
||||||
|
order_by=order_by
|
||||||
|
), filters, as_dict=1)
|
||||||
|
|
||||||
|
def concat_mode_of_payments(pos_entries):
|
||||||
|
mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries]))
|
||||||
|
for entry in pos_entries:
|
||||||
|
if mode_of_payments.get(entry.pos_invoice):
|
||||||
|
entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
|
||||||
|
|
||||||
|
def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
|
||||||
|
grand_total = sum([d.grand_total for d in group_invoices])
|
||||||
|
paid_amount = sum([d.paid_amount for d in group_invoices])
|
||||||
|
data.append({
|
||||||
|
group_by_field: group_by_value,
|
||||||
|
"grand_total": grand_total,
|
||||||
|
"paid_amount": paid_amount,
|
||||||
|
"bold": 1
|
||||||
|
})
|
||||||
|
data.append({})
|
||||||
|
|
||||||
|
def validate_filters(filters):
|
||||||
|
if not filters.get("company"):
|
||||||
|
frappe.throw(_("{0} is mandatory").format(_("Company")))
|
||||||
|
|
||||||
|
if not filters.get("from_date") and not filters.get("to_date"):
|
||||||
|
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
||||||
|
|
||||||
|
if filters.from_date > filters.to_date:
|
||||||
|
frappe.throw(_("From Date must be before To Date"))
|
||||||
|
|
||||||
|
if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')):
|
||||||
|
frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile"))
|
||||||
|
|
||||||
|
if (filters.get("customer") and filters.get("group_by") == _('Customer')):
|
||||||
|
frappe.throw(_("Can not filter based on Customer, if grouped by Customer"))
|
||||||
|
|
||||||
|
if (filters.get("owner") and filters.get("group_by") == _('Cashier')):
|
||||||
|
frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier"))
|
||||||
|
|
||||||
|
if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')):
|
||||||
|
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
|
||||||
|
|
||||||
|
def get_conditions(filters):
|
||||||
|
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
|
||||||
|
company=filters.get("company"),
|
||||||
|
from_date=filters.get("from_date"),
|
||||||
|
to_date=filters.get("to_date"))
|
||||||
|
|
||||||
|
if filters.get("pos_profile"):
|
||||||
|
conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
|
||||||
|
|
||||||
|
if filters.get("owner"):
|
||||||
|
conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
|
||||||
|
|
||||||
|
if filters.get("customer"):
|
||||||
|
conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
|
||||||
|
|
||||||
|
if filters.get("is_return"):
|
||||||
|
conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
|
||||||
|
|
||||||
|
if filters.get("mode_of_payment"):
|
||||||
|
conditions += """
|
||||||
|
AND EXISTS(
|
||||||
|
SELECT name FROM `tabSales Invoice Payment` sip
|
||||||
|
WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s
|
||||||
|
)"""
|
||||||
|
|
||||||
|
return conditions
|
||||||
|
|
||||||
|
def get_group_by_field(group_by):
|
||||||
|
group_by_field = ""
|
||||||
|
|
||||||
|
if group_by == "POS Profile":
|
||||||
|
group_by_field = "pos_profile"
|
||||||
|
elif group_by == "Cashier":
|
||||||
|
group_by_field = "owner"
|
||||||
|
elif group_by == "Customer":
|
||||||
|
group_by_field = "customer"
|
||||||
|
elif group_by == "Payment Method":
|
||||||
|
group_by_field = "mode_of_payment"
|
||||||
|
|
||||||
|
return group_by_field
|
||||||
|
|
||||||
|
def get_columns(filters):
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
"label": _("Posting Date"),
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("POS Invoice"),
|
||||||
|
"fieldname": "pos_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "POS Invoice",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Customer"),
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Customer",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("POS Profile"),
|
||||||
|
"fieldname": "pos_profile",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "POS Profile",
|
||||||
|
"width": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Cashier"),
|
||||||
|
"fieldname": "owner",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "User",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Grand Total"),
|
||||||
|
"fieldname": "grand_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "company:currency",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Paid Amount"),
|
||||||
|
"fieldname": "paid_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "company:currency",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Payment Method"),
|
||||||
|
"fieldname": "mode_of_payment",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Is Return"),
|
||||||
|
"fieldname": "is_return",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 80
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return columns
|
@ -32,12 +32,12 @@ def execute(filters=None):
|
|||||||
|
|
||||||
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
|
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||||
|
|
||||||
default_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
|
currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||||
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, default_currency)
|
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency)
|
||||||
|
|
||||||
return columns, data, None, chart, report_summary
|
return columns, data, None, chart, report_summary
|
||||||
|
|
||||||
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, default_currency, consolidated=False):
|
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False):
|
||||||
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
|
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
|
||||||
|
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
@ -64,19 +64,19 @@ def get_report_summary(period_list, periodicity, income, expense, net_profit_los
|
|||||||
"indicator": "Green" if net_profit > 0 else "Red",
|
"indicator": "Green" if net_profit > 0 else "Red",
|
||||||
"label": profit_label,
|
"label": profit_label,
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": net_profit_loss.get("currency") if net_profit_loss else default_currency
|
"currency": currency
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": net_income,
|
"value": net_income,
|
||||||
"label": income_label,
|
"label": income_label,
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": income[-1].get('currency') if income else default_currency
|
"currency": currency
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": net_expense,
|
"value": net_expense,
|
||||||
"label": expense_label,
|
"label": expense_label,
|
||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": expense[-1].get('currency') if expense else default_currency
|
"currency": currency
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -143,4 +143,4 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
|
|||||||
|
|
||||||
chart["fieldtype"] = "Currency"
|
chart["fieldtype"] = "Currency"
|
||||||
|
|
||||||
return chart
|
return chart
|
||||||
|
@ -72,6 +72,12 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Finance Book",
|
"options": "Finance Book",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "presentation_currency",
|
||||||
|
"label": __("Currency"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": erpnext.get_presentation_currency_list()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "with_period_closing_entry",
|
"fieldname": "with_period_closing_entry",
|
||||||
"label": __("Period Closing Entry"),
|
"label": __("Period Closing Entry"),
|
||||||
|
@ -56,7 +56,7 @@ def get_data(filters):
|
|||||||
accounts = frappe.db.sql("""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
|
accounts = frappe.db.sql("""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
|
||||||
|
|
||||||
from `tabAccount` where company=%s order by lft""", filters.company, as_dict=True)
|
from `tabAccount` where company=%s order by lft""", filters.company, as_dict=True)
|
||||||
company_currency = erpnext.get_company_currency(filters.company)
|
company_currency = filters.presentation_currency or erpnext.get_company_currency(filters.company)
|
||||||
|
|
||||||
if not accounts:
|
if not accounts:
|
||||||
return None
|
return None
|
||||||
|
@ -683,6 +683,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
|||||||
where
|
where
|
||||||
party_type = %(party_type)s and party = %(party)s
|
party_type = %(party_type)s and party = %(party)s
|
||||||
and account = %(account)s and {dr_or_cr} > 0
|
and account = %(account)s and {dr_or_cr} > 0
|
||||||
|
and is_cancelled=0
|
||||||
{condition}
|
{condition}
|
||||||
and ((voucher_type = 'Journal Entry'
|
and ((voucher_type = 'Journal Entry'
|
||||||
and (against_voucher = '' or against_voucher is null))
|
and (against_voucher = '' or against_voucher is null))
|
||||||
@ -705,6 +706,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
|||||||
and account = %(account)s
|
and account = %(account)s
|
||||||
and {payment_dr_or_cr} > 0
|
and {payment_dr_or_cr} > 0
|
||||||
and against_voucher is not null and against_voucher != ''
|
and against_voucher is not null and against_voucher != ''
|
||||||
|
and is_cancelled=0
|
||||||
group by against_voucher_type, against_voucher
|
group by against_voucher_type, against_voucher
|
||||||
""".format(payment_dr_or_cr=payment_dr_or_cr), {
|
""".format(payment_dr_or_cr=payment_dr_or_cr), {
|
||||||
"party_type": party_type,
|
"party_type": party_type,
|
||||||
|
@ -252,13 +252,6 @@ frappe.ui.form.on('Asset', {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
available_for_use_date: function(frm) {
|
|
||||||
$.each(frm.doc.finance_books || [], function(i, d) {
|
|
||||||
if(!d.depreciation_start_date) d.depreciation_start_date = frm.doc.available_for_use_date;
|
|
||||||
});
|
|
||||||
refresh_field("finance_books");
|
|
||||||
},
|
|
||||||
|
|
||||||
is_existing_asset: function(frm) {
|
is_existing_asset: function(frm) {
|
||||||
frm.trigger("toggle_reference_doc");
|
frm.trigger("toggle_reference_doc");
|
||||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||||
@ -438,6 +431,15 @@ frappe.ui.form.on('Asset Finance Book', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
frappe.flags.dont_change_rate = false;
|
frappe.flags.dont_change_rate = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
depreciation_start_date: function(frm, cdt, cdn) {
|
||||||
|
const book = locals[cdt][cdn];
|
||||||
|
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
|
||||||
|
frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
|
||||||
|
book.depreciation_start_date = "";
|
||||||
|
frm.refresh_field("finance_books");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe, erpnext, math, json
|
import frappe, erpnext, math, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from six import string_types
|
from six import string_types
|
||||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days
|
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days, get_last_day, get_datetime
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
from erpnext.assets.doctype.asset.depreciation \
|
from erpnext.assets.doctype.asset.depreciation \
|
||||||
@ -83,6 +83,11 @@ class Asset(AccountsController):
|
|||||||
if not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
frappe.throw(_("Available for use date is required"))
|
frappe.throw(_("Available for use date is required"))
|
||||||
|
|
||||||
|
for d in self.finance_books:
|
||||||
|
if d.depreciation_start_date == self.available_for_use_date:
|
||||||
|
frappe.throw(_("Row #{}: Depreciation Posting Date should not be equal to Available for Use Date.").format(d.idx),
|
||||||
|
title=_("Incorrect Date"))
|
||||||
|
|
||||||
def set_missing_values(self):
|
def set_missing_values(self):
|
||||||
if not self.asset_category:
|
if not self.asset_category:
|
||||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
@ -126,7 +131,7 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
def validate_gross_and_purchase_amount(self):
|
def validate_gross_and_purchase_amount(self):
|
||||||
if self.is_existing_asset: return
|
if self.is_existing_asset: return
|
||||||
|
|
||||||
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
|
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
|
||||||
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
|
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
|
||||||
Please do not book expense of multiple assets against one single Asset.")
|
Please do not book expense of multiple assets against one single Asset.")
|
||||||
@ -135,6 +140,10 @@ class Asset(AccountsController):
|
|||||||
def make_asset_movement(self):
|
def make_asset_movement(self):
|
||||||
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
|
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'
|
||||||
reference_docname = self.purchase_receipt or self.purchase_invoice
|
reference_docname = self.purchase_receipt or self.purchase_invoice
|
||||||
|
transaction_date = getdate(self.purchase_date)
|
||||||
|
if reference_docname:
|
||||||
|
posting_date, posting_time = frappe.db.get_value(reference_doctype, reference_docname, ["posting_date", "posting_time"])
|
||||||
|
transaction_date = get_datetime("{} {}".format(posting_date, posting_time))
|
||||||
assets = [{
|
assets = [{
|
||||||
'asset': self.name,
|
'asset': self.name,
|
||||||
'asset_name': self.asset_name,
|
'asset_name': self.asset_name,
|
||||||
@ -146,7 +155,7 @@ class Asset(AccountsController):
|
|||||||
'assets': assets,
|
'assets': assets,
|
||||||
'purpose': 'Receipt',
|
'purpose': 'Receipt',
|
||||||
'company': self.company,
|
'company': self.company,
|
||||||
'transaction_date': getdate(nowdate()),
|
'transaction_date': transaction_date,
|
||||||
'reference_doctype': reference_doctype,
|
'reference_doctype': reference_doctype,
|
||||||
'reference_name': reference_docname
|
'reference_name': reference_docname
|
||||||
}).insert()
|
}).insert()
|
||||||
@ -294,7 +303,7 @@ class Asset(AccountsController):
|
|||||||
if not row.depreciation_start_date:
|
if not row.depreciation_start_date:
|
||||||
if not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
|
||||||
row.depreciation_start_date = self.available_for_use_date
|
row.depreciation_start_date = get_last_day(self.available_for_use_date)
|
||||||
|
|
||||||
if not self.is_existing_asset:
|
if not self.is_existing_asset:
|
||||||
self.opening_accumulated_depreciation = 0
|
self.opening_accumulated_depreciation = 0
|
||||||
@ -457,50 +466,63 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
def validate_make_gl_entry(self):
|
def validate_make_gl_entry(self):
|
||||||
purchase_document = self.get_purchase_document()
|
purchase_document = self.get_purchase_document()
|
||||||
asset_bought_with_invoice = purchase_document == self.purchase_invoice
|
if not purchase_document:
|
||||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
|
||||||
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
|
||||||
# check if expense already has been booked in case of cwip was enabled after purchasing asset
|
|
||||||
expense_booked = False
|
|
||||||
cwip_booked = False
|
|
||||||
|
|
||||||
if asset_bought_with_invoice:
|
|
||||||
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
|
||||||
(purchase_document, fixed_asset_account), as_dict=1)
|
|
||||||
else:
|
|
||||||
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
|
||||||
(purchase_document, cwip_account), as_dict=1)
|
|
||||||
|
|
||||||
if cwip_enabled and (expense_booked or not cwip_booked):
|
|
||||||
# if expense has already booked from invoice or cwip is booked from receipt
|
|
||||||
return False
|
return False
|
||||||
elif not cwip_enabled and (not expense_booked or cwip_booked):
|
|
||||||
# if cwip is disabled but expense hasn't been booked yet
|
asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
|
||||||
return True
|
fixed_asset_account = self.get_fixed_asset_account()
|
||||||
elif cwip_enabled:
|
|
||||||
# default condition
|
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||||
return True
|
cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled)
|
||||||
|
|
||||||
|
query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s"""
|
||||||
|
if asset_bought_with_invoice:
|
||||||
|
# with invoice purchase either expense or cwip has been booked
|
||||||
|
expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1)
|
||||||
|
if expense_booked:
|
||||||
|
# if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled
|
||||||
|
return False
|
||||||
|
|
||||||
|
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
|
||||||
|
if cwip_booked:
|
||||||
|
# if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# with receipt purchase either cwip has been booked or no entries have been made
|
||||||
|
if not cwip_account:
|
||||||
|
# if cwip account isn't available do not make gl entries
|
||||||
|
return False
|
||||||
|
|
||||||
|
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
|
||||||
|
# if cwip is not booked from receipt then do not make gl entries
|
||||||
|
# if cwip is booked from receipt then make gl entries
|
||||||
|
return cwip_booked
|
||||||
|
|
||||||
def get_purchase_document(self):
|
def get_purchase_document(self):
|
||||||
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
|
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
|
||||||
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
|
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
|
||||||
|
|
||||||
return purchase_document
|
return purchase_document
|
||||||
|
|
||||||
|
def get_fixed_asset_account(self):
|
||||||
|
return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
|
||||||
|
|
||||||
|
def get_cwip_account(self, cwip_enabled=False):
|
||||||
|
cwip_account = None
|
||||||
|
try:
|
||||||
|
cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company)
|
||||||
|
except:
|
||||||
|
# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass
|
||||||
|
if cwip_enabled:
|
||||||
|
raise
|
||||||
|
|
||||||
def get_asset_accounts(self):
|
return cwip_account
|
||||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
|
||||||
asset_category = self.asset_category, company = self.company)
|
|
||||||
|
|
||||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
|
||||||
self.name, self.asset_category, self.company)
|
|
||||||
|
|
||||||
return fixed_asset_account, cwip_account
|
|
||||||
|
|
||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
purchase_document = self.get_purchase_document()
|
purchase_document = self.get_purchase_document()
|
||||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
||||||
|
|
||||||
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
||||||
|
|
||||||
@ -552,14 +574,18 @@ class Asset(AccountsController):
|
|||||||
return 100 * (1 - flt(depreciation_rate, float_precision))
|
return 100 * (1 - flt(depreciation_rate, float_precision))
|
||||||
|
|
||||||
def update_maintenance_status():
|
def update_maintenance_status():
|
||||||
assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1})
|
assets = frappe.get_all(
|
||||||
|
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
|
||||||
|
)
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
asset = frappe.get_doc("Asset", asset.name)
|
asset = frappe.get_doc("Asset", asset.name)
|
||||||
if frappe.db.exists('Asset Maintenance Task', {'parent': asset.name, 'next_due_date': today()}):
|
if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}):
|
||||||
asset.set_status('In Maintenance')
|
asset.set_status("Out of Order")
|
||||||
if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}):
|
elif frappe.db.exists("Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}):
|
||||||
asset.set_status('Out of Order')
|
asset.set_status("In Maintenance")
|
||||||
|
else:
|
||||||
|
asset.set_status()
|
||||||
|
|
||||||
def make_post_gl_entry():
|
def make_post_gl_entry():
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad
|
|||||||
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
|
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
|
||||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice
|
from erpnext.assets.doctype.asset.asset import make_sales_invoice
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
|
||||||
|
|
||||||
class TestAsset(unittest.TestCase):
|
class TestAsset(unittest.TestCase):
|
||||||
@ -371,19 +372,18 @@ class TestAsset(unittest.TestCase):
|
|||||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||||
asset = frappe.get_doc('Asset', asset_name)
|
asset = frappe.get_doc('Asset', asset_name)
|
||||||
asset.calculate_depreciation = 1
|
asset.calculate_depreciation = 1
|
||||||
asset.available_for_use_date = nowdate()
|
asset.available_for_use_date = '2020-01-01'
|
||||||
asset.purchase_date = nowdate()
|
asset.purchase_date = '2020-01-01'
|
||||||
asset.append("finance_books", {
|
asset.append("finance_books", {
|
||||||
"expected_value_after_useful_life": 10000,
|
"expected_value_after_useful_life": 10000,
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 10,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 1
|
||||||
"depreciation_start_date": nowdate()
|
|
||||||
})
|
})
|
||||||
asset.insert()
|
asset.insert()
|
||||||
asset.submit()
|
asset.submit()
|
||||||
|
|
||||||
post_depreciation_entries(date=add_months(nowdate(), 10))
|
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
||||||
|
|
||||||
scrap_asset(asset.name)
|
scrap_asset(asset.name)
|
||||||
|
|
||||||
@ -392,9 +392,9 @@ class TestAsset(unittest.TestCase):
|
|||||||
self.assertTrue(asset.journal_entry_for_scrap)
|
self.assertTrue(asset.journal_entry_for_scrap)
|
||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
|
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
|
||||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||||
("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
|
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||||
@ -466,8 +466,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
"expected_value_after_useful_life": 10000,
|
"expected_value_after_useful_life": 10000,
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10
|
||||||
"depreciation_start_date": "2020-06-06"
|
|
||||||
})
|
})
|
||||||
asset.insert()
|
asset.insert()
|
||||||
accumulated_depreciation_after_full_schedule = \
|
accumulated_depreciation_after_full_schedule = \
|
||||||
@ -560,81 +559,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(gle, expected_gle)
|
self.assertEqual(gle, expected_gle)
|
||||||
|
|
||||||
def test_gle_with_cwip_toggling(self):
|
|
||||||
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
|
|
||||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
|
||||||
|
|
||||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
|
||||||
qty=1, rate=5000, do_not_submit=True, location="Test Location")
|
|
||||||
pr.set('taxes', [{
|
|
||||||
'category': 'Total',
|
|
||||||
'add_deduct_tax': 'Add',
|
|
||||||
'charge_type': 'On Net Total',
|
|
||||||
'account_head': '_Test Account Service Tax - _TC',
|
|
||||||
'description': '_Test Account Service Tax',
|
|
||||||
'cost_center': 'Main - _TC',
|
|
||||||
'rate': 5.0
|
|
||||||
}, {
|
|
||||||
'category': 'Valuation and Total',
|
|
||||||
'add_deduct_tax': 'Add',
|
|
||||||
'charge_type': 'On Net Total',
|
|
||||||
'account_head': '_Test Account Shipping Charges - _TC',
|
|
||||||
'description': '_Test Account Shipping Charges',
|
|
||||||
'cost_center': 'Main - _TC',
|
|
||||||
'rate': 5.0
|
|
||||||
}])
|
|
||||||
pr.submit()
|
|
||||||
expected_gle = (
|
|
||||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
|
||||||
("CWIP Account - _TC", 5250.0, 0.0)
|
|
||||||
)
|
|
||||||
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
|
||||||
where voucher_type='Purchase Receipt' and voucher_no = %s
|
|
||||||
order by account""", pr.name)
|
|
||||||
self.assertEqual(pr_gle, expected_gle)
|
|
||||||
|
|
||||||
pi = make_invoice(pr.name)
|
|
||||||
pi.submit()
|
|
||||||
expected_gle = (
|
|
||||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
|
||||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
|
||||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
|
||||||
("Creditors - _TC", 0.0, 5500.0),
|
|
||||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
|
||||||
)
|
|
||||||
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
|
||||||
where voucher_type='Purchase Invoice' and voucher_no = %s
|
|
||||||
order by account""", pi.name)
|
|
||||||
self.assertEqual(pi_gle, expected_gle)
|
|
||||||
|
|
||||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
|
||||||
asset_doc = frappe.get_doc('Asset', asset)
|
|
||||||
month_end_date = get_last_day(nowdate())
|
|
||||||
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
|
||||||
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
|
|
||||||
asset_doc.append("finance_books", {
|
|
||||||
"expected_value_after_useful_life": 200,
|
|
||||||
"depreciation_method": "Straight Line",
|
|
||||||
"total_number_of_depreciations": 3,
|
|
||||||
"frequency_of_depreciation": 10,
|
|
||||||
"depreciation_start_date": month_end_date
|
|
||||||
})
|
|
||||||
|
|
||||||
# disable cwip and try submitting
|
|
||||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
|
||||||
asset_doc.submit()
|
|
||||||
# asset should have gl entries even if cwip is disabled
|
|
||||||
expected_gle = (
|
|
||||||
("_Test Fixed Asset - _TC", 5250.0, 0.0),
|
|
||||||
("CWIP Account - _TC", 0.0, 5250.0)
|
|
||||||
)
|
|
||||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
|
||||||
where voucher_type='Asset' and voucher_no = %s
|
|
||||||
order by account""", asset_doc.name)
|
|
||||||
self.assertEqual(gle, expected_gle)
|
|
||||||
|
|
||||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
|
||||||
|
|
||||||
def test_expense_head(self):
|
def test_expense_head(self):
|
||||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||||
qty=2, rate=200000.0, location="Test Location")
|
qty=2, rate=200000.0, location="Test Location")
|
||||||
@ -642,6 +566,74 @@ class TestAsset(unittest.TestCase):
|
|||||||
doc = make_invoice(pr.name)
|
doc = make_invoice(pr.name)
|
||||||
|
|
||||||
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||||
|
|
||||||
|
def test_asset_cwip_toggling_cases(self):
|
||||||
|
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
||||||
|
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
|
||||||
|
cwip_acc = "CWIP Account - _TC"
|
||||||
|
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||||
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||||
|
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "")
|
||||||
|
|
||||||
|
# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set
|
||||||
|
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||||
|
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||||
|
asset_doc = frappe.get_doc('Asset', asset)
|
||||||
|
asset_doc.available_for_use_date = nowdate()
|
||||||
|
asset_doc.calculate_depreciation = 0
|
||||||
|
asset_doc.submit()
|
||||||
|
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||||
|
self.assertFalse(gle)
|
||||||
|
|
||||||
|
# case 1 -- PR with cwip disabled, Asset with cwip enabled
|
||||||
|
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||||
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||||
|
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||||
|
asset_doc = frappe.get_doc('Asset', asset)
|
||||||
|
asset_doc.available_for_use_date = nowdate()
|
||||||
|
asset_doc.calculate_depreciation = 0
|
||||||
|
asset_doc.submit()
|
||||||
|
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||||
|
self.assertFalse(gle)
|
||||||
|
|
||||||
|
# case 2 -- PR with cwip enabled, Asset with cwip disabled
|
||||||
|
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||||
|
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||||
|
asset_doc = frappe.get_doc('Asset', asset)
|
||||||
|
asset_doc.available_for_use_date = nowdate()
|
||||||
|
asset_doc.calculate_depreciation = 0
|
||||||
|
asset_doc.submit()
|
||||||
|
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||||
|
self.assertTrue(gle)
|
||||||
|
|
||||||
|
# case 3 -- PI with cwip disabled, Asset with cwip enabled
|
||||||
|
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||||
|
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||||
|
asset_doc = frappe.get_doc('Asset', asset)
|
||||||
|
asset_doc.available_for_use_date = nowdate()
|
||||||
|
asset_doc.calculate_depreciation = 0
|
||||||
|
asset_doc.submit()
|
||||||
|
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||||
|
self.assertFalse(gle)
|
||||||
|
|
||||||
|
# case 4 -- PI with cwip enabled, Asset with cwip disabled
|
||||||
|
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||||
|
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||||
|
asset_doc = frappe.get_doc('Asset', asset)
|
||||||
|
asset_doc.available_for_use_date = nowdate()
|
||||||
|
asset_doc.calculate_depreciation = 0
|
||||||
|
asset_doc.submit()
|
||||||
|
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||||
|
self.assertTrue(gle)
|
||||||
|
|
||||||
|
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
|
||||||
|
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||||
|
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
||||||
|
|
||||||
def create_asset_data():
|
def create_asset_data():
|
||||||
if not frappe.db.exists("Asset Category", "Computers"):
|
if not frappe.db.exists("Asset Category", "Computers"):
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint, get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class AssetCategory(Document):
|
class AssetCategory(Document):
|
||||||
@ -13,6 +13,7 @@ class AssetCategory(Document):
|
|||||||
self.validate_finance_books()
|
self.validate_finance_books()
|
||||||
self.validate_account_types()
|
self.validate_account_types()
|
||||||
self.validate_account_currency()
|
self.validate_account_currency()
|
||||||
|
self.valide_cwip_account()
|
||||||
|
|
||||||
def validate_finance_books(self):
|
def validate_finance_books(self):
|
||||||
for d in self.finance_books:
|
for d in self.finance_books:
|
||||||
@ -58,6 +59,21 @@ class AssetCategory(Document):
|
|||||||
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
||||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
||||||
title=_("Invalid Account"))
|
title=_("Invalid Account"))
|
||||||
|
|
||||||
|
def valide_cwip_account(self):
|
||||||
|
if self.enable_cwip_accounting:
|
||||||
|
missing_cwip_accounts_for_company = []
|
||||||
|
for d in self.accounts:
|
||||||
|
if (not d.capital_work_in_progress_account and
|
||||||
|
not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
|
||||||
|
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
|
||||||
|
|
||||||
|
if missing_cwip_accounts_for_company:
|
||||||
|
msg = _("""To enable Capital Work in Progress Accounting, """)
|
||||||
|
msg += _("""you must select Capital Work in Progress Account in accounts table""")
|
||||||
|
msg += "<br><br>"
|
||||||
|
msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company))
|
||||||
|
frappe.throw(msg, title=_("Missing Account"))
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -26,4 +26,22 @@ class TestAssetCategory(unittest.TestCase):
|
|||||||
asset_category.insert()
|
asset_category.insert()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_cwip_accounting(self):
|
||||||
|
company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account")
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "")
|
||||||
|
|
||||||
|
asset_category = frappe.new_doc("Asset Category")
|
||||||
|
asset_category.asset_category_name = "Computers"
|
||||||
|
asset_category.enable_cwip_accounting = 1
|
||||||
|
|
||||||
|
asset_category.total_number_of_depreciations = 3
|
||||||
|
asset_category.frequency_of_depreciation = 3
|
||||||
|
asset_category.append("accounts", {
|
||||||
|
"company_name": "_Test Company",
|
||||||
|
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||||
|
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||||
|
"depreciation_expense_account": "_Test Depreciations - _TC"
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, asset_category.insert)
|
@ -1,347 +1,99 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
"creation": "2018-05-08 14:44:37.095570",
|
||||||
"allow_guest_to_view": 0,
|
"doctype": "DocType",
|
||||||
"allow_import": 0,
|
"editable_grid": 1,
|
||||||
"allow_rename": 0,
|
"engine": "InnoDB",
|
||||||
"beta": 0,
|
"field_order": [
|
||||||
"creation": "2018-05-08 14:44:37.095570",
|
"finance_book",
|
||||||
"custom": 0,
|
"depreciation_method",
|
||||||
"docstatus": 0,
|
"total_number_of_depreciations",
|
||||||
"doctype": "DocType",
|
"column_break_5",
|
||||||
"document_type": "",
|
"frequency_of_depreciation",
|
||||||
"editable_grid": 1,
|
"depreciation_start_date",
|
||||||
"engine": "InnoDB",
|
"expected_value_after_useful_life",
|
||||||
|
"value_after_depreciation",
|
||||||
|
"rate_of_depreciation"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "finance_book",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Link",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Finance Book",
|
||||||
"collapsible": 0,
|
"options": "Finance Book"
|
||||||
"columns": 0,
|
},
|
||||||
"depends_on": "",
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "finance_book",
|
|
||||||
"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": "Finance Book",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Finance Book",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "depreciation_method",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Select",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Depreciation Method",
|
||||||
"collapsible": 0,
|
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fetch_if_empty": 0,
|
},
|
||||||
"fieldname": "depreciation_method",
|
|
||||||
"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": 0,
|
|
||||||
"label": "Depreciation Method",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "total_number_of_depreciations",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Int",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Total Number of Depreciations",
|
||||||
"collapsible": 0,
|
"reqd": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "total_number_of_depreciations",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"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": "Total Number of Depreciations",
|
|
||||||
"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": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_5",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Column Break"
|
||||||
"allow_on_submit": 0,
|
},
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "column_break_5",
|
|
||||||
"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,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "frequency_of_depreciation",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Int",
|
||||||
"allow_on_submit": 0,
|
"in_list_view": 1,
|
||||||
"bold": 0,
|
"label": "Frequency of Depreciation (Months)",
|
||||||
"collapsible": 0,
|
"reqd": 1
|
||||||
"columns": 0,
|
},
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "frequency_of_depreciation",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"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": "Frequency of Depreciation (Months)",
|
|
||||||
"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": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldname": "depreciation_start_date",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Date",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Depreciation Posting Date",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
},
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "depreciation_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": "Depreciation Start Date",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_in_quick_entry": 0,
|
"depends_on": "eval:parent.doctype == 'Asset'",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "expected_value_after_useful_life",
|
||||||
"bold": 0,
|
"fieldtype": "Currency",
|
||||||
"collapsible": 0,
|
"label": "Expected Value After Useful Life",
|
||||||
"columns": 0,
|
"options": "Company:company:default_currency"
|
||||||
"default": "0",
|
},
|
||||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "expected_value_after_useful_life",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"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": "Expected Value After Useful Life",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "value_after_depreciation",
|
||||||
"allow_in_quick_entry": 0,
|
"fieldtype": "Currency",
|
||||||
"allow_on_submit": 0,
|
"hidden": 1,
|
||||||
"bold": 0,
|
"label": "Value After Depreciation",
|
||||||
"collapsible": 0,
|
"no_copy": 1,
|
||||||
"columns": 0,
|
"options": "Company:company:default_currency",
|
||||||
"fetch_if_empty": 0,
|
"print_hide": 1,
|
||||||
"fieldname": "value_after_depreciation",
|
"read_only": 1
|
||||||
"fieldtype": "Currency",
|
},
|
||||||
"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": "Value After Depreciation",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Company:company:default_currency",
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
||||||
"allow_in_quick_entry": 0,
|
"description": "In Percentage",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "rate_of_depreciation",
|
||||||
"bold": 0,
|
"fieldtype": "Percent",
|
||||||
"collapsible": 0,
|
"label": "Rate of Depreciation"
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:doc.depreciation_method == 'Written Down Value'",
|
|
||||||
"description": "In Percentage",
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "rate_of_depreciation",
|
|
||||||
"fieldtype": "Percent",
|
|
||||||
"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": "Rate of Depreciation",
|
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_toolbar": 0,
|
"istable": 1,
|
||||||
"idx": 0,
|
"links": [],
|
||||||
"in_create": 0,
|
"modified": "2020-09-16 12:11:30.631788",
|
||||||
"is_submittable": 0,
|
"modified_by": "Administrator",
|
||||||
"issingle": 0,
|
"module": "Assets",
|
||||||
"istable": 1,
|
"name": "Asset Finance Book",
|
||||||
"max_attachments": 0,
|
"owner": "Administrator",
|
||||||
"modified": "2019-04-09 19:45:14.523488",
|
"permissions": [],
|
||||||
"modified_by": "Administrator",
|
"quick_entry": 1,
|
||||||
"module": "Assets",
|
"sort_field": "modified",
|
||||||
"name": "Asset Finance Book",
|
"sort_order": "DESC",
|
||||||
"name_case": "",
|
"track_changes": 1
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"quick_entry": 1,
|
|
||||||
"read_only": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_changes": 1,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -32,8 +32,7 @@ class TestAssetMovement(unittest.TestCase):
|
|||||||
"next_depreciation_date": "2020-12-31",
|
"next_depreciation_date": "2020-12-31",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10
|
||||||
"depreciation_start_date": "2020-06-06"
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if asset.docstatus == 0:
|
if asset.docstatus == 0:
|
||||||
@ -82,8 +81,7 @@ class TestAssetMovement(unittest.TestCase):
|
|||||||
"next_depreciation_date": "2020-12-31",
|
"next_depreciation_date": "2020-12-31",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10
|
||||||
"depreciation_start_date": "2020-06-06"
|
|
||||||
})
|
})
|
||||||
if asset.docstatus == 0:
|
if asset.docstatus == 0:
|
||||||
asset.submit()
|
asset.submit()
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Other Reports",
|
"label": "Other Reports",
|
||||||
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
|
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Quotation Comparison\",\n \"name\": \"Supplier Quotation Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Buying",
|
"label": "Buying",
|
||||||
"modified": "2020-06-29 19:30:24.983050",
|
"modified": "2020-09-30 14:40:55.638458",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying",
|
"name": "Buying",
|
||||||
|
@ -1084,7 +1084,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-31 14:13:44.610190",
|
"modified": "2020-10-07 14:31:57.661221",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
@ -1130,10 +1130,11 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"search_fields": "status, transaction_date, supplier,grand_total",
|
"search_fields": "status, transaction_date, supplier, grand_total",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"timeline_field": "supplier",
|
"timeline_field": "supplier",
|
||||||
"title_field": "supplier"
|
"title_field": "supplier_name",
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
@ -89,7 +89,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
|
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
|
||||||
|
|
||||||
|
|
||||||
def test_update_child_qty_rate(self):
|
def test_update_child(self):
|
||||||
mr = make_material_request(qty=10)
|
mr = make_material_request(qty=10)
|
||||||
po = make_purchase_order(mr.name)
|
po = make_purchase_order(mr.name)
|
||||||
po.supplier = "_Test Supplier"
|
po.supplier = "_Test Supplier"
|
||||||
@ -119,7 +119,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
|
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
|
||||||
|
|
||||||
|
|
||||||
def test_add_new_item_in_update_child_qty_rate(self):
|
def test_update_child_adding_new_item(self):
|
||||||
po = create_purchase_order(do_not_save=1)
|
po = create_purchase_order(do_not_save=1)
|
||||||
po.items[0].qty = 4
|
po.items[0].qty = 4
|
||||||
po.save()
|
po.save()
|
||||||
@ -145,7 +145,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertEqual(po.status, 'To Receive and Bill')
|
self.assertEqual(po.status, 'To Receive and Bill')
|
||||||
|
|
||||||
|
|
||||||
def test_remove_item_in_update_child_qty_rate(self):
|
def test_update_child_removing_item(self):
|
||||||
po = create_purchase_order(do_not_save=1)
|
po = create_purchase_order(do_not_save=1)
|
||||||
po.items[0].qty = 4
|
po.items[0].qty = 4
|
||||||
po.save()
|
po.save()
|
||||||
@ -185,7 +185,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertEquals(len(po.get('items')), 1)
|
self.assertEquals(len(po.get('items')), 1)
|
||||||
self.assertEqual(po.status, 'To Receive and Bill')
|
self.assertEqual(po.status, 'To Receive and Bill')
|
||||||
|
|
||||||
def test_update_child_qty_rate_perm(self):
|
def test_update_child_perm(self):
|
||||||
po = create_purchase_order(item_code= "_Test Item", qty=4)
|
po = create_purchase_order(item_code= "_Test Item", qty=4)
|
||||||
|
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
@ -202,6 +202,110 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
def test_update_child_with_tax_template(self):
|
||||||
|
"""
|
||||||
|
Test Action: Create a PO with one item having its tax account head already in the PO.
|
||||||
|
Add the same item + new item with tax template via Update Items.
|
||||||
|
Expected result: First Item's tax row is updated. New tax row is added for second Item.
|
||||||
|
"""
|
||||||
|
if not frappe.db.exists("Item", "Test Item with Tax"):
|
||||||
|
make_item("Test Item with Tax", {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Item Tax Template',
|
||||||
|
'title': 'Test Update Items Template',
|
||||||
|
'company': '_Test Company',
|
||||||
|
'taxes': [
|
||||||
|
{
|
||||||
|
'tax_type': "_Test Account Service Tax - _TC",
|
||||||
|
'tax_rate': 10,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
|
||||||
|
|
||||||
|
new_item_with_tax.append("taxes", {
|
||||||
|
"item_tax_template": "Test Update Items Template",
|
||||||
|
"valid_from": nowdate()
|
||||||
|
})
|
||||||
|
new_item_with_tax.save()
|
||||||
|
|
||||||
|
tax_template = "_Test Account Excise Duty @ 10"
|
||||||
|
item = "_Test Item Home Desktop 100"
|
||||||
|
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
||||||
|
item_doc = frappe.get_doc("Item", item)
|
||||||
|
item_doc.append("taxes", {
|
||||||
|
"item_tax_template": tax_template,
|
||||||
|
"valid_from": nowdate()
|
||||||
|
})
|
||||||
|
item_doc.save()
|
||||||
|
else:
|
||||||
|
# update valid from
|
||||||
|
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE()
|
||||||
|
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||||
|
{"item": item, "tax": tax_template})
|
||||||
|
|
||||||
|
po = create_purchase_order(item_code=item, qty=1, do_not_save=1)
|
||||||
|
|
||||||
|
po.append("taxes", {
|
||||||
|
"account_head": "_Test Account Excise Duty - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "Excise Duty",
|
||||||
|
"doctype": "Purchase Taxes and Charges",
|
||||||
|
"rate": 10
|
||||||
|
})
|
||||||
|
po.insert()
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
self.assertEqual(po.taxes[0].tax_amount, 50)
|
||||||
|
self.assertEqual(po.taxes[0].total, 550)
|
||||||
|
|
||||||
|
items = json.dumps([
|
||||||
|
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
|
||||||
|
{'item_code' : item, 'rate' : 100, 'qty' : 1}, # added item whose tax account head already exists in PO
|
||||||
|
{'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO
|
||||||
|
])
|
||||||
|
update_child_qty_rate('Purchase Order', items, po.name)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.taxes[0].tax_amount, 70)
|
||||||
|
self.assertEqual(po.taxes[0].total, 770)
|
||||||
|
self.assertEqual(po.taxes[1].account_head, "_Test Account Service Tax - _TC")
|
||||||
|
self.assertEqual(po.taxes[1].tax_amount, 70)
|
||||||
|
self.assertEqual(po.taxes[1].total, 840)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
|
||||||
|
where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
|
||||||
|
po.cancel()
|
||||||
|
po.delete()
|
||||||
|
new_item_with_tax.delete()
|
||||||
|
frappe.get_doc("Item Tax Template", "Test Update Items Template").delete()
|
||||||
|
|
||||||
|
def test_update_child_uom_conv_factor_change(self):
|
||||||
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
||||||
|
total_reqd_qty = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
|
||||||
|
|
||||||
|
trans_item = json.dumps([{
|
||||||
|
'item_code': po.get("items")[0].item_code,
|
||||||
|
'rate': po.get("items")[0].rate,
|
||||||
|
'qty': po.get("items")[0].qty,
|
||||||
|
'uom': "_Test UOM 1",
|
||||||
|
'conversion_factor': 2,
|
||||||
|
'docname': po.get("items")[0].name
|
||||||
|
}])
|
||||||
|
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||||
|
po.reload()
|
||||||
|
|
||||||
|
total_reqd_qty_after_change = sum([d.get("required_qty") for d in po.as_dict().get("supplied_items")])
|
||||||
|
|
||||||
|
self.assertEqual(total_reqd_qty_after_change, 2 * total_reqd_qty)
|
||||||
|
|
||||||
def test_update_qty(self):
|
def test_update_qty(self):
|
||||||
po = create_purchase_order()
|
po = create_purchase_order()
|
||||||
|
|
||||||
@ -585,12 +689,12 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
make_subcontracted_item(item_code)
|
make_subcontracted_item(item_code)
|
||||||
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=1,
|
po = create_purchase_order(item_code=item_code, qty=1,
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1)
|
||||||
|
|
||||||
name = frappe.db.get_value('BOM', {'item': item_code}, 'name')
|
name = frappe.db.get_value('BOM', {'item': item_code}, 'name')
|
||||||
bom = frappe.get_doc('BOM', name)
|
bom = frappe.get_doc('BOM', name)
|
||||||
|
|
||||||
exploded_items = sorted([d.item_code for d in bom.exploded_items])
|
exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
|
||||||
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
||||||
self.assertEquals(exploded_items, supplied_items)
|
self.assertEquals(exploded_items, supplied_items)
|
||||||
|
|
||||||
@ -598,7 +702,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
|
||||||
|
|
||||||
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
|
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
|
||||||
bom_items = sorted([d.item_code for d in bom.items])
|
bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
|
||||||
|
|
||||||
self.assertEquals(supplied_items1, bom_items)
|
self.assertEquals(supplied_items1, bom_items)
|
||||||
|
|
||||||
|
@ -148,11 +148,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-03-12 15:43:53.862897",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item Supplied",
|
"name": "Purchase Order Item Supplied",
|
||||||
"owner": "dhanalekshmi@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC"
|
||||||
|
@ -188,11 +188,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-10 18:09:33.997618",
|
"modified": "2020-09-18 17:26:09.703215",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Receipt Item Supplied",
|
"name": "Purchase Receipt Item Supplied",
|
||||||
"owner": "wasim@webnotestech.com",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
@ -22,8 +22,6 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.add_fetch('email_template', 'response', 'message_for_supplier');
|
|
||||||
|
|
||||||
if(!frm.doc.message_for_supplier) {
|
if(!frm.doc.message_for_supplier) {
|
||||||
frm.set_value("message_for_supplier", __("Please supply the specified items at the best possible rates"))
|
frm.set_value("message_for_supplier", __("Please supply the specified items at the best possible rates"))
|
||||||
}
|
}
|
||||||
@ -179,7 +177,7 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
dialog.hide();
|
dialog.hide();
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation",
|
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||||
args: {
|
args: {
|
||||||
"source_name": doc.name,
|
"source_name": doc.name,
|
||||||
"for_supplier": args.supplier
|
"for_supplier": args.supplier
|
||||||
@ -194,6 +192,66 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
},
|
||||||
|
|
||||||
|
preview: (frm) => {
|
||||||
|
let dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Preview Email'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: __('Supplier'),
|
||||||
|
fieldtype: 'Select',
|
||||||
|
fieldname: 'supplier',
|
||||||
|
options: frm.doc.suppliers.map(row => row.supplier),
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Column Break',
|
||||||
|
fieldname: 'col_break_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Subject'),
|
||||||
|
fieldtype: 'Data',
|
||||||
|
fieldname: 'subject',
|
||||||
|
read_only: 1,
|
||||||
|
depends_on: 'subject'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Section Break',
|
||||||
|
fieldname: 'sec_break_1',
|
||||||
|
hide_border: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Email'),
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
fieldname: 'email_preview'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Section Break',
|
||||||
|
fieldname: 'sec_break_2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Note'),
|
||||||
|
fieldtype: 'HTML',
|
||||||
|
fieldname: 'note'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.fields_dict['supplier'].df.onchange = () => {
|
||||||
|
var supplier = dialog.get_value('supplier');
|
||||||
|
frm.call('get_supplier_email_preview', {supplier: supplier}).then(result => {
|
||||||
|
dialog.fields_dict.email_preview.$wrapper.empty();
|
||||||
|
dialog.fields_dict.email_preview.$wrapper.append(result.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.fields_dict.note.$wrapper.append(`<p class="small text-muted">This is a preview of the email to be sent. A PDF of the document will
|
||||||
|
automatically be attached with the email.</p>`);
|
||||||
|
|
||||||
|
dialog.set_value("subject", frm.doc.subject);
|
||||||
|
dialog.show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -276,7 +334,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
|||||||
})
|
})
|
||||||
}, __("Get items from"));
|
}, __("Get items from"));
|
||||||
// Get items from Opportunity
|
// Get items from Opportunity
|
||||||
this.frm.add_custom_button(__('Opportunity'),
|
this.frm.add_custom_button(__('Opportunity'),
|
||||||
function() {
|
function() {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
|
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"actions": "",
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2016-02-25 01:24:07.224790",
|
"creation": "2016-02-25 01:24:07.224790",
|
||||||
@ -19,7 +19,12 @@
|
|||||||
"items",
|
"items",
|
||||||
"link_to_mrs",
|
"link_to_mrs",
|
||||||
"supplier_response_section",
|
"supplier_response_section",
|
||||||
|
"salutation",
|
||||||
"email_template",
|
"email_template",
|
||||||
|
"col_break_email_1",
|
||||||
|
"subject",
|
||||||
|
"preview",
|
||||||
|
"sec_break_email_2",
|
||||||
"message_for_supplier",
|
"message_for_supplier",
|
||||||
"terms_section_break",
|
"terms_section_break",
|
||||||
"tc_name",
|
"tc_name",
|
||||||
@ -126,8 +131,10 @@
|
|||||||
"label": "Link to Material Requests"
|
"label": "Link to Material Requests"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "supplier_response_section",
|
"fieldname": "supplier_response_section",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Email Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "email_template",
|
"fieldname": "email_template",
|
||||||
@ -137,6 +144,8 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "email_template.response",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "message_for_supplier",
|
"fieldname": "message_for_supplier",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -230,12 +239,45 @@
|
|||||||
"options": "Request for Quotation",
|
"options": "Request for Quotation",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "email_template.subject",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "subject",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Subject",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Select a greeting for the receiver. E.g. Mr., Ms., etc.",
|
||||||
|
"fieldname": "salutation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Salutation",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Salutation",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_break_email_1",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.docstatus==1",
|
||||||
|
"fieldname": "preview",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Preview Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "sec_break_email_2",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-25 14:37:21.140194",
|
"modified": "2020-10-01 14:54:50.888729",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
@ -51,7 +51,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
|
|
||||||
def validate_email_id(self, args):
|
def validate_email_id(self, args):
|
||||||
if not args.email_id:
|
if not args.email_id:
|
||||||
frappe.throw(_("Row {0}: For Supplier {0}, Email Address is Required to Send Email").format(args.idx, args.supplier))
|
frappe.throw(_("Row {0}: For Supplier {1}, Email Address is Required to send an email").format(args.idx, frappe.bold(args.supplier)))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
frappe.db.set(self, 'status', 'Submitted')
|
frappe.db.set(self, 'status', 'Submitted')
|
||||||
@ -62,17 +62,31 @@ class RequestforQuotation(BuyingController):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
|
|
||||||
|
def get_supplier_email_preview(self, supplier):
|
||||||
|
"""Returns formatted email preview as string."""
|
||||||
|
rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
|
||||||
|
rfq_supplier = rfq_suppliers[0]
|
||||||
|
|
||||||
|
self.validate_email_id(rfq_supplier)
|
||||||
|
|
||||||
|
message = self.supplier_rfq_mail(rfq_supplier, '', self.get_link(), True)
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
def send_to_supplier(self):
|
def send_to_supplier(self):
|
||||||
|
"""Sends RFQ mail to involved suppliers."""
|
||||||
for rfq_supplier in self.suppliers:
|
for rfq_supplier in self.suppliers:
|
||||||
if rfq_supplier.send_email:
|
if rfq_supplier.send_email:
|
||||||
self.validate_email_id(rfq_supplier)
|
self.validate_email_id(rfq_supplier)
|
||||||
|
|
||||||
# make new user if required
|
# make new user if required
|
||||||
update_password_link = self.update_supplier_contact(rfq_supplier, self.get_link())
|
update_password_link, contact = self.update_supplier_contact(rfq_supplier, self.get_link())
|
||||||
|
|
||||||
self.update_supplier_part_no(rfq_supplier)
|
self.update_supplier_part_no(rfq_supplier)
|
||||||
self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link())
|
self.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link())
|
||||||
rfq_supplier.email_sent = 1
|
rfq_supplier.email_sent = 1
|
||||||
|
if not rfq_supplier.contact:
|
||||||
|
rfq_supplier.contact = contact
|
||||||
rfq_supplier.save()
|
rfq_supplier.save()
|
||||||
|
|
||||||
def get_link(self):
|
def get_link(self):
|
||||||
@ -87,18 +101,19 @@ class RequestforQuotation(BuyingController):
|
|||||||
|
|
||||||
def update_supplier_contact(self, rfq_supplier, link):
|
def update_supplier_contact(self, rfq_supplier, link):
|
||||||
'''Create a new user for the supplier if not set in contact'''
|
'''Create a new user for the supplier if not set in contact'''
|
||||||
update_password_link = ''
|
update_password_link, contact = '', ''
|
||||||
|
|
||||||
if frappe.db.exists("User", rfq_supplier.email_id):
|
if frappe.db.exists("User", rfq_supplier.email_id):
|
||||||
user = frappe.get_doc("User", rfq_supplier.email_id)
|
user = frappe.get_doc("User", rfq_supplier.email_id)
|
||||||
else:
|
else:
|
||||||
user, update_password_link = self.create_user(rfq_supplier, link)
|
user, update_password_link = self.create_user(rfq_supplier, link)
|
||||||
|
|
||||||
self.update_contact_of_supplier(rfq_supplier, user)
|
contact = self.link_supplier_contact(rfq_supplier, user)
|
||||||
|
|
||||||
return update_password_link
|
return update_password_link, contact
|
||||||
|
|
||||||
def update_contact_of_supplier(self, rfq_supplier, user):
|
def link_supplier_contact(self, rfq_supplier, user):
|
||||||
|
"""If no Contact, create a new contact against Supplier. If Contact exists, check if email and user id set."""
|
||||||
if rfq_supplier.contact:
|
if rfq_supplier.contact:
|
||||||
contact = frappe.get_doc("Contact", rfq_supplier.contact)
|
contact = frappe.get_doc("Contact", rfq_supplier.contact)
|
||||||
else:
|
else:
|
||||||
@ -115,6 +130,10 @@ class RequestforQuotation(BuyingController):
|
|||||||
|
|
||||||
contact.save(ignore_permissions=True)
|
contact.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
if not rfq_supplier.contact:
|
||||||
|
# return contact to later update, RFQ supplier row's contact
|
||||||
|
return contact.name
|
||||||
|
|
||||||
def create_user(self, rfq_supplier, link):
|
def create_user(self, rfq_supplier, link):
|
||||||
user = frappe.get_doc({
|
user = frappe.get_doc({
|
||||||
'doctype': 'User',
|
'doctype': 'User',
|
||||||
@ -129,22 +148,36 @@ class RequestforQuotation(BuyingController):
|
|||||||
|
|
||||||
return user, update_password_link
|
return user, update_password_link
|
||||||
|
|
||||||
def supplier_rfq_mail(self, data, update_password_link, rfq_link):
|
def supplier_rfq_mail(self, data, update_password_link, rfq_link, preview=False):
|
||||||
full_name = get_user_fullname(frappe.session['user'])
|
full_name = get_user_fullname(frappe.session['user'])
|
||||||
if full_name == "Guest":
|
if full_name == "Guest":
|
||||||
full_name = "Administrator"
|
full_name = "Administrator"
|
||||||
|
|
||||||
|
# send document dict and some important data from suppliers row
|
||||||
|
# to render message_for_supplier from any template
|
||||||
|
doc_args = self.as_dict()
|
||||||
|
doc_args.update({
|
||||||
|
'supplier': data.get('supplier'),
|
||||||
|
'supplier_name': data.get('supplier_name')
|
||||||
|
})
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
'update_password_link': update_password_link,
|
'update_password_link': update_password_link,
|
||||||
'message': frappe.render_template(self.message_for_supplier, data.as_dict()),
|
'message': frappe.render_template(self.message_for_supplier, doc_args),
|
||||||
'rfq_link': rfq_link,
|
'rfq_link': rfq_link,
|
||||||
'user_fullname': full_name
|
'user_fullname': full_name,
|
||||||
|
'supplier_name' : data.get('supplier_name'),
|
||||||
|
'supplier_salutation' : self.salutation or 'Dear Mx.',
|
||||||
}
|
}
|
||||||
|
|
||||||
subject = _("Request for Quotation")
|
subject = self.subject or _("Request for Quotation")
|
||||||
template = "templates/emails/request_for_quotation.html"
|
template = "templates/emails/request_for_quotation.html"
|
||||||
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
||||||
message = frappe.get_template(template).render(args)
|
message = frappe.get_template(template).render(args)
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
return message
|
||||||
|
|
||||||
attachments = self.get_attachments()
|
attachments = self.get_attachments()
|
||||||
|
|
||||||
self.send_email(data, sender, subject, message, attachments)
|
self.send_email(data, sender, subject, message, attachments)
|
||||||
@ -214,14 +247,14 @@ def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
|
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
|
||||||
limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')})
|
limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')})
|
||||||
|
|
||||||
# This method is used to make supplier quotation from material request form.
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_supplier_quotation(source_name, for_supplier, target_doc=None):
|
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
|
||||||
def postprocess(source, target_doc):
|
def postprocess(source, target_doc):
|
||||||
target_doc.supplier = for_supplier
|
if for_supplier:
|
||||||
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
|
target_doc.supplier = for_supplier
|
||||||
target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
|
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
|
||||||
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
|
target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
|
||||||
|
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
|
||||||
set_missing_values(source, target_doc)
|
set_missing_values(source, target_doc)
|
||||||
|
|
||||||
doclist = get_mapped_doc("Request for Quotation", source_name, {
|
doclist = get_mapped_doc("Request for Quotation", source_name, {
|
||||||
@ -354,3 +387,32 @@ def get_supplier_tag():
|
|||||||
frappe.cache().hset("Supplier", "Tags", tags)
|
frappe.cache().hset("Supplier", "Tags", tags)
|
||||||
|
|
||||||
return frappe.cache().hget("Supplier", "Tags")
|
return frappe.cache().hget("Supplier", "Tags")
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
conditions = ""
|
||||||
|
if txt:
|
||||||
|
conditions += "and rfq.name like '%%"+txt+"%%' "
|
||||||
|
|
||||||
|
if filters.get("transaction_date"):
|
||||||
|
conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date"))
|
||||||
|
|
||||||
|
rfq_data = frappe.db.sql("""
|
||||||
|
select
|
||||||
|
distinct rfq.name, rfq.transaction_date,
|
||||||
|
rfq.company
|
||||||
|
from
|
||||||
|
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
|
||||||
|
where
|
||||||
|
rfq.name = rfq_supplier.parent
|
||||||
|
and rfq_supplier.supplier = '{0}'
|
||||||
|
and rfq.docstatus = 1
|
||||||
|
and rfq.company = '{1}'
|
||||||
|
{2}
|
||||||
|
order by rfq.transaction_date ASC
|
||||||
|
limit %(page_len)s offset %(start)s """ \
|
||||||
|
.format(filters.get("supplier"), filters.get("company"), conditions),
|
||||||
|
{"page_len": page_len, "start": start}, as_dict=1)
|
||||||
|
|
||||||
|
return rfq_data
|
@ -9,7 +9,7 @@ import frappe
|
|||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
||||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation_from_rfq
|
||||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
|
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
|
||||||
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
|
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
|
||||||
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
||||||
@ -22,7 +22,7 @@ class TestRequestforQuotation(unittest.TestCase):
|
|||||||
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
|
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
|
||||||
|
|
||||||
# Submit the first supplier quotation
|
# Submit the first supplier quotation
|
||||||
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
|
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
|
||||||
sq.submit()
|
sq.submit()
|
||||||
|
|
||||||
# No Quote first supplier quotation
|
# No Quote first supplier quotation
|
||||||
@ -37,10 +37,10 @@ class TestRequestforQuotation(unittest.TestCase):
|
|||||||
def test_make_supplier_quotation(self):
|
def test_make_supplier_quotation(self):
|
||||||
rfq = make_request_for_quotation()
|
rfq = make_request_for_quotation()
|
||||||
|
|
||||||
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
|
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier)
|
||||||
sq.submit()
|
sq.submit()
|
||||||
|
|
||||||
sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier)
|
sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[1].supplier)
|
||||||
sq1.submit()
|
sq1.submit()
|
||||||
|
|
||||||
self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier)
|
self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier)
|
||||||
@ -62,7 +62,7 @@ class TestRequestforQuotation(unittest.TestCase):
|
|||||||
|
|
||||||
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
|
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
|
||||||
|
|
||||||
sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier"))
|
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier_wt_appos[0].get("supplier"))
|
||||||
sq.submit()
|
sq.submit()
|
||||||
|
|
||||||
frappe.form_dict = frappe.local("form_dict")
|
frappe.form_dict = frappe.local("form_dict")
|
||||||
|
@ -1,362 +1,112 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"creation": "2016-03-29 05:59:11.896885",
|
||||||
"allow_import": 0,
|
"doctype": "DocType",
|
||||||
"allow_rename": 0,
|
"editable_grid": 1,
|
||||||
"beta": 0,
|
"engine": "InnoDB",
|
||||||
"creation": "2016-03-29 05:59:11.896885",
|
"field_order": [
|
||||||
"custom": 0,
|
"send_email",
|
||||||
"docstatus": 0,
|
"email_sent",
|
||||||
"doctype": "DocType",
|
"supplier",
|
||||||
"document_type": "",
|
"contact",
|
||||||
"editable_grid": 1,
|
"no_quote",
|
||||||
"engine": "InnoDB",
|
"quote_status",
|
||||||
|
"column_break_3",
|
||||||
|
"supplier_name",
|
||||||
|
"email_id",
|
||||||
|
"download_pdf"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 1,
|
"default": "1",
|
||||||
"bold": 0,
|
"fieldname": "send_email",
|
||||||
"collapsible": 0,
|
"fieldtype": "Check",
|
||||||
"columns": 0,
|
"label": "Send Email"
|
||||||
"default": "1",
|
},
|
||||||
"fieldname": "send_email",
|
|
||||||
"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": "Send Email",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 1,
|
"default": "0",
|
||||||
"bold": 0,
|
"depends_on": "eval:doc.docstatus >= 1",
|
||||||
"collapsible": 0,
|
"fieldname": "email_sent",
|
||||||
"columns": 0,
|
"fieldtype": "Check",
|
||||||
"default": "0",
|
"label": "Email Sent",
|
||||||
"depends_on": "eval:doc.docstatus >= 1",
|
"no_copy": 1,
|
||||||
"fieldname": "email_sent",
|
"read_only": 1
|
||||||
"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": "Email Sent",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 4,
|
||||||
"allow_on_submit": 0,
|
"fieldname": "supplier",
|
||||||
"bold": 0,
|
"fieldtype": "Link",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 4,
|
"label": "Supplier",
|
||||||
"fieldname": "supplier",
|
"options": "Supplier",
|
||||||
"fieldtype": "Link",
|
"reqd": 1
|
||||||
"hidden": 0,
|
},
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Supplier",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Supplier",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 0,
|
"columns": 3,
|
||||||
"bold": 0,
|
"fieldname": "contact",
|
||||||
"collapsible": 0,
|
"fieldtype": "Link",
|
||||||
"columns": 3,
|
"in_list_view": 1,
|
||||||
"fieldname": "contact",
|
"label": "Contact",
|
||||||
"fieldtype": "Link",
|
"no_copy": 1,
|
||||||
"hidden": 0,
|
"options": "Contact"
|
||||||
"ignore_user_permissions": 0,
|
},
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Contact",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Contact",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 1,
|
"default": "0",
|
||||||
"bold": 0,
|
"depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
|
||||||
"collapsible": 0,
|
"fieldname": "no_quote",
|
||||||
"columns": 0,
|
"fieldtype": "Check",
|
||||||
"depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
|
"label": "No Quote"
|
||||||
"fieldname": "no_quote",
|
},
|
||||||
"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": "No Quote",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 1,
|
"depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
|
||||||
"bold": 0,
|
"fieldname": "quote_status",
|
||||||
"collapsible": 0,
|
"fieldtype": "Select",
|
||||||
"columns": 0,
|
"label": "Quote Status",
|
||||||
"depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
|
"options": "Pending\nReceived\nNo Quote",
|
||||||
"fieldname": "quote_status",
|
"read_only": 1
|
||||||
"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": "Quote Status",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Pending\nReceived\nNo Quote",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_3",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Column Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"bold": 1,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_from": "supplier.supplier_name",
|
"fetch_from": "supplier.supplier_name",
|
||||||
"fieldname": "supplier_name",
|
"fieldname": "supplier_name",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Read Only",
|
||||||
"hidden": 0,
|
"in_global_search": 1,
|
||||||
"ignore_user_permissions": 0,
|
"label": "Supplier Name"
|
||||||
"ignore_xss_filter": 0,
|
},
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Supplier Name",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"columns": 3,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
|
||||||
"fetch_from": "contact.email_id",
|
"fetch_from": "contact.email_id",
|
||||||
"fieldname": "email_id",
|
"fieldname": "email_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"in_list_view": 1,
|
||||||
"ignore_user_permissions": 0,
|
"label": "Email Id",
|
||||||
"ignore_xss_filter": 0,
|
"no_copy": 1
|
||||||
"in_filter": 0,
|
},
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Email Id",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_on_submit": 1,
|
||||||
"allow_on_submit": 1,
|
"fieldname": "download_pdf",
|
||||||
"bold": 0,
|
"fieldtype": "Button",
|
||||||
"collapsible": 0,
|
"label": "Download PDF"
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "download_pdf",
|
|
||||||
"fieldtype": "Button",
|
|
||||||
"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": "Download PDF",
|
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"istable": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2020-09-28 19:31:11.855588",
|
||||||
"idx": 0,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "Buying",
|
||||||
"in_create": 0,
|
"name": "Request for Quotation Supplier",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
"permissions": [],
|
||||||
"istable": 1,
|
"sort_field": "modified",
|
||||||
"max_attachments": 0,
|
"sort_order": "DESC",
|
||||||
"modified": "2018-05-16 22:43:30.212408",
|
"track_changes": 1
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Buying",
|
|
||||||
"name": "Request for Quotation Supplier",
|
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"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
|
|
||||||
}
|
}
|
@ -8,8 +8,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
|||||||
setup: function() {
|
setup: function() {
|
||||||
this.frm.custom_make_buttons = {
|
this.frm.custom_make_buttons = {
|
||||||
'Purchase Order': 'Purchase Order',
|
'Purchase Order': 'Purchase Order',
|
||||||
'Quotation': 'Quotation',
|
'Quotation': 'Quotation'
|
||||||
'Subscription': 'Subscription'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._super();
|
this._super();
|
||||||
@ -28,12 +27,6 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
|||||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
|
cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
|
||||||
__('Create'));
|
__('Create'));
|
||||||
|
|
||||||
if(!this.frm.doc.auto_repeat) {
|
|
||||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
|
||||||
erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name)
|
|
||||||
}, __('Create'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (this.frm.doc.docstatus===0) {
|
else if (this.frm.doc.docstatus===0) {
|
||||||
|
|
||||||
@ -54,6 +47,27 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, __("Get items from"));
|
}, __("Get items from"));
|
||||||
|
|
||||||
|
this.frm.add_custom_button(__("Request for Quotation"),
|
||||||
|
function() {
|
||||||
|
if (!me.frm.doc.supplier) {
|
||||||
|
frappe.throw({message:__("Please select a Supplier"), title:__("Mandatory")})
|
||||||
|
}
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||||
|
source_doctype: "Request for Quotation",
|
||||||
|
target: me.frm,
|
||||||
|
setters: {
|
||||||
|
company: me.frm.doc.company,
|
||||||
|
transaction_date: null
|
||||||
|
},
|
||||||
|
get_query_filters: {
|
||||||
|
supplier: me.frm.doc.supplier
|
||||||
|
},
|
||||||
|
get_query_method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_rfq_containing_supplier"
|
||||||
|
|
||||||
|
})
|
||||||
|
}, __("Get items from"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fieldname": "transaction_date",
|
"fieldname": "transaction_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Date",
|
"label": "Date",
|
||||||
"oldfieldname": "transaction_date",
|
"oldfieldname": "transaction_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
@ -798,6 +799,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "valid_till",
|
"fieldname": "valid_till",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Valid Till"
|
"label": "Valid Till"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -805,7 +807,7 @@
|
|||||||
"idx": 29,
|
"idx": 29,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-18 05:10:45.556792",
|
"modified": "2020-10-01 20:56:17.932007",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation",
|
"name": "Supplier Quotation",
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
"item_name",
|
"item_name",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"lead_time_days",
|
"lead_time_days",
|
||||||
|
"expected_delivery_date",
|
||||||
|
"is_free_item",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"description",
|
"description",
|
||||||
"item_group",
|
"item_group",
|
||||||
@ -19,20 +21,18 @@
|
|||||||
"col_break1",
|
"col_break1",
|
||||||
"image",
|
"image",
|
||||||
"image_view",
|
"image_view",
|
||||||
"manufacture_details",
|
|
||||||
"manufacturer",
|
|
||||||
"column_break_15",
|
|
||||||
"manufacturer_part_no",
|
|
||||||
"quantity_and_rate",
|
"quantity_and_rate",
|
||||||
"qty",
|
"qty",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"price_list_rate",
|
|
||||||
"discount_percentage",
|
|
||||||
"discount_amount",
|
|
||||||
"col_break2",
|
"col_break2",
|
||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
|
"sec_break_price_list",
|
||||||
|
"price_list_rate",
|
||||||
|
"discount_percentage",
|
||||||
|
"discount_amount",
|
||||||
|
"col_break_price_list",
|
||||||
"base_price_list_rate",
|
"base_price_list_rate",
|
||||||
"sec_break1",
|
"sec_break1",
|
||||||
"rate",
|
"rate",
|
||||||
@ -42,7 +42,6 @@
|
|||||||
"base_rate",
|
"base_rate",
|
||||||
"base_amount",
|
"base_amount",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"is_free_item",
|
|
||||||
"section_break_24",
|
"section_break_24",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
"net_amount",
|
"net_amount",
|
||||||
@ -56,7 +55,6 @@
|
|||||||
"weight_uom",
|
"weight_uom",
|
||||||
"warehouse_and_reference",
|
"warehouse_and_reference",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"project",
|
|
||||||
"prevdoc_doctype",
|
"prevdoc_doctype",
|
||||||
"material_request",
|
"material_request",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
@ -65,13 +63,19 @@
|
|||||||
"material_request_item",
|
"material_request_item",
|
||||||
"request_for_quotation_item",
|
"request_for_quotation_item",
|
||||||
"item_tax_rate",
|
"item_tax_rate",
|
||||||
|
"manufacture_details",
|
||||||
|
"manufacturer",
|
||||||
|
"column_break_15",
|
||||||
|
"manufacturer_part_no",
|
||||||
|
"ad_sec_break",
|
||||||
|
"project",
|
||||||
"section_break_44",
|
"section_break_44",
|
||||||
"page_break"
|
"page_break"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
"columns": 4,
|
"columns": 2,
|
||||||
"fieldname": "item_code",
|
"fieldname": "item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@ -107,7 +111,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "lead_time_days",
|
"fieldname": "lead_time_days",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Lead Time in days"
|
"label": "Supplier Lead Time (days)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -162,7 +166,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "stock_uom",
|
"fieldname": "stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Stock UOM",
|
"label": "Stock UOM",
|
||||||
"options": "UOM",
|
"options": "UOM",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
@ -196,6 +199,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "uom",
|
"fieldname": "uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "UOM",
|
"label": "UOM",
|
||||||
"options": "UOM",
|
"options": "UOM",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
@ -237,7 +241,7 @@
|
|||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate ",
|
"label": "Rate",
|
||||||
"oldfieldname": "import_rate",
|
"oldfieldname": "import_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "currency"
|
"options": "currency"
|
||||||
@ -289,14 +293,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_free_item",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Free Item",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_24",
|
"fieldname": "section_break_24",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
@ -528,12 +524,43 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_15",
|
"fieldname": "column_break_15",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sec_break_price_list",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_break_price_list",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "ad_sec_break",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "is_free_item",
|
||||||
|
"fieldname": "is_free_item",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Free Item",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"bold": 1,
|
||||||
|
"fieldname": "expected_delivery_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Expected Delivery Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-07 18:35:51.175947",
|
"modified": "2020-10-19 12:36:26.913211",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier Quotation Item",
|
"name": "Supplier Quotation Item",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user