fix: Move filter setup to doctype controllers
This commit is contained in:
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
|
||||||
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.0.0-beta.4'
|
__version__ = '13.0.0-beta.6'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"_comments": null,
|
"_comments": null,
|
||||||
"_liked_by": null,
|
"_liked_by": null,
|
||||||
"_user_tags": null,
|
"_user_tags": null,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@@ -17,17 +18,24 @@
|
|||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"dt": "Address",
|
"dt": "Address",
|
||||||
"fetch_from": null,
|
"fetch_from": null,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "tax_category",
|
"fieldname": "tax_category",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"idx": 14,
|
"hide_border": 0,
|
||||||
|
"hide_days": 0,
|
||||||
|
"hide_seconds": 0,
|
||||||
|
"idx": 15,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_preview": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"insert_after": "fax",
|
"insert_after": "fax",
|
||||||
"label": "Tax Category",
|
"label": "Tax Category",
|
||||||
|
"length": 0,
|
||||||
|
"mandatory_depends_on": null,
|
||||||
"modified": "2018-12-28 22:29:21.828090",
|
"modified": "2018-12-28 22:29:21.828090",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Address-tax_category",
|
"name": "Address-tax_category",
|
||||||
@@ -43,6 +51,66 @@
|
|||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"print_width": null,
|
"print_width": null,
|
||||||
"read_only": 0,
|
"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,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
|
|||||||
42
erpnext/accounts/custom/address.py
Normal file
42
erpnext/accounts/custom/address.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.contacts.doctype.address.address import Address
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_templates
|
||||||
|
|
||||||
|
class ERPNextAddress(Address):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_reference()
|
||||||
|
super(ERPNextAddress, self).validate()
|
||||||
|
|
||||||
|
def link_address(self):
|
||||||
|
"""Link address based on owner"""
|
||||||
|
if self.is_your_company_address:
|
||||||
|
return
|
||||||
|
|
||||||
|
return super(ERPNextAddress, self).link_address()
|
||||||
|
|
||||||
|
def validate_reference(self):
|
||||||
|
if self.is_your_company_address and not [
|
||||||
|
row for row in self.links if row.link_doctype == "Company"
|
||||||
|
]:
|
||||||
|
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
||||||
|
title=_("Company Not Linked"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_shipping_address(company, address = None):
|
||||||
|
filters = [
|
||||||
|
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||||
|
["Dynamic Link", "link_name", "=", company],
|
||||||
|
["Address", "is_your_company_address", "=", 1]
|
||||||
|
]
|
||||||
|
fields = ["*"]
|
||||||
|
if address and frappe.db.get_value('Dynamic Link',
|
||||||
|
{'parent': address, 'link_name': company}):
|
||||||
|
filters.append(["Address", "name", "=", address])
|
||||||
|
|
||||||
|
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
||||||
|
|
||||||
|
if address:
|
||||||
|
address_as_dict = address[0]
|
||||||
|
name, address_template = get_address_templates(address_as_dict)
|
||||||
|
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)
|
||||||
@@ -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 Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\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,
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Accounting",
|
"label": "Accounting",
|
||||||
"modified": "2020-10-08 20:31:46.022470",
|
"modified": "2020-11-06 13:05:58.650150",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
|
|||||||
@@ -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'));
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ frappe.ui.form.on('Accounting Dimension', {
|
|||||||
frm.set_query('document_type', () => {
|
frm.set_query('document_type', () => {
|
||||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||||
invalid_doctypes.push('Accounting Dimension', 'Project',
|
invalid_doctypes.push('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail');
|
'Cost Center', 'Accounting Dimension Detail', 'Company');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail') :
|
'Cost Center', 'Accounting Dimension Detail', 'Company') :
|
||||||
|
|
||||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"description": "If enabled, the system will post accounting entries for inventory automatically.",
|
"description": "If enabled, the system will post accounting entries for inventory automatically",
|
||||||
"fieldname": "auto_accounting_for_stock",
|
"fieldname": "auto_accounting_for_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
@@ -48,23 +48,23 @@
|
|||||||
"label": "Make Accounting Entry For Every Stock Movement"
|
"label": "Make Accounting Entry For Every Stock Movement"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
|
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
|
||||||
"fieldname": "acc_frozen_upto",
|
"fieldname": "acc_frozen_upto",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Accounts Frozen Upto"
|
"label": "Accounts Frozen Till Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
|
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
|
||||||
"fieldname": "frozen_accounts_modifier",
|
"fieldname": "frozen_accounts_modifier",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
|
"label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
|
||||||
"options": "Role"
|
"options": "Role"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Billing Address",
|
"default": "Billing Address",
|
||||||
"description": "Address used to determine Tax Category in transactions.",
|
"description": "Address used to determine Tax Category in transactions",
|
||||||
"fieldname": "determine_address_tax_category_from",
|
"fieldname": "determine_address_tax_category_from",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Determine Address Tax Category From",
|
"label": "Determine Address Tax Category From",
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Role that is allowed to submit transactions that exceed credit limits set.",
|
"description": "This role is allowed to submit transactions that exceed credit limits",
|
||||||
"fieldname": "credit_controller",
|
"fieldname": "credit_controller",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "show_inclusive_tax_in_print",
|
"fieldname": "show_inclusive_tax_in_print",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Inclusive Tax In Print"
|
"label": "Show Inclusive Tax in Print"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Only select if you have setup Cash Flow Mapper documents",
|
"description": "Only select this if you have set up the Cash Flow Mapper documents",
|
||||||
"fieldname": "use_custom_cash_flow",
|
"fieldname": "use_custom_cash_flow",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Use Custom Cash Flow Format"
|
"label": "Use Custom Cash Flow Format"
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"label": "Automatically Fetch Payment Terms"
|
"label": "Automatically Fetch Payment Terms"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
|
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||||
"fieldname": "over_billing_allowance",
|
"fieldname": "over_billing_allowance",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Over Billing Allowance (%)"
|
"label": "Over Billing Allowance (%)"
|
||||||
@@ -199,7 +199,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "If this is unchecked direct GL Entries will be created to book Deferred Revenue/Expense",
|
"description": "If this is unchecked, direct GL entries will be created to book deferred revenue or expense",
|
||||||
"fieldname": "book_deferred_entries_via_journal_entry",
|
"fieldname": "book_deferred_entries_via_journal_entry",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Book Deferred Entries Via Journal Entry"
|
"label": "Book Deferred Entries Via Journal Entry"
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "Days",
|
"default": "Days",
|
||||||
"description": "If \"Months\" is selected then fixed amount will be booked as deferred revenue or expense for each month irrespective of number of days in a month. Will be prorated if deferred revenue or expense is not booked for an entire month.",
|
"description": "If \"Months\" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month",
|
||||||
"fieldname": "book_deferred_entries_based_on",
|
"fieldname": "book_deferred_entries_based_on",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Book Deferred Entries Based On",
|
"label": "Book Deferred Entries Based On",
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-07 14:58:50.325577",
|
"modified": "2020-10-13 11:32:52.268826",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -158,8 +158,11 @@ class TestBudget(unittest.TestCase):
|
|||||||
set_total_expense_zero(nowdate(), "cost_center")
|
set_total_expense_zero(nowdate(), "cost_center")
|
||||||
|
|
||||||
budget = make_budget(budget_against="Cost Center")
|
budget = make_budget(budget_against="Cost Center")
|
||||||
|
month = now_datetime().month
|
||||||
|
if month > 10:
|
||||||
|
month = 10
|
||||||
|
|
||||||
for i in range(now_datetime().month):
|
for i in range(month):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||||
|
|
||||||
@@ -177,8 +180,11 @@ class TestBudget(unittest.TestCase):
|
|||||||
set_total_expense_zero(nowdate(), "project")
|
set_total_expense_zero(nowdate(), "project")
|
||||||
|
|
||||||
budget = make_budget(budget_against="Project")
|
budget = make_budget(budget_against="Project")
|
||||||
|
month = now_datetime().month
|
||||||
|
if month > 10:
|
||||||
|
month = 10
|
||||||
|
|
||||||
for i in range(now_datetime().month):
|
for i in range(month):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class CashierClosing(Document):
|
|||||||
for i in self.payments:
|
for i in self.payments:
|
||||||
total += flt(i.amount)
|
total += flt(i.amount)
|
||||||
|
|
||||||
self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns
|
self.net_amount = total + self.outstanding_amount + flt(self.expense) - flt(self.custody) + flt(self.returns)
|
||||||
|
|
||||||
def validate_time(self):
|
def validate_time(self):
|
||||||
if self.from_time >= self.time:
|
if self.from_time >= self.time:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -213,7 +213,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());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -195,9 +195,7 @@ 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({
|
frappe.get_doc({
|
||||||
"doctype": "Account",
|
"doctype": "Account",
|
||||||
"account_name": "Loyalty",
|
"account_name": "Loyalty",
|
||||||
@@ -208,6 +206,7 @@ def create_records():
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
# create a new loyalty program Single tier
|
# create a new loyalty program Single tier
|
||||||
|
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Loyalty Program",
|
"doctype": "Loyalty Program",
|
||||||
"loyalty_program_name": "Test Single Loyalty",
|
"loyalty_program_name": "Test Single Loyalty",
|
||||||
@@ -227,6 +226,7 @@ def create_records():
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
# create a new customer
|
# create a new customer
|
||||||
|
if not frappe.db.exists("Customer","Test Loyalty Customer"):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"customer_group": "_Test Customer Group",
|
"customer_group": "_Test Customer Group",
|
||||||
"customer_name": "Test Loyalty Customer",
|
"customer_name": "Test Loyalty Customer",
|
||||||
@@ -236,6 +236,7 @@ def create_records():
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
# create a new loyalty program Multiple tier
|
# create a new loyalty program Multiple tier
|
||||||
|
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Loyalty Program",
|
"doctype": "Loyalty Program",
|
||||||
"loyalty_program_name": "Test Multiple Loyalty",
|
"loyalty_program_name": "Test Multiple Loyalty",
|
||||||
@@ -262,7 +263,8 @@ def create_records():
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
# create an item
|
# create an item
|
||||||
item = frappe.get_doc({
|
if not frappe.db.exists("Item", "Loyal Item"):
|
||||||
|
frappe.get_doc({
|
||||||
"doctype": "Item",
|
"doctype": "Item",
|
||||||
"item_code": "Loyal Item",
|
"item_code": "Loyal Item",
|
||||||
"item_name": "Loyal Item",
|
"item_name": "Loyal Item",
|
||||||
@@ -274,9 +276,10 @@ def create_records():
|
|||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
# create item price
|
# create item price
|
||||||
|
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Item Price",
|
"doctype": "Item Price",
|
||||||
"price_list": "Standard Selling",
|
"price_list": "Standard Selling",
|
||||||
"item_code": item.item_code,
|
"item_code": "Loyal Item",
|
||||||
"price_list_rate": 10000
|
"price_list_rate": 10000
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:mode_of_payment",
|
"autoname": "field:mode_of_payment",
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
"options": "Cash\nBank\nGeneral"
|
"options": "Cash\nBank\nGeneral\nPhone"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "accounts",
|
"fieldname": "accounts",
|
||||||
@@ -45,7 +46,9 @@
|
|||||||
],
|
],
|
||||||
"icon": "fa fa-credit-card",
|
"icon": "fa fa-credit-card",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-09-18 17:57:23.835236",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Mode of Payment",
|
"name": "Mode of Payment",
|
||||||
|
|||||||
@@ -42,25 +42,20 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.disable_save();
|
frm.disable_save();
|
||||||
frm.trigger("make_dashboard");
|
!frm.doc.import_in_progress && frm.trigger("make_dashboard");
|
||||||
frm.page.set_primary_action(__('Create Invoices'), () => {
|
frm.page.set_primary_action(__('Create Invoices'), () => {
|
||||||
let btn_primary = frm.page.btn_primary.get(0);
|
let btn_primary = frm.page.btn_primary.get(0);
|
||||||
return frm.call({
|
return frm.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
freeze: true,
|
|
||||||
btn: $(btn_primary),
|
btn: $(btn_primary),
|
||||||
method: "make_invoices",
|
method: "make_invoices",
|
||||||
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
|
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
|
||||||
callback: (r) => {
|
|
||||||
if(!r.exc){
|
|
||||||
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
|
|
||||||
frm.clear_table("invoices");
|
|
||||||
frm.refresh_fields();
|
|
||||||
frm.reload_doc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frm.doc.create_missing_party) {
|
||||||
|
frm.set_df_property("party", "fieldtype", "Data", frm.doc.name, "invoices");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup_company_filters: function(frm) {
|
setup_company_filters: function(frm) {
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
import traceback
|
||||||
|
from json import dumps
|
||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils.background_jobs import enqueue
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||||
|
|
||||||
|
|
||||||
@@ -62,66 +65,47 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
|
|
||||||
return invoices_summary, max_count
|
return invoices_summary, max_count
|
||||||
|
|
||||||
def make_invoices(self):
|
def validate_company(self):
|
||||||
names = []
|
|
||||||
mandatory_error_msg = _("Row {0}: {1} is required to create the Opening {2} Invoices")
|
|
||||||
if not self.company:
|
if not self.company:
|
||||||
frappe.throw(_("Please select the Company"))
|
frappe.throw(_("Please select the Company"))
|
||||||
|
|
||||||
company_details = frappe.get_cached_value('Company', self.company,
|
def set_missing_values(self, row):
|
||||||
["default_currency", "default_letter_head"], as_dict=1) or {}
|
row.qty = row.qty or 1.0
|
||||||
|
row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company)
|
||||||
for row in self.invoices:
|
|
||||||
if not row.qty:
|
|
||||||
row.qty = 1.0
|
|
||||||
|
|
||||||
# always mandatory fields for the invoices
|
|
||||||
if not row.temporary_opening_account:
|
|
||||||
row.temporary_opening_account = get_temporary_opening_account(self.company)
|
|
||||||
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
|
row.party_type = "Customer" if self.invoice_type == "Sales" else "Supplier"
|
||||||
|
row.item_name = row.item_name or _("Opening Invoice Item")
|
||||||
|
row.posting_date = row.posting_date or nowdate()
|
||||||
|
row.due_date = row.due_date or nowdate()
|
||||||
|
|
||||||
# Allow to create invoice even if no party present in customer or supplier.
|
def validate_mandatory_invoice_fields(self, row):
|
||||||
if not frappe.db.exists(row.party_type, row.party):
|
if not frappe.db.exists(row.party_type, row.party):
|
||||||
if self.create_missing_party:
|
if self.create_missing_party:
|
||||||
self.add_party(row.party_type, row.party)
|
self.add_party(row.party_type, row.party)
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("{0} {1} does not exist.").format(frappe.bold(row.party_type), frappe.bold(row.party)))
|
frappe.throw(_("Row #{}: {} {} does not exist.").format(row.idx, frappe.bold(row.party_type), frappe.bold(row.party)))
|
||||||
|
|
||||||
if not row.item_name:
|
|
||||||
row.item_name = _("Opening Invoice Item")
|
|
||||||
if not row.posting_date:
|
|
||||||
row.posting_date = nowdate()
|
|
||||||
if not row.due_date:
|
|
||||||
row.due_date = nowdate()
|
|
||||||
|
|
||||||
|
mandatory_error_msg = _("Row #{0}: {1} is required to create the Opening {2} Invoices")
|
||||||
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
|
for d in ("Party", "Outstanding Amount", "Temporary Opening Account"):
|
||||||
if not row.get(scrub(d)):
|
if not row.get(scrub(d)):
|
||||||
frappe.throw(mandatory_error_msg.format(row.idx, _(d), self.invoice_type))
|
frappe.throw(mandatory_error_msg.format(row.idx, d, self.invoice_type))
|
||||||
|
|
||||||
args = self.get_invoice_dict(row=row)
|
def get_invoices(self):
|
||||||
if not args:
|
invoices = []
|
||||||
|
for row in self.invoices:
|
||||||
|
if not row:
|
||||||
continue
|
continue
|
||||||
|
self.set_missing_values(row)
|
||||||
|
self.validate_mandatory_invoice_fields(row)
|
||||||
|
invoice = self.get_invoice_dict(row)
|
||||||
|
company_details = frappe.get_cached_value('Company', self.company, ["default_currency", "default_letter_head"], as_dict=1) or {}
|
||||||
if company_details:
|
if company_details:
|
||||||
args.update({
|
invoice.update({
|
||||||
"currency": company_details.get("default_currency"),
|
"currency": company_details.get("default_currency"),
|
||||||
"letter_head": company_details.get("default_letter_head")
|
"letter_head": company_details.get("default_letter_head")
|
||||||
})
|
})
|
||||||
|
invoices.append(invoice)
|
||||||
|
|
||||||
doc = frappe.get_doc(args).insert()
|
return invoices
|
||||||
doc.submit()
|
|
||||||
names.append(doc.name)
|
|
||||||
|
|
||||||
if len(self.invoices) > 5:
|
|
||||||
frappe.publish_realtime(
|
|
||||||
"progress", dict(
|
|
||||||
progress=[row.idx, len(self.invoices)],
|
|
||||||
title=_('Creating {0}').format(doc.doctype)
|
|
||||||
),
|
|
||||||
user=frappe.session.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return names
|
|
||||||
|
|
||||||
def add_party(self, party_type, party):
|
def add_party(self, party_type, party):
|
||||||
party_doc = frappe.new_doc(party_type)
|
party_doc = frappe.new_doc(party_type)
|
||||||
@@ -140,14 +124,12 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
|
|
||||||
def get_invoice_dict(self, row=None):
|
def get_invoice_dict(self, row=None):
|
||||||
def get_item_dict():
|
def get_item_dict():
|
||||||
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
cost_center = row.get('cost_center') or frappe.get_cached_value('Company', self.company, "cost_center")
|
||||||
cost_center = row.get('cost_center') or frappe.get_cached_value('Company',
|
|
||||||
self.company, "cost_center")
|
|
||||||
|
|
||||||
if not cost_center:
|
if not cost_center:
|
||||||
frappe.throw(
|
frappe.throw(_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company)))
|
||||||
_("Please set the Default Cost Center in {0} company.").format(frappe.bold(self.company))
|
|
||||||
)
|
income_expense_account_field = "income_account" if row.party_type == "Customer" else "expense_account"
|
||||||
|
default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
|
||||||
rate = flt(row.outstanding_amount) / flt(row.qty)
|
rate = flt(row.outstanding_amount) / flt(row.qty)
|
||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
@@ -161,18 +143,9 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
"cost_center": cost_center
|
"cost_center": cost_center
|
||||||
})
|
})
|
||||||
|
|
||||||
if not row:
|
|
||||||
return None
|
|
||||||
|
|
||||||
party_type = "Customer"
|
|
||||||
income_expense_account_field = "income_account"
|
|
||||||
if self.invoice_type == "Purchase":
|
|
||||||
party_type = "Supplier"
|
|
||||||
income_expense_account_field = "expense_account"
|
|
||||||
|
|
||||||
item = get_item_dict()
|
item = get_item_dict()
|
||||||
|
|
||||||
args = frappe._dict({
|
invoice = frappe._dict({
|
||||||
"items": [item],
|
"items": [item],
|
||||||
"is_opening": "Yes",
|
"is_opening": "Yes",
|
||||||
"set_posting_time": 1,
|
"set_posting_time": 1,
|
||||||
@@ -180,21 +153,76 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"due_date": row.due_date,
|
"due_date": row.due_date,
|
||||||
"posting_date": row.posting_date,
|
"posting_date": row.posting_date,
|
||||||
frappe.scrub(party_type): row.party,
|
frappe.scrub(row.party_type): row.party,
|
||||||
|
"is_pos": 0,
|
||||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
|
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
|
||||||
})
|
})
|
||||||
|
|
||||||
accounting_dimension = get_accounting_dimensions()
|
accounting_dimension = get_accounting_dimensions()
|
||||||
|
|
||||||
for dimension in accounting_dimension:
|
for dimension in accounting_dimension:
|
||||||
args.update({
|
invoice.update({
|
||||||
dimension: item.get(dimension)
|
dimension: item.get(dimension)
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.invoice_type == "Sales":
|
return invoice
|
||||||
args["is_pos"] = 0
|
|
||||||
|
|
||||||
return args
|
def make_invoices(self):
|
||||||
|
self.validate_company()
|
||||||
|
invoices = self.get_invoices()
|
||||||
|
if len(invoices) < 50:
|
||||||
|
return start_import(invoices)
|
||||||
|
else:
|
||||||
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
|
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||||
|
if self.name not in enqueued_jobs:
|
||||||
|
enqueue(
|
||||||
|
start_import,
|
||||||
|
queue="default",
|
||||||
|
timeout=6000,
|
||||||
|
event="opening_invoice_creation",
|
||||||
|
job_name=self.name,
|
||||||
|
invoices=invoices,
|
||||||
|
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_import(invoices):
|
||||||
|
errors = 0
|
||||||
|
names = []
|
||||||
|
for idx, d in enumerate(invoices):
|
||||||
|
try:
|
||||||
|
publish(idx, len(invoices), d.doctype)
|
||||||
|
doc = frappe.get_doc(d)
|
||||||
|
doc.insert()
|
||||||
|
doc.submit()
|
||||||
|
frappe.db.commit()
|
||||||
|
names.append(doc.name)
|
||||||
|
except Exception:
|
||||||
|
errors += 1
|
||||||
|
frappe.db.rollback()
|
||||||
|
message = "\n".join(["Data:", dumps(d, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
|
||||||
|
frappe.log_error(title="Error while creating Opening Invoice", message=message)
|
||||||
|
frappe.db.commit()
|
||||||
|
if errors:
|
||||||
|
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
|
||||||
|
.format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
|
||||||
|
return names
|
||||||
|
|
||||||
|
def publish(index, total, doctype):
|
||||||
|
if total < 5: return
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"opening_invoice_creation_progress",
|
||||||
|
dict(
|
||||||
|
title=_("Opening Invoice Creation In Progress"),
|
||||||
|
message=_('Creating {} out of {} {}').format(index + 1, total, doctype),
|
||||||
|
user=frappe.session.user,
|
||||||
|
count=index+1,
|
||||||
|
total=total
|
||||||
|
))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_temporary_opening_account(company=None):
|
def get_temporary_opening_account(company=None):
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
0: ["_Test Supplier", 300, "Overdue"],
|
0: ["_Test Supplier", 300, "Overdue"],
|
||||||
1: ["_Test Supplier 1", 250, "Overdue"],
|
1: ["_Test Supplier 1", 250, "Overdue"],
|
||||||
}
|
}
|
||||||
self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
|
self.check_expected_values(invoices, expected_value, "Purchase")
|
||||||
|
|
||||||
def get_opening_invoice_creation_dict(**args):
|
def get_opening_invoice_creation_dict(**args):
|
||||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||||
|
|||||||
@@ -1,313 +1,98 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2015-12-23 21:31:52.699821",
|
"creation": "2015-12-23 21:31:52.699821",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"field_order": [
|
||||||
|
"payment_gateway",
|
||||||
|
"payment_channel",
|
||||||
|
"is_default",
|
||||||
|
"column_break_4",
|
||||||
|
"payment_account",
|
||||||
|
"currency",
|
||||||
|
"payment_request_message",
|
||||||
|
"message",
|
||||||
|
"message_examples"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "payment_gateway",
|
"fieldname": "payment_gateway",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Payment Gateway",
|
"label": "Payment Gateway",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Payment Gateway",
|
"options": "Payment Gateway",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "is_default",
|
"fieldname": "is_default",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"label": "Is Default"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Is Default",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
"fieldtype": "Column Break",
|
"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,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "payment_account",
|
"fieldname": "payment_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Payment Account",
|
"label": "Payment Account",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fetch_from": "payment_account.account_currency",
|
"fetch_from": "payment_account.account_currency",
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Read Only",
|
||||||
"hidden": 0,
|
"label": "Currency"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Currency",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "eval: doc.payment_channel !== \"Phone\"",
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "payment_request_message",
|
"fieldname": "payment_request_message",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "Please click on the link below to make your payment",
|
"default": "Please click on the link below to make your payment",
|
||||||
"fieldname": "message",
|
"fieldname": "message",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 0,
|
"label": "Default Payment Request Message"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Default Payment Request Message",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "message_examples",
|
"fieldname": "message_examples",
|
||||||
"fieldtype": "HTML",
|
"fieldtype": "HTML",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Message Examples",
|
"label": "Message Examples",
|
||||||
"length": 0,
|
"options": "<pre><h5>Message Example</h5>\n\n<p> Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.</p>\n\n<p> Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.</p>\n\n<p> We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! </p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n</pre>\n"
|
||||||
"no_copy": 0,
|
},
|
||||||
"options": "<pre><h5>Message Example</h5>\n\n<p> Thank You for being a part of {{ doc.company }}! We hope you are enjoying the service.</p>\n\n<p> Please find enclosed the E Bill statement. The outstanding amount is {{ doc.grand_total }}.</p>\n\n<p> We don't want you to be spending time running around in order to pay for your Bill.<br>After all, life is beautiful and the time you have in hand should be spent to enjoy it!<br>So here are our little ways to help you get more time for life! </p>\n\n<a href=\"{{ payment_url }}\"> click here to pay </a>\n\n</pre>\n",
|
{
|
||||||
"permlevel": 0,
|
"default": "Email",
|
||||||
"precision": "",
|
"fieldname": "payment_channel",
|
||||||
"print_hide": 0,
|
"fieldtype": "Select",
|
||||||
"print_hide_if_no_value": 0,
|
"label": "Payment Channel",
|
||||||
"read_only": 0,
|
"options": "\nEmail\nPhone"
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2020-09-20 13:30:27.722852",
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-05-16 22:43:34.970491",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Gateway Account",
|
"name": "Payment Gateway Account",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Accounts Manager",
|
"role": "Accounts Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC"
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@@ -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,9 +51,7 @@ 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"));
|
|
||||||
} else {
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"company": me.frm.doc.company,
|
"company": me.frm.doc.company,
|
||||||
@@ -56,14 +59,10 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
"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"));
|
|
||||||
} else {
|
|
||||||
return {
|
return {
|
||||||
filters:[
|
filters:[
|
||||||
['Account', 'company', '=', me.frm.doc.company],
|
['Account', 'company', '=', me.frm.doc.company],
|
||||||
@@ -71,12 +70,20 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
['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() {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ class PaymentReconciliation(Document):
|
|||||||
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
||||||
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
||||||
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
||||||
|
and `tabGL Entry`.is_cancelled = 0
|
||||||
GROUP BY `tab{doc}`.name
|
GROUP BY `tab{doc}`.name
|
||||||
Having
|
Having
|
||||||
amount > 0
|
amount > 0
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
|
|||||||
})
|
})
|
||||||
|
|
||||||
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
||||||
if(frm.doc.payment_request_type == 'Inward' &&
|
if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
|
||||||
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
|
||||||
frm.add_custom_button(__('Resend Payment Email'), function(){
|
frm.add_custom_button(__('Resend Payment Email'), function(){
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"section_break_7",
|
"section_break_7",
|
||||||
"payment_gateway",
|
"payment_gateway",
|
||||||
"payment_account",
|
"payment_account",
|
||||||
|
"payment_channel",
|
||||||
"payment_order",
|
"payment_order",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
@@ -230,6 +231,7 @@
|
|||||||
"label": "Recipient Message And Payment Details"
|
"label": "Recipient Message And Payment Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "print_format",
|
"fieldname": "print_format",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Print Format"
|
"label": "Print Format"
|
||||||
@@ -241,6 +243,7 @@
|
|||||||
"label": "To"
|
"label": "To"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
@@ -277,16 +280,18 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.payment_request_type == 'Inward'",
|
"depends_on": "eval: doc.payment_request_type == 'Inward' || doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "section_break_10",
|
"fieldname": "section_break_10",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "message",
|
"fieldname": "message",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Message"
|
"label": "Message"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
"fieldname": "message_examples",
|
"fieldname": "message_examples",
|
||||||
"fieldtype": "HTML",
|
"fieldtype": "HTML",
|
||||||
"label": "Message Examples",
|
"label": "Message Examples",
|
||||||
@@ -347,12 +352,21 @@
|
|||||||
"options": "Payment Request",
|
"options": "Payment Request",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "payment_gateway_account.payment_channel",
|
||||||
|
"fieldname": "payment_channel",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Payment Channel",
|
||||||
|
"options": "\nEmail\nPhone",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-17 14:06:42.185763",
|
"modified": "2020-09-18 12:24:14.178853",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class PaymentRequest(Document):
|
|||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
if (hasattr(ref_doc, "order_type") \
|
if (hasattr(ref_doc, "order_type") \
|
||||||
and getattr(ref_doc, "order_type") != "Shopping Cart"):
|
and getattr(ref_doc, "order_type") != "Shopping Cart"):
|
||||||
ref_amount = get_amount(ref_doc)
|
ref_amount = get_amount(ref_doc, self.payment_account)
|
||||||
|
|
||||||
if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
|
if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
|
||||||
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
|
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
|
||||||
@@ -76,11 +76,25 @@ class PaymentRequest(Document):
|
|||||||
or self.flags.mute_email:
|
or self.flags.mute_email:
|
||||||
send_mail = False
|
send_mail = False
|
||||||
|
|
||||||
if send_mail:
|
if send_mail and self.payment_channel != "Phone":
|
||||||
self.set_payment_request_url()
|
self.set_payment_request_url()
|
||||||
self.send_email()
|
self.send_email()
|
||||||
self.make_communication_entry()
|
self.make_communication_entry()
|
||||||
|
|
||||||
|
elif self.payment_channel == "Phone":
|
||||||
|
controller = get_payment_gateway_controller(self.payment_gateway)
|
||||||
|
payment_record = dict(
|
||||||
|
reference_doctype="Payment Request",
|
||||||
|
reference_docname=self.name,
|
||||||
|
payment_reference=self.reference_name,
|
||||||
|
grand_total=self.grand_total,
|
||||||
|
sender=self.email_to,
|
||||||
|
currency=self.currency,
|
||||||
|
payment_gateway=self.payment_gateway
|
||||||
|
)
|
||||||
|
controller.validate_transaction_currency(self.currency)
|
||||||
|
controller.request_for_payment(**payment_record)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_if_payment_entry_exists()
|
self.check_if_payment_entry_exists()
|
||||||
self.set_as_cancelled()
|
self.set_as_cancelled()
|
||||||
@@ -105,13 +119,14 @@ class PaymentRequest(Document):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def set_payment_request_url(self):
|
def set_payment_request_url(self):
|
||||||
if self.payment_account:
|
if self.payment_account and self.payment_channel != "Phone":
|
||||||
self.payment_url = self.get_payment_url()
|
self.payment_url = self.get_payment_url()
|
||||||
|
|
||||||
if self.payment_url:
|
if self.payment_url:
|
||||||
self.db_set('payment_url', self.payment_url)
|
self.db_set('payment_url', self.payment_url)
|
||||||
|
|
||||||
if self.payment_url or not self.payment_gateway_account:
|
if self.payment_url or not self.payment_gateway_account \
|
||||||
|
or (self.payment_gateway_account and self.payment_channel == "Phone"):
|
||||||
self.db_set('status', 'Initiated')
|
self.db_set('status', 'Initiated')
|
||||||
|
|
||||||
def get_payment_url(self):
|
def get_payment_url(self):
|
||||||
@@ -140,6 +155,10 @@ class PaymentRequest(Document):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def set_as_paid(self):
|
def set_as_paid(self):
|
||||||
|
if self.payment_channel == "Phone":
|
||||||
|
self.db_set("status", "Paid")
|
||||||
|
|
||||||
|
else:
|
||||||
payment_entry = self.create_payment_entry()
|
payment_entry = self.create_payment_entry()
|
||||||
self.make_invoice()
|
self.make_invoice()
|
||||||
|
|
||||||
@@ -151,7 +170,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
|
|
||||||
if self.reference_doctype == "Sales Invoice":
|
if self.reference_doctype in ["Sales Invoice", "POS Invoice"]:
|
||||||
party_account = ref_doc.debit_to
|
party_account = ref_doc.debit_to
|
||||||
elif self.reference_doctype == "Purchase Invoice":
|
elif self.reference_doctype == "Purchase Invoice":
|
||||||
party_account = ref_doc.credit_to
|
party_account = ref_doc.credit_to
|
||||||
@@ -166,8 +185,8 @@ class PaymentRequest(Document):
|
|||||||
else:
|
else:
|
||||||
party_amount = self.grand_total
|
party_amount = self.grand_total
|
||||||
|
|
||||||
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name,
|
payment_entry = get_payment_entry(self.reference_doctype, self.reference_name, party_amount=party_amount,
|
||||||
party_amount=party_amount, bank_account=self.payment_account, bank_amount=bank_amount)
|
bank_account=self.payment_account, bank_amount=bank_amount)
|
||||||
|
|
||||||
payment_entry.update({
|
payment_entry.update({
|
||||||
"reference_no": self.name,
|
"reference_no": self.name,
|
||||||
@@ -255,7 +274,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
# if shopping cart enabled and in session
|
# if shopping cart enabled and in session
|
||||||
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
||||||
and frappe.local.session.user != "Guest"):
|
and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
|
||||||
|
|
||||||
success_url = shopping_cart_settings.payment_success_url
|
success_url = shopping_cart_settings.payment_success_url
|
||||||
if success_url:
|
if success_url:
|
||||||
@@ -280,7 +299,9 @@ def make_payment_request(**args):
|
|||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
ref_doc = frappe.get_doc(args.dt, args.dn)
|
ref_doc = frappe.get_doc(args.dt, args.dn)
|
||||||
grand_total = get_amount(ref_doc)
|
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||||
|
|
||||||
|
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||||
if args.loyalty_points and args.dt == "Sales Order":
|
if args.loyalty_points and args.dt == "Sales Order":
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||||
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
|
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
|
||||||
@@ -288,8 +309,6 @@ def make_payment_request(**args):
|
|||||||
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
||||||
grand_total = grand_total - loyalty_amount
|
grand_total = grand_total - loyalty_amount
|
||||||
|
|
||||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
|
||||||
|
|
||||||
bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
|
bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
|
||||||
if args.get('party_type') else '')
|
if args.get('party_type') else '')
|
||||||
|
|
||||||
@@ -314,9 +333,11 @@ def make_payment_request(**args):
|
|||||||
"payment_gateway_account": gateway_account.get("name"),
|
"payment_gateway_account": gateway_account.get("name"),
|
||||||
"payment_gateway": gateway_account.get("payment_gateway"),
|
"payment_gateway": gateway_account.get("payment_gateway"),
|
||||||
"payment_account": gateway_account.get("payment_account"),
|
"payment_account": gateway_account.get("payment_account"),
|
||||||
|
"payment_channel": gateway_account.get("payment_channel"),
|
||||||
"payment_request_type": args.get("payment_request_type"),
|
"payment_request_type": args.get("payment_request_type"),
|
||||||
"currency": ref_doc.currency,
|
"currency": ref_doc.currency,
|
||||||
"grand_total": grand_total,
|
"grand_total": grand_total,
|
||||||
|
"mode_of_payment": args.mode_of_payment,
|
||||||
"email_to": args.recipient_id or ref_doc.owner,
|
"email_to": args.recipient_id or ref_doc.owner,
|
||||||
"subject": _("Payment Request for {0}").format(args.dn),
|
"subject": _("Payment Request for {0}").format(args.dn),
|
||||||
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
||||||
@@ -344,7 +365,7 @@ def make_payment_request(**args):
|
|||||||
|
|
||||||
return pr.as_dict()
|
return pr.as_dict()
|
||||||
|
|
||||||
def get_amount(ref_doc):
|
def get_amount(ref_doc, payment_account=None):
|
||||||
"""get amount based on doctype"""
|
"""get amount based on doctype"""
|
||||||
dt = ref_doc.doctype
|
dt = ref_doc.doctype
|
||||||
if dt in ["Sales Order", "Purchase Order"]:
|
if dt in ["Sales Order", "Purchase Order"]:
|
||||||
@@ -356,6 +377,12 @@ def get_amount(ref_doc):
|
|||||||
else:
|
else:
|
||||||
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
|
||||||
|
|
||||||
|
elif dt == "POS Invoice":
|
||||||
|
for pay in ref_doc.payments:
|
||||||
|
if pay.type == "Phone" and pay.account == payment_account:
|
||||||
|
grand_total = pay.amount
|
||||||
|
break
|
||||||
|
|
||||||
elif dt == "Fees":
|
elif dt == "Fees":
|
||||||
grand_total = ref_doc.outstanding_amount
|
grand_total = ref_doc.outstanding_amount
|
||||||
|
|
||||||
@@ -366,6 +393,10 @@ def get_amount(ref_doc):
|
|||||||
frappe.throw(_("Payment Entry is already created"))
|
frappe.throw(_("Payment Entry is already created"))
|
||||||
|
|
||||||
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||||
|
"""
|
||||||
|
Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
|
||||||
|
and get the summation of existing paid payment request for Phone payment channel.
|
||||||
|
"""
|
||||||
existing_payment_request_amount = frappe.db.sql("""
|
existing_payment_request_amount = frappe.db.sql("""
|
||||||
select sum(grand_total)
|
select sum(grand_total)
|
||||||
from `tabPayment Request`
|
from `tabPayment Request`
|
||||||
@@ -373,7 +404,9 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
|||||||
reference_doctype = %s
|
reference_doctype = %s
|
||||||
and reference_name = %s
|
and reference_name = %s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and status != 'Paid'
|
and (status != 'Paid'
|
||||||
|
or (payment_channel = 'Phone'
|
||||||
|
and status = 'Paid'))
|
||||||
""", (ref_dt, ref_dn))
|
""", (ref_dt, ref_dn))
|
||||||
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Provide the invoice portion in percent",
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@@ -170,6 +171,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "Give number of days according to prior selection",
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@@ -305,7 +307,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-03-08 10:47:32.830478",
|
"modified": "2020-10-14 10:47:32.830478",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Term",
|
"name": "Payment Term",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
args: {
|
args: {
|
||||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||||
|
pos_profile: frm.doc.pos_profile,
|
||||||
user: frm.doc.user
|
user: frm.doc.user
|
||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
|
|||||||
@@ -14,19 +14,51 @@ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import
|
|||||||
|
|
||||||
class POSClosingEntry(Document):
|
class POSClosingEntry(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
user = frappe.get_all('POS Closing Entry',
|
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||||
filters = { 'user': self.user, 'docstatus': 1 },
|
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||||
|
|
||||||
|
self.validate_pos_closing()
|
||||||
|
self.validate_pos_invoices()
|
||||||
|
|
||||||
|
def validate_pos_closing(self):
|
||||||
|
user = frappe.get_all("POS Closing Entry",
|
||||||
|
filters = { "user": self.user, "docstatus": 1, "pos_profile": self.pos_profile },
|
||||||
or_filters = {
|
or_filters = {
|
||||||
'period_start_date': ('between', [self.period_start_date, self.period_end_date]),
|
"period_start_date": ("between", [self.period_start_date, self.period_end_date]),
|
||||||
'period_end_date': ('between', [self.period_start_date, self.period_end_date])
|
"period_end_date": ("between", [self.period_start_date, self.period_end_date])
|
||||||
})
|
})
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
frappe.throw(_("POS Closing Entry {} against {} between selected period"
|
bold_already_exists = frappe.bold(_("already exists"))
|
||||||
.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
|
bold_user = frappe.bold(self.user)
|
||||||
|
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||||
|
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||||
|
|
||||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
def validate_pos_invoices(self):
|
||||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
invalid_rows = []
|
||||||
|
for d in self.pos_transactions:
|
||||||
|
invalid_row = {'idx': d.idx}
|
||||||
|
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||||
|
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||||
|
if pos_invoice.consolidated_invoice:
|
||||||
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||||
|
invalid_rows.append(invalid_row)
|
||||||
|
continue
|
||||||
|
if pos_invoice.pos_profile != self.pos_profile:
|
||||||
|
invalid_row.setdefault('msg', []).append(_("POS Profile doesn't matches {}").format(frappe.bold(self.pos_profile)))
|
||||||
|
if pos_invoice.docstatus != 1:
|
||||||
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is not {}').format(frappe.bold("submitted")))
|
||||||
|
if pos_invoice.owner != self.user:
|
||||||
|
invalid_row.setdefault('msg', []).append(_("POS Invoice isn't created by user {}").format(frappe.bold(self.owner)))
|
||||||
|
|
||||||
|
if invalid_row.get('msg'):
|
||||||
|
invalid_rows.append(invalid_row)
|
||||||
|
|
||||||
|
if not invalid_rows:
|
||||||
|
return
|
||||||
|
|
||||||
|
error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
|
||||||
|
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
merge_pos_invoices(self.pos_transactions)
|
merge_pos_invoices(self.pos_transactions)
|
||||||
@@ -47,16 +79,15 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
return [c['user'] for c in cashiers_list]
|
return [c['user'] for c in cashiers_list]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pos_invoices(start, end, user):
|
def get_pos_invoices(start, end, pos_profile, user):
|
||||||
data = frappe.db.sql("""
|
data = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||||
from
|
from
|
||||||
`tabPOS Invoice`
|
`tabPOS Invoice`
|
||||||
where
|
where
|
||||||
owner = %s and docstatus = 1 and
|
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
|
||||||
(consolidated_invoice is NULL or consolidated_invoice = '')
|
""", (user, pos_profile), as_dict=1)
|
||||||
""", (user), as_dict=1)
|
|
||||||
|
|
||||||
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data))
|
||||||
# need to get taxes and payments so can't avoid get_doc
|
# need to get taxes and payments so can't avoid get_doc
|
||||||
@@ -76,7 +107,8 @@ def make_closing_entry_from_opening(opening_entry):
|
|||||||
closing_entry.net_total = 0
|
closing_entry.net_total = 0
|
||||||
closing_entry.total_quantity = 0
|
closing_entry.total_quantity = 0
|
||||||
|
|
||||||
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date, closing_entry.user)
|
invoices = get_pos_invoices(closing_entry.period_start_date, closing_entry.period_end_date,
|
||||||
|
closing_entry.pos_profile, closing_entry.user)
|
||||||
|
|
||||||
pos_transactions = []
|
pos_transactions = []
|
||||||
taxes = []
|
taxes = []
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
def init_user_and_profile():
|
def init_user_and_profile(**args):
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
test_user = frappe.get_doc('User', user)
|
test_user = frappe.get_doc('User', user)
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ def init_user_and_profile():
|
|||||||
test_user.add_roles(*roles)
|
test_user.add_roles(*roles)
|
||||||
frappe.set_user(user)
|
frappe.set_user(user)
|
||||||
|
|
||||||
pos_profile = make_pos_profile()
|
pos_profile = make_pos_profile(**args)
|
||||||
pos_profile.append('applicable_for_users', {
|
pos_profile.append('applicable_for_users', {
|
||||||
'default': 1,
|
'default': 1,
|
||||||
'user': user
|
'user': user
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"mode_of_payment",
|
"mode_of_payment",
|
||||||
"opening_amount",
|
"opening_amount",
|
||||||
"closing_amount",
|
|
||||||
"expected_amount",
|
"expected_amount",
|
||||||
|
"closing_amount",
|
||||||
"difference"
|
"difference"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -26,8 +26,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Expected Amount",
|
"label": "Expected Amount",
|
||||||
"options": "company:company_currency",
|
"options": "company:company_currency",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "difference",
|
"fieldname": "difference",
|
||||||
@@ -55,9 +54,10 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:03:34.533607",
|
"modified": "2020-10-23 16:45:43.662034",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Closing Entry Detail",
|
"name": "POS Closing Entry Detail",
|
||||||
|
|||||||
@@ -9,80 +9,63 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
this._super(doc);
|
this._super(doc);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload() {
|
onload(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
if(this.frm.doc.__islocal && this.frm.doc.is_pos) {
|
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
|
||||||
//Load pos profile data on the invoice if the default value of Is POS is 1
|
this.frm.script_manager.trigger("is_pos");
|
||||||
|
this.frm.refresh_fields();
|
||||||
me.frm.script_manager.trigger("is_pos");
|
|
||||||
me.frm.refresh_fields();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(doc) {
|
refresh(doc) {
|
||||||
this._super();
|
this._super();
|
||||||
if (doc.docstatus == 1 && !doc.is_return) {
|
if (doc.docstatus == 1 && !doc.is_return) {
|
||||||
if(doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
|
this.frm.add_custom_button(__('Return'), this.make_sales_return, __('Create'));
|
||||||
cur_frm.add_custom_button(__('Return'),
|
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
this.make_sales_return, __('Create'));
|
|
||||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.frm.doc.is_return) {
|
if (doc.is_return && doc.__islocal) {
|
||||||
this.frm.return_print_format = "Sales Invoice Return";
|
this.frm.return_print_format = "Sales Invoice Return";
|
||||||
cur_frm.set_value('consolidated_invoice', '');
|
this.frm.set_value('consolidated_invoice', '');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
is_pos: function(frm){
|
is_pos: function() {
|
||||||
this.set_pos_data();
|
this.set_pos_data();
|
||||||
},
|
},
|
||||||
|
|
||||||
set_pos_data: function() {
|
set_pos_data: async function() {
|
||||||
if(this.frm.doc.is_pos) {
|
if(this.frm.doc.is_pos) {
|
||||||
this.frm.set_value("allocate_advances_automatically", 0);
|
this.frm.set_value("allocate_advances_automatically", 0);
|
||||||
if(!this.frm.doc.company) {
|
if(!this.frm.doc.company) {
|
||||||
this.frm.set_value("is_pos", 0);
|
this.frm.set_value("is_pos", 0);
|
||||||
frappe.msgprint(__("Please specify Company to proceed"));
|
frappe.msgprint(__("Please specify Company to proceed"));
|
||||||
} else {
|
} else {
|
||||||
var me = this;
|
const r = await this.frm.call({
|
||||||
return this.frm.call({
|
doc: this.frm.doc,
|
||||||
doc: me.frm.doc,
|
|
||||||
method: "set_missing_values",
|
method: "set_missing_values",
|
||||||
callback: function(r) {
|
freeze: true
|
||||||
|
});
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
me.frm.pos_print_format = r.message.print_format || "";
|
this.frm.pos_print_format = r.message.print_format || "";
|
||||||
me.frm.meta.default_print_format = r.message.print_format || "";
|
this.frm.meta.default_print_format = r.message.print_format || "";
|
||||||
me.frm.allow_edit_rate = r.message.allow_edit_rate;
|
this.frm.doc.campaign = r.message.campaign;
|
||||||
me.frm.allow_edit_discount = r.message.allow_edit_discount;
|
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||||
me.frm.doc.campaign = r.message.campaign;
|
|
||||||
me.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
|
||||||
}
|
}
|
||||||
me.frm.script_manager.trigger("update_stock");
|
this.frm.script_manager.trigger("update_stock");
|
||||||
me.calculate_taxes_and_totals();
|
this.calculate_taxes_and_totals();
|
||||||
if(me.frm.doc.taxes_and_charges) {
|
this.frm.doc.taxes_and_charges && this.frm.script_manager.trigger("taxes_and_charges");
|
||||||
me.frm.script_manager.trigger("taxes_and_charges");
|
frappe.model.set_default_values(this.frm.doc);
|
||||||
}
|
this.set_dynamic_labels();
|
||||||
frappe.model.set_default_values(me.frm.doc);
|
|
||||||
me.set_dynamic_labels();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else this.frm.trigger("refresh");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
customer() {
|
customer() {
|
||||||
if (!this.frm.doc.customer) return
|
if (!this.frm.doc.customer) return
|
||||||
|
const pos_profile = this.frm.doc.pos_profile;
|
||||||
if (this.frm.doc.is_pos){
|
|
||||||
var pos_profile = this.frm.doc.pos_profile;
|
|
||||||
}
|
|
||||||
var me = this;
|
|
||||||
if(this.frm.updating_party_details) return;
|
if(this.frm.updating_party_details) return;
|
||||||
erpnext.utils.get_party_details(this.frm,
|
erpnext.utils.get_party_details(this.frm,
|
||||||
"erpnext.accounts.party.get_party_details", {
|
"erpnext.accounts.party.get_party_details", {
|
||||||
@@ -92,8 +75,8 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
|
|||||||
account: this.frm.doc.debit_to,
|
account: this.frm.doc.debit_to,
|
||||||
price_list: this.frm.doc.selling_price_list,
|
price_list: this.frm.doc.selling_price_list,
|
||||||
pos_profile: pos_profile
|
pos_profile: pos_profile
|
||||||
}, function() {
|
}, () => {
|
||||||
me.apply_pricing_rule();
|
this.apply_pricing_rule();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -201,5 +184,22 @@ frappe.ui.form.on('POS Invoice', {
|
|||||||
}
|
}
|
||||||
frm.set_value("loyalty_amount", loyalty_amount);
|
frm.set_value("loyalty_amount", loyalty_amount);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
request_for_payment: function (frm) {
|
||||||
|
frm.save().then(() => {
|
||||||
|
frappe.dom.freeze();
|
||||||
|
frappe.call({
|
||||||
|
method: 'create_payment_request',
|
||||||
|
doc: frm.doc,
|
||||||
|
})
|
||||||
|
.fail(() => {
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
frappe.msgprint('Payment request failed');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
frappe.msgprint('Payment request sent successfully');
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -279,8 +279,7 @@
|
|||||||
"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",
|
||||||
@@ -461,7 +460,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "contact_mobile",
|
"fieldname": "contact_mobile",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Mobile No",
|
"label": "Mobile No",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@@ -1579,10 +1578,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"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-09-07 12:43:09.138720",
|
"modified": "2020-09-28 16:51:24.641755",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ from erpnext.controllers.selling_controller import SellingController
|
|||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||||
get_loyalty_program_details_with_points, validate_loyalty_points
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice, get_bank_cash_account, update_multi_mode_option, get_mode_of_payment_info
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
@@ -29,8 +28,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
# run on validate method of selling controller
|
# run on validate method of selling controller
|
||||||
super(SalesInvoice, self).validate()
|
super(SalesInvoice, self).validate()
|
||||||
self.validate_auto_set_posting_time()
|
self.validate_auto_set_posting_time()
|
||||||
self.validate_pos_paid_amount()
|
self.validate_mode_of_payment()
|
||||||
self.validate_pos_return()
|
|
||||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||||
self.validate_uom_is_integer("uom", "qty")
|
self.validate_uom_is_integer("uom", "qty")
|
||||||
self.validate_debit_to_acc()
|
self.validate_debit_to_acc()
|
||||||
@@ -40,11 +38,11 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.validate_item_cost_centers()
|
self.validate_item_cost_centers()
|
||||||
self.validate_serialised_or_batched_item()
|
self.validate_serialised_or_batched_item()
|
||||||
self.validate_stock_availablility()
|
self.validate_stock_availablility()
|
||||||
self.validate_return_items()
|
self.validate_return_items_qty()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.set_account_for_mode_of_payment()
|
self.set_account_for_mode_of_payment()
|
||||||
self.validate_pos()
|
self.validate_pos()
|
||||||
self.verify_payment_amount()
|
self.validate_payment_amount()
|
||||||
self.validate_loyalty_transaction()
|
self.validate_loyalty_transaction()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@@ -57,6 +55,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
against_psi_doc.make_loyalty_point_entry()
|
against_psi_doc.make_loyalty_point_entry()
|
||||||
if self.redeem_loyalty_points and self.loyalty_points:
|
if self.redeem_loyalty_points and self.loyalty_points:
|
||||||
self.apply_loyalty_points()
|
self.apply_loyalty_points()
|
||||||
|
self.check_phone_payments()
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@@ -69,77 +68,119 @@ class POSInvoice(SalesInvoice):
|
|||||||
against_psi_doc.delete_loyalty_point_entry()
|
against_psi_doc.delete_loyalty_point_entry()
|
||||||
against_psi_doc.make_loyalty_point_entry()
|
against_psi_doc.make_loyalty_point_entry()
|
||||||
|
|
||||||
def validate_stock_availablility(self):
|
def check_phone_payments(self):
|
||||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
for pay in self.payments:
|
||||||
|
if pay.type == "Phone" and pay.amount >= 0:
|
||||||
|
paid_amt = frappe.db.get_value("Payment Request",
|
||||||
|
filters=dict(
|
||||||
|
reference_doctype="POS Invoice", reference_name=self.name,
|
||||||
|
mode_of_payment=pay.mode_of_payment, status="Paid"),
|
||||||
|
fieldname="grand_total")
|
||||||
|
|
||||||
|
if pay.amount != paid_amt:
|
||||||
|
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||||
|
|
||||||
|
def validate_stock_availablility(self):
|
||||||
|
if self.is_return:
|
||||||
|
return
|
||||||
|
|
||||||
|
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||||
|
error_msg = []
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
|
msg = ""
|
||||||
if d.serial_no:
|
if d.serial_no:
|
||||||
filters = {
|
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||||
"item_code": d.item_code,
|
|
||||||
"warehouse": d.warehouse,
|
|
||||||
"delivery_document_no": "",
|
|
||||||
"sales_invoice": ""
|
|
||||||
}
|
|
||||||
if d.batch_no:
|
if d.batch_no:
|
||||||
filters["batch_no"] = d.batch_no
|
filters["batch_no"] = d.batch_no
|
||||||
reserved_serial_nos, unreserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||||
serial_nos = d.serial_no.split("\n")
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
serial_nos = ' '.join(serial_nos).split() # remove whitespaces
|
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||||
invalid_serial_nos = []
|
|
||||||
for s in serial_nos:
|
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||||
if s in reserved_serial_nos:
|
if len(invalid_serial_nos) == 1:
|
||||||
invalid_serial_nos.append(s)
|
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||||
|
.format(d.idx, bold_invalid_serial_nos))
|
||||||
|
elif invalid_serial_nos:
|
||||||
|
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||||
|
.format(d.idx, bold_invalid_serial_nos))
|
||||||
|
|
||||||
if len(invalid_serial_nos):
|
|
||||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
|
||||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
|
|
||||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
|
||||||
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
|
|
||||||
else:
|
else:
|
||||||
if allow_negative_stock:
|
if allow_negative_stock:
|
||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||||
if not (flt(available_stock) > 0):
|
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
|
if flt(available_stock) <= 0:
|
||||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||||
elif flt(available_stock) < flt(d.qty):
|
elif flt(available_stock) < flt(d.qty):
|
||||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
.format(d.idx, item_code, warehouse, qty))
|
||||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
if msg:
|
||||||
|
error_msg.append(msg)
|
||||||
|
|
||||||
|
if error_msg:
|
||||||
|
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
|
||||||
|
|
||||||
def validate_serialised_or_batched_item(self):
|
def validate_serialised_or_batched_item(self):
|
||||||
|
error_msg = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
serialized = d.get("has_serial_no")
|
serialized = d.get("has_serial_no")
|
||||||
batched = d.get("has_batch_no")
|
batched = d.get("has_batch_no")
|
||||||
no_serial_selected = not d.get("serial_no")
|
no_serial_selected = not d.get("serial_no")
|
||||||
no_batch_selected = not d.get("batch_no")
|
no_batch_selected = not d.get("batch_no")
|
||||||
|
|
||||||
|
msg = ""
|
||||||
|
item_code = frappe.bold(d.item_code)
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
if serialized and batched and (no_batch_selected or no_serial_selected):
|
if serialized and batched and (no_batch_selected or no_serial_selected):
|
||||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.'
|
msg = (_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.')
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
if serialized and no_serial_selected:
|
elif serialized and no_serial_selected:
|
||||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.'
|
msg = (_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.')
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
if batched and no_batch_selected:
|
elif batched and no_batch_selected:
|
||||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
|
msg = (_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.')
|
||||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
.format(d.idx, item_code))
|
||||||
|
elif serialized and not no_serial_selected and len(serial_nos) != d.qty:
|
||||||
|
msg = (_("Row #{}: You must select {} serial numbers for item {}.").format(d.idx, frappe.bold(cint(d.qty)), item_code))
|
||||||
|
|
||||||
def validate_return_items(self):
|
if msg:
|
||||||
|
error_msg.append(msg)
|
||||||
|
|
||||||
|
if error_msg:
|
||||||
|
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||||
|
|
||||||
|
def validate_return_items_qty(self):
|
||||||
if not self.get("is_return"): return
|
if not self.get("is_return"): return
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.get("qty") > 0:
|
if d.get("qty") > 0:
|
||||||
frappe.throw(_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
frappe.throw(
|
||||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
_("Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return.")
|
||||||
|
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")
|
||||||
|
)
|
||||||
|
if d.get("serial_no"):
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
|
for sr in serial_nos:
|
||||||
|
serial_no_exists = frappe.db.exists("POS Invoice Item", {
|
||||||
|
"parent": self.return_against,
|
||||||
|
"serial_no": ["like", d.get("serial_no")]
|
||||||
|
})
|
||||||
|
if not serial_no_exists:
|
||||||
|
bold_return_against = frappe.bold(self.return_against)
|
||||||
|
bold_serial_no = frappe.bold(sr)
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}")
|
||||||
|
.format(d.idx, bold_serial_no, bold_return_against)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_pos_paid_amount(self):
|
def validate_mode_of_payment(self):
|
||||||
if len(self.payments) == 0 and self.is_pos:
|
if len(self.payments) == 0:
|
||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def validate_change_account(self):
|
def validate_change_account(self):
|
||||||
if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
if self.change_amount and self.account_for_change_amount and \
|
||||||
|
frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
||||||
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
||||||
|
|
||||||
def validate_change_amount(self):
|
def validate_change_amount(self):
|
||||||
@@ -150,23 +191,21 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
||||||
|
|
||||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||||
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||||
|
|
||||||
def verify_payment_amount(self):
|
def validate_payment_amount(self):
|
||||||
|
total_amount_in_payments = 0
|
||||||
for entry in self.payments:
|
for entry in self.payments:
|
||||||
|
total_amount_in_payments += entry.amount
|
||||||
if not self.is_return and entry.amount < 0:
|
if not self.is_return and entry.amount < 0:
|
||||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||||
if self.is_return and entry.amount > 0:
|
if self.is_return and entry.amount > 0:
|
||||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||||
|
|
||||||
def validate_pos_return(self):
|
if self.is_return:
|
||||||
if self.is_pos and self.is_return:
|
|
||||||
total_amount_in_payments = 0
|
|
||||||
for payment in self.payments:
|
|
||||||
total_amount_in_payments += payment.amount
|
|
||||||
invoice_total = self.rounded_total or self.grand_total
|
invoice_total = self.rounded_total or self.grand_total
|
||||||
if total_amount_in_payments < invoice_total:
|
if total_amount_in_payments and total_amount_in_payments < invoice_total:
|
||||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
|
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
||||||
|
|
||||||
def validate_loyalty_transaction(self):
|
def validate_loyalty_transaction(self):
|
||||||
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
||||||
@@ -220,55 +259,45 @@ class POSInvoice(SalesInvoice):
|
|||||||
pos_profile = get_pos_profile(self.company) or {}
|
pos_profile = get_pos_profile(self.company) or {}
|
||||||
self.pos_profile = pos_profile.get('name')
|
self.pos_profile = pos_profile.get('name')
|
||||||
|
|
||||||
pos = {}
|
profile = {}
|
||||||
if self.pos_profile:
|
if self.pos_profile:
|
||||||
pos = frappe.get_doc('POS Profile', self.pos_profile)
|
profile = frappe.get_doc('POS Profile', self.pos_profile)
|
||||||
|
|
||||||
if not self.get('payments') and not for_validate:
|
if not self.get('payments') and not for_validate:
|
||||||
update_multi_mode_option(self, pos)
|
update_multi_mode_option(self, profile)
|
||||||
|
|
||||||
if not self.account_for_change_amount:
|
if self.is_return and not for_validate:
|
||||||
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
add_return_modes(self, profile)
|
||||||
|
|
||||||
if pos:
|
|
||||||
if not for_validate:
|
|
||||||
self.tax_category = pos.get("tax_category")
|
|
||||||
|
|
||||||
|
if profile:
|
||||||
if not for_validate and not self.customer:
|
if not for_validate and not self.customer:
|
||||||
self.customer = pos.customer
|
self.customer = profile.customer
|
||||||
|
|
||||||
self.ignore_pricing_rule = pos.ignore_pricing_rule
|
self.ignore_pricing_rule = profile.ignore_pricing_rule
|
||||||
if pos.get('account_for_change_amount'):
|
self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
|
||||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
self.set_warehouse = profile.get('warehouse') or self.set_warehouse
|
||||||
if pos.get('warehouse'):
|
|
||||||
self.set_warehouse = pos.get('warehouse')
|
|
||||||
|
|
||||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
'write_off_cost_center', 'apply_discount_on', 'cost_center', 'tax_category',
|
||||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
'ignore_pricing_rule', 'company_address', 'update_stock'):
|
||||||
self.set(fieldname, pos.get(fieldname))
|
if not for_validate:
|
||||||
|
self.set(fieldname, profile.get(fieldname))
|
||||||
if pos.get("company_address"):
|
|
||||||
self.company_address = pos.get("company_address")
|
|
||||||
|
|
||||||
if self.customer:
|
if self.customer:
|
||||||
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
|
||||||
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
|
||||||
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
|
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
|
||||||
else:
|
else:
|
||||||
selling_price_list = pos.get('selling_price_list')
|
selling_price_list = profile.get('selling_price_list')
|
||||||
|
|
||||||
if selling_price_list:
|
if selling_price_list:
|
||||||
self.set('selling_price_list', selling_price_list)
|
self.set('selling_price_list', selling_price_list)
|
||||||
|
|
||||||
if not for_validate:
|
|
||||||
self.update_stock = cint(pos.get("update_stock"))
|
|
||||||
|
|
||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get('item_code'):
|
if item.get('item_code'):
|
||||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
profile_details = get_pos_profile_item_details(profile.get("company"), frappe._dict(item.as_dict()), profile)
|
||||||
for fname, val in iteritems(profile_details):
|
for fname, val in iteritems(profile_details):
|
||||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||||
item.set(fname, val)
|
item.set(fname, val)
|
||||||
@@ -281,10 +310,13 @@ class POSInvoice(SalesInvoice):
|
|||||||
if self.taxes_and_charges and not len(self.get("taxes")):
|
if self.taxes_and_charges and not len(self.get("taxes")):
|
||||||
self.set_taxes()
|
self.set_taxes()
|
||||||
|
|
||||||
return pos
|
if not self.account_for_change_amount:
|
||||||
|
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
pos = self.set_pos_fields(for_validate)
|
profile = self.set_pos_fields(for_validate)
|
||||||
|
|
||||||
if not self.debit_to:
|
if not self.debit_to:
|
||||||
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||||
@@ -294,17 +326,15 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
super(SalesInvoice, self).set_missing_values(for_validate)
|
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
print_format = pos.get("print_format") if pos else None
|
print_format = profile.get("print_format") if profile else None
|
||||||
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
if not print_format and not cint(frappe.db.get_value('Print Format', 'POS Invoice', 'disabled')):
|
||||||
print_format = 'POS Invoice'
|
print_format = 'POS Invoice'
|
||||||
|
|
||||||
if pos:
|
if profile:
|
||||||
return {
|
return {
|
||||||
"print_format": print_format,
|
"print_format": print_format,
|
||||||
"allow_edit_rate": pos.get("allow_user_to_edit_rate"),
|
"campaign": profile.get("campaign"),
|
||||||
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
|
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||||
"campaign": pos.get("campaign"),
|
|
||||||
"allow_print_before_pay": pos.get("allow_print_before_pay")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def set_account_for_mode_of_payment(self):
|
def set_account_for_mode_of_payment(self):
|
||||||
@@ -313,6 +343,47 @@ class POSInvoice(SalesInvoice):
|
|||||||
if not pay.account:
|
if not pay.account:
|
||||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||||
|
|
||||||
|
def create_payment_request(self):
|
||||||
|
for pay in self.payments:
|
||||||
|
if pay.type == "Phone":
|
||||||
|
if pay.amount <= 0:
|
||||||
|
frappe.throw(_("Payment amount cannot be less than or equal to 0"))
|
||||||
|
|
||||||
|
if not self.contact_mobile:
|
||||||
|
frappe.throw(_("Please enter the phone number first"))
|
||||||
|
|
||||||
|
payment_gateway = frappe.db.get_value("Payment Gateway Account", {
|
||||||
|
"payment_account": pay.account,
|
||||||
|
})
|
||||||
|
record = {
|
||||||
|
"payment_gateway": payment_gateway,
|
||||||
|
"dt": "POS Invoice",
|
||||||
|
"dn": self.name,
|
||||||
|
"payment_request_type": "Inward",
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": self.customer,
|
||||||
|
"mode_of_payment": pay.mode_of_payment,
|
||||||
|
"recipient_id": self.contact_mobile,
|
||||||
|
"submit_doc": True
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_payment_request(**record)
|
||||||
|
|
||||||
|
def add_return_modes(doc, pos_profile):
|
||||||
|
def append_payment(payment_mode):
|
||||||
|
payment = doc.append('payments', {})
|
||||||
|
payment.default = payment_mode.default
|
||||||
|
payment.mode_of_payment = payment_mode.parent
|
||||||
|
payment.account = payment_mode.default_account
|
||||||
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
|
for pos_payment_method in pos_profile.get('payments'):
|
||||||
|
pos_payment_method = pos_payment_method.as_dict()
|
||||||
|
mode_of_payment = pos_payment_method.mode_of_payment
|
||||||
|
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
|
||||||
|
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||||
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_stock_availability(item_code, warehouse):
|
def get_stock_availability(item_code, warehouse):
|
||||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||||
@@ -334,11 +405,9 @@ def get_stock_availability(item_code, warehouse):
|
|||||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||||
|
|
||||||
if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
|
if sle_qty and pos_sales_qty:
|
||||||
return sle_qty - pos_sales_qty
|
return sle_qty - pos_sales_qty
|
||||||
else:
|
else:
|
||||||
# when sle_qty is 0
|
|
||||||
# when sle_qty > 0 and pos_sales_qty is 0
|
|
||||||
return sle_qty
|
return sle_qty
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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.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):
|
||||||
@@ -222,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)
|
||||||
|
|
||||||
@@ -295,7 +296,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
test_user, pos_profile = init_user_and_profile()
|
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 = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
||||||
pos_inv.append('payments', {
|
pos_inv.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270
|
||||||
})
|
})
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
|
||||||
@@ -309,7 +310,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
self.assertEqual(rounded_total, 3500)
|
self.assertEqual(rounded_total, 3470)
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||||
@@ -361,7 +362,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||||
|
|
||||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=1, basic_rate=300)
|
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
@@ -413,8 +414,6 @@ def create_pos_invoice(**args):
|
|||||||
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()
|
||||||
@@ -428,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",
|
||||||
|
|||||||
@@ -27,17 +27,24 @@ class POSInvoiceMergeLog(Document):
|
|||||||
status, docstatus, is_return, return_against = frappe.db.get_value(
|
status, docstatus, is_return, return_against = frappe.db.get_value(
|
||||||
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
|
||||||
|
|
||||||
|
bold_pos_invoice = frappe.bold(d.pos_invoice)
|
||||||
|
bold_status = frappe.bold(status)
|
||||||
if docstatus != 1:
|
if docstatus != 1:
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
|
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, bold_pos_invoice))
|
||||||
if status == "Consolidated":
|
if status == "Consolidated":
|
||||||
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
|
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, bold_pos_invoice, bold_status))
|
||||||
if is_return and return_against not in [d.pos_invoice for d in self.pos_invoices] and status != "Consolidated":
|
if is_return and return_against and return_against not in [d.pos_invoice for d in self.pos_invoices]:
|
||||||
|
bold_return_against = frappe.bold(return_against)
|
||||||
|
return_against_status = frappe.db.get_value('POS Invoice', return_against, "status")
|
||||||
|
if return_against_status != "Consolidated":
|
||||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||||
frappe.throw(
|
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||||
_("Row #{}: Return Invoice {} cannot be made against unconsolidated invoice. \
|
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ")
|
||||||
You can add original invoice {} manually to proceed.")
|
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
||||||
.format(d.idx, frappe.bold(d.pos_invoice), frappe.bold(return_against))
|
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
||||||
)
|
msg += "<br><br>"
|
||||||
|
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
|
||||||
|
|||||||
@@ -17,18 +17,25 @@ class POSOpeningEntry(StatusUpdater):
|
|||||||
|
|
||||||
def validate_pos_profile_and_cashier(self):
|
def validate_pos_profile_and_cashier(self):
|
||||||
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
if self.company != frappe.db.get_value("POS Profile", self.pos_profile, "company"):
|
||||||
frappe.throw(_("POS Profile {} does not belongs to company {}".format(self.pos_profile, self.company)))
|
frappe.throw(_("POS Profile {} does not belongs to company {}").format(self.pos_profile, self.company))
|
||||||
|
|
||||||
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
if not cint(frappe.db.get_value("User", self.user, "enabled")):
|
||||||
frappe.throw(_("User {} has been disabled. Please select valid user/cashier".format(self.user)))
|
frappe.throw(_("User {} is disabled. Please select valid user/cashier").format(self.user))
|
||||||
|
|
||||||
def validate_payment_method_account(self):
|
def validate_payment_method_account(self):
|
||||||
|
invalid_modes = []
|
||||||
for d in self.balance_details:
|
for d in self.balance_details:
|
||||||
account = frappe.db.get_value("Mode of Payment Account",
|
account = frappe.db.get_value("Mode of Payment Account",
|
||||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
||||||
if not account:
|
if not account:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"default",
|
"default",
|
||||||
|
"allow_in_returns",
|
||||||
"mode_of_payment"
|
"mode_of_payment"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -24,11 +25,19 @@
|
|||||||
"label": "Mode of Payment",
|
"label": "Mode of Payment",
|
||||||
"options": "Mode of Payment",
|
"options": "Mode of Payment",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_in_returns",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Allow In Returns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-29 15:08:41.704844",
|
"modified": "2020-10-20 12:58:46.114456",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Payment Method",
|
"name": "POS Payment Method",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"column_break_9",
|
"column_break_9",
|
||||||
"update_stock",
|
"update_stock",
|
||||||
"ignore_pricing_rule",
|
"ignore_pricing_rule",
|
||||||
|
"hide_unavailable_items",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"campaign",
|
"campaign",
|
||||||
"company_address",
|
"company_address",
|
||||||
@@ -290,28 +291,36 @@
|
|||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"mandatory_depends_on": "update_stock",
|
|
||||||
"oldfieldname": "warehouse",
|
"oldfieldname": "warehouse",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse",
|
||||||
},
|
"reqd": 1
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "update_stock",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Update Stock"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "ignore_pricing_rule",
|
"fieldname": "ignore_pricing_rule",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Pricing Rule"
|
"label": "Ignore Pricing Rule"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "update_stock",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Update Stock",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "hide_unavailable_items",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Hide Unavailable Items"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-01 17:29:27.759088",
|
"modified": "2020-10-29 13:18:38.795925",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Profile",
|
"name": "POS Profile",
|
||||||
|
|||||||
@@ -56,19 +56,29 @@ class POSProfile(Document):
|
|||||||
if not self.payments:
|
if not self.payments:
|
||||||
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
frappe.throw(_("Payment methods are mandatory. Please add at least one payment method."))
|
||||||
|
|
||||||
default_mode_of_payment = [d.default for d in self.payments if d.default]
|
default_mode = [d.default for d in self.payments if d.default]
|
||||||
if not default_mode_of_payment:
|
if not default_mode:
|
||||||
frappe.throw(_("Please select a default mode of payment"))
|
frappe.throw(_("Please select a default mode of payment"))
|
||||||
|
|
||||||
if len(default_mode_of_payment) > 1:
|
if len(default_mode) > 1:
|
||||||
frappe.throw(_("You can only select one mode of payment as default"))
|
frappe.throw(_("You can only select one mode of payment as default"))
|
||||||
|
|
||||||
|
invalid_modes = []
|
||||||
for d in self.payments:
|
for d in self.payments:
|
||||||
account = frappe.db.get_value("Mode of Payment Account",
|
account = frappe.db.get_value(
|
||||||
{"parent": d.mode_of_payment, "company": self.company}, "default_account")
|
"Mode of Payment Account",
|
||||||
|
{"parent": d.mode_of_payment, "company": self.company},
|
||||||
|
"default_account"
|
||||||
|
)
|
||||||
if not account:
|
if not account:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||||
.format(get_link_to_form("Mode of Payment", mode_of_payment)), title=_("Missing Account"))
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
get_invoice_fields: function(frm) {
|
get_invoice_fields: function(frm) {
|
||||||
frappe.model.with_doctype("POS Invoice", () => {
|
frappe.model.with_doctype("POS Invoice", () => {
|
||||||
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
var fields = $.map(frappe.get_doc("DocType", "POS Invoice").fields, function(d) {
|
||||||
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
|
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 || ['Button'].includes(d.fieldtype)) {
|
||||||
['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;
|
||||||
|
|||||||
@@ -505,10 +505,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
"depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
||||||
"fieldname": "apply_discount_on_rate",
|
"fieldname": "apply_discount_on_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply Discount on Rate"
|
"label": "Apply Discount on Discounted Rate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -564,7 +564,7 @@
|
|||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-26 12:24:44.740734",
|
"modified": "2020-10-28 16:53:14.416172",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ class PricingRule(Document):
|
|||||||
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
|
||||||
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
|
||||||
|
|
||||||
|
if self.apply_discount_on_rate:
|
||||||
|
if not self.priority:
|
||||||
|
throw(_("As the field {0} is enabled, the field {1} is mandatory.")
|
||||||
|
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
|
||||||
|
|
||||||
|
if self.priority and cint(self.priority) == 1:
|
||||||
|
throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.")
|
||||||
|
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
|
||||||
|
|
||||||
def validate_applicable_for_selling_or_buying(self):
|
def validate_applicable_for_selling_or_buying(self):
|
||||||
if not self.selling and not self.buying:
|
if not self.selling and not self.buying:
|
||||||
throw(_("Atleast one of the Selling or Buying must be selected"))
|
throw(_("Atleast one of the Selling or Buying must be selected"))
|
||||||
@@ -226,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
|
|
||||||
item_details = frappe._dict({
|
item_details = frappe._dict({
|
||||||
"doctype": args.doctype,
|
"doctype": args.doctype,
|
||||||
|
"has_margin": False,
|
||||||
"name": args.name,
|
"name": args.name,
|
||||||
"parent": args.parent,
|
"parent": args.parent,
|
||||||
"parenttype": args.parenttype,
|
"parenttype": args.parenttype,
|
||||||
"child_docname": args.get('child_docname'),
|
"child_docname": args.get('child_docname')
|
||||||
"discount_percentage_on_rate": [],
|
|
||||||
"discount_amount_on_rate": []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if args.ignore_pricing_rule or not args.item_code:
|
if args.ignore_pricing_rule or not args.item_code:
|
||||||
@@ -279,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
else:
|
else:
|
||||||
get_product_discount_rule(pricing_rule, item_details, args, doc)
|
get_product_discount_rule(pricing_rule, item_details, args, doc)
|
||||||
|
|
||||||
|
if not item_details.get("has_margin"):
|
||||||
|
item_details.margin_type = None
|
||||||
|
item_details.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
item_details.has_pricing_rule = 1
|
item_details.has_pricing_rule = 1
|
||||||
|
|
||||||
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
|
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
|
||||||
@@ -330,13 +342,11 @@ def get_pricing_rule_details(args, pricing_rule):
|
|||||||
def apply_price_discount_rule(pricing_rule, item_details, args):
|
def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||||
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
item_details.pricing_rule_for = pricing_rule.rate_or_discount
|
||||||
|
|
||||||
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency)
|
if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
|
||||||
or (pricing_rule.margin_type == 'Percentage')):
|
or (pricing_rule.margin_type == 'Percentage')):
|
||||||
item_details.margin_type = pricing_rule.margin_type
|
item_details.margin_type = pricing_rule.margin_type
|
||||||
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||||
else:
|
item_details.has_margin = True
|
||||||
item_details.margin_type = None
|
|
||||||
item_details.margin_rate_or_amount = 0.0
|
|
||||||
|
|
||||||
if pricing_rule.rate_or_discount == 'Rate':
|
if pricing_rule.rate_or_discount == 'Rate':
|
||||||
pricing_rule_rate = 0.0
|
pricing_rule_rate = 0.0
|
||||||
@@ -351,9 +361,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
if pricing_rule.rate_or_discount != apply_on: continue
|
if pricing_rule.rate_or_discount != apply_on: continue
|
||||||
|
|
||||||
field = frappe.scrub(apply_on)
|
field = frappe.scrub(apply_on)
|
||||||
if pricing_rule.apply_discount_on_rate:
|
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
|
||||||
discount_field = "{0}_on_rate".format(field)
|
# Apply discount on discounted rate
|
||||||
item_details[discount_field].append(pricing_rule.get(field, 0))
|
item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
|
||||||
else:
|
else:
|
||||||
if field not in item_details:
|
if field not in item_details:
|
||||||
item_details.setdefault(field, 0)
|
item_details.setdefault(field, 0)
|
||||||
@@ -361,14 +371,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
|||||||
item_details[field] += (pricing_rule.get(field, 0)
|
item_details[field] += (pricing_rule.get(field, 0)
|
||||||
if pricing_rule else args.get(field, 0))
|
if pricing_rule else args.get(field, 0))
|
||||||
|
|
||||||
def set_discount_amount(rate, item_details):
|
|
||||||
for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']:
|
|
||||||
for d in item_details.get(field):
|
|
||||||
dis_amount = (rate * d / 100
|
|
||||||
if field == 'discount_percentage_on_rate' else d)
|
|
||||||
rate -= dis_amount
|
|
||||||
item_details.rate = rate
|
|
||||||
|
|
||||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
||||||
get_pricing_rule_items)
|
get_pricing_rule_items)
|
||||||
|
|||||||
@@ -457,6 +457,33 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
item = si.items[0]
|
item = si.items[0]
|
||||||
self.assertEquals(item.rate, 900)
|
self.assertEquals(item.rate, 900)
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules(self):
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||||
|
title="_Test Pricing Rule 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
|
||||||
|
apply_multiple_pricing_rules=1)
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
self.assertEqual(si.items[0].discount_percentage, 30)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
|
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
|
||||||
|
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||||
|
title="_Test Pricing Rule 1")
|
||||||
|
make_pricing_rule(discount_percentage=10, selling=1, priority=2,
|
||||||
|
apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
self.assertEqual(si.items[0].discount_percentage, 28)
|
||||||
|
si.delete()
|
||||||
|
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@@ -468,6 +495,7 @@ def make_pricing_rule(**args):
|
|||||||
"applicable_for": args.applicable_for,
|
"applicable_for": args.applicable_for,
|
||||||
"selling": args.selling or 0,
|
"selling": args.selling or 0,
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
|
||||||
"buying": args.buying or 0,
|
"buying": args.buying or 0,
|
||||||
"min_qty": args.min_qty or 0.0,
|
"min_qty": args.min_qty or 0.0,
|
||||||
"max_qty": args.max_qty or 0.0,
|
"max_qty": args.max_qty or 0.0,
|
||||||
@@ -476,9 +504,13 @@ def make_pricing_rule(**args):
|
|||||||
"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 ''
|
"condition": args.condition or '',
|
||||||
|
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if args.get("priority"):
|
||||||
|
doc.priority = args.get("priority")
|
||||||
|
|
||||||
apply_on = doc.apply_on.replace(' ', '_').lower()
|
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||||
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
||||||
doc.append(child_table.get(doc.apply_on), {
|
doc.append(child_table.get(doc.apply_on), {
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ import frappe
|
|||||||
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
||||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
from frappe import _, throw
|
from frappe import _, bold
|
||||||
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
|
from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
|
||||||
|
|
||||||
|
|
||||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||||
|
|
||||||
@@ -42,6 +41,7 @@ def get_pricing_rules(args, doc=None):
|
|||||||
if not pricing_rules: return []
|
if not pricing_rules: return []
|
||||||
|
|
||||||
if apply_multiple_pricing_rules(pricing_rules):
|
if apply_multiple_pricing_rules(pricing_rules):
|
||||||
|
pricing_rules = sorted_by_priority(pricing_rules)
|
||||||
for pricing_rule in pricing_rules:
|
for pricing_rule in pricing_rules:
|
||||||
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
|
||||||
if pricing_rule:
|
if pricing_rule:
|
||||||
@@ -53,6 +53,20 @@ def get_pricing_rules(args, doc=None):
|
|||||||
|
|
||||||
return rules
|
return rules
|
||||||
|
|
||||||
|
def sorted_by_priority(pricing_rules):
|
||||||
|
# If more than one pricing rules, then sort by priority
|
||||||
|
pricing_rules_list = []
|
||||||
|
pricing_rule_dict = {}
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
if not pricing_rule.get("priority"): continue
|
||||||
|
|
||||||
|
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
|
||||||
|
|
||||||
|
for key in sorted(pricing_rule_dict):
|
||||||
|
pricing_rules_list.append(pricing_rule_dict.get(key))
|
||||||
|
|
||||||
|
return pricing_rules_list or pricing_rules
|
||||||
|
|
||||||
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
|
||||||
filtered_pricing_rules = []
|
filtered_pricing_rules = []
|
||||||
if doc:
|
if doc:
|
||||||
@@ -284,12 +298,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
|
|||||||
fieldname = field
|
fieldname = field
|
||||||
|
|
||||||
if fieldname:
|
if fieldname:
|
||||||
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b>
|
msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
|
||||||
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
|
.format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
|
||||||
|
|
||||||
if fieldname in ['min_amt', 'max_amt']:
|
if fieldname in ['min_amt', 'max_amt']:
|
||||||
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item.
|
msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
|
||||||
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
|
.format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
|
||||||
|
bold(item_code), bold(args.rule_description)))
|
||||||
|
|
||||||
frappe.msgprint(msg)
|
frappe.msgprint(msg)
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frm.refresh_field('customers');
|
frm.refresh_field('customers');
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.msgprint('No Customers found with selected options.');
|
frappe.throw('No Customers found with selected options.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,9 +126,11 @@ def get_customers_based_on_sales_person(sales_person):
|
|||||||
sales_person_records = frappe._dict()
|
sales_person_records = frappe._dict()
|
||||||
for d in records:
|
for d in records:
|
||||||
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
if sales_person_records.get('Customer'):
|
||||||
|
return frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||||
filters=[['name', 'in', list(sales_person_records['Customer'])]])
|
filters=[['name', 'in', list(sales_person_records['Customer'])]])
|
||||||
return customers
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
def get_recipients_and_cc(customer, doc):
|
def get_recipients_and_cc(customer, doc):
|
||||||
recipients = []
|
recipients = []
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -151,14 +151,16 @@ class PurchaseInvoice(BuyingController):
|
|||||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||||
|
|
||||||
if account.report_type != "Balance Sheet":
|
if account.report_type != "Balance Sheet":
|
||||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
frappe.throw(
|
||||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
_("Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account.")
|
||||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||||
|
)
|
||||||
|
|
||||||
if self.supplier and account.account_type != "Payable":
|
if self.supplier and account.account_type != "Payable":
|
||||||
frappe.throw(_("Please ensure {} account is a Payable account. \
|
frappe.throw(
|
||||||
Change the account type to Payable or select a different account.")
|
_("Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.")
|
||||||
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
|
.format(frappe.bold("Credit To")), title=_("Invalid Account")
|
||||||
|
)
|
||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
@@ -244,10 +246,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if self.update_stock and (not item.from_warehouse):
|
if self.update_stock and (not item.from_warehouse):
|
||||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because account {2}
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
||||||
is not linked to warehouse {3} or it is not the default inventory account'''.format(
|
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]),
|
msg += _("or it is not the default inventory account")
|
||||||
frappe.bold(item.expense_account), frappe.bold(item.warehouse))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||||
else:
|
else:
|
||||||
@@ -259,19 +261,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if negative_expense_booked_in_pr:
|
if negative_expense_booked_in_pr:
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} because
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||||
expense is booked against this account in Purchase Receipt {2}'''.format(
|
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
||||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
else:
|
else:
|
||||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
frappe.msgprint(_('''Row {0}: Expense Head changed to {1} as no Purchase
|
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||||
Receipt is created against Item {2}. This is done to handle accounting for cases
|
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
||||||
when Purchase Receipt is created after Purchase Invoice'''.format(
|
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
|
|
||||||
@@ -299,10 +301,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.purchase_order:
|
if not d.purchase_order:
|
||||||
throw(_("""Purchase Order Required for item {0}
|
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||||
To submit the invoice without purchase order please set
|
msg += "<br><br>"
|
||||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Order Required')),
|
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
||||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
|
throw(msg, title=_("Mandatory Purchase Order"))
|
||||||
|
|
||||||
def pr_required(self):
|
def pr_required(self):
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
@@ -313,10 +316,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if not d.purchase_receipt and d.item_code in stock_items:
|
if not d.purchase_receipt and d.item_code in stock_items:
|
||||||
throw(_("""Purchase Receipt Required for item {0}
|
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||||
To submit the invoice without purchase receipt please set
|
msg += "<br><br>"
|
||||||
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Receipt Required')),
|
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
||||||
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
|
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
|
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||||
|
|
||||||
def validate_write_off_account(self):
|
def validate_write_off_account(self):
|
||||||
if self.write_off_amount and not self.write_off_account:
|
if self.write_off_amount and not self.write_off_account:
|
||||||
|
|||||||
@@ -479,14 +479,14 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||||
|
|
||||||
if account.report_type != "Balance Sheet":
|
if account.report_type != "Balance Sheet":
|
||||||
frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
|
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||||
You can change the parent account to a Balance Sheet account or select a different account.")
|
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
frappe.throw(msg, title=_("Invalid Account"))
|
||||||
|
|
||||||
if self.customer and account.account_type != "Receivable":
|
if self.customer and account.account_type != "Receivable":
|
||||||
frappe.throw(_("Please ensure {} account is a Receivable account. \
|
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||||
Change the account type to Receivable or select a different account.")
|
msg += _("Change the account type to Receivable or select a different account.")
|
||||||
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
|
frappe.throw(msg, title=_("Invalid Account"))
|
||||||
|
|
||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
@@ -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"))
|
||||||
|
|
||||||
@@ -1140,8 +1141,10 @@ class SalesInvoice(SellingController):
|
|||||||
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
where redeem_against=%s''', (lp_entry[0].name), as_dict=1)
|
||||||
if against_lp_entry:
|
if against_lp_entry:
|
||||||
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
invoice_list = ", ".join([d.invoice for d in against_lp_entry])
|
||||||
frappe.throw(_('''{} can't be cancelled since the Loyalty Points earned has been redeemed.
|
frappe.throw(
|
||||||
First cancel the {} No {}''').format(self.doctype, self.doctype, invoice_list))
|
_('''{} can't be cancelled since the Loyalty Points earned has been redeemed. First cancel the {} No {}''')
|
||||||
|
.format(self.doctype, self.doctype, invoice_list)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
frappe.db.sql('''delete from `tabLoyalty Point Entry` where invoice=%s''', (self.name))
|
||||||
# Set loyalty program
|
# Set loyalty program
|
||||||
@@ -1612,17 +1615,25 @@ def update_multi_mode_option(doc, pos_profile):
|
|||||||
payment.type = payment_mode.type
|
payment.type = payment_mode.type
|
||||||
|
|
||||||
doc.set('payments', [])
|
doc.set('payments', [])
|
||||||
|
invalid_modes = []
|
||||||
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 not payment_mode:
|
if not payment_mode:
|
||||||
frappe.throw(_("Please set default Cash or Bank account in Mode of Payment {0}")
|
invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment))
|
||||||
.format(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)), title=_("Missing Account"))
|
continue
|
||||||
|
|
||||||
payment_mode[0].default = pos_payment_method.default
|
payment_mode[0].default = pos_payment_method.default
|
||||||
append_payment(payment_mode[0])
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
|
if invalid_modes:
|
||||||
|
if invalid_modes == 1:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payment {}")
|
||||||
|
else:
|
||||||
|
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||||
|
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||||
|
|
||||||
def get_all_mode_of_payments(doc):
|
def get_all_mode_of_payments(doc):
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select mpa.default_account, mpa.parent, mp.type as type
|
select mpa.default_account, mpa.parent, mp.type as type
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ class TestSubscription(unittest.TestCase):
|
|||||||
subscription.party_type = 'Customer'
|
subscription.party_type = 'Customer'
|
||||||
subscription.party = '_Test Customer'
|
subscription.party = '_Test Customer'
|
||||||
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
||||||
subscription.start_date = '2018-01-01'
|
subscription.start_date = add_days(nowdate(), -1000)
|
||||||
subscription.insert()
|
subscription.insert()
|
||||||
subscription.process() # generate first invoice
|
subscription.process() # generate first invoice
|
||||||
|
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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,32 +82,12 @@ 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
|
|
||||||
'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
|
|
||||||
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 = %s
|
|
||||||
AND
|
|
||||||
jea.debit_in_account_currency like %s
|
|
||||||
AND
|
|
||||||
je.docstatus = 1
|
|
||||||
""", (bank_account, amount), as_dict=True)
|
|
||||||
else:
|
|
||||||
journal_entries = frappe.db.sql("""
|
|
||||||
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,
|
||||||
jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
|
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
|
jea.{jea_side}_in_account_currency as paid_amount
|
||||||
FROM
|
FROM
|
||||||
`tabJournal Entry Account` as jea
|
`tabJournal Entry Account` as jea
|
||||||
JOIN
|
JOIN
|
||||||
@@ -120,7 +99,7 @@ def check_matching_amount(bank_account, company, transaction):
|
|||||||
AND
|
AND
|
||||||
jea.account = %(bank_account)s
|
jea.account = %(bank_account)s
|
||||||
AND
|
AND
|
||||||
jea.credit_in_account_currency like %(txt)s
|
jea.{jea_side}_in_account_currency like %(txt)s
|
||||||
AND
|
AND
|
||||||
je.docstatus = 1
|
je.docstatus = 1
|
||||||
""", {
|
""", {
|
||||||
@@ -264,7 +243,11 @@ def check_amount_vs_description(amount_matching, description_matching):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if "reference_no" in am_match and "reference_no" in des_match:
|
if "reference_no" in am_match and "reference_no" in des_match:
|
||||||
if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70:
|
# Sequence Matcher does not handle None as input
|
||||||
|
am_reference = am_match["reference_no"] or ""
|
||||||
|
des_reference = des_match["reference_no"] or ""
|
||||||
|
|
||||||
|
if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70:
|
||||||
if am_match not in result:
|
if am_match not in result:
|
||||||
result.append(am_match)
|
result.append(am_match)
|
||||||
if result:
|
if result:
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<table>
|
<table>
|
||||||
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
|
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<table>
|
<table>
|
||||||
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
|
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
|
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
|
||||||
|
|
||||||
{%- for label, value in (
|
{%- for label, value in (
|
||||||
(_("Received On"), frappe.utils.formatdate(doc.voucher_date)),
|
(_("Received On"), frappe.utils.format_date(doc.voucher_date)),
|
||||||
(_("Received From"), doc.pay_to_recd_from),
|
(_("Received From"), doc.pay_to_recd_from),
|
||||||
(_("Amount"), "<strong>" + doc.get_formatted("total_amount") + "</strong><br>" + (doc.total_amount_in_words or "") + "<br>"),
|
(_("Amount"), "<strong>" + doc.get_formatted("total_amount") + "</strong><br>" + (doc.total_amount_in_words or "") + "<br>"),
|
||||||
(_("Remarks"), doc.remark)
|
(_("Remarks"), doc.remark)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<table>
|
<table>
|
||||||
<tr><td><strong>Supplier Name: </strong></td><td>{{ doc.supplier }}</td></tr>
|
<tr><td><strong>Supplier Name: </strong></td><td>{{ doc.supplier }}</td></tr>
|
||||||
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.formatdate(doc.due_date) }}</td></tr>
|
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.format_date(doc.due_date) }}</td></tr>
|
||||||
<tr><td><strong>Address: </strong></td><td>{{doc.address_display}}</td></tr>
|
<tr><td><strong>Address: </strong></td><td>{{doc.address_display}}</td></tr>
|
||||||
<tr><td><strong>Contact: </strong></td><td>{{doc.contact_display}}</td></tr>
|
<tr><td><strong>Contact: </strong></td><td>{{doc.contact_display}}</td></tr>
|
||||||
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
|
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<table>
|
<table>
|
||||||
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
|
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
|
||||||
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
|
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<table>
|
<table>
|
||||||
<tr><td><strong>Customer Name: </strong></td><td>{{ doc.customer }}</td></tr>
|
<tr><td><strong>Customer Name: </strong></td><td>{{ doc.customer }}</td></tr>
|
||||||
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.formatdate(doc.due_date) }}</td></tr>
|
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.format_date(doc.due_date) }}</td></tr>
|
||||||
<tr><td><strong>Address: </strong></td><td>{{doc.address_display}}</td></tr>
|
<tr><td><strong>Address: </strong></td><td>{{doc.address_display}}</td></tr>
|
||||||
<tr><td><strong>Contact: </strong></td><td>{{doc.contact_display}}</td></tr>
|
<tr><td><strong>Contact: </strong></td><td>{{doc.contact_display}}</td></tr>
|
||||||
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
|
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<table>
|
<table>
|
||||||
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
|
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
|
||||||
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
|
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.format_date(doc.creation) }}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ def get_accounts(company, root_type):
|
|||||||
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
|
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
def filter_accounts(accounts, depth=10):
|
def filter_accounts(accounts, depth=20):
|
||||||
parent_children_map = {}
|
parent_children_map = {}
|
||||||
accounts_by_name = {}
|
accounts_by_name = {}
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ def get_pos_entries(filters, group_by_field):
|
|||||||
FROM
|
FROM
|
||||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||||
WHERE
|
WHERE
|
||||||
|
p.docstatus = 1 and
|
||||||
{group_by_mop_condition}
|
{group_by_mop_condition}
|
||||||
{conditions}
|
{conditions}
|
||||||
ORDER BY
|
ORDER BY
|
||||||
|
|||||||
@@ -796,7 +796,7 @@ def get_children(doctype, parent, company, is_root=False):
|
|||||||
|
|
||||||
return acc
|
return acc
|
||||||
|
|
||||||
def create_payment_gateway_account(gateway):
|
def create_payment_gateway_account(gateway, payment_channel="Email"):
|
||||||
from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account
|
from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account
|
||||||
|
|
||||||
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
||||||
@@ -831,7 +831,8 @@ def create_payment_gateway_account(gateway):
|
|||||||
"is_default": 1,
|
"is_default": 1,
|
||||||
"payment_gateway": gateway,
|
"payment_gateway": gateway,
|
||||||
"payment_account": bank_account.name,
|
"payment_account": bank_account.name,
|
||||||
"currency": bank_account.account_currency
|
"currency": bank_account.account_currency,
|
||||||
|
"payment_channel": payment_channel
|
||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
|
|||||||
@@ -50,12 +50,11 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:parent.doctype == 'Asset'",
|
|
||||||
"fieldname": "depreciation_start_date",
|
"fieldname": "depreciation_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Depreciation Posting Date",
|
"label": "Depreciation Posting Date",
|
||||||
"reqd": 1
|
"mandatory_depends_on": "eval:parent.doctype == 'Asset'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -86,7 +85,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-16 12:11:30.631788",
|
"modified": "2020-11-05 16:30:09.213479",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
|||||||
@@ -46,26 +46,26 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "po_required",
|
"fieldname": "po_required",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Purchase Order Required for Purchase Invoice & Receipt Creation",
|
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
|
||||||
"options": "No\nYes"
|
"options": "No\nYes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "pr_required",
|
"fieldname": "pr_required",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Purchase Receipt Required for Purchase Invoice Creation",
|
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
|
||||||
"options": "No\nYes"
|
"options": "No\nYes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "maintain_same_rate",
|
"fieldname": "maintain_same_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Maintain same rate throughout purchase cycle"
|
"label": "Maintain Same Rate Throughout the Purchase Cycle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "allow_multiple_items",
|
"fieldname": "allow_multiple_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Item to be added multiple times in a transaction"
|
"label": "Allow Item To Be Added Multiple Times in a Transaction"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "subcontract",
|
"fieldname": "subcontract",
|
||||||
@@ -93,9 +93,10 @@
|
|||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-15 14:49:32.513611",
|
"modified": "2020-10-13 12:00:23.276329",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -203,9 +203,39 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
def test_update_child_with_tax_template(self):
|
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"
|
tax_template = "_Test Account Excise Duty @ 10"
|
||||||
item = "_Test Item Home Desktop 100"
|
item = "_Test Item Home Desktop 100"
|
||||||
|
|
||||||
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}):
|
||||||
item_doc = frappe.get_doc("Item", item)
|
item_doc = frappe.get_doc("Item", item)
|
||||||
item_doc.append("taxes", {
|
item_doc.append("taxes", {
|
||||||
@@ -237,17 +267,25 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
items = json.dumps([
|
items = json.dumps([
|
||||||
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
|
{'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name},
|
||||||
{'item_code' : item, 'rate' : 100, 'qty' : 1} # added item
|
{'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)
|
update_child_qty_rate('Purchase Order', items, po.name)
|
||||||
|
|
||||||
po.reload()
|
po.reload()
|
||||||
self.assertEqual(po.taxes[0].tax_amount, 60)
|
self.assertEqual(po.taxes[0].tax_amount, 70)
|
||||||
self.assertEqual(po.taxes[0].total, 660)
|
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
|
frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL
|
||||||
where parent = %(item)s and item_tax_template = %(tax)s""",
|
where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template})
|
||||||
{"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):
|
def test_update_child_uom_conv_factor_change(self):
|
||||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
||||||
@@ -651,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)
|
||||||
|
|
||||||
@@ -664,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"))
|
||||||
}
|
}
|
||||||
@@ -31,14 +29,12 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
|
|
||||||
refresh: function(frm, cdt, cdn) {
|
refresh: function(frm, cdt, cdn) {
|
||||||
if (frm.doc.docstatus === 1) {
|
if (frm.doc.docstatus === 1) {
|
||||||
frm.add_custom_button(__('Create'),
|
|
||||||
function(){ frm.trigger("make_suppplier_quotation") }, __("Supplier Quotation"));
|
|
||||||
|
|
||||||
frm.add_custom_button(__("View"),
|
frm.add_custom_button(__('Supplier Quotation'),
|
||||||
function(){ frappe.set_route('List', 'Supplier Quotation',
|
function(){ frm.trigger("make_suppplier_quotation") }, __("Create"));
|
||||||
{'request_for_quotation': frm.doc.name}) }, __("Supplier Quotation"));
|
|
||||||
|
|
||||||
frm.add_custom_button(__("Send Supplier Emails"), function() {
|
|
||||||
|
frm.add_custom_button(__("Send Emails to Suppliers"), function() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.send_supplier_emails',
|
method: 'erpnext.buying.doctype.request_for_quotation.request_for_quotation.send_supplier_emails',
|
||||||
freeze: true,
|
freeze: true,
|
||||||
@@ -49,11 +45,257 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}, __("Tools"));
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Download PDF'), () => {
|
||||||
|
var suppliers = [];
|
||||||
|
const fields = [{
|
||||||
|
fieldtype: 'Link',
|
||||||
|
label: __('Select a Supplier'),
|
||||||
|
fieldname: 'supplier',
|
||||||
|
options: 'Supplier',
|
||||||
|
reqd: 1,
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
filters: [
|
||||||
|
["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
frappe.prompt(fields, data => {
|
||||||
|
var child = locals[cdt][cdn]
|
||||||
|
|
||||||
|
var w = window.open(
|
||||||
|
frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
|
||||||
|
+"doctype="+encodeURIComponent(frm.doc.doctype)
|
||||||
|
+"&name="+encodeURIComponent(frm.doc.name)
|
||||||
|
+"&supplier="+encodeURIComponent(data.supplier)
|
||||||
|
+"&no_letterhead=0"));
|
||||||
|
if(!w) {
|
||||||
|
frappe.msgprint(__("Please enable pop-ups")); return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Download PDF for Supplier',
|
||||||
|
'Download');
|
||||||
|
},
|
||||||
|
__("Tools"));
|
||||||
|
|
||||||
|
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
make_suppplier_quotation: function(frm) {
|
||||||
|
var doc = frm.doc;
|
||||||
|
var dialog = new frappe.ui.Dialog({
|
||||||
|
title: __("Create Supplier Quotation"),
|
||||||
|
fields: [
|
||||||
|
{ "fieldtype": "Select", "label": __("Supplier"),
|
||||||
|
"fieldname": "supplier",
|
||||||
|
"options": doc.suppliers.map(d => d.supplier),
|
||||||
|
"reqd": 1,
|
||||||
|
"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
|
||||||
|
],
|
||||||
|
primary_action_label: __("Create"),
|
||||||
|
primary_action: (args) => {
|
||||||
|
if(!args) return;
|
||||||
|
dialog.hide();
|
||||||
|
|
||||||
|
return frappe.call({
|
||||||
|
type: "GET",
|
||||||
|
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||||
|
args: {
|
||||||
|
"source_name": doc.name,
|
||||||
|
"for_supplier": args.supplier
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc) {
|
||||||
|
var doc = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.ui.form.on("Request for Quotation Supplier",{
|
||||||
|
supplier: function(frm, cdt, cdn) {
|
||||||
|
var d = locals[cdt][cdn]
|
||||||
|
frappe.call({
|
||||||
|
method:"erpnext.accounts.party.get_party_details",
|
||||||
|
args:{
|
||||||
|
party: d.supplier,
|
||||||
|
party_type: 'Supplier'
|
||||||
|
},
|
||||||
|
callback: function(r){
|
||||||
|
if(r.message){
|
||||||
|
frappe.model.set_value(cdt, cdn, 'contact', r.message.contact_person)
|
||||||
|
frappe.model.set_value(cdt, cdn, 'email_id', r.message.contact_email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
|
||||||
|
refresh: function() {
|
||||||
|
var me = this;
|
||||||
|
this._super();
|
||||||
|
if (this.frm.doc.docstatus===0) {
|
||||||
|
this.frm.add_custom_button(__('Material Request'),
|
||||||
|
function() {
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: "erpnext.stock.doctype.material_request.material_request.make_request_for_quotation",
|
||||||
|
source_doctype: "Material Request",
|
||||||
|
target: me.frm,
|
||||||
|
setters: {
|
||||||
|
company: me.frm.doc.company
|
||||||
|
},
|
||||||
|
get_query_filters: {
|
||||||
|
material_request_type: "Purchase",
|
||||||
|
docstatus: 1,
|
||||||
|
status: ["!=", "Stopped"],
|
||||||
|
per_ordered: ["<", 99.99]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, __("Get Items From"));
|
||||||
|
|
||||||
|
// Get items from Opportunity
|
||||||
|
this.frm.add_custom_button(__('Opportunity'),
|
||||||
|
function() {
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
|
||||||
|
source_doctype: "Opportunity",
|
||||||
|
target: me.frm,
|
||||||
|
setters: {
|
||||||
|
company: me.frm.doc.company
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, __("Get Items From"));
|
||||||
|
|
||||||
|
// Get items from open Material Requests based on supplier
|
||||||
|
this.frm.add_custom_button(__('Possible Supplier'), function() {
|
||||||
|
// Create a dialog window for the user to pick their supplier
|
||||||
|
var d = new frappe.ui.Dialog({
|
||||||
|
title: __('Select Possible Supplier'),
|
||||||
|
fields: [
|
||||||
|
{fieldname: 'supplier', fieldtype:'Link', options:'Supplier', label:'Supplier', reqd:1},
|
||||||
|
{fieldname: 'ok_button', fieldtype:'Button', label:'Get Items from Material Requests'},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// On the user clicking the ok button
|
||||||
|
d.fields_dict.ok_button.input.onclick = function() {
|
||||||
|
var btn = d.fields_dict.ok_button.input;
|
||||||
|
var v = d.get_values();
|
||||||
|
if(v) {
|
||||||
|
$(btn).set_working();
|
||||||
|
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_item_from_material_requests_based_on_supplier",
|
||||||
|
source_name: v.supplier,
|
||||||
|
target: me.frm,
|
||||||
|
setters: {
|
||||||
|
company: me.frm.doc.company
|
||||||
|
},
|
||||||
|
get_query_filters: {
|
||||||
|
material_request_type: "Purchase",
|
||||||
|
docstatus: 1,
|
||||||
|
status: ["!=", "Stopped"],
|
||||||
|
per_ordered: ["<", 99.99]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$(btn).done_working();
|
||||||
|
d.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.show();
|
||||||
|
}, __("Get Items From"));
|
||||||
|
|
||||||
|
// Get Suppliers
|
||||||
|
this.frm.add_custom_button(__('Get Suppliers'),
|
||||||
|
function() {
|
||||||
|
me.get_suppliers_button(me.frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
calculate_taxes_and_totals: function() {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
tc_name: function() {
|
||||||
|
this.get_terms();
|
||||||
|
},
|
||||||
|
|
||||||
get_suppliers_button: function (frm) {
|
get_suppliers_button: function (frm) {
|
||||||
var doc = frm.doc;
|
var doc = frm.doc;
|
||||||
var dialog = new frappe.ui.Dialog({
|
var dialog = new frappe.ui.Dialog({
|
||||||
@@ -62,7 +304,7 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
{
|
{
|
||||||
"fieldtype": "Select", "label": __("Get Suppliers By"),
|
"fieldtype": "Select", "label": __("Get Suppliers By"),
|
||||||
"fieldname": "search_type",
|
"fieldname": "search_type",
|
||||||
"options": ["Tag","Supplier Group"],
|
"options": ["Supplier Group", "Tag"],
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
onchange() {
|
onchange() {
|
||||||
if(dialog.get_value('search_type') == 'Tag'){
|
if(dialog.get_value('search_type') == 'Tag'){
|
||||||
@@ -86,16 +328,10 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
"fieldname": "tag",
|
"fieldname": "tag",
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"depends_on": "eval:doc.search_type == 'Tag'",
|
"depends_on": "eval:doc.search_type == 'Tag'",
|
||||||
},
|
}
|
||||||
{
|
],
|
||||||
"fieldtype": "Button", "label": __("Add All Suppliers"),
|
primary_action_label: __("Add Suppliers"),
|
||||||
"fieldname": "add_suppliers"
|
primary_action : (args) => {
|
||||||
},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.fields_dict.add_suppliers.$input.click(function() {
|
|
||||||
var args = dialog.get_values();
|
|
||||||
if(!args) return;
|
if(!args) return;
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
|
|
||||||
@@ -154,190 +390,12 @@ frappe.ui.form.on("Request for Quotation",{
|
|||||||
callback: load_suppliers
|
callback: load_suppliers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
},
|
},
|
||||||
make_suppplier_quotation: function(frm) {
|
|
||||||
var doc = frm.doc;
|
|
||||||
var dialog = new frappe.ui.Dialog({
|
|
||||||
title: __("For Supplier"),
|
|
||||||
fields: [
|
|
||||||
{ "fieldtype": "Select", "label": __("Supplier"),
|
|
||||||
"fieldname": "supplier",
|
|
||||||
"options": doc.suppliers.map(d => d.supplier),
|
|
||||||
"reqd": 1,
|
|
||||||
"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
|
|
||||||
{ "fieldtype": "Button", "label": __('Create Supplier Quotation'),
|
|
||||||
"fieldname": "make_supplier_quotation", "cssClass": "btn-primary" },
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.fields_dict.make_supplier_quotation.$input.click(function() {
|
|
||||||
var args = dialog.get_values();
|
|
||||||
if(!args) return;
|
|
||||||
dialog.hide();
|
|
||||||
return frappe.call({
|
|
||||||
type: "GET",
|
|
||||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
|
||||||
args: {
|
|
||||||
"source_name": doc.name,
|
|
||||||
"for_supplier": args.supplier
|
|
||||||
},
|
|
||||||
freeze: true,
|
|
||||||
callback: function(r) {
|
|
||||||
if(!r.exc) {
|
|
||||||
var doc = frappe.model.sync(r.message);
|
|
||||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
dialog.show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frappe.ui.form.on("Request for Quotation Supplier",{
|
|
||||||
supplier: function(frm, cdt, cdn) {
|
|
||||||
var d = locals[cdt][cdn]
|
|
||||||
frappe.call({
|
|
||||||
method:"erpnext.accounts.party.get_party_details",
|
|
||||||
args:{
|
|
||||||
party: d.supplier,
|
|
||||||
party_type: 'Supplier'
|
|
||||||
},
|
|
||||||
callback: function(r){
|
|
||||||
if(r.message){
|
|
||||||
frappe.model.set_value(cdt, cdn, 'contact', r.message.contact_person)
|
|
||||||
frappe.model.set_value(cdt, cdn, 'email_id', r.message.contact_email)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
download_pdf: function(frm, cdt, cdn) {
|
|
||||||
var child = locals[cdt][cdn]
|
|
||||||
|
|
||||||
var w = window.open(
|
|
||||||
frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?"
|
|
||||||
+"doctype="+encodeURIComponent(frm.doc.doctype)
|
|
||||||
+"&name="+encodeURIComponent(frm.doc.name)
|
|
||||||
+"&supplier_idx="+encodeURIComponent(child.idx)
|
|
||||||
+"&no_letterhead=0"));
|
|
||||||
if(!w) {
|
|
||||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
no_quote: function(frm, cdt, cdn) {
|
|
||||||
var d = locals[cdt][cdn];
|
|
||||||
if (d.no_quote) {
|
|
||||||
if (d.quote_status != __('Received')) {
|
|
||||||
frappe.model.set_value(cdt, cdn, 'quote_status', 'No Quote');
|
|
||||||
} else {
|
|
||||||
frappe.msgprint(__("Cannot set a received RFQ to No Quote"));
|
|
||||||
frappe.model.set_value(cdt, cdn, 'no_quote', 0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d.quote_status = __('Pending');
|
|
||||||
frm.call({
|
|
||||||
method:"update_rfq_supplier_status",
|
|
||||||
doc: frm.doc,
|
|
||||||
args: {
|
|
||||||
sup_name: d.supplier
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
|
||||||
frm.refresh_field("suppliers");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
|
|
||||||
refresh: function() {
|
|
||||||
var me = this;
|
|
||||||
this._super();
|
|
||||||
if (this.frm.doc.docstatus===0) {
|
|
||||||
this.frm.add_custom_button(__('Material Request'),
|
|
||||||
function() {
|
|
||||||
erpnext.utils.map_current_doc({
|
|
||||||
method: "erpnext.stock.doctype.material_request.material_request.make_request_for_quotation",
|
|
||||||
source_doctype: "Material Request",
|
|
||||||
target: me.frm,
|
|
||||||
setters: {
|
|
||||||
company: me.frm.doc.company
|
|
||||||
},
|
|
||||||
get_query_filters: {
|
|
||||||
material_request_type: "Purchase",
|
|
||||||
docstatus: 1,
|
|
||||||
status: ["!=", "Stopped"],
|
|
||||||
per_ordered: ["<", 99.99]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, __("Get items from"));
|
|
||||||
// Get items from Opportunity
|
|
||||||
this.frm.add_custom_button(__('Opportunity'),
|
|
||||||
function() {
|
|
||||||
erpnext.utils.map_current_doc({
|
|
||||||
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
|
|
||||||
source_doctype: "Opportunity",
|
|
||||||
target: me.frm,
|
|
||||||
setters: {
|
|
||||||
company: me.frm.doc.company
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}, __("Get items from"));
|
|
||||||
// Get items from open Material Requests based on supplier
|
|
||||||
this.frm.add_custom_button(__('Possible Supplier'), function() {
|
|
||||||
// Create a dialog window for the user to pick their supplier
|
|
||||||
var d = new frappe.ui.Dialog({
|
|
||||||
title: __('Select Possible Supplier'),
|
|
||||||
fields: [
|
|
||||||
{fieldname: 'supplier', fieldtype:'Link', options:'Supplier', label:'Supplier', reqd:1},
|
|
||||||
{fieldname: 'ok_button', fieldtype:'Button', label:'Get Items from Material Requests'},
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// On the user clicking the ok button
|
|
||||||
d.fields_dict.ok_button.input.onclick = function() {
|
|
||||||
var btn = d.fields_dict.ok_button.input;
|
|
||||||
var v = d.get_values();
|
|
||||||
if(v) {
|
|
||||||
$(btn).set_working();
|
|
||||||
|
|
||||||
erpnext.utils.map_current_doc({
|
|
||||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_item_from_material_requests_based_on_supplier",
|
|
||||||
source_name: v.supplier,
|
|
||||||
target: me.frm,
|
|
||||||
setters: {
|
|
||||||
company: me.frm.doc.company
|
|
||||||
},
|
|
||||||
get_query_filters: {
|
|
||||||
material_request_type: "Purchase",
|
|
||||||
docstatus: 1,
|
|
||||||
status: ["!=", "Stopped"],
|
|
||||||
per_ordered: ["<", 99.99]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$(btn).done_working();
|
|
||||||
d.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.show();
|
|
||||||
}, __("Get items from"));
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
calculate_taxes_and_totals: function() {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
tc_name: function() {
|
|
||||||
this.get_terms();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
$.extend(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
|
$.extend(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -12,25 +12,27 @@
|
|||||||
"vendor",
|
"vendor",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
|
"status",
|
||||||
|
"amended_from",
|
||||||
"suppliers_section",
|
"suppliers_section",
|
||||||
"suppliers",
|
"suppliers",
|
||||||
"get_suppliers_button",
|
|
||||||
"items_section",
|
"items_section",
|
||||||
"items",
|
"items",
|
||||||
"link_to_mrs",
|
"link_to_mrs",
|
||||||
"supplier_response_section",
|
"supplier_response_section",
|
||||||
|
"salutation",
|
||||||
|
"subject",
|
||||||
|
"col_break_email_1",
|
||||||
"email_template",
|
"email_template",
|
||||||
|
"preview",
|
||||||
|
"sec_break_email_2",
|
||||||
"message_for_supplier",
|
"message_for_supplier",
|
||||||
"terms_section_break",
|
"terms_section_break",
|
||||||
"tc_name",
|
"tc_name",
|
||||||
"terms",
|
"terms",
|
||||||
"printing_settings",
|
"printing_settings",
|
||||||
"select_print_heading",
|
"select_print_heading",
|
||||||
"letter_head",
|
"letter_head"
|
||||||
"more_info",
|
|
||||||
"status",
|
|
||||||
"column_break3",
|
|
||||||
"amended_from"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -78,6 +80,7 @@
|
|||||||
"width": "50%"
|
"width": "50%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Today",
|
||||||
"fieldname": "transaction_date",
|
"fieldname": "transaction_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -94,16 +97,11 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "suppliers",
|
"fieldname": "suppliers",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Supplier Detail",
|
"label": "Suppliers",
|
||||||
"options": "Request for Quotation Supplier",
|
"options": "Request for Quotation Supplier",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "get_suppliers_button",
|
|
||||||
"fieldtype": "Button",
|
|
||||||
"label": "Get Suppliers"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "items_section",
|
"fieldname": "items_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -126,8 +124,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 +137,9 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 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,
|
||||||
@@ -197,14 +200,6 @@
|
|||||||
"options": "Letter Head",
|
"options": "Letter Head",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"collapsible": 1,
|
|
||||||
"fieldname": "more_info",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "More Information",
|
|
||||||
"oldfieldtype": "Section Break",
|
|
||||||
"options": "fa fa-file-text"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -218,10 +213,6 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break3",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -230,12 +221,46 @@
|
|||||||
"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",
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-25 14:37:21.140194",
|
"modified": "2020-11-04 22:04:29.017134",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation",
|
"name": "Request for Quotation",
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ class RequestforQuotation(BuyingController):
|
|||||||
super(RequestforQuotation, self).set_qty_as_per_stock_uom()
|
super(RequestforQuotation, self).set_qty_as_per_stock_uom()
|
||||||
self.update_email_id()
|
self.update_email_id()
|
||||||
|
|
||||||
|
if self.docstatus < 1:
|
||||||
|
# after amend and save, status still shows as cancelled, until submit
|
||||||
|
frappe.db.set(self, 'status', 'Draft')
|
||||||
|
|
||||||
def validate_duplicate_supplier(self):
|
def validate_duplicate_supplier(self):
|
||||||
supplier_list = [d.supplier for d in self.suppliers]
|
supplier_list = [d.supplier for d in self.suppliers]
|
||||||
if len(supplier_list) != len(set(supplier_list)):
|
if len(supplier_list) != len(set(supplier_list)):
|
||||||
@@ -51,7 +55,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,43 +66,58 @@ 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.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):
|
||||||
# RFQ link for supplier portal
|
# RFQ link for supplier portal
|
||||||
return get_url("/rfq/" + self.name)
|
return get_url("/rfq/" + self.name)
|
||||||
|
|
||||||
def update_supplier_part_no(self, args):
|
def update_supplier_part_no(self, supplier):
|
||||||
self.vendor = args.supplier
|
self.vendor = supplier
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.supplier_part_no = frappe.db.get_value('Item Supplier',
|
item.supplier_part_no = frappe.db.get_value('Item Supplier',
|
||||||
{'parent': item.item_code, 'supplier': args.supplier}, 'supplier_part_no')
|
{'parent': item.item_code, 'supplier': supplier}, 'supplier_part_no')
|
||||||
|
|
||||||
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 +134,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 +152,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)
|
||||||
@@ -164,7 +201,6 @@ class RequestforQuotation(BuyingController):
|
|||||||
def update_rfq_supplier_status(self, sup_name=None):
|
def update_rfq_supplier_status(self, sup_name=None):
|
||||||
for supplier in self.suppliers:
|
for supplier in self.suppliers:
|
||||||
if sup_name == None or supplier.supplier == sup_name:
|
if sup_name == None or supplier.supplier == sup_name:
|
||||||
if supplier.quote_status != _('No Quote'):
|
|
||||||
quote_status = _('Received')
|
quote_status = _('Received')
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
sqi_count = frappe.db.sql("""
|
sqi_count = frappe.db.sql("""
|
||||||
@@ -289,16 +325,15 @@ def create_rfq_items(sq_doc, supplier, data):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pdf(doctype, name, supplier_idx):
|
def get_pdf(doctype, name, supplier):
|
||||||
doc = get_rfq_doc(doctype, name, supplier_idx)
|
doc = get_rfq_doc(doctype, name, supplier)
|
||||||
if doc:
|
if doc:
|
||||||
download_pdf(doctype, name, doc=doc)
|
download_pdf(doctype, name, doc=doc)
|
||||||
|
|
||||||
def get_rfq_doc(doctype, name, supplier_idx):
|
def get_rfq_doc(doctype, name, supplier):
|
||||||
if cint(supplier_idx):
|
if supplier:
|
||||||
doc = frappe.get_doc(doctype, name)
|
doc = frappe.get_doc(doctype, name)
|
||||||
args = doc.get('suppliers')[cint(supplier_idx) - 1]
|
doc.update_supplier_part_no(supplier)
|
||||||
doc.update_supplier_part_no(args)
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -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,25 +22,21 @@ 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
|
|
||||||
rfq.get('suppliers')[1].no_quote = 1
|
|
||||||
rfq.get('suppliers')[1].quote_status = 'No Quote'
|
|
||||||
|
|
||||||
rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
|
rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
|
||||||
|
|
||||||
self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Received')
|
self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Received')
|
||||||
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote')
|
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
|
||||||
|
|
||||||
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 +58,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")
|
||||||
|
|||||||
@@ -84,9 +84,6 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
|||||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
||||||
},
|
},
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => {
|
|
||||||
frappe.click_check('No Quote');
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => {
|
() => {
|
||||||
cur_frm.cur_grid.toggle_view();
|
cur_frm.cur_grid.toggle_view();
|
||||||
@@ -125,7 +122,6 @@ QUnit.test("Test: Request for Quotation", function (assert) {
|
|||||||
() => frappe.timeout(1),
|
() => frappe.timeout(1),
|
||||||
() => {
|
() => {
|
||||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received");
|
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received");
|
||||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.no_quote == 1);
|
|
||||||
},
|
},
|
||||||
() => done()
|
() => done()
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -27,10 +27,11 @@
|
|||||||
"stock_qty",
|
"stock_qty",
|
||||||
"warehouse_and_reference",
|
"warehouse_and_reference",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"project_name",
|
|
||||||
"col_break4",
|
"col_break4",
|
||||||
"material_request",
|
"material_request",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
|
"section_break_24",
|
||||||
|
"project_name",
|
||||||
"section_break_23",
|
"section_break_23",
|
||||||
"page_break"
|
"page_break"
|
||||||
],
|
],
|
||||||
@@ -161,7 +162,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "project_name",
|
"fieldname": "project_name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project Name",
|
"label": "Project",
|
||||||
"options": "Project",
|
"options": "Project",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
@@ -249,11 +250,18 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_24",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-12 19:10:36.333441",
|
"modified": "2020-09-24 17:26:46.276934",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation Item",
|
"name": "Request for Quotation Item",
|
||||||
|
|||||||
@@ -1,362 +1,99 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2016-03-29 05:59:11.896885",
|
"creation": "2016-03-29 05:59:11.896885",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"supplier",
|
||||||
|
"contact",
|
||||||
|
"quote_status",
|
||||||
|
"column_break_3",
|
||||||
|
"supplier_name",
|
||||||
|
"email_id",
|
||||||
|
"send_email",
|
||||||
|
"email_sent"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
"columns": 2,
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "send_email",
|
"fieldname": "send_email",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"in_list_view": 1,
|
||||||
"ignore_user_permissions": 0,
|
"label": "Send Email"
|
||||||
"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,
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:doc.docstatus >= 1",
|
"depends_on": "eval:doc.docstatus >= 1",
|
||||||
"fieldname": "email_sent",
|
"fieldname": "email_sent",
|
||||||
"fieldtype": "Check",
|
"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",
|
"label": "Email Sent",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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": 2,
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 4,
|
|
||||||
"fieldname": "supplier",
|
"fieldname": "supplier",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Supplier",
|
"label": "Supplier",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Supplier",
|
"options": "Supplier",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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": 2,
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
|
||||||
"fieldname": "contact",
|
"fieldname": "contact",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Contact",
|
"label": "Contact",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Contact",
|
"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,
|
||||||
"bold": 0,
|
"depends_on": "eval:doc.docstatus >= 1",
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
|
|
||||||
"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,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
|
|
||||||
"fieldname": "quote_status",
|
"fieldname": "quote_status",
|
||||||
"fieldtype": "Select",
|
"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",
|
"label": "Quote Status",
|
||||||
"length": 0,
|
"options": "Pending\nReceived",
|
||||||
"no_copy": 0,
|
"read_only": 1
|
||||||
"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,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"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,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
"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,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 0,
|
"label": "Supplier Name"
|
||||||
"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,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fetch_from": "contact.email_id",
|
"fetch_from": "contact.email_id",
|
||||||
"fieldname": "email_id",
|
"fieldname": "email_id",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Email Id",
|
"label": "Email Id",
|
||||||
"length": 0,
|
"no_copy": 1
|
||||||
"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,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"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,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2018-05-16 22:43:30.212408",
|
"modified": "2020-11-04 22:01:43.832942",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Request for Quotation Supplier",
|
"name": "Request for Quotation Supplier",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@@ -91,12 +91,7 @@ class SupplierQuotation(BuyingController):
|
|||||||
for my_item in self.items) if include_me else 0
|
for my_item in self.items) if include_me else 0
|
||||||
if (sqi_count.count + self_count) == 0:
|
if (sqi_count.count + self_count) == 0:
|
||||||
quote_status = _('Pending')
|
quote_status = _('Pending')
|
||||||
if quote_status == _('Received') and doc_sup.quote_status == _('No Quote'):
|
|
||||||
frappe.msgprint(_("{0} indicates that {1} will not provide a quotation, but all items \
|
|
||||||
have been quoted. Updating the RFQ quote status.").format(doc.name, self.supplier))
|
|
||||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
|
||||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'no_quote', 0)
|
|
||||||
elif doc_sup.quote_status != _('No Quote'):
|
|
||||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
||||||
|
|
||||||
def get_list_context(context=None):
|
def get_list_context(context=None):
|
||||||
|
|||||||
@@ -560,7 +560,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-01 16:34:39.703033",
|
"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",
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ def get_conditions(filters):
|
|||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
if filters.get("company"):
|
if filters.get("company"):
|
||||||
conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
|
conditions += " AND parent.company=%s" % frappe.db.escape(filters.get('company'))
|
||||||
|
|
||||||
if filters.get("cost_center") or filters.get("project"):
|
if filters.get("cost_center") or filters.get("project"):
|
||||||
conditions += """
|
conditions += """
|
||||||
@@ -151,10 +151,10 @@ def get_conditions(filters):
|
|||||||
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
|
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
|
||||||
|
|
||||||
if filters.get("from_date"):
|
if filters.get("from_date"):
|
||||||
conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
|
conditions += " AND parent.transaction_date>='%s'" % filters.get('from_date')
|
||||||
|
|
||||||
if filters.get("to_date"):
|
if filters.get("to_date"):
|
||||||
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
|
conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date')
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
@@ -198,21 +198,23 @@ def get_mapped_mr_details(conditions):
|
|||||||
mr_records = {}
|
mr_records = {}
|
||||||
mr_details = frappe.db.sql("""
|
mr_details = frappe.db.sql("""
|
||||||
SELECT
|
SELECT
|
||||||
par.transaction_date,
|
parent.transaction_date,
|
||||||
par.per_ordered,
|
parent.per_ordered,
|
||||||
par.owner,
|
parent.owner,
|
||||||
child.name,
|
child.name,
|
||||||
child.parent,
|
child.parent,
|
||||||
child.amount,
|
child.amount,
|
||||||
child.qty,
|
child.qty,
|
||||||
child.item_code,
|
child.item_code,
|
||||||
child.uom,
|
child.uom,
|
||||||
par.status
|
parent.status,
|
||||||
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
|
child.project,
|
||||||
|
child.cost_center
|
||||||
|
FROM `tabMaterial Request` parent, `tabMaterial Request Item` child
|
||||||
WHERE
|
WHERE
|
||||||
par.per_ordered>=0
|
parent.per_ordered>=0
|
||||||
AND par.name=child.parent
|
AND parent.name=child.parent
|
||||||
AND par.docstatus=1
|
AND parent.docstatus=1
|
||||||
{conditions}
|
{conditions}
|
||||||
""".format(conditions=conditions), as_dict=1) #nosec
|
""".format(conditions=conditions), as_dict=1) #nosec
|
||||||
|
|
||||||
@@ -232,7 +234,9 @@ def get_mapped_mr_details(conditions):
|
|||||||
status=record.status,
|
status=record.status,
|
||||||
actual_cost=0,
|
actual_cost=0,
|
||||||
purchase_order_amt=0,
|
purchase_order_amt=0,
|
||||||
purchase_order_amt_in_company_currency=0
|
purchase_order_amt_in_company_currency=0,
|
||||||
|
project = record.project,
|
||||||
|
cost_center = record.cost_center
|
||||||
)
|
)
|
||||||
procurement_record_against_mr.append(procurement_record_details)
|
procurement_record_against_mr.append(procurement_record_details)
|
||||||
return mr_records, procurement_record_against_mr
|
return mr_records, procurement_record_against_mr
|
||||||
@@ -280,16 +284,16 @@ def get_po_entries(conditions):
|
|||||||
child.amount,
|
child.amount,
|
||||||
child.base_amount,
|
child.base_amount,
|
||||||
child.schedule_date,
|
child.schedule_date,
|
||||||
par.transaction_date,
|
parent.transaction_date,
|
||||||
par.supplier,
|
parent.supplier,
|
||||||
par.status,
|
parent.status,
|
||||||
par.owner
|
parent.owner
|
||||||
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
|
FROM `tabPurchase Order` parent, `tabPurchase Order Item` child
|
||||||
WHERE
|
WHERE
|
||||||
par.docstatus = 1
|
parent.docstatus = 1
|
||||||
AND par.name = child.parent
|
AND parent.name = child.parent
|
||||||
AND par.status not in ("Closed","Completed","Cancelled")
|
AND parent.status not in ("Closed","Completed","Cancelled")
|
||||||
{conditions}
|
{conditions}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
par.name, child.item_code
|
parent.name, child.item_code
|
||||||
""".format(conditions=conditions), as_dict=1) #nosec
|
""".format(conditions=conditions), as_dict=1) #nosec
|
||||||
89
erpnext/change_log/v13/v13_0_0-beta_5.md
Normal file
89
erpnext/change_log/v13/v13_0_0-beta_5.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
### Version 13.0.0 Beta 5 Release Notes
|
||||||
|
|
||||||
|
#### Features and Enhancements
|
||||||
|
- Add company and correct filter in bank reconciliation statement ([#23614](https://github.com/frappe/erpnext/pull/23614))
|
||||||
|
- Process Statement Of Accounts ([#22901](https://github.com/frappe/erpnext/pull/22901))
|
||||||
|
- Added Condition field in Pricing Rule ([#23014](https://github.com/frappe/erpnext/pull/23014))
|
||||||
|
- Material Request and Stock Entry Enhancement ([#22671](https://github.com/frappe/erpnext/pull/22671))
|
||||||
|
- Added sequence id in routing for the completion of operations sequentially ([#23641](https://github.com/frappe/erpnext/pull/23641))
|
||||||
|
- Appraisal form Enhancements ([#23500](https://github.com/frappe/erpnext/pull/23500))
|
||||||
|
- Crm reports cleanup ([#22844](https://github.com/frappe/erpnext/pull/22844))
|
||||||
|
- Open lead status on next contact date ([#23445](https://github.com/frappe/erpnext/pull/23445))
|
||||||
|
- Quoted Item Comparison Report Enhancements v2 ([#23127](https://github.com/frappe/erpnext/pull/23127))
|
||||||
|
- Added phone field in product Inquiry ([#23170](https://github.com/frappe/erpnext/pull/23170))
|
||||||
|
- Added address template for luxembourg ([#23621](https://github.com/frappe/erpnext/pull/23621))
|
||||||
|
- Provision to draft quotation from portal ([#23416](https://github.com/frappe/erpnext/pull/23416))
|
||||||
|
- M-pesa integration ([#23439](https://github.com/frappe/erpnext/pull/23439))
|
||||||
|
- Education Desk, Dashboard, and Onboarding ([#22825](https://github.com/frappe/erpnext/pull/22825))
|
||||||
|
- Added search to support page ([#22447](https://github.com/frappe/erpnext/pull/22447))
|
||||||
|
- Balance Serial Nos in Stock Ledger report ([#23675](https://github.com/frappe/erpnext/pull/23675))
|
||||||
|
- Added POS Register report ([#23313](https://github.com/frappe/erpnext/pull/23313))
|
||||||
|
- Added Project in Sales Analytics report ([#23309](https://github.com/frappe/erpnext/pull/23309))
|
||||||
|
- Enhancement in Loan Topups ([#23049](https://github.com/frappe/erpnext/pull/23049))
|
||||||
|
- Inpatient Medication Order and Entry ([#23473](https://github.com/frappe/erpnext/pull/23473))
|
||||||
|
- Option to print UOM after quantity ([#23263](https://github.com/frappe/erpnext/pull/23263))
|
||||||
|
- Supplier Quotation Comparison report ([#23323](https://github.com/frappe/erpnext/pull/23323))
|
||||||
|
- Supplier Sourced Items in BOM ([#23557](https://github.com/frappe/erpnext/pull/23557))
|
||||||
|
- Therapy Plan Template ([#23558](https://github.com/frappe/erpnext/pull/23558))
|
||||||
|
- Youtube interactions via Video ([#22867](https://github.com/frappe/erpnext/pull/22867))
|
||||||
|
- Laboratory Module ([#22853](https://github.com/frappe/erpnext/pull/22853))
|
||||||
|
- Shift management ([#22262](https://github.com/frappe/erpnext/pull/22262))
|
||||||
|
|
||||||
|
#### Fixes
|
||||||
|
- Multiple pos issues ([#23725](https://github.com/frappe/erpnext/pull/23725))
|
||||||
|
- Calculate taxes if tax is based on item quantity and inclusive on item price ([#23001](https://github.com/frappe/erpnext/pull/23001))
|
||||||
|
- Contact us button not visible in the website for the non variant items ([#23217](https://github.com/frappe/erpnext/pull/23217))
|
||||||
|
- Loan Security shortfall calculation fixes ([#22866](https://github.com/frappe/erpnext/pull/22866))
|
||||||
|
- Not able to make Material Request from Sales Order ([#23669](https://github.com/frappe/erpnext/pull/23669))
|
||||||
|
- Capture advance payments in payment order ([#23256](https://github.com/frappe/erpnext/pull/23256))
|
||||||
|
- Program and Course Enrollment fixes ([#23333](https://github.com/frappe/erpnext/pull/23333))
|
||||||
|
- Cannot create asset if cwip disabled and account not set ([#23580](https://github.com/frappe/erpnext/pull/23580))
|
||||||
|
- Cannot merge pos invoices with inclusive tax ([#23541](https://github.com/frappe/erpnext/pull/23541))
|
||||||
|
- Do not allow Company as accounting dimension ([#23755](https://github.com/frappe/erpnext/pull/23755))
|
||||||
|
- Set value of wrong Bank Account field in Payment Entry ([#22302](https://github.com/frappe/erpnext/pull/22302))
|
||||||
|
- Reverse journal entry for multi-currency ([#23165](https://github.com/frappe/erpnext/pull/23165))
|
||||||
|
- Updated integrations desk page ([#23772](https://github.com/frappe/erpnext/pull/23772))
|
||||||
|
- Assessment Result child table not visible when accessed via Assessment Plan dashboard ([#22880](https://github.com/frappe/erpnext/pull/22880))
|
||||||
|
- Conversion factor fixes in Stock Entry ([#23407](https://github.com/frappe/erpnext/pull/23407))
|
||||||
|
- Total calculations for multi-currency RCM invoices ([#23072](https://github.com/frappe/erpnext/pull/23072))
|
||||||
|
- Show accounts in financial statements upto level 20 ([#23718](https://github.com/frappe/erpnext/pull/23718))
|
||||||
|
- Consolidated financial statement sums values into wrong parent ([#23288](https://github.com/frappe/erpnext/pull/23288))
|
||||||
|
- Set SLA variance in seconds for Duration fieldtype ([#23765](https://github.com/frappe/erpnext/pull/23765))
|
||||||
|
- Added missing reports on selling desk ([#23548](https://github.com/frappe/erpnext/pull/23548))
|
||||||
|
- Fixed heading in the mobile view ([#23145](https://github.com/frappe/erpnext/pull/23145))
|
||||||
|
- Misleading filters on Item tax Template Link field ([#22918](https://github.com/frappe/erpnext/pull/22918))
|
||||||
|
- Do not consider opening entries for TDS calculation ([#23597](https://github.com/frappe/erpnext/pull/23597))
|
||||||
|
- Attendance calendar map fix ([#23245](https://github.com/frappe/erpnext/pull/23245))
|
||||||
|
- Post cancellation accounting entry on posting date instead of current ([#23361](https://github.com/frappe/erpnext/pull/23361))
|
||||||
|
- Set Customer only if Contact is present ([#23704](https://github.com/frappe/erpnext/pull/23704))
|
||||||
|
- Add Delivery Note Count in Sales Invoice Dashboard ([#23161](https://github.com/frappe/erpnext/pull/23161))
|
||||||
|
- Breadcrumbs for Maintenance Visit and Schedule ([#23369](https://github.com/frappe/erpnext/pull/23369))
|
||||||
|
- Raise Error on over receipt/consumption for sub-contracted PR ([#23195](https://github.com/frappe/erpnext/pull/23195))
|
||||||
|
- Validate if company not set in the Payment Entry ([#23419](https://github.com/frappe/erpnext/pull/23419))
|
||||||
|
- Ignore company and bank account doctype while deleting company transactions ([#22953](https://github.com/frappe/erpnext/pull/22953))
|
||||||
|
- Sales funnel data is inconsistent ([#23110](https://github.com/frappe/erpnext/pull/23110))
|
||||||
|
- Credit Limit Email not working ([#23059](https://github.com/frappe/erpnext/pull/23059))
|
||||||
|
- Add Company in list fields to fetch for Expense Claim ([#23007](https://github.com/frappe/erpnext/pull/23007))
|
||||||
|
- Issue form cleaned up and renamed Minutes to First Response field ([#23066](https://github.com/frappe/erpnext/pull/23066))
|
||||||
|
- Quotation lost reason options fix ([#22814](https://github.com/frappe/erpnext/pull/22814))
|
||||||
|
- Tax amounts in HSN Wise Outward summary ([#23076](https://github.com/frappe/erpnext/pull/23076))
|
||||||
|
- Patient Appointment not able to save ([#23434](https://github.com/frappe/erpnext/pull/23434))
|
||||||
|
- Removed Working Hours field from Company ([#23009](https://github.com/frappe/erpnext/pull/23009))
|
||||||
|
- Added check-in time validation in the Inpatient Record - Transfer ([#22958](https://github.com/frappe/erpnext/pull/22958))
|
||||||
|
- Handle Blank from/to range in Numeric Item Attribute ([#23483](https://github.com/frappe/erpnext/pull/23483))
|
||||||
|
- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539))
|
||||||
|
- Fixed Conversion Factor rate for the BOM Exploded Item ([#23151](https://github.com/frappe/erpnext/pull/23151))
|
||||||
|
- Payment Schedule not fetching ([#23476](https://github.com/frappe/erpnext/pull/23476))
|
||||||
|
- Validate if removed Item Attributes exist in variant items ([#22911](https://github.com/frappe/erpnext/pull/22911))
|
||||||
|
- Set default billing address for purchase documents ([#22950](https://github.com/frappe/erpnext/pull/22950))
|
||||||
|
- Added help link in navbar settings ([#22943](https://github.com/frappe/erpnext/pull/22943))
|
||||||
|
- Apply TDS on Purchase Invoice creation from Purchase Order and Purchase Receipt ([#23282](https://github.com/frappe/erpnext/pull/23282))
|
||||||
|
- Education Module fixes ([#23714](https://github.com/frappe/erpnext/pull/23714))
|
||||||
|
- Filter out cancelled entries in customer ledger summary ([#23205](https://github.com/frappe/erpnext/pull/23205))
|
||||||
|
- Fiscal Year and Tax Rates for Italy ([#23623](https://github.com/frappe/erpnext/pull/23623))
|
||||||
|
- Production Plan incorrect Work Order qty ([#23264](https://github.com/frappe/erpnext/pull/23264))
|
||||||
|
- Added new filters in the Batch-wise Balance History report ([#23676](https://github.com/frappe/erpnext/pull/23676))
|
||||||
|
- Update state code and union territory for Daman and Diu ([#22988](https://github.com/frappe/erpnext/pull/22988))
|
||||||
|
- Set Stock UOM in item while creating Material Request from Stock Entry ([#23436](https://github.com/frappe/erpnext/pull/23436))
|
||||||
|
- Sales Order to Purchase Order flow improvement ([#23357](https://github.com/frappe/erpnext/pull/23357))
|
||||||
|
- Student Admission and Student Applicant fixes ([#23515](https://github.com/frappe/erpnext/pull/23515))
|
||||||
@@ -15,7 +15,7 @@ class CallLog(Document):
|
|||||||
number = strip_number(self.get('from'))
|
number = strip_number(self.get('from'))
|
||||||
self.contact = get_contact_with_phone_number(number)
|
self.contact = get_contact_with_phone_number(number)
|
||||||
self.lead = get_lead_with_phone_number(number)
|
self.lead = get_lead_with_phone_number(number)
|
||||||
|
if self.contact:
|
||||||
contact = frappe.get_doc("Contact", self.contact)
|
contact = frappe.get_doc("Contact", self.contact)
|
||||||
self.customer = contact.get_link_for("Customer")
|
self.customer = contact.get_link_for("Customer")
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ def get_data():
|
|||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Chart of Accounts Importer",
|
"name": "Chart of Accounts Importer",
|
||||||
"labe": _("Chart Of Accounts Importer"),
|
"label": _("Chart of Accounts Importer"),
|
||||||
"description": _("Import Chart Of Accounts from CSV / Excel files"),
|
"description": _("Import Chart of Accounts from CSV / Excel files"),
|
||||||
"onboard": 1
|
"onboard": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
|
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
|
||||||
parent_dict.update({"customer": parent_dict.get("party_name")})
|
parent_dict.update({"customer": parent_dict.get("party_name")})
|
||||||
|
|
||||||
|
self.pricing_rules = []
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get("item_code"):
|
if item.get("item_code"):
|
||||||
args = parent_dict.copy()
|
args = parent_dict.copy()
|
||||||
@@ -307,6 +308,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
if ret.get("pricing_rules"):
|
if ret.get("pricing_rules"):
|
||||||
self.apply_pricing_rule_on_items(item, ret)
|
self.apply_pricing_rule_on_items(item, ret)
|
||||||
|
self.set_pricing_rule_details(item, ret)
|
||||||
|
|
||||||
if self.doctype == "Purchase Invoice":
|
if self.doctype == "Purchase Invoice":
|
||||||
self.set_expense_account(for_validate)
|
self.set_expense_account(for_validate)
|
||||||
@@ -328,6 +330,9 @@ class AccountsController(TransactionBase):
|
|||||||
if item.get('discount_amount'):
|
if item.get('discount_amount'):
|
||||||
item.rate = item.price_list_rate - item.discount_amount
|
item.rate = item.price_list_rate - item.discount_amount
|
||||||
|
|
||||||
|
if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
|
||||||
|
item.rate = pricing_rule_args.get("rate")
|
||||||
|
|
||||||
elif pricing_rule_args.get('free_item_data'):
|
elif pricing_rule_args.get('free_item_data'):
|
||||||
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
||||||
|
|
||||||
@@ -341,6 +346,18 @@ class AccountsController(TransactionBase):
|
|||||||
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
|
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
|
||||||
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
|
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
|
||||||
|
|
||||||
|
def set_pricing_rule_details(self, item_row, args):
|
||||||
|
pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
|
||||||
|
if not pricing_rules: return
|
||||||
|
|
||||||
|
for pricing_rule in pricing_rules:
|
||||||
|
self.append("pricing_rules", {
|
||||||
|
"pricing_rule": pricing_rule,
|
||||||
|
"item_code": item_row.item_code,
|
||||||
|
"child_docname": item_row.name,
|
||||||
|
"rule_applied": True
|
||||||
|
})
|
||||||
|
|
||||||
def set_taxes(self):
|
def set_taxes(self):
|
||||||
if not self.meta.get_field("taxes"):
|
if not self.meta.get_field("taxes"):
|
||||||
return
|
return
|
||||||
@@ -611,8 +628,6 @@ class AccountsController(TransactionBase):
|
|||||||
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
|
||||||
|
|
||||||
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
if self.is_return: return
|
|
||||||
|
|
||||||
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
|
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
|
||||||
unlink_ref_doc_from_payment_entries(self)
|
unlink_ref_doc_from_payment_entries(self)
|
||||||
|
|
||||||
@@ -954,8 +969,10 @@ def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, c
|
|||||||
company_currency = frappe.get_cached_value('Company', company, "default_currency")
|
company_currency = frappe.get_cached_value('Company', company, "default_currency")
|
||||||
|
|
||||||
if not conversion_rate:
|
if not conversion_rate:
|
||||||
throw(_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.").format(
|
throw(
|
||||||
conversion_rate_label, currency, company_currency))
|
_("{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}.")
|
||||||
|
.format(conversion_rate_label, currency, company_currency)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_taxes_and_charges(tax):
|
def validate_taxes_and_charges(tax):
|
||||||
@@ -1176,6 +1193,31 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
|
|||||||
if child_item.get("item_tax_template"):
|
if child_item.get("item_tax_template"):
|
||||||
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
|
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
|
||||||
|
|
||||||
|
def add_taxes_from_tax_template(child_item, parent_doc):
|
||||||
|
add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
|
||||||
|
|
||||||
|
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
|
||||||
|
tax_map = json.loads(child_item.get("item_tax_rate"))
|
||||||
|
for tax_type in tax_map:
|
||||||
|
tax_rate = flt(tax_map[tax_type])
|
||||||
|
taxes = parent_doc.get('taxes') or []
|
||||||
|
# add new row for tax head only if missing
|
||||||
|
found = any(tax.account_head == tax_type for tax in taxes)
|
||||||
|
if not found:
|
||||||
|
tax_row = parent_doc.append("taxes", {})
|
||||||
|
tax_row.update({
|
||||||
|
"description" : str(tax_type).split(' - ')[0],
|
||||||
|
"charge_type" : "On Net Total",
|
||||||
|
"account_head" : tax_type,
|
||||||
|
"rate" : tax_rate
|
||||||
|
})
|
||||||
|
if parent_doc.doctype == "Purchase Order":
|
||||||
|
tax_row.update({
|
||||||
|
"category" : "Total",
|
||||||
|
"add_deduct_tax" : "Add"
|
||||||
|
})
|
||||||
|
tax_row.db_insert()
|
||||||
|
|
||||||
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item):
|
||||||
"""
|
"""
|
||||||
Returns a Sales Order Item child item containing the default values
|
Returns a Sales Order Item child item containing the default values
|
||||||
@@ -1191,6 +1233,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
|
|||||||
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
|
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
|
||||||
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
|
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
|
||||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||||
|
add_taxes_from_tax_template(child_item, p_doc)
|
||||||
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
|
||||||
if not child_item.warehouse:
|
if not child_item.warehouse:
|
||||||
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
|
||||||
@@ -1215,6 +1258,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
|
|||||||
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
child_item.base_rate = 1 # Initiallize value will update in parent validation
|
||||||
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
child_item.base_amount = 1 # Initiallize value will update in parent validation
|
||||||
set_child_tax_template_and_map(item, child_item, p_doc)
|
set_child_tax_template_and_map(item, child_item, p_doc)
|
||||||
|
add_taxes_from_tax_template(child_item, p_doc)
|
||||||
return child_item
|
return child_item
|
||||||
|
|
||||||
def validate_and_delete_children(parent, data):
|
def validate_and_delete_children(parent, data):
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class StockController(AccountsController):
|
|||||||
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
|
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
|
||||||
.format(d.idx, serial_no_data.name, d.batch_no))
|
.format(d.idx, serial_no_data.name, d.batch_no))
|
||||||
|
|
||||||
if d.qty > 0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
||||||
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||||
|
|
||||||
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
|
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
|
||||||
@@ -229,8 +229,10 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
def check_expense_account(self, item):
|
def check_expense_account(self, item):
|
||||||
if not item.get("expense_account"):
|
if not item.get("expense_account"):
|
||||||
frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense Account in the Items table")
|
msg = _("Please set an Expense Account in the Items table")
|
||||||
.format(item.idx, frappe.bold(item.item_code)), title=_("Expense Account Missing"))
|
frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
|
||||||
|
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
is_expense_account = frappe.db.get_value("Account",
|
is_expense_account = frappe.db.get_value("Account",
|
||||||
item.get("expense_account"), "report_type")=="Profit and Loss"
|
item.get("expense_account"), "report_type")=="Profit and Loss"
|
||||||
@@ -245,7 +247,9 @@ class StockController(AccountsController):
|
|||||||
for d in self.items:
|
for d in self.items:
|
||||||
if not d.batch_no: continue
|
if not d.batch_no: continue
|
||||||
|
|
||||||
serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': d.batch_no})]
|
serial_nos = [sr.name for sr in frappe.get_all("Serial No",
|
||||||
|
{'batch_no': d.batch_no, 'status': 'Inactive'})]
|
||||||
|
|
||||||
if serial_nos:
|
if serial_nos:
|
||||||
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
|
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
|
||||||
|
|
||||||
|
|||||||
@@ -608,14 +608,17 @@ class calculate_taxes_and_totals(object):
|
|||||||
base_rate_with_margin = 0.0
|
base_rate_with_margin = 0.0
|
||||||
if item.price_list_rate:
|
if item.price_list_rate:
|
||||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||||
|
has_margin = False
|
||||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||||
|
|
||||||
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
|
if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\
|
||||||
or (pricing_rule.margin_type == 'Percentage'):
|
or (pricing_rule.margin_type == 'Percentage'):
|
||||||
item.margin_type = pricing_rule.margin_type
|
item.margin_type = pricing_rule.margin_type
|
||||||
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||||
else:
|
has_margin = True
|
||||||
|
|
||||||
|
if not has_margin:
|
||||||
item.margin_type = None
|
item.margin_type = None
|
||||||
item.margin_rate_or_amount = 0.0
|
item.margin_rate_or_amount = 0.0
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
|||||||
|
|
||||||
if not filters: filters = []
|
if not filters: filters = []
|
||||||
|
|
||||||
if doctype in ['Supplier Quotation', 'Purchase Invoice', 'Quotation']:
|
if doctype in ['Supplier Quotation', 'Purchase Invoice']:
|
||||||
filters.append((doctype, 'docstatus', '<', 2))
|
filters.append((doctype, 'docstatus', '<', 2))
|
||||||
else:
|
else:
|
||||||
filters.append((doctype, 'docstatus', '=', 1))
|
filters.append((doctype, 'docstatus', '=', 1))
|
||||||
|
|||||||
@@ -241,6 +241,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.__islocal",
|
"depends_on": "eval: doc.__islocal",
|
||||||
|
"description": "Home, Work, etc.",
|
||||||
"fieldname": "address_title",
|
"fieldname": "address_title",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Address Title"
|
"label": "Address Title"
|
||||||
@@ -249,7 +250,8 @@
|
|||||||
"depends_on": "eval: doc.__islocal",
|
"depends_on": "eval: doc.__islocal",
|
||||||
"fieldname": "address_line1",
|
"fieldname": "address_line1",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Address Line 1"
|
"label": "Address Line 1",
|
||||||
|
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.__islocal",
|
"depends_on": "eval: doc.__islocal",
|
||||||
@@ -261,7 +263,8 @@
|
|||||||
"depends_on": "eval: doc.__islocal",
|
"depends_on": "eval: doc.__islocal",
|
||||||
"fieldname": "city",
|
"fieldname": "city",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "City/Town"
|
"label": "City/Town",
|
||||||
|
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.__islocal",
|
"depends_on": "eval: doc.__islocal",
|
||||||
@@ -280,6 +283,7 @@
|
|||||||
"fieldname": "country",
|
"fieldname": "country",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Country",
|
"label": "Country",
|
||||||
|
"mandatory_depends_on": "eval: doc.address_title && doc.address_type",
|
||||||
"options": "Country"
|
"options": "Country"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -449,7 +453,7 @@
|
|||||||
"idx": 5,
|
"idx": 5,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-18 14:39:41.835416",
|
"modified": "2020-10-13 15:24:00.094811",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "CRM",
|
"module": "CRM",
|
||||||
"name": "Lead",
|
"name": "Lead",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class Lead(SellingController):
|
|||||||
load_address_and_contact(self)
|
load_address_and_contact(self)
|
||||||
|
|
||||||
def before_insert(self):
|
def before_insert(self):
|
||||||
|
if self.address_title and self.address_type:
|
||||||
self.address_doc = self.create_address()
|
self.address_doc = self.create_address()
|
||||||
self.contact_doc = self.create_contact()
|
self.contact_doc = self.create_contact()
|
||||||
|
|
||||||
@@ -133,15 +134,6 @@ class Lead(SellingController):
|
|||||||
# skipping country since the system auto-sets it from system defaults
|
# skipping country since the system auto-sets it from system defaults
|
||||||
address = frappe.new_doc("Address")
|
address = frappe.new_doc("Address")
|
||||||
|
|
||||||
mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ]
|
|
||||||
|
|
||||||
if not all([self.get(field) for field in mandatory_fields]):
|
|
||||||
frappe.msgprint(_('Missing mandatory fields in address. \
|
|
||||||
{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \
|
|
||||||
> Click here </a>"),
|
|
||||||
alert=True, indicator='yellow')
|
|
||||||
return
|
|
||||||
|
|
||||||
address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
|
address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
|
||||||
address.update({info_field: self.get(info_field) for info_field in info_fields})
|
address.update({info_field: self.get(info_field) for info_field in info_fields})
|
||||||
address.insert()
|
address.insert()
|
||||||
@@ -190,7 +182,7 @@ class Lead(SellingController):
|
|||||||
|
|
||||||
def update_links(self):
|
def update_links(self):
|
||||||
# update address links
|
# update address links
|
||||||
if self.address_doc:
|
if hasattr(self, 'address_doc'):
|
||||||
self.address_doc.append("links", {
|
self.address_doc.append("links", {
|
||||||
"link_doctype": "Lead",
|
"link_doctype": "Lead",
|
||||||
"link_name": self.name,
|
"link_name": self.name,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from erpnext.accounts.party import get_party_account_currency
|
|||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
|
from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
|
||||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \
|
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \
|
||||||
make_supplier_quotation as make_quotation_from_rfq
|
make_supplier_quotation_from_rfq
|
||||||
|
|
||||||
def work():
|
def work():
|
||||||
frappe.set_user(frappe.db.get_global('demo_purchase_user'))
|
frappe.set_user(frappe.db.get_global('demo_purchase_user'))
|
||||||
@@ -44,7 +44,7 @@ def work():
|
|||||||
rfq = frappe.get_doc('Request for Quotation', rfq.name)
|
rfq = frappe.get_doc('Request for Quotation', rfq.name)
|
||||||
|
|
||||||
for supplier in rfq.suppliers:
|
for supplier in rfq.suppliers:
|
||||||
supplier_quotation = make_quotation_from_rfq(rfq.name, supplier.supplier)
|
supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier)
|
||||||
supplier_quotation.save()
|
supplier_quotation.save()
|
||||||
supplier_quotation.submit()
|
supplier_quotation.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,22 @@ data = {
|
|||||||
'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt',
|
'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt',
|
||||||
'insert_after': 'reference_dt'
|
'insert_after': 'reference_dt'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
'Stock Entry': [
|
||||||
|
{
|
||||||
|
'fieldname': 'inpatient_medication_entry', 'label': 'Inpatient Medication Entry', 'fieldtype': 'Link', 'options': 'Inpatient Medication Entry',
|
||||||
|
'insert_after': 'credit_note', 'read_only': True
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'Stock Entry Detail': [
|
||||||
|
{
|
||||||
|
'fieldname': 'patient', 'label': 'Patient', 'fieldtype': 'Link', 'options': 'Patient',
|
||||||
|
'insert_after': 'po_detail', 'read_only': True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'inpatient_medication_entry_child', 'label': 'Inpatient Medication Entry Child', 'fieldtype': 'Data',
|
||||||
|
'insert_after': 'patient', 'read_only': True
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'on_setup': 'erpnext.healthcare.setup.setup_healthcare'
|
'on_setup': 'erpnext.healthcare.setup.setup_healthcare'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils import flt, cstr
|
from frappe.utils import flt, cstr, getdate
|
||||||
from frappe.email.doctype.email_group.email_group import add_subscribers
|
from frappe.email.doctype.email_group.email_group import add_subscribers
|
||||||
|
|
||||||
def get_course(program):
|
def get_course(program):
|
||||||
@@ -67,6 +67,13 @@ def mark_attendance(students_present, students_absent, course_schedule=None, stu
|
|||||||
:param date: Date.
|
:param date: Date.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if student_group:
|
||||||
|
academic_year = frappe.db.get_value('Student Group', student_group, 'academic_year')
|
||||||
|
if academic_year:
|
||||||
|
year_start_date, year_end_date = frappe.db.get_value('Academic Year', academic_year, ['year_start_date', 'year_end_date'])
|
||||||
|
if getdate(date) < getdate(year_start_date) or getdate(date) > getdate(year_end_date):
|
||||||
|
frappe.throw(_('Attendance cannot be marked outside of Academic Year {0}').format(academic_year))
|
||||||
|
|
||||||
present = json.loads(students_present)
|
present = json.loads(students_present)
|
||||||
absent = json.loads(students_absent)
|
absent = json.loads(students_absent)
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,23 @@ frappe.ui.form.on('Assessment Plan', {
|
|||||||
frappe.set_route('Form', 'Assessment Result Tool');
|
frappe.set_route('Form', 'Assessment Result Tool');
|
||||||
}, __('Tools'));
|
}, __('Tools'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.set_query('course', function() {
|
||||||
|
return {
|
||||||
|
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses',
|
||||||
|
filters: {
|
||||||
|
'program': frm.doc.program
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('academic_term', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'academic_year': frm.doc.academic_year
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
course: function(frm) {
|
course: function(frm) {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
"assessment_group",
|
"assessment_group",
|
||||||
"grading_scale",
|
"grading_scale",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"course",
|
|
||||||
"program",
|
"program",
|
||||||
|
"course",
|
||||||
"academic_year",
|
"academic_year",
|
||||||
"academic_term",
|
"academic_term",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-09 14:56:26.746988",
|
"modified": "2020-10-23 15:55:35.076251",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Education",
|
"module": "Education",
|
||||||
"name": "Assessment Plan",
|
"name": "Assessment Plan",
|
||||||
|
|||||||
@@ -7,6 +7,23 @@ frappe.ui.form.on('Assessment Result', {
|
|||||||
frm.trigger('setup_chart');
|
frm.trigger('setup_chart');
|
||||||
}
|
}
|
||||||
frm.set_df_property('details', 'read_only', 1);
|
frm.set_df_property('details', 'read_only', 1);
|
||||||
|
|
||||||
|
frm.set_query('course', function() {
|
||||||
|
return {
|
||||||
|
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses',
|
||||||
|
filters: {
|
||||||
|
'program': frm.doc.program
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('academic_term', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'academic_year': frm.doc.academic_year
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import get_link_to_form
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
class CourseEnrollment(Document):
|
class CourseEnrollment(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_duplication()
|
||||||
|
|
||||||
def get_progress(self, student):
|
def get_progress(self, student):
|
||||||
"""
|
"""
|
||||||
Returns Progress of given student for a particular course enrollment
|
Returns Progress of given student for a particular course enrollment
|
||||||
@@ -27,13 +31,15 @@ class CourseEnrollment(Document):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def validate_duplication(self):
|
def validate_duplication(self):
|
||||||
enrollment = frappe.get_all("Course Enrollment", filters={
|
enrollment = frappe.db.exists("Course Enrollment", {
|
||||||
"student": self.student,
|
"student": self.student,
|
||||||
"course": self.course,
|
"course": self.course,
|
||||||
"program_enrollment": self.program_enrollment
|
"program_enrollment": self.program_enrollment,
|
||||||
|
"name": ("!=", self.name)
|
||||||
})
|
})
|
||||||
if enrollment:
|
if enrollment:
|
||||||
frappe.throw(_("Student is already enrolled."))
|
frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
|
||||||
|
get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
|
||||||
|
|
||||||
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
|
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
|
||||||
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
|
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ class TestCourseEnrollment(unittest.TestCase):
|
|||||||
setup_program()
|
setup_program()
|
||||||
student = create_student({"first_name": "_Test First", "last_name": "_Test Last", "email": "_test_student_1@example.com"})
|
student = create_student({"first_name": "_Test First", "last_name": "_Test Last", "email": "_test_student_1@example.com"})
|
||||||
program_enrollment = student.enroll_in_program("_Test Program")
|
program_enrollment = student.enroll_in_program("_Test Program")
|
||||||
course_enrollment = student.enroll_in_course("_Test Course 1", program_enrollment.name)
|
course_enrollment = frappe.db.get_value("Course Enrollment",
|
||||||
make_course_activity(course_enrollment.name, "Article", "_Test Article 1-1")
|
{"course": "_Test Course 1", "student": student.name, "program_enrollment": program_enrollment.name}, 'name')
|
||||||
|
make_course_activity(course_enrollment, "Article", "_Test Article 1-1")
|
||||||
|
|
||||||
def test_get_progress(self):
|
def test_get_progress(self):
|
||||||
student = get_student("_test_student_1@example.com")
|
student = get_student("_test_student_1@example.com")
|
||||||
@@ -30,5 +31,14 @@ class TestCourseEnrollment(unittest.TestCase):
|
|||||||
self.assertTrue(finished in progress)
|
self.assertTrue(finished in progress)
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for entry in frappe.db.get_all("Course Enrollment"):
|
||||||
|
frappe.delete_doc("Course Enrollment", entry.name)
|
||||||
|
|
||||||
|
for entry in frappe.db.get_all("Program Enrollment"):
|
||||||
|
doc = frappe.get_doc("Program Enrollment", entry.name)
|
||||||
|
doc.cancel()
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,5 +41,24 @@ frappe.ui.form.on("Instructor", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("academic_term", "instructor_log", function(_doc, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"academic_year": d.academic_year
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("course", "instructor_log", function(_doc, cdt, cdn) {
|
||||||
|
let d = locals[cdt][cdn];
|
||||||
|
return {
|
||||||
|
query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses",
|
||||||
|
filters: {
|
||||||
|
"program": d.program
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1,336 +1,88 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2017-12-27 08:55:52.680284",
|
"creation": "2017-12-27 08:55:52.680284",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"academic_year",
|
||||||
|
"academic_term",
|
||||||
|
"department",
|
||||||
|
"column_break_3",
|
||||||
|
"program",
|
||||||
|
"course",
|
||||||
|
"student_group",
|
||||||
|
"section_break_8",
|
||||||
|
"other_details"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "academic_year",
|
"fieldname": "academic_year",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Academic Year",
|
"label": "Academic Year",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Academic Year",
|
"options": "Academic Year",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "academic_term",
|
"fieldname": "academic_term",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Academic Term",
|
"label": "Academic Term",
|
||||||
"length": 0,
|
"options": "Academic Term"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Academic Term",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "department",
|
"fieldname": "department",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Department",
|
"label": "Department",
|
||||||
"length": 0,
|
"options": "Department"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Department",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"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,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "program",
|
"fieldname": "program",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Program",
|
"label": "Program",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Program",
|
"options": "Program",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "course",
|
"fieldname": "course",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Course",
|
"label": "Course",
|
||||||
"length": 0,
|
"options": "Course"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Course",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "student_group",
|
"fieldname": "student_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Student Group",
|
"label": "Student Group",
|
||||||
"length": 0,
|
"options": "Student Group"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Student Group",
|
|
||||||
"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_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "other_details",
|
"fieldname": "other_details",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 0,
|
"label": "Other details"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Other details",
|
|
||||||
"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,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2018-11-04 03:38:30.902942",
|
"modified": "2020-10-23 15:15:50.759657",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Education",
|
"module": "Education",
|
||||||
"name": "Instructor Log",
|
"name": "Instructor Log",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"restrict_to_domain": "Education",
|
"restrict_to_domain": "Education",
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,11 @@ class TestProgram(unittest.TestCase):
|
|||||||
self.assertEqual(course[1].name, "_Test Course 2")
|
self.assertEqual(course[1].name, "_Test Course 2")
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for dt in ["Program", "Course", "Topic", "Article"]:
|
||||||
|
for entry in frappe.get_all(dt):
|
||||||
|
frappe.delete_doc(dt, entry.program)
|
||||||
|
|
||||||
def make_program(name):
|
def make_program(name):
|
||||||
program = frappe.get_doc({
|
program = frappe.get_doc({
|
||||||
"doctype": "Program",
|
"doctype": "Program",
|
||||||
@@ -68,7 +73,7 @@ def make_program_and_linked_courses(program_name, course_name_list):
|
|||||||
program = frappe.get_doc("Program", program_name)
|
program = frappe.get_doc("Program", program_name)
|
||||||
course_list = [make_course(course_name) for course_name in course_name_list]
|
course_list = [make_course(course_name) for course_name in course_name_list]
|
||||||
for course in course_list:
|
for course in course_list:
|
||||||
program.append("courses", {"course": course})
|
program.append("courses", {"course": course, "required": 1})
|
||||||
program.save()
|
program.save()
|
||||||
return program
|
return program
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Course",
|
"label": "Course",
|
||||||
"options": "Course",
|
"options": "Course",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "course.course_name",
|
"fetch_from": "course.course_name",
|
||||||
@@ -27,23 +25,19 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Course Name",
|
"label": "Course Name",
|
||||||
"read_only": 1,
|
"read_only": 1
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "1",
|
||||||
"fieldname": "required",
|
"fieldname": "required",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Mandatory",
|
"label": "Mandatory"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-09 18:56:10.213241",
|
"modified": "2020-09-15 18:14:22.816795",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Education",
|
"module": "Education",
|
||||||
"name": "Program Course",
|
"name": "Program Course",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user