Merge branch 'develop' of https://github.com/frappe/erpnext into develop
This commit is contained in:
commit
78ec254da4
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,6 +5,7 @@
|
||||
"_comments": null,
|
||||
"_liked_by": null,
|
||||
"_user_tags": null,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@ -17,17 +18,24 @@
|
||||
"docstatus": 0,
|
||||
"dt": "Address",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "tax_category",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"idx": 14,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"idx": 15,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "fax",
|
||||
"label": "Tax Category",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2018-12-28 22:29:21.828090",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Address-tax_category",
|
||||
@ -43,6 +51,66 @@
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"_assign": null,
|
||||
"_comments": null,
|
||||
"_liked_by": null,
|
||||
"_user_tags": null,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"creation": "2020-10-14 17:41:40.878179",
|
||||
"default": "0",
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"dt": "Address",
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_your_company_address",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"idx": 20,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"insert_after": "linked_with",
|
||||
"label": "Is Your Company Address",
|
||||
"length": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2020-10-14 17:41:40.878179",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Address-is_your_company_address",
|
||||
"no_copy": 0,
|
||||
"options": null,
|
||||
"owner": "Administrator",
|
||||
"parent": null,
|
||||
"parentfield": null,
|
||||
"parenttype": null,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
|
42
erpnext/accounts/custom/address.py
Normal file
42
erpnext/accounts/custom/address.py
Normal file
@ -0,0 +1,42 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.address.address import Address
|
||||
from frappe.contacts.doctype.address.address import get_address_templates
|
||||
|
||||
class ERPNextAddress(Address):
|
||||
def validate(self):
|
||||
self.validate_reference()
|
||||
super(ERPNextAddress, self).validate()
|
||||
|
||||
def link_address(self):
|
||||
"""Link address based on owner"""
|
||||
if self.is_your_company_address:
|
||||
return
|
||||
|
||||
return super(ERPNextAddress, self).link_address()
|
||||
|
||||
def validate_reference(self):
|
||||
if self.is_your_company_address and not [
|
||||
row for row in self.links if row.link_doctype == "Company"
|
||||
]:
|
||||
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
||||
title=_("Company Not Linked"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shipping_address(company, address = None):
|
||||
filters = [
|
||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||
["Dynamic Link", "link_name", "=", company],
|
||||
["Address", "is_your_company_address", "=", 1]
|
||||
]
|
||||
fields = ["*"]
|
||||
if address and frappe.db.get_value('Dynamic Link',
|
||||
{'parent': address, 'link_name': company}):
|
||||
filters.append(["Address", "name", "=", address])
|
||||
|
||||
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
||||
|
||||
if address:
|
||||
address_as_dict = address[0]
|
||||
name, address_template = get_address_templates(address_as_dict)
|
||||
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)
|
@ -108,7 +108,7 @@
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "Chart Of Accounts",
|
||||
"label": "Chart of Accounts",
|
||||
"link_to": "Account",
|
||||
"type": "DocType"
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ frappe.provide("frappe.treeview_settings")
|
||||
|
||||
frappe.treeview_settings["Account"] = {
|
||||
breadcrumb: "Accounts",
|
||||
title: __("Chart Of Accounts"),
|
||||
title: __("Chart of Accounts"),
|
||||
get_tree_root: false,
|
||||
filters: [
|
||||
{
|
||||
@ -97,7 +97,7 @@ frappe.treeview_settings["Account"] = {
|
||||
treeview.page.add_inner_button(__("Journal Entry"), function() {
|
||||
frappe.new_doc('Journal Entry', {company: get_company()});
|
||||
}, __('Create'));
|
||||
treeview.page.add_inner_button(__("New Company"), function() {
|
||||
treeview.page.add_inner_button(__("Company"), function() {
|
||||
frappe.new_doc('Company');
|
||||
}, __('Create'));
|
||||
|
||||
|
@ -195,7 +195,7 @@ def build_response_as_excel(writer):
|
||||
reader = csv.reader(f)
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
|
||||
xlsx_file = make_xlsx(reader, "Chart of Accounts Importer Template")
|
||||
|
||||
f.close()
|
||||
os.remove(filename)
|
||||
|
@ -210,7 +210,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
$.each(this.frm.doc.accounts || [], function(i, jvd) {
|
||||
frappe.model.set_default_values(jvd);
|
||||
});
|
||||
var posting_date = this.frm.posting_date;
|
||||
var posting_date = this.frm.doc.posting_date;
|
||||
if(!this.frm.doc.amended_from) this.frm.set_value('posting_date', posting_date || frappe.datetime.get_today());
|
||||
}
|
||||
},
|
||||
|
@ -195,88 +195,91 @@ def create_sales_invoice_record(qty=1):
|
||||
|
||||
def create_records():
|
||||
# create a new loyalty Account
|
||||
if frappe.db.exists("Account", "Loyalty - _TC"):
|
||||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"account_name": "Loyalty",
|
||||
"parent_account": "Direct Expenses - _TC",
|
||||
"company": "_Test Company",
|
||||
"is_group": 0,
|
||||
"account_type": "Expense Account",
|
||||
}).insert()
|
||||
if not frappe.db.exists("Account", "Loyalty - _TC"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"account_name": "Loyalty",
|
||||
"parent_account": "Direct Expenses - _TC",
|
||||
"company": "_Test Company",
|
||||
"is_group": 0,
|
||||
"account_type": "Expense Account",
|
||||
}).insert()
|
||||
|
||||
# create a new loyalty program Single tier
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Single Loyalty",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Single Tier Program",
|
||||
"conversion_factor": 1,
|
||||
"expiry_duration": 10,
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [{
|
||||
'tier_name': 'Silver',
|
||||
'collection_factor': 1000,
|
||||
'min_spent': 1000
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
# create a new customer
|
||||
frappe.get_doc({
|
||||
"customer_group": "_Test Customer Group",
|
||||
"customer_name": "Test Loyalty Customer",
|
||||
"customer_type": "Individual",
|
||||
"doctype": "Customer",
|
||||
"territory": "_Test Territory"
|
||||
}).insert()
|
||||
|
||||
# create a new loyalty program Multiple tier
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Multiple Loyalty",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Multiple Tier Program",
|
||||
"conversion_factor": 1,
|
||||
"expiry_duration": 10,
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [
|
||||
{
|
||||
if not frappe.db.exists("Loyalty Program","Test Single Loyalty"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Single Loyalty",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Single Tier Program",
|
||||
"conversion_factor": 1,
|
||||
"expiry_duration": 10,
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [{
|
||||
'tier_name': 'Silver',
|
||||
'collection_factor': 1000,
|
||||
'min_spent': 10000
|
||||
},
|
||||
{
|
||||
'tier_name': 'Gold',
|
||||
'collection_factor': 1000,
|
||||
'min_spent': 19000
|
||||
}
|
||||
]
|
||||
}).insert()
|
||||
'min_spent': 1000
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
# create a new customer
|
||||
if not frappe.db.exists("Customer","Test Loyalty Customer"):
|
||||
frappe.get_doc({
|
||||
"customer_group": "_Test Customer Group",
|
||||
"customer_name": "Test Loyalty Customer",
|
||||
"customer_type": "Individual",
|
||||
"doctype": "Customer",
|
||||
"territory": "_Test Territory"
|
||||
}).insert()
|
||||
|
||||
# create a new loyalty program Multiple tier
|
||||
if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Multiple Loyalty",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Multiple Tier Program",
|
||||
"conversion_factor": 1,
|
||||
"expiry_duration": 10,
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [
|
||||
{
|
||||
'tier_name': 'Silver',
|
||||
'collection_factor': 1000,
|
||||
'min_spent': 10000
|
||||
},
|
||||
{
|
||||
'tier_name': 'Gold',
|
||||
'collection_factor': 1000,
|
||||
'min_spent': 19000
|
||||
}
|
||||
]
|
||||
}).insert()
|
||||
|
||||
# create an item
|
||||
item = frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": "Loyal Item",
|
||||
"item_name": "Loyal Item",
|
||||
"item_group": "All Item Groups",
|
||||
"company": "_Test Company",
|
||||
"is_stock_item": 1,
|
||||
"opening_stock": 100,
|
||||
"valuation_rate": 10000,
|
||||
}).insert()
|
||||
if not frappe.db.exists("Item", "Loyal Item"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": "Loyal Item",
|
||||
"item_name": "Loyal Item",
|
||||
"item_group": "All Item Groups",
|
||||
"company": "_Test Company",
|
||||
"is_stock_item": 1,
|
||||
"opening_stock": 100,
|
||||
"valuation_rate": 10000,
|
||||
}).insert()
|
||||
|
||||
# create item price
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"price_list": "Standard Selling",
|
||||
"item_code": item.item_code,
|
||||
"price_list_rate": 10000
|
||||
}).insert()
|
||||
if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"price_list": "Standard Selling",
|
||||
"item_code": "Loyal Item",
|
||||
"price_list_rate": 10000
|
||||
}).insert()
|
||||
|
@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", {
|
||||
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
|
||||
onload: function() {
|
||||
var me = this;
|
||||
|
||||
this.frm.set_query("party", function() {
|
||||
check_mandatory(me.frm);
|
||||
});
|
||||
|
||||
this.frm.set_query("party_type", function() {
|
||||
return {
|
||||
"filters": {
|
||||
@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
});
|
||||
|
||||
this.frm.set_query('receivable_payable_account', function() {
|
||||
if(!me.frm.doc.company || !me.frm.doc.party_type) {
|
||||
frappe.msgprint(__("Please select Company and Party Type first"));
|
||||
} else {
|
||||
return{
|
||||
filters: {
|
||||
"company": me.frm.doc.company,
|
||||
"is_group": 0,
|
||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check_mandatory(me.frm);
|
||||
return {
|
||||
filters: {
|
||||
"company": me.frm.doc.company,
|
||||
"is_group": 0,
|
||||
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_query('bank_cash_account', function() {
|
||||
if(!me.frm.doc.company) {
|
||||
frappe.msgprint(__("Please select Company first"));
|
||||
} else {
|
||||
return{
|
||||
filters:[
|
||||
['Account', 'company', '=', me.frm.doc.company],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
||||
]
|
||||
};
|
||||
}
|
||||
check_mandatory(me.frm, true);
|
||||
return {
|
||||
filters:[
|
||||
['Account', 'company', '=', me.frm.doc.company],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'account_type', 'in', ['Bank', 'Cash']]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_value('party_type', '');
|
||||
this.frm.set_value('party', '');
|
||||
this.frm.set_value('receivable_payable_account', '');
|
||||
|
||||
var check_mandatory = (frm, only_company=false) => {
|
||||
var title = __("Mandatory");
|
||||
if (only_company && !frm.doc.company) {
|
||||
frappe.throw({message: __("Please Select a Company First"), title: title});
|
||||
} else if (!frm.doc.company || !frm.doc.party_type) {
|
||||
frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
|
||||
party: function() {
|
||||
var me = this
|
||||
if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
|
||||
if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.party.get_party_account",
|
||||
args: {
|
||||
@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
party: me.frm.doc.party
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc && r.message) {
|
||||
if (!r.exc && r.message) {
|
||||
me.frm.set_value("receivable_payable_account", r.message);
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ class PaymentReconciliation(Document):
|
||||
and `tabGL Entry`.against_voucher_type = %(voucher_type)s
|
||||
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
|
||||
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
|
||||
and `tabGL Entry`.is_cancelled = 0
|
||||
GROUP BY `tab{doc}`.name
|
||||
Having
|
||||
amount > 0
|
||||
|
@ -45,6 +45,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"description": "Provide the invoice portion in percent",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
@ -170,6 +171,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"description": "Give number of days according to prior selection",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
@ -305,7 +307,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-03-08 10:47:32.830478",
|
||||
"modified": "2020-10-14 10:47:32.830478",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
|
@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def init_user_and_profile():
|
||||
def init_user_and_profile(**args):
|
||||
user = 'test@example.com'
|
||||
test_user = frappe.get_doc('User', user)
|
||||
|
||||
@ -53,7 +53,7 @@ def init_user_and_profile():
|
||||
test_user.add_roles(*roles)
|
||||
frappe.set_user(user)
|
||||
|
||||
pos_profile = make_pos_profile()
|
||||
pos_profile = make_pos_profile(**args)
|
||||
pos_profile.append('applicable_for_users', {
|
||||
'default': 1,
|
||||
'user': user
|
||||
|
@ -92,21 +92,19 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
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"))
|
||||
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:
|
||||
if allow_negative_stock:
|
||||
return
|
||||
|
||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||
if not (flt(available_stock) > 0):
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.'
|
||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.').format(
|
||||
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse)), title=_("Not Available"))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.').format(
|
||||
d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)), title=_("Not Available"))
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
for d in self.get("items"):
|
||||
@ -117,14 +115,14 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
|
||||
if serialized and batched and (no_batch_selected or no_serial_selected):
|
||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.'
|
||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
||||
frappe.throw(_('Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.').format(
|
||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
if serialized and no_serial_selected:
|
||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.'
|
||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
||||
frappe.throw(_('Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.').format(
|
||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
if batched and no_batch_selected:
|
||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
|
||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.').format(
|
||||
d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
|
||||
def validate_return_items(self):
|
||||
if not self.get("is_return"): return
|
||||
@ -139,7 +137,8 @@ class POSInvoice(SalesInvoice):
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
|
||||
def validate_change_account(self):
|
||||
if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
||||
if self.change_amount and self.account_for_change_amount and \
|
||||
frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company:
|
||||
frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company))
|
||||
|
||||
def validate_change_amount(self):
|
||||
@ -150,7 +149,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
||||
|
||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||
msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||
|
||||
def verify_payment_amount(self):
|
||||
for entry in self.payments:
|
||||
@ -166,7 +165,7 @@ class POSInvoice(SalesInvoice):
|
||||
total_amount_in_payments += payment.amount
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if total_amount_in_payments < invoice_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
|
||||
frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
|
||||
|
||||
def validate_loyalty_transaction(self):
|
||||
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
||||
|
@ -7,6 +7,8 @@ import frappe
|
||||
import unittest, copy, time
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
class TestPOSInvoice(unittest.TestCase):
|
||||
def test_timestamp_change(self):
|
||||
@ -221,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
se = make_serialized_item(company='_Test Company with perpetual inventory',
|
||||
target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1')
|
||||
se = make_serialized_item(company='_Test Company',
|
||||
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
|
||||
pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
||||
pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0]
|
||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
|
||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
|
||||
pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1',
|
||||
account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1',
|
||||
expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1',
|
||||
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000})
|
||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
|
||||
@ -294,7 +296,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270
|
||||
})
|
||||
pos_inv.submit()
|
||||
|
||||
@ -307,8 +309,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
merge_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice)
|
||||
self.assertEqual(sales_invoice.grand_total, 3500)
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 3470)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
@ -348,8 +351,55 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
merge_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice)
|
||||
self.assertEqual(sales_invoice.rounded_total, 840)
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 840)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_merging_with_validate_selling_price(self):
|
||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
|
||||
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||
|
||||
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.append('taxes', {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Service Tax",
|
||||
"rate": 14,
|
||||
'included_in_print_rate': 1
|
||||
})
|
||||
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
|
||||
})
|
||||
pos_inv2.append('taxes', {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Service Tax",
|
||||
"rate": 14,
|
||||
'included_in_print_rate': 1
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
merge_pos_invoices()
|
||||
|
||||
pos_inv2.load_from_db()
|
||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||
self.assertEqual(rounded_total, 400)
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
|
||||
|
||||
def create_pos_invoice(**args):
|
||||
args = frappe._dict(args)
|
||||
@ -364,8 +414,6 @@ def create_pos_invoice(**args):
|
||||
pos_inv.is_pos = 1
|
||||
pos_inv.pos_profile = args.pos_profile or pos_profile.name
|
||||
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
if args.posting_date:
|
||||
pos_inv.set_posting_time = 1
|
||||
pos_inv.posting_date = args.posting_date or frappe.utils.nowdate()
|
||||
@ -379,6 +427,8 @@ def create_pos_invoice(**args):
|
||||
pos_inv.conversion_rate = args.conversion_rate or 1
|
||||
pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC"
|
||||
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
pos_inv.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
|
@ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
|
||||
frm.call({
|
||||
method: "erpnext.accounts.doctype.pos_profile.pos_profile.get_series",
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
set_field_options("naming_series", r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Profile', {
|
||||
|
@ -8,7 +8,6 @@
|
||||
"field_order": [
|
||||
"disabled",
|
||||
"section_break_2",
|
||||
"naming_series",
|
||||
"customer",
|
||||
"company",
|
||||
"country",
|
||||
@ -59,17 +58,6 @@
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "[Select]",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
@ -323,7 +311,7 @@
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-29 12:20:30.977272",
|
||||
"modified": "2020-10-01 17:29:27.759088",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
@ -109,10 +109,6 @@ def get_child_nodes(group_type, root):
|
||||
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
|
||||
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_series():
|
||||
return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s"
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
@ -361,6 +361,7 @@
|
||||
"fieldname": "bill_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Supplier Invoice Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "bill_date",
|
||||
"oldfieldtype": "Date",
|
||||
"print_hide": 1
|
||||
@ -1333,8 +1334,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 23:20:04.466153",
|
||||
"modified": "2020-09-21 12:22:09.164068",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -711,7 +711,8 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount / self.conversion_rate)
|
||||
}, item=item))
|
||||
else:
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company)
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
asset_category=item.asset_category,company=self.company)
|
||||
|
||||
cwip_account_currency = get_account_currency(cwip_account)
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
|
@ -1002,7 +1002,8 @@ def make_purchase_invoice(**args):
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"project": args.project,
|
||||
"rejected_warehouse": args.rejected_warehouse or "",
|
||||
"rejected_serial_no": args.rejected_serial_no or ""
|
||||
"rejected_serial_no": args.rejected_serial_no or "",
|
||||
"asset_location": args.location or ""
|
||||
})
|
||||
|
||||
if args.get_taxes_and_charges:
|
||||
|
@ -19,6 +19,7 @@
|
||||
"is_return",
|
||||
"column_break1",
|
||||
"company",
|
||||
"company_tax_id",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"set_posting_time",
|
||||
@ -1926,6 +1927,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.is_pos && doc.is_consolidated)",
|
||||
"fieldname": "is_consolidated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Consolidated",
|
||||
@ -1940,6 +1942,13 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Is Internal Customer",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "company.tax_id",
|
||||
"fieldname": "company_tax_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Company Tax ID",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
|
@ -428,7 +428,7 @@ class SalesInvoice(SellingController):
|
||||
if pos.get('account_for_change_amount'):
|
||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||
|
||||
for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name',
|
||||
for fieldname in ('currency', 'letter_head', 'tc_name',
|
||||
'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges',
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||
@ -572,7 +572,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def validate_pos(self):
|
||||
if self.is_return:
|
||||
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \
|
||||
1.0/(10.0**(self.precision("grand_total") + 1.0)):
|
||||
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
||||
|
||||
|
@ -345,13 +345,14 @@ class Subscription(Document):
|
||||
invoice.set_taxes()
|
||||
|
||||
# Due date
|
||||
invoice.append(
|
||||
'payment_schedule',
|
||||
{
|
||||
'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
|
||||
'invoice_portion': 100
|
||||
}
|
||||
)
|
||||
if self.days_until_due:
|
||||
invoice.append(
|
||||
'payment_schedule',
|
||||
{
|
||||
'due_date': add_days(invoice.posting_date, cint(self.days_until_due)),
|
||||
'invoice_portion': 100
|
||||
}
|
||||
)
|
||||
|
||||
# Discounts
|
||||
if self.additional_discount_percentage:
|
||||
|
@ -106,6 +106,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai
|
||||
from `tabGL Entry`
|
||||
where company = %s and
|
||||
party in %s and fiscal_year=%s and credit > 0
|
||||
and is_opening = 'No'
|
||||
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
|
||||
|
||||
vouchers = [d.voucher_no for d in entries]
|
||||
@ -192,6 +193,7 @@ def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=No
|
||||
select distinct voucher_no
|
||||
from `tabGL Entry`
|
||||
where party in %s and %s and debit > 0
|
||||
and is_opening = 'No'
|
||||
""", (tuple(suppliers), condition)) or []
|
||||
|
||||
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
|
||||
|
@ -171,7 +171,7 @@ def validate_account_for_perpetual_inventory(gl_map):
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||
.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"),
|
||||
currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency"))
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Chart Of Accounts"
|
||||
"step": "Chart of Accounts"
|
||||
},
|
||||
{
|
||||
"step": "Setup Taxes"
|
||||
|
@ -10,11 +10,11 @@
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-05-14 17:40:28.410447",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Chart Of Accounts",
|
||||
"name": "Chart of Accounts",
|
||||
"owner": "Administrator",
|
||||
"path": "Tree/Account",
|
||||
"reference_document": "Account",
|
||||
"show_full_form": 0,
|
||||
"title": "Review Chart Of Accounts",
|
||||
"title": "Review Chart of Accounts",
|
||||
"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))
|
||||
|
||||
if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount:
|
||||
frappe.throw(_("The unallocated amount of Payment Entry {0} \
|
||||
is greater than the Bank Transaction's unallocated amount").format(payment_name))
|
||||
frappe.throw(_("The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount").format(payment_name))
|
||||
|
||||
if transaction.unallocated_amount == 0:
|
||||
frappe.throw(_("This bank transaction is already fully reconciled"))
|
||||
@ -83,50 +82,30 @@ def check_matching_amount(bank_account, company, transaction):
|
||||
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
|
||||
["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
|
||||
|
||||
if transaction.credit > 0:
|
||||
journal_entries = frappe.db.sql("""
|
||||
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
|
||||
'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.credit_in_account_currency as paid_amount
|
||||
FROM
|
||||
`tabJournal Entry Account` as jea
|
||||
JOIN
|
||||
`tabJournal Entry` as je
|
||||
ON
|
||||
jea.parent = je.name
|
||||
WHERE
|
||||
(je.clearance_date is null or je.clearance_date='0000-00-00')
|
||||
AND
|
||||
jea.account = %(bank_account)s
|
||||
AND
|
||||
jea.credit_in_account_currency like %(txt)s
|
||||
AND
|
||||
je.docstatus = 1
|
||||
""", {
|
||||
'bank_account': bank_account,
|
||||
'txt': '%%%s%%' % amount
|
||||
}, as_dict=True)
|
||||
jea_side = "debit" if transaction.credit > 0 else "credit"
|
||||
journal_entries = frappe.db.sql(f"""
|
||||
SELECT
|
||||
'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.{jea_side}_in_account_currency as paid_amount
|
||||
FROM
|
||||
`tabJournal Entry Account` as jea
|
||||
JOIN
|
||||
`tabJournal Entry` as je
|
||||
ON
|
||||
jea.parent = je.name
|
||||
WHERE
|
||||
(je.clearance_date is null or je.clearance_date='0000-00-00')
|
||||
AND
|
||||
jea.account = %(bank_account)s
|
||||
AND
|
||||
jea.{jea_side}_in_account_currency like %(txt)s
|
||||
AND
|
||||
je.docstatus = 1
|
||||
""", {
|
||||
'bank_account': bank_account,
|
||||
'txt': '%%%s%%' % amount
|
||||
}, as_dict=True)
|
||||
|
||||
if transaction.credit > 0:
|
||||
sales_invoices = frappe.db.sql("""
|
||||
|
@ -203,7 +203,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
|
||||
return out
|
||||
|
||||
@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`.
|
||||
Will first search in party (Customer / Supplier) record, if not found,
|
||||
will search in group (Customer Group / Supplier Group),
|
||||
|
@ -3,6 +3,14 @@
|
||||
|
||||
frappe.query_reports["Bank Reconciliation Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname":"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"]: "",
|
||||
"reqd": 1,
|
||||
"get_query": function() {
|
||||
var company = frappe.query_report.get_filter_value('company')
|
||||
return {
|
||||
"query": "erpnext.controllers.queries.get_account_list",
|
||||
"filters": [
|
||||
['Account', 'account_type', 'in', 'Bank, Cash'],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'disabled', '=', 0],
|
||||
['Account', 'company', '=', company],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
76
erpnext/accounts/report/pos_register/pos_register.js
Normal file
76
erpnext/accounts/report/pos_register/pos_register.js
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["POS Register"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
"reqd": 1,
|
||||
"width": "60px"
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1,
|
||||
"width": "60px"
|
||||
},
|
||||
{
|
||||
"fieldname":"pos_profile",
|
||||
"label": __("POS Profile"),
|
||||
"fieldtype": "Link",
|
||||
"options": "POS Profile"
|
||||
},
|
||||
{
|
||||
"fieldname":"cashier",
|
||||
"label": __("Cashier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname":"customer",
|
||||
"label": __("Customer"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"fieldname":"mode_of_payment",
|
||||
"label": __("Payment Method"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"fieldname":"group_by",
|
||||
"label": __("Group by"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["", "POS Profile", "Cashier", "Payment Method", "Customer"],
|
||||
"default": "POS Profile"
|
||||
},
|
||||
{
|
||||
"fieldname":"is_return",
|
||||
"label": __("Is Return"),
|
||||
"fieldtype": "Check"
|
||||
},
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
if (data && data.bold) {
|
||||
value = value.bold();
|
||||
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
30
erpnext/accounts/report/pos_register/pos_register.json
Normal file
30
erpnext/accounts/report/pos_register/pos_register.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2020-09-10 19:25:03.766871",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{}",
|
||||
"modified": "2020-09-10 19:25:15.851331",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Register",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "POS Invoice",
|
||||
"report_name": "POS Register",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
}
|
||||
]
|
||||
}
|
222
erpnext/accounts/report/pos_register/pos_register.py
Normal file
222
erpnext/accounts/report/pos_register/pos_register.py
Normal file
@ -0,0 +1,222 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _, _dict
|
||||
from erpnext import get_company_currency, get_default_company
|
||||
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
return [], []
|
||||
|
||||
validate_filters(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
|
||||
group_by_field = get_group_by_field(filters.get("group_by"))
|
||||
|
||||
pos_entries = get_pos_entries(filters, group_by_field)
|
||||
if group_by_field != "mode_of_payment":
|
||||
concat_mode_of_payments(pos_entries)
|
||||
|
||||
# return only entries if group by is unselected
|
||||
if not group_by_field:
|
||||
return columns, pos_entries
|
||||
|
||||
# handle grouping
|
||||
invoice_map, grouped_data = {}, []
|
||||
for d in pos_entries:
|
||||
invoice_map.setdefault(d[group_by_field], []).append(d)
|
||||
|
||||
for key in invoice_map:
|
||||
invoices = invoice_map[key]
|
||||
grouped_data += invoices
|
||||
add_subtotal_row(grouped_data, invoices, group_by_field, key)
|
||||
|
||||
# move group by column to first position
|
||||
column_index = next((index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None)
|
||||
columns.insert(0, columns.pop(column_index))
|
||||
|
||||
return columns, grouped_data
|
||||
|
||||
def get_pos_entries(filters, group_by_field):
|
||||
conditions = get_conditions(filters)
|
||||
order_by = "p.posting_date"
|
||||
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
||||
if group_by_field == "mode_of_payment":
|
||||
select_mop_field = ", sip.mode_of_payment"
|
||||
from_sales_invoice_payment = ", `tabSales Invoice Payment` sip"
|
||||
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND"
|
||||
order_by += ", sip.mode_of_payment"
|
||||
|
||||
elif group_by_field:
|
||||
order_by += ", p.{}".format(group_by_field)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
||||
p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount,
|
||||
p.customer, p.is_return {select_mop_field}
|
||||
FROM
|
||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||
WHERE
|
||||
{group_by_mop_condition}
|
||||
{conditions}
|
||||
ORDER BY
|
||||
{order_by}
|
||||
""".format(
|
||||
select_mop_field=select_mop_field,
|
||||
from_sales_invoice_payment=from_sales_invoice_payment,
|
||||
group_by_mop_condition=group_by_mop_condition,
|
||||
conditions=conditions,
|
||||
order_by=order_by
|
||||
), filters, as_dict=1)
|
||||
|
||||
def concat_mode_of_payments(pos_entries):
|
||||
mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries]))
|
||||
for entry in pos_entries:
|
||||
if mode_of_payments.get(entry.pos_invoice):
|
||||
entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, []))
|
||||
|
||||
def add_subtotal_row(data, group_invoices, group_by_field, group_by_value):
|
||||
grand_total = sum([d.grand_total for d in group_invoices])
|
||||
paid_amount = sum([d.paid_amount for d in group_invoices])
|
||||
data.append({
|
||||
group_by_field: group_by_value,
|
||||
"grand_total": grand_total,
|
||||
"paid_amount": paid_amount,
|
||||
"bold": 1
|
||||
})
|
||||
data.append({})
|
||||
|
||||
def validate_filters(filters):
|
||||
if not filters.get("company"):
|
||||
frappe.throw(_("{0} is mandatory").format(_("Company")))
|
||||
|
||||
if not filters.get("from_date") and not filters.get("to_date"):
|
||||
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
||||
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')):
|
||||
frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile"))
|
||||
|
||||
if (filters.get("customer") and filters.get("group_by") == _('Customer')):
|
||||
frappe.throw(_("Can not filter based on Customer, if grouped by Customer"))
|
||||
|
||||
if (filters.get("owner") and filters.get("group_by") == _('Cashier')):
|
||||
frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier"))
|
||||
|
||||
if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')):
|
||||
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
|
||||
company=filters.get("company"),
|
||||
from_date=filters.get("from_date"),
|
||||
to_date=filters.get("to_date"))
|
||||
|
||||
if filters.get("pos_profile"):
|
||||
conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
|
||||
|
||||
if filters.get("owner"):
|
||||
conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
|
||||
|
||||
if filters.get("customer"):
|
||||
conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
|
||||
|
||||
if filters.get("is_return"):
|
||||
conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += """
|
||||
AND EXISTS(
|
||||
SELECT name FROM `tabSales Invoice Payment` sip
|
||||
WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s
|
||||
)"""
|
||||
|
||||
return conditions
|
||||
|
||||
def get_group_by_field(group_by):
|
||||
group_by_field = ""
|
||||
|
||||
if group_by == "POS Profile":
|
||||
group_by_field = "pos_profile"
|
||||
elif group_by == "Cashier":
|
||||
group_by_field = "owner"
|
||||
elif group_by == "Customer":
|
||||
group_by_field = "customer"
|
||||
elif group_by == "Payment Method":
|
||||
group_by_field = "mode_of_payment"
|
||||
|
||||
return group_by_field
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{
|
||||
"label": _("Posting Date"),
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"label": _("POS Invoice"),
|
||||
"fieldname": "pos_invoice",
|
||||
"fieldtype": "Link",
|
||||
"options": "POS Invoice",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Customer"),
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("POS Profile"),
|
||||
"fieldname": "pos_profile",
|
||||
"fieldtype": "Link",
|
||||
"options": "POS Profile",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"label": _("Cashier"),
|
||||
"fieldname": "owner",
|
||||
"fieldtype": "Link",
|
||||
"options": "User",
|
||||
"width": 140
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "company:currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Paid Amount"),
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "company:currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Payment Method"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Is Return"),
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Data",
|
||||
"width": 80
|
||||
},
|
||||
]
|
||||
|
||||
return columns
|
@ -466,29 +466,37 @@ class Asset(AccountsController):
|
||||
|
||||
def validate_make_gl_entry(self):
|
||||
purchase_document = self.get_purchase_document()
|
||||
asset_bought_with_invoice = purchase_document == self.purchase_invoice
|
||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||
# check if expense already has been booked in case of cwip was enabled after purchasing asset
|
||||
expense_booked = False
|
||||
cwip_booked = False
|
||||
|
||||
if asset_bought_with_invoice:
|
||||
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||
(purchase_document, fixed_asset_account), as_dict=1)
|
||||
else:
|
||||
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||
(purchase_document, cwip_account), as_dict=1)
|
||||
|
||||
if cwip_enabled and (expense_booked or not cwip_booked):
|
||||
# if expense has already booked from invoice or cwip is booked from receipt
|
||||
if not purchase_document:
|
||||
return False
|
||||
elif not cwip_enabled and (not expense_booked or cwip_booked):
|
||||
# if cwip is disabled but expense hasn't been booked yet
|
||||
return True
|
||||
elif cwip_enabled:
|
||||
# default condition
|
||||
return True
|
||||
|
||||
asset_bought_with_invoice = (purchase_document == self.purchase_invoice)
|
||||
fixed_asset_account = self.get_fixed_asset_account()
|
||||
|
||||
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||
cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled)
|
||||
|
||||
query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s"""
|
||||
if asset_bought_with_invoice:
|
||||
# with invoice purchase either expense or cwip has been booked
|
||||
expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1)
|
||||
if expense_booked:
|
||||
# if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled
|
||||
return False
|
||||
|
||||
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
|
||||
if cwip_booked:
|
||||
# if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled
|
||||
return True
|
||||
else:
|
||||
# with receipt purchase either cwip has been booked or no entries have been made
|
||||
if not cwip_account:
|
||||
# if cwip account isn't available do not make gl entries
|
||||
return False
|
||||
|
||||
cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1)
|
||||
# if cwip is not booked from receipt then do not make gl entries
|
||||
# if cwip is booked from receipt then make gl entries
|
||||
return cwip_booked
|
||||
|
||||
def get_purchase_document(self):
|
||||
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
|
||||
@ -496,20 +504,25 @@ class Asset(AccountsController):
|
||||
|
||||
return purchase_document
|
||||
|
||||
def get_asset_accounts(self):
|
||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
||||
asset_category = self.asset_category, company = self.company)
|
||||
def get_fixed_asset_account(self):
|
||||
return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company)
|
||||
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
self.name, self.asset_category, self.company)
|
||||
def get_cwip_account(self, cwip_enabled=False):
|
||||
cwip_account = None
|
||||
try:
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company)
|
||||
except:
|
||||
# if no cwip account found in category or company and "cwip is enabled" then raise else silently pass
|
||||
if cwip_enabled:
|
||||
raise
|
||||
|
||||
return fixed_asset_account, cwip_account
|
||||
return cwip_account
|
||||
|
||||
def make_gl_entries(self):
|
||||
gl_entries = []
|
||||
|
||||
purchase_document = self.get_purchase_document()
|
||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
||||
|
||||
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
||||
|
||||
@ -561,14 +574,18 @@ class Asset(AccountsController):
|
||||
return 100 * (1 - flt(depreciation_rate, float_precision))
|
||||
|
||||
def update_maintenance_status():
|
||||
assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1})
|
||||
assets = frappe.get_all(
|
||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
|
||||
)
|
||||
|
||||
for asset in assets:
|
||||
asset = frappe.get_doc("Asset", asset.name)
|
||||
if frappe.db.exists('Asset Maintenance Task', {'parent': asset.name, 'next_due_date': today()}):
|
||||
asset.set_status('In Maintenance')
|
||||
if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}):
|
||||
asset.set_status('Out of Order')
|
||||
if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}):
|
||||
asset.set_status("Out of Order")
|
||||
elif frappe.db.exists("Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}):
|
||||
asset.set_status("In Maintenance")
|
||||
else:
|
||||
asset.set_status()
|
||||
|
||||
def make_post_gl_entry():
|
||||
|
||||
|
@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad
|
||||
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset
|
||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice
|
||||
|
||||
class TestAsset(unittest.TestCase):
|
||||
@ -558,81 +559,6 @@ class TestAsset(unittest.TestCase):
|
||||
|
||||
self.assertEqual(gle, expected_gle)
|
||||
|
||||
def test_gle_with_cwip_toggling(self):
|
||||
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=5000, do_not_submit=True, location="Test Location")
|
||||
pr.set('taxes', [{
|
||||
'category': 'Total',
|
||||
'add_deduct_tax': 'Add',
|
||||
'charge_type': 'On Net Total',
|
||||
'account_head': '_Test Account Service Tax - _TC',
|
||||
'description': '_Test Account Service Tax',
|
||||
'cost_center': 'Main - _TC',
|
||||
'rate': 5.0
|
||||
}, {
|
||||
'category': 'Valuation and Total',
|
||||
'add_deduct_tax': 'Add',
|
||||
'charge_type': 'On Net Total',
|
||||
'account_head': '_Test Account Shipping Charges - _TC',
|
||||
'description': '_Test Account Shipping Charges',
|
||||
'cost_center': 'Main - _TC',
|
||||
'rate': 5.0
|
||||
}])
|
||||
pr.submit()
|
||||
expected_gle = (
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0)
|
||||
)
|
||||
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Receipt' and voucher_no = %s
|
||||
order by account""", pr.name)
|
||||
self.assertEqual(pr_gle, expected_gle)
|
||||
|
||||
pi = make_invoice(pr.name)
|
||||
pi.submit()
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no = %s
|
||||
order by account""", pi.name)
|
||||
self.assertEqual(pi_gle, expected_gle)
|
||||
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
month_end_date = get_last_day(nowdate())
|
||||
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
||||
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
|
||||
asset_doc.append("finance_books", {
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": month_end_date
|
||||
})
|
||||
|
||||
# disable cwip and try submitting
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset_doc.submit()
|
||||
# asset should have gl entries even if cwip is disabled
|
||||
expected_gle = (
|
||||
("_Test Fixed Asset - _TC", 5250.0, 0.0),
|
||||
("CWIP Account - _TC", 0.0, 5250.0)
|
||||
)
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Asset' and voucher_no = %s
|
||||
order by account""", asset_doc.name)
|
||||
self.assertEqual(gle, expected_gle)
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
|
||||
def test_expense_head(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=2, rate=200000.0, location="Test Location")
|
||||
@ -641,6 +567,74 @@ class TestAsset(unittest.TestCase):
|
||||
|
||||
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||
|
||||
def test_asset_cwip_toggling_cases(self):
|
||||
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
||||
name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"])
|
||||
cwip_acc = "CWIP Account - _TC"
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "")
|
||||
|
||||
# case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set
|
||||
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 1 -- PR with cwip disabled, Asset with cwip enabled
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 2 -- PR with cwip enabled, Asset with cwip disabled
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location")
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertTrue(gle)
|
||||
|
||||
# case 3 -- PI with cwip disabled, Asset with cwip enabled
|
||||
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 4 -- PI with cwip enabled, Asset with cwip disabled
|
||||
pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1)
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name)
|
||||
self.assertTrue(gle)
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
||||
|
||||
def create_asset_data():
|
||||
if not frappe.db.exists("Asset Category", "Computers"):
|
||||
create_asset_category()
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint
|
||||
from frappe.utils import cint, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AssetCategory(Document):
|
||||
@ -13,6 +13,7 @@ class AssetCategory(Document):
|
||||
self.validate_finance_books()
|
||||
self.validate_account_types()
|
||||
self.validate_account_currency()
|
||||
self.valide_cwip_account()
|
||||
|
||||
def validate_finance_books(self):
|
||||
for d in self.finance_books:
|
||||
@ -59,6 +60,21 @@ class AssetCategory(Document):
|
||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
||||
title=_("Invalid Account"))
|
||||
|
||||
def valide_cwip_account(self):
|
||||
if self.enable_cwip_accounting:
|
||||
missing_cwip_accounts_for_company = []
|
||||
for d in self.accounts:
|
||||
if (not d.capital_work_in_progress_account and
|
||||
not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")):
|
||||
missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name))
|
||||
|
||||
if missing_cwip_accounts_for_company:
|
||||
msg = _("""To enable Capital Work in Progress Accounting, """)
|
||||
msg += _("""you must select Capital Work in Progress Account in accounts table""")
|
||||
msg += "<br><br>"
|
||||
msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company))
|
||||
frappe.throw(msg, title=_("Missing Account"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):
|
||||
|
@ -27,3 +27,21 @@ class TestAssetCategory(unittest.TestCase):
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
def test_cwip_accounting(self):
|
||||
company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account")
|
||||
frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "")
|
||||
|
||||
asset_category = frappe.new_doc("Asset Category")
|
||||
asset_category.asset_category_name = "Computers"
|
||||
asset_category.enable_cwip_accounting = 1
|
||||
|
||||
asset_category.total_number_of_depreciations = 3
|
||||
asset_category.frequency_of_depreciation = 3
|
||||
asset_category.append("accounts", {
|
||||
"company_name": "_Test Company",
|
||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||
"depreciation_expense_account": "_Test Depreciations - _TC"
|
||||
})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, asset_category.insert)
|
@ -33,7 +33,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Other Reports",
|
||||
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
|
||||
"links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Quotation Comparison\",\n \"name\": \"Supplier Quotation Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -60,7 +60,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Buying",
|
||||
"modified": "2020-06-29 19:30:24.983050",
|
||||
"modified": "2020-09-30 14:40:55.638458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying",
|
||||
|
@ -651,12 +651,12 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
make_subcontracted_item(item_code)
|
||||
|
||||
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')
|
||||
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])
|
||||
self.assertEquals(exploded_items, supplied_items)
|
||||
|
||||
@ -664,7 +664,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
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])
|
||||
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)
|
||||
|
||||
|
@ -22,8 +22,6 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.add_fetch('email_template', 'response', '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"))
|
||||
}
|
||||
@ -179,7 +177,7 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
dialog.hide();
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation",
|
||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||
args: {
|
||||
"source_name": doc.name,
|
||||
"for_supplier": args.supplier
|
||||
@ -194,6 +192,66 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
});
|
||||
});
|
||||
dialog.show()
|
||||
},
|
||||
|
||||
preview: (frm) => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Preview Email'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Supplier'),
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'supplier',
|
||||
options: frm.doc.suppliers.map(row => row.supplier),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldtype: 'Column Break',
|
||||
fieldname: 'col_break_1',
|
||||
},
|
||||
{
|
||||
label: __('Subject'),
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'subject',
|
||||
read_only: 1,
|
||||
depends_on: 'subject'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Section Break',
|
||||
fieldname: 'sec_break_1',
|
||||
hide_border: 1
|
||||
},
|
||||
{
|
||||
label: __('Email'),
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'email_preview'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Section Break',
|
||||
fieldname: 'sec_break_2'
|
||||
},
|
||||
{
|
||||
label: __('Note'),
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'note'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
dialog.fields_dict['supplier'].df.onchange = () => {
|
||||
var supplier = dialog.get_value('supplier');
|
||||
frm.call('get_supplier_email_preview', {supplier: supplier}).then(result => {
|
||||
dialog.fields_dict.email_preview.$wrapper.empty();
|
||||
dialog.fields_dict.email_preview.$wrapper.append(result.message);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
dialog.fields_dict.note.$wrapper.append(`<p class="small text-muted">This is a preview of the email to be sent. A PDF of the document will
|
||||
automatically be attached with the email.</p>`);
|
||||
|
||||
dialog.set_value("subject", frm.doc.subject);
|
||||
dialog.show();
|
||||
}
|
||||
})
|
||||
|
||||
@ -276,7 +334,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
})
|
||||
}, __("Get items from"));
|
||||
// Get items from Opportunity
|
||||
this.frm.add_custom_button(__('Opportunity'),
|
||||
this.frm.add_custom_button(__('Opportunity'),
|
||||
function() {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"actions": "",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2016-02-25 01:24:07.224790",
|
||||
@ -19,7 +19,12 @@
|
||||
"items",
|
||||
"link_to_mrs",
|
||||
"supplier_response_section",
|
||||
"salutation",
|
||||
"email_template",
|
||||
"col_break_email_1",
|
||||
"subject",
|
||||
"preview",
|
||||
"sec_break_email_2",
|
||||
"message_for_supplier",
|
||||
"terms_section_break",
|
||||
"tc_name",
|
||||
@ -126,8 +131,10 @@
|
||||
"label": "Link to Material Requests"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "supplier_response_section",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "email_template",
|
||||
@ -137,6 +144,8 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "email_template.response",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "message_for_supplier",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
@ -230,12 +239,45 @@
|
||||
"options": "Request for Quotation",
|
||||
"print_hide": 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",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-25 14:37:21.140194",
|
||||
"modified": "2020-10-01 14:54:50.888729",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
|
@ -51,7 +51,7 @@ class RequestforQuotation(BuyingController):
|
||||
|
||||
def validate_email_id(self, args):
|
||||
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):
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
@ -62,17 +62,31 @@ class RequestforQuotation(BuyingController):
|
||||
def on_cancel(self):
|
||||
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):
|
||||
"""Sends RFQ mail to involved suppliers."""
|
||||
for rfq_supplier in self.suppliers:
|
||||
if rfq_supplier.send_email:
|
||||
self.validate_email_id(rfq_supplier)
|
||||
|
||||
# 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.supplier_rfq_mail(rfq_supplier, update_password_link, self.get_link())
|
||||
rfq_supplier.email_sent = 1
|
||||
if not rfq_supplier.contact:
|
||||
rfq_supplier.contact = contact
|
||||
rfq_supplier.save()
|
||||
|
||||
def get_link(self):
|
||||
@ -87,18 +101,19 @@ class RequestforQuotation(BuyingController):
|
||||
|
||||
def update_supplier_contact(self, rfq_supplier, link):
|
||||
'''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):
|
||||
user = frappe.get_doc("User", rfq_supplier.email_id)
|
||||
else:
|
||||
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:
|
||||
contact = frappe.get_doc("Contact", rfq_supplier.contact)
|
||||
else:
|
||||
@ -115,6 +130,10 @@ class RequestforQuotation(BuyingController):
|
||||
|
||||
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):
|
||||
user = frappe.get_doc({
|
||||
'doctype': 'User',
|
||||
@ -129,22 +148,36 @@ class RequestforQuotation(BuyingController):
|
||||
|
||||
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'])
|
||||
if full_name == "Guest":
|
||||
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 = {
|
||||
'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,
|
||||
'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"
|
||||
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
||||
message = frappe.get_template(template).render(args)
|
||||
|
||||
if preview:
|
||||
return message
|
||||
|
||||
attachments = self.get_attachments()
|
||||
|
||||
self.send_email(data, sender, subject, message, attachments)
|
||||
@ -214,14 +247,14 @@ def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
|
||||
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
|
||||
limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')})
|
||||
|
||||
# This method is used to make supplier quotation from material request form.
|
||||
@frappe.whitelist()
|
||||
def make_supplier_quotation(source_name, for_supplier, target_doc=None):
|
||||
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
|
||||
def postprocess(source, target_doc):
|
||||
target_doc.supplier = for_supplier
|
||||
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
|
||||
target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
|
||||
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
|
||||
if for_supplier:
|
||||
target_doc.supplier = for_supplier
|
||||
args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True)
|
||||
target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company)
|
||||
target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
|
||||
set_missing_values(source, target_doc)
|
||||
|
||||
doclist = get_mapped_doc("Request for Quotation", source_name, {
|
||||
@ -354,3 +387,32 @@ def get_supplier_tag():
|
||||
frappe.cache().hset("Supplier", "Tags", tags)
|
||||
|
||||
return frappe.cache().hget("Supplier", "Tags")
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = ""
|
||||
if txt:
|
||||
conditions += "and rfq.name like '%%"+txt+"%%' "
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date"))
|
||||
|
||||
rfq_data = frappe.db.sql("""
|
||||
select
|
||||
distinct rfq.name, rfq.transaction_date,
|
||||
rfq.company
|
||||
from
|
||||
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
|
||||
where
|
||||
rfq.name = rfq_supplier.parent
|
||||
and rfq_supplier.supplier = '{0}'
|
||||
and rfq.docstatus = 1
|
||||
and rfq.company = '{1}'
|
||||
{2}
|
||||
order by rfq.transaction_date ASC
|
||||
limit %(page_len)s offset %(start)s """ \
|
||||
.format(filters.get("supplier"), filters.get("company"), conditions),
|
||||
{"page_len": page_len, "start": start}, as_dict=1)
|
||||
|
||||
return rfq_data
|
@ -9,7 +9,7 @@ import frappe
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
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.crm.doctype.opportunity.test_opportunity import make_opportunity
|
||||
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
||||
@ -22,7 +22,7 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending')
|
||||
|
||||
# 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()
|
||||
|
||||
# No Quote first supplier quotation
|
||||
@ -37,10 +37,10 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
def test_make_supplier_quotation(self):
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier)
|
||||
@ -62,7 +62,7 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
|
||||
rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
|
||||
|
||||
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()
|
||||
|
||||
frappe.form_dict = frappe.local("form_dict")
|
||||
|
@ -1,362 +1,112 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2016-03-29 05:59:11.896885",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"send_email",
|
||||
"email_sent",
|
||||
"supplier",
|
||||
"contact",
|
||||
"no_quote",
|
||||
"quote_status",
|
||||
"column_break_3",
|
||||
"supplier_name",
|
||||
"email_id",
|
||||
"download_pdf"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "send_email",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Send Email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Send Email"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus >= 1",
|
||||
"fieldname": "email_sent",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Email Sent",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 4,
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Supplier",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Supplier",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"allow_on_submit": 1,
|
||||
"columns": 3,
|
||||
"fieldname": "contact",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Contact",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Contact",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Contact"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "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
|
||||
"label": "No Quote"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Quote Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Pending\nReceived\nNo Quote",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "supplier.supplier_name",
|
||||
"fieldname": "supplier_name",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Supplier Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Supplier Name"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fetch_from": "contact.email_id",
|
||||
"fieldname": "email_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Email Id",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"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
|
||||
"label": "Download PDF"
|
||||
}
|
||||
],
|
||||
"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,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-05-16 22:43:30.212408",
|
||||
"links": [],
|
||||
"modified": "2020-09-28 19:31:11.855588",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Supplier",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -8,8 +8,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
setup: function() {
|
||||
this.frm.custom_make_buttons = {
|
||||
'Purchase Order': 'Purchase Order',
|
||||
'Quotation': 'Quotation',
|
||||
'Subscription': 'Subscription'
|
||||
'Quotation': 'Quotation'
|
||||
}
|
||||
|
||||
this._super();
|
||||
@ -28,12 +27,6 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
cur_frm.add_custom_button(__("Quotation"), this.make_quotation,
|
||||
__('Create'));
|
||||
|
||||
if(!this.frm.doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
}
|
||||
else if (this.frm.doc.docstatus===0) {
|
||||
|
||||
@ -54,6 +47,27 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
|
||||
this.frm.add_custom_button(__("Request for Quotation"),
|
||||
function() {
|
||||
if (!me.frm.doc.supplier) {
|
||||
frappe.throw({message:__("Please select a Supplier"), title:__("Mandatory")})
|
||||
}
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq",
|
||||
source_doctype: "Request for Quotation",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
company: me.frm.doc.company,
|
||||
transaction_date: null
|
||||
},
|
||||
get_query_filters: {
|
||||
supplier: me.frm.doc.supplier
|
||||
},
|
||||
get_query_method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_rfq_containing_supplier"
|
||||
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -159,6 +159,7 @@
|
||||
"default": "Today",
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date",
|
||||
"oldfieldname": "transaction_date",
|
||||
"oldfieldtype": "Date",
|
||||
@ -798,6 +799,7 @@
|
||||
{
|
||||
"fieldname": "valid_till",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Valid Till"
|
||||
}
|
||||
],
|
||||
@ -805,7 +807,7 @@
|
||||
"idx": 29,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-18 05:10:45.556792",
|
||||
"modified": "2020-10-01 20:56:17.932007",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
@ -12,6 +12,8 @@
|
||||
"item_name",
|
||||
"column_break_3",
|
||||
"lead_time_days",
|
||||
"expected_delivery_date",
|
||||
"is_free_item",
|
||||
"section_break_5",
|
||||
"description",
|
||||
"item_group",
|
||||
@ -19,20 +21,18 @@
|
||||
"col_break1",
|
||||
"image",
|
||||
"image_view",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"column_break_15",
|
||||
"manufacturer_part_no",
|
||||
"quantity_and_rate",
|
||||
"qty",
|
||||
"stock_uom",
|
||||
"price_list_rate",
|
||||
"discount_percentage",
|
||||
"discount_amount",
|
||||
"col_break2",
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"stock_qty",
|
||||
"sec_break_price_list",
|
||||
"price_list_rate",
|
||||
"discount_percentage",
|
||||
"discount_amount",
|
||||
"col_break_price_list",
|
||||
"base_price_list_rate",
|
||||
"sec_break1",
|
||||
"rate",
|
||||
@ -42,7 +42,6 @@
|
||||
"base_rate",
|
||||
"base_amount",
|
||||
"pricing_rules",
|
||||
"is_free_item",
|
||||
"section_break_24",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@ -56,7 +55,6 @@
|
||||
"weight_uom",
|
||||
"warehouse_and_reference",
|
||||
"warehouse",
|
||||
"project",
|
||||
"prevdoc_doctype",
|
||||
"material_request",
|
||||
"sales_order",
|
||||
@ -65,13 +63,19 @@
|
||||
"material_request_item",
|
||||
"request_for_quotation_item",
|
||||
"item_tax_rate",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"column_break_15",
|
||||
"manufacturer_part_no",
|
||||
"ad_sec_break",
|
||||
"project",
|
||||
"section_break_44",
|
||||
"page_break"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 4,
|
||||
"columns": 2,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -107,7 +111,7 @@
|
||||
{
|
||||
"fieldname": "lead_time_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Lead Time in days"
|
||||
"label": "Supplier Lead Time (days)"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@ -162,7 +166,6 @@
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"print_hide": 1,
|
||||
@ -196,6 +199,7 @@
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "UOM",
|
||||
"options": "UOM",
|
||||
"print_hide": 1,
|
||||
@ -237,7 +241,7 @@
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate ",
|
||||
"label": "Rate",
|
||||
"oldfieldname": "import_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency"
|
||||
@ -289,14 +293,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_free_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Free Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_24",
|
||||
"fieldtype": "Section Break"
|
||||
@ -528,12 +524,43 @@
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "sec_break_price_list",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break_price_list",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "ad_sec_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "is_free_item",
|
||||
"fieldname": "is_free_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Free Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"bold": 1,
|
||||
"fieldname": "expected_delivery_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Delivery Date"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-07 18:35:51.175947",
|
||||
"modified": "2020-10-19 12:36:26.913211",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation Item",
|
||||
|
@ -143,7 +143,7 @@ def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
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"):
|
||||
conditions += """
|
||||
@ -151,10 +151,10 @@ def get_conditions(filters):
|
||||
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
|
||||
|
||||
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"):
|
||||
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
|
||||
conditions += " AND parent.transaction_date<='%s'" % filters.get('to_date')
|
||||
return conditions
|
||||
|
||||
def get_data(filters):
|
||||
@ -198,21 +198,23 @@ def get_mapped_mr_details(conditions):
|
||||
mr_records = {}
|
||||
mr_details = frappe.db.sql("""
|
||||
SELECT
|
||||
par.transaction_date,
|
||||
par.per_ordered,
|
||||
par.owner,
|
||||
parent.transaction_date,
|
||||
parent.per_ordered,
|
||||
parent.owner,
|
||||
child.name,
|
||||
child.parent,
|
||||
child.amount,
|
||||
child.qty,
|
||||
child.item_code,
|
||||
child.uom,
|
||||
par.status
|
||||
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
|
||||
parent.status,
|
||||
child.project,
|
||||
child.cost_center
|
||||
FROM `tabMaterial Request` parent, `tabMaterial Request Item` child
|
||||
WHERE
|
||||
par.per_ordered>=0
|
||||
AND par.name=child.parent
|
||||
AND par.docstatus=1
|
||||
parent.per_ordered>=0
|
||||
AND parent.name=child.parent
|
||||
AND parent.docstatus=1
|
||||
{conditions}
|
||||
""".format(conditions=conditions), as_dict=1) #nosec
|
||||
|
||||
@ -232,7 +234,9 @@ def get_mapped_mr_details(conditions):
|
||||
status=record.status,
|
||||
actual_cost=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)
|
||||
return mr_records, procurement_record_against_mr
|
||||
@ -280,16 +284,16 @@ def get_po_entries(conditions):
|
||||
child.amount,
|
||||
child.base_amount,
|
||||
child.schedule_date,
|
||||
par.transaction_date,
|
||||
par.supplier,
|
||||
par.status,
|
||||
par.owner
|
||||
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
|
||||
parent.transaction_date,
|
||||
parent.supplier,
|
||||
parent.status,
|
||||
parent.owner
|
||||
FROM `tabPurchase Order` parent, `tabPurchase Order Item` child
|
||||
WHERE
|
||||
par.docstatus = 1
|
||||
AND par.name = child.parent
|
||||
AND par.status not in ("Closed","Completed","Cancelled")
|
||||
parent.docstatus = 1
|
||||
AND parent.name = child.parent
|
||||
AND parent.status not in ("Closed","Completed","Cancelled")
|
||||
{conditions}
|
||||
GROUP BY
|
||||
par.name, child.item_code
|
||||
parent.name, child.item_code
|
||||
""".format(conditions=conditions), as_dict=1) #nosec
|
@ -1,32 +0,0 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2016-07-21 08:31:05.890362",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:04:58.784351",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Quoted Item Comparison",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Supplier Quotation",
|
||||
"report_name": "Quoted Item Comparison",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Quoted Item Comparison"] = {
|
||||
frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
@ -78,6 +78,13 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
return { filters: { "docstatus": ["<", 2] } }
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"group_by",
|
||||
"label": __("Group by"),
|
||||
"fieldtype": "Select",
|
||||
"options": [__("Group by Supplier"), __("Group by Item")],
|
||||
"default": __("Group by Supplier")
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
label: __("Include Expired"),
|
||||
@ -98,6 +105,9 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
}
|
||||
}
|
||||
|
||||
if(column.fieldname === "price_per_unit" && data.price_per_unit && data.min && data.min === 1){
|
||||
value = `<div style="color:green">${value}</div>`;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2016-07-21 08:31:05.890362",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:04:58.784351",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation Comparison",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Supplier Quotation",
|
||||
"report_name": "Supplier Quotation Comparison",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
@ -12,9 +12,9 @@ def execute(filters=None):
|
||||
if not filters:
|
||||
return [], []
|
||||
|
||||
columns = get_columns(filters)
|
||||
conditions = get_conditions(filters)
|
||||
supplier_quotation_data = get_data(filters, conditions)
|
||||
columns = get_columns()
|
||||
|
||||
data, chart_data = prepare_data(supplier_quotation_data, filters)
|
||||
message = get_message()
|
||||
@ -41,9 +41,13 @@ def get_conditions(filters):
|
||||
return conditions
|
||||
|
||||
def get_data(filters, conditions):
|
||||
supplier_quotation_data = frappe.db.sql("""SELECT
|
||||
sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
||||
sqi.lead_time_days, sq.supplier, sq.valid_till
|
||||
supplier_quotation_data = frappe.db.sql("""
|
||||
SELECT
|
||||
sqi.parent, sqi.item_code,
|
||||
sqi.qty, sqi.stock_qty, sqi.amount,
|
||||
sqi.uom, sqi.stock_uom,
|
||||
sqi.request_for_quotation,
|
||||
sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till
|
||||
FROM
|
||||
`tabSupplier Quotation Item` sqi,
|
||||
`tabSupplier Quotation` sq
|
||||
@ -58,16 +62,18 @@ def get_data(filters, conditions):
|
||||
return supplier_quotation_data
|
||||
|
||||
def prepare_data(supplier_quotation_data, filters):
|
||||
out, suppliers, qty_list, chart_data = [], [], [], []
|
||||
supplier_wise_map = defaultdict(list)
|
||||
out, groups, qty_list, suppliers, chart_data = [], [], [], [], []
|
||||
group_wise_map = defaultdict(list)
|
||||
supplier_qty_price_map = {}
|
||||
|
||||
group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code"
|
||||
company_currency = frappe.db.get_default("currency")
|
||||
float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
|
||||
for data in supplier_quotation_data:
|
||||
supplier = data.get("supplier")
|
||||
supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency")
|
||||
group = data.get(group_by_field) # get item or supplier value for this row
|
||||
|
||||
supplier_currency = frappe.db.get_value("Supplier", data.get("supplier_name"), "default_currency")
|
||||
|
||||
if supplier_currency:
|
||||
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
|
||||
@ -75,38 +81,55 @@ def prepare_data(supplier_quotation_data, filters):
|
||||
exchange_rate = 1
|
||||
|
||||
row = {
|
||||
"item_code": data.get('item_code'),
|
||||
"item_code": "" if group_by_field=="item_code" else data.get("item_code"), # leave blank if group by field
|
||||
"supplier_name": "" if group_by_field=="supplier_name" else data.get("supplier_name"),
|
||||
"quotation": data.get("parent"),
|
||||
"qty": data.get("qty"),
|
||||
"price": flt(data.get("rate") * exchange_rate, float_precision),
|
||||
"price": flt(data.get("amount") * exchange_rate, float_precision),
|
||||
"uom": data.get("uom"),
|
||||
"stock_uom": data.get('stock_uom'),
|
||||
"request_for_quotation": data.get("request_for_quotation"),
|
||||
"valid_till": data.get('valid_till'),
|
||||
"lead_time_days": data.get('lead_time_days')
|
||||
}
|
||||
row["price_per_unit"] = flt(row["price"]) / (flt(data.get("stock_qty")) or 1)
|
||||
|
||||
# map for report view of form {'supplier1':[{},{},...]}
|
||||
supplier_wise_map[supplier].append(row)
|
||||
# map for report view of form {'supplier1'/'item1':[{},{},...]}
|
||||
group_wise_map[group].append(row)
|
||||
|
||||
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
|
||||
supplier = data.get("supplier_name")
|
||||
if filters.get("item_code"):
|
||||
if not supplier in supplier_qty_price_map:
|
||||
supplier_qty_price_map[supplier] = {}
|
||||
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
|
||||
|
||||
groups.append(group)
|
||||
suppliers.append(supplier)
|
||||
qty_list.append(data.get("qty"))
|
||||
|
||||
groups = list(set(groups))
|
||||
suppliers = list(set(suppliers))
|
||||
qty_list = list(set(qty_list))
|
||||
|
||||
highlight_min_price = group_by_field == "item_code" or filters.get("item_code")
|
||||
|
||||
# final data format for report view
|
||||
for supplier in suppliers:
|
||||
supplier_wise_map[supplier][0].update({"supplier_name": supplier})
|
||||
for entry in supplier_wise_map[supplier]:
|
||||
for group in groups:
|
||||
group_entries = group_wise_map[group] # all entries pertaining to item/supplier
|
||||
group_entries[0].update({group_by_field : group}) # Add item/supplier name in first group row
|
||||
|
||||
if highlight_min_price:
|
||||
prices = [group_entry["price_per_unit"] for group_entry in group_entries]
|
||||
min_price = min(prices)
|
||||
|
||||
for entry in group_entries:
|
||||
if highlight_min_price and entry["price_per_unit"] == min_price:
|
||||
entry["min"] = 1
|
||||
out.append(entry)
|
||||
|
||||
if filters.get("item_code"):
|
||||
# render chart only for one item comparison
|
||||
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
|
||||
|
||||
return out, chart_data
|
||||
@ -145,8 +168,9 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
||||
|
||||
return chart_data
|
||||
|
||||
def get_columns():
|
||||
columns = [{
|
||||
def get_columns(filters):
|
||||
group_by_columns = [
|
||||
{
|
||||
"fieldname": "supplier_name",
|
||||
"label": _("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
@ -158,8 +182,10 @@ def get_columns():
|
||||
"label": _("Item"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 200
|
||||
},
|
||||
"width": 150
|
||||
}]
|
||||
|
||||
columns = [
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"label": _("UOM"),
|
||||
@ -180,6 +206,20 @@ def get_columns():
|
||||
"options": "Company:company:default_currency",
|
||||
"width": 110
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"label": _("Stock UOM"),
|
||||
"fieldtype": "Link",
|
||||
"options": "UOM",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"fieldname": "price_per_unit",
|
||||
"label": _("Price per Unit (Stock UOM)"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "quotation",
|
||||
"label": _("Supplier Quotation"),
|
||||
@ -205,9 +245,12 @@ def get_columns():
|
||||
"fieldtype": "Link",
|
||||
"options": "Request for Quotation",
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
if filters.get("group_by") == "Group by Item":
|
||||
group_by_columns.reverse()
|
||||
|
||||
columns[0:0] = group_by_columns # add positioned group by columns to the report
|
||||
return columns
|
||||
|
||||
def get_message():
|
@ -63,8 +63,8 @@ def get_data():
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Chart of Accounts Importer",
|
||||
"labe": _("Chart Of Accounts Importer"),
|
||||
"description": _("Import Chart Of Accounts from CSV / Excel files"),
|
||||
"label": _("Chart of Accounts Importer"),
|
||||
"description": _("Import Chart of Accounts from CSV / Excel files"),
|
||||
"onboard": 1
|
||||
},
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint, flt, cstr, comma_or
|
||||
from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
|
||||
from frappe import _, throw
|
||||
from erpnext.stock.get_item_details import get_bin_details
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
@ -173,12 +173,16 @@ class SellingController(StockController):
|
||||
|
||||
def validate_selling_price(self):
|
||||
def throw_message(idx, item_name, rate, ref_rate_field):
|
||||
frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""")
|
||||
.format(idx, item_name, ref_rate_field, rate))
|
||||
bold_net_rate = frappe.bold("net rate")
|
||||
msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""")
|
||||
.format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate)))
|
||||
msg += "<br><br>"
|
||||
msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""")
|
||||
.format(get_link_to_form("Selling Settings", "Selling Settings")))
|
||||
frappe.throw(msg, title=_("Invalid Selling Price"))
|
||||
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
return
|
||||
|
||||
if hasattr(self, "is_return") and self.is_return:
|
||||
return
|
||||
|
||||
@ -187,8 +191,8 @@ class SellingController(StockController):
|
||||
continue
|
||||
|
||||
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
|
||||
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
|
||||
if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
|
||||
|
||||
last_valuation_rate = frappe.db.sql("""
|
||||
@ -197,8 +201,8 @@ class SellingController(StockController):
|
||||
ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1
|
||||
""", (it.item_code, it.warehouse))
|
||||
if last_valuation_rate:
|
||||
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
|
||||
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
|
||||
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1)
|
||||
if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \
|
||||
and not self.get('is_internal_customer'):
|
||||
throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")
|
||||
|
||||
|
@ -241,6 +241,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.__islocal",
|
||||
"description": "Home, Work, etc.",
|
||||
"fieldname": "address_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Address Title"
|
||||
@ -249,7 +250,8 @@
|
||||
"depends_on": "eval: doc.__islocal",
|
||||
"fieldname": "address_line1",
|
||||
"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",
|
||||
@ -261,7 +263,8 @@
|
||||
"depends_on": "eval: doc.__islocal",
|
||||
"fieldname": "city",
|
||||
"fieldtype": "Data",
|
||||
"label": "City/Town"
|
||||
"label": "City/Town",
|
||||
"mandatory_depends_on": "eval: doc.address_title && doc.address_type"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.__islocal",
|
||||
@ -280,6 +283,7 @@
|
||||
"fieldname": "country",
|
||||
"fieldtype": "Link",
|
||||
"label": "Country",
|
||||
"mandatory_depends_on": "eval: doc.address_title && doc.address_type",
|
||||
"options": "Country"
|
||||
},
|
||||
{
|
||||
@ -449,7 +453,7 @@
|
||||
"idx": 5,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-06-18 14:39:41.835416",
|
||||
"modified": "2020-10-13 15:24:00.094811",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead",
|
||||
|
@ -22,7 +22,8 @@ class Lead(SellingController):
|
||||
load_address_and_contact(self)
|
||||
|
||||
def before_insert(self):
|
||||
self.address_doc = self.create_address()
|
||||
if self.address_title and self.address_type:
|
||||
self.address_doc = self.create_address()
|
||||
self.contact_doc = self.create_contact()
|
||||
|
||||
def after_insert(self):
|
||||
@ -133,15 +134,6 @@ class Lead(SellingController):
|
||||
# skipping country since the system auto-sets it from system defaults
|
||||
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({info_field: self.get(info_field) for info_field in info_fields})
|
||||
address.insert()
|
||||
@ -190,7 +182,7 @@ class Lead(SellingController):
|
||||
|
||||
def update_links(self):
|
||||
# update address links
|
||||
if self.address_doc:
|
||||
if hasattr(self, 'address_doc'):
|
||||
self.address_doc.append("links", {
|
||||
"link_doctype": "Lead",
|
||||
"link_name": self.name,
|
||||
|
@ -11,7 +11,7 @@ from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
|
||||
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():
|
||||
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)
|
||||
|
||||
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.submit()
|
||||
|
||||
|
@ -6,9 +6,13 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_link_to_form
|
||||
from functools import reduce
|
||||
|
||||
class CourseEnrollment(Document):
|
||||
def validate(self):
|
||||
self.validate_duplication()
|
||||
|
||||
def get_progress(self, student):
|
||||
"""
|
||||
Returns Progress of given student for a particular course enrollment
|
||||
@ -27,13 +31,15 @@ class CourseEnrollment(Document):
|
||||
return []
|
||||
|
||||
def validate_duplication(self):
|
||||
enrollment = frappe.get_all("Course Enrollment", filters={
|
||||
enrollment = frappe.db.exists("Course Enrollment", {
|
||||
"student": self.student,
|
||||
"course": self.course,
|
||||
"program_enrollment": self.program_enrollment
|
||||
"program_enrollment": self.program_enrollment,
|
||||
"name": ("!=", self.name)
|
||||
})
|
||||
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):
|
||||
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
|
||||
|
@ -17,8 +17,9 @@ class TestCourseEnrollment(unittest.TestCase):
|
||||
setup_program()
|
||||
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")
|
||||
course_enrollment = student.enroll_in_course("_Test Course 1", program_enrollment.name)
|
||||
make_course_activity(course_enrollment.name, "Article", "_Test Article 1-1")
|
||||
course_enrollment = frappe.db.get_value("Course Enrollment",
|
||||
{"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):
|
||||
student = get_student("_test_student_1@example.com")
|
||||
@ -30,5 +31,14 @@ class TestCourseEnrollment(unittest.TestCase):
|
||||
self.assertTrue(finished in progress)
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
@ -49,6 +49,11 @@ class TestProgram(unittest.TestCase):
|
||||
self.assertEqual(course[1].name, "_Test Course 2")
|
||||
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):
|
||||
program = frappe.get_doc({
|
||||
"doctype": "Program",
|
||||
@ -68,7 +73,7 @@ def make_program_and_linked_courses(program_name, course_name_list):
|
||||
program = frappe.get_doc("Program", program_name)
|
||||
course_list = [make_course(course_name) for course_name in course_name_list]
|
||||
for course in course_list:
|
||||
program.append("courses", {"course": course})
|
||||
program.append("courses", {"course": course, "required": 1})
|
||||
program.save()
|
||||
return program
|
||||
|
||||
|
@ -17,9 +17,7 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "Course",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "course.course_name",
|
||||
@ -27,23 +25,19 @@
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Course Name",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"fieldname": "required",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Mandatory"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-09 18:56:10.213241",
|
||||
"modified": "2020-09-15 18:14:22.816795",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Course",
|
||||
|
@ -2,16 +2,24 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
|
||||
frappe.ui.form.on("Program Enrollment", {
|
||||
frappe.ui.form.on('Program Enrollment', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('fee_structure', 'total_amount', 'amount');
|
||||
},
|
||||
|
||||
onload: function(frm, cdt, cdn){
|
||||
frm.set_query("academic_term", "fees", function(){
|
||||
return{
|
||||
"filters":{
|
||||
"academic_year": (frm.doc.academic_year)
|
||||
onload: function(frm) {
|
||||
frm.set_query('academic_term', function() {
|
||||
return {
|
||||
'filters':{
|
||||
'academic_year': frm.doc.academic_year
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('academic_term', 'fees', function() {
|
||||
return {
|
||||
'filters':{
|
||||
'academic_year': frm.doc.academic_year
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -24,9 +32,9 @@ frappe.ui.form.on("Program Enrollment", {
|
||||
};
|
||||
|
||||
if (frm.doc.program) {
|
||||
frm.set_query("course", "courses", function(doc, cdt, cdn) {
|
||||
return{
|
||||
query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses",
|
||||
frm.set_query('course', 'courses', function() {
|
||||
return {
|
||||
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_program_courses',
|
||||
filters: {
|
||||
'program': frm.doc.program
|
||||
}
|
||||
@ -34,9 +42,9 @@ frappe.ui.form.on("Program Enrollment", {
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query("student", function() {
|
||||
frm.set_query('student', function() {
|
||||
return{
|
||||
query: "erpnext.education.doctype.program_enrollment.program_enrollment.get_students",
|
||||
query: 'erpnext.education.doctype.program_enrollment.program_enrollment.get_students',
|
||||
filters: {
|
||||
'academic_year': frm.doc.academic_year,
|
||||
'academic_term': frm.doc.academic_term
|
||||
@ -49,14 +57,14 @@ frappe.ui.form.on("Program Enrollment", {
|
||||
frm.events.get_courses(frm);
|
||||
if (frm.doc.program) {
|
||||
frappe.call({
|
||||
method: "erpnext.education.api.get_fee_schedule",
|
||||
method: 'erpnext.education.api.get_fee_schedule',
|
||||
args: {
|
||||
"program": frm.doc.program,
|
||||
"student_category": frm.doc.student_category
|
||||
'program': frm.doc.program,
|
||||
'student_category': frm.doc.student_category
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("fees" ,r.message);
|
||||
if (r.message) {
|
||||
frm.set_value('fees' ,r.message);
|
||||
frm.events.get_courses(frm);
|
||||
}
|
||||
}
|
||||
@ -65,17 +73,17 @@ frappe.ui.form.on("Program Enrollment", {
|
||||
},
|
||||
|
||||
student_category: function() {
|
||||
frappe.ui.form.trigger("Program Enrollment", "program");
|
||||
frappe.ui.form.trigger('Program Enrollment', 'program');
|
||||
},
|
||||
|
||||
get_courses: function(frm) {
|
||||
frm.set_value("courses",[]);
|
||||
frm.set_value('courses',[]);
|
||||
frappe.call({
|
||||
method: "get_courses",
|
||||
method: 'get_courses',
|
||||
doc:frm.doc,
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("courses", r.message);
|
||||
if (r.message) {
|
||||
frm.set_value('courses', r.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -84,10 +92,10 @@ frappe.ui.form.on("Program Enrollment", {
|
||||
|
||||
frappe.ui.form.on('Program Enrollment Course', {
|
||||
courses_add: function(frm){
|
||||
frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc){
|
||||
frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc) {
|
||||
var course_list = [];
|
||||
if(!doc.__islocal) course_list.push(doc.name);
|
||||
$.each(doc.courses, function(idx, val){
|
||||
$.each(doc.courses, function(_idx, val) {
|
||||
if (val.course) course_list.push(val.course);
|
||||
});
|
||||
return { filters: [['Course', 'name', 'not in', course_list]] };
|
||||
|
@ -1,725 +1,185 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "EDU-ENR-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2015-12-02 12:58:32.916080",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"student",
|
||||
"student_name",
|
||||
"student_category",
|
||||
"student_batch_name",
|
||||
"school_house",
|
||||
"column_break_4",
|
||||
"program",
|
||||
"academic_year",
|
||||
"academic_term",
|
||||
"enrollment_date",
|
||||
"boarding_student",
|
||||
"enrolled_courses",
|
||||
"courses",
|
||||
"transportation",
|
||||
"mode_of_transportation",
|
||||
"column_break_13",
|
||||
"vehicle_no",
|
||||
"section_break_7",
|
||||
"fees",
|
||||
"amended_from",
|
||||
"image"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "student.title",
|
||||
"fieldname": "student_name",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"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
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_category",
|
||||
"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 Category",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Category",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Student Category"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_batch_name",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Batch",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Batch Name",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Student Batch Name"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "school_house",
|
||||
"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": "School House",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "School House",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "School House"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Program",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Academic Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Year",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_term",
|
||||
"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": "Academic Term",
|
||||
"length": 0,
|
||||
"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
|
||||
"options": "Academic Term"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Today",
|
||||
"fieldname": "enrollment_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Enrollment Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "Check this if the Student is residing at the Institute's Hostel.",
|
||||
"fieldname": "boarding_student",
|
||||
"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": "Boarding Student",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Boarding Student"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "vehicle_no",
|
||||
"columns": 0,
|
||||
"fieldname": "transportation",
|
||||
"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": "Transportation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Transportation"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_transportation",
|
||||
"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": "Mode of Transportation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "vehicle_no",
|
||||
"fieldtype": "Data",
|
||||
"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": "Vehicle/Bus Number",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Vehicle/Bus Number"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "enrolled_courses",
|
||||
"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": "Enrolled courses",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Enrolled courses"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "courses",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Courses",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program Enrollment 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
|
||||
"options": "Program Enrollment Course"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"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": "Fees",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Fees"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "fees",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fees",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program Fee",
|
||||
"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,
|
||||
"width": ""
|
||||
"options": "Program Fee"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Program Enrollment",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Image",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Image"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_field": "image",
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-11-07 21:13:06.502279",
|
||||
"links": [],
|
||||
"modified": "2020-09-15 18:12:11.988565",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Enrollment",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@ -729,47 +189,30 @@
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "LMS User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "student_name",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"title_field": "student_name"
|
||||
}
|
@ -7,12 +7,15 @@ import frappe
|
||||
from frappe import msgprint, _
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||
from frappe.utils import comma_and
|
||||
from frappe.utils import comma_and, get_link_to_form, getdate
|
||||
import erpnext.www.lms as lms
|
||||
|
||||
class ProgramEnrollment(Document):
|
||||
def validate(self):
|
||||
self.validate_duplication()
|
||||
self.validate_academic_year()
|
||||
if self.academic_term:
|
||||
self.validate_academic_term()
|
||||
if not self.student_name:
|
||||
self.student_name = frappe.db.get_value("Student", self.student, "title")
|
||||
if not self.courses:
|
||||
@ -23,11 +26,34 @@ class ProgramEnrollment(Document):
|
||||
self.make_fee_records()
|
||||
self.create_course_enrollments()
|
||||
|
||||
def validate_academic_year(self):
|
||||
start_date, end_date = frappe.db.get_value("Academic Year", self.academic_year, ["year_start_date", "year_end_date"])
|
||||
if self.enrollment_date:
|
||||
if start_date and getdate(self.enrollment_date) < getdate(start_date):
|
||||
frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Year {0}").format(
|
||||
get_link_to_form("Academic Year", self.academic_year)))
|
||||
|
||||
if end_date and getdate(self.enrollment_date) > getdate(end_date):
|
||||
frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
|
||||
get_link_to_form("Academic Year", self.academic_year)))
|
||||
|
||||
def validate_academic_term(self):
|
||||
start_date, end_date = frappe.db.get_value("Academic Term", self.academic_term, ["term_start_date", "term_end_date"])
|
||||
if self.enrollment_date:
|
||||
if start_date and getdate(self.enrollment_date) < getdate(start_date):
|
||||
frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Term {0}").format(
|
||||
get_link_to_form("Academic Term", self.academic_term)))
|
||||
|
||||
if end_date and getdate(self.enrollment_date) > getdate(end_date):
|
||||
frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
|
||||
get_link_to_form("Academic Term", self.academic_term)))
|
||||
|
||||
def validate_duplication(self):
|
||||
enrollment = frappe.get_all("Program Enrollment", filters={
|
||||
"student": self.student,
|
||||
"program": self.program,
|
||||
"academic_year": self.academic_year,
|
||||
"academic_term": self.academic_term,
|
||||
"docstatus": ("<", 2),
|
||||
"name": ("!=", self.name)
|
||||
})
|
||||
@ -70,10 +96,9 @@ class ProgramEnrollment(Document):
|
||||
|
||||
def create_course_enrollments(self):
|
||||
student = frappe.get_doc("Student", self.student)
|
||||
program = frappe.get_doc("Program", self.program)
|
||||
course_list = [course.course for course in program.courses]
|
||||
course_list = [course.course for course in self.courses]
|
||||
for course_name in course_list:
|
||||
student.enroll_in_course(course_name=course_name, program_enrollment=self.name)
|
||||
student.enroll_in_course(course_name=course_name, program_enrollment=self.name, enrollment_date=self.enrollment_date)
|
||||
|
||||
def get_all_course_enrollments(self):
|
||||
course_enrollment_names = frappe.get_list("Course Enrollment", filters={'program_enrollment': self.name})
|
||||
|
@ -24,3 +24,12 @@ class TestProgramEnrollment(unittest.TestCase):
|
||||
self.assertTrue("_Test Course 1" in course_enrollments.keys())
|
||||
self.assertTrue("_Test Course 2" in course_enrollments.keys())
|
||||
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()
|
@ -147,7 +147,7 @@ class Student(Document):
|
||||
enrollment.save(ignore_permissions=True)
|
||||
except frappe.exceptions.ValidationError:
|
||||
enrollment_name = frappe.get_list("Course Enrollment", filters={"student": self.name, "course": course_name, "program_enrollment": program_enrollment})[0].name
|
||||
return frappe.get_doc("Program Enrollment", enrollment_name)
|
||||
return frappe.get_doc("Course Enrollment", enrollment_name)
|
||||
else:
|
||||
return enrollment
|
||||
|
||||
|
@ -42,6 +42,16 @@ class TestStudent(unittest.TestCase):
|
||||
self.assertTrue("_Test Course 2" in course_enrollments.keys())
|
||||
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()
|
||||
|
||||
|
||||
def create_student(student_dict):
|
||||
student = get_student(student_dict['email'])
|
||||
if not student:
|
||||
|
@ -51,12 +51,14 @@
|
||||
"fieldname": "admission_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Admission Start Date",
|
||||
"mandatory_depends_on": "enable_admission_application",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "admission_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Admission End Date",
|
||||
"mandatory_depends_on": "enable_admission_application",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
@ -83,6 +85,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "published",
|
||||
"fieldname": "enable_admission_application",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Admission Application"
|
||||
@ -91,7 +94,7 @@
|
||||
"has_web_view": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2020-06-15 20:18:38.591626",
|
||||
"modified": "2020-09-18 00:14:54.615321",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission",
|
||||
|
@ -19,6 +19,9 @@ class StudentAdmission(WebsiteGenerator):
|
||||
if not self.route: #pylint: disable=E0203
|
||||
self.route = "admissions/" + "-".join(self.title.split(" "))
|
||||
|
||||
if self.enable_admission_application and not self.program_details:
|
||||
frappe.throw(_("Please add programs to enable admission application."))
|
||||
|
||||
def get_context(self, context):
|
||||
context.no_cache = 1
|
||||
context.show_sidebar = True
|
||||
|
@ -43,31 +43,35 @@
|
||||
<thead>
|
||||
<tr class="active">
|
||||
<th style="width: 90px">Program/Std.</th>
|
||||
<th style="width: 170px">Minumum Age</th>
|
||||
<th style="width: 170px">Maximum Age</th>
|
||||
<th style="width: 180px">Description</th>
|
||||
<th style="width: 100px">Minumum Age</th>
|
||||
<th style="width: 100px">Maximum Age</th>
|
||||
<th style="width: 100px">Application Fee</th>
|
||||
{%- if doc.enable_admission_application and frappe.utils.getdate(doc.admission_start_date) <= today -%}
|
||||
<th style="width: 120px"></th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in program_details %}
|
||||
<tr>
|
||||
<td>{{ row.program }}</td>
|
||||
<td><div class="text-muted">{{ row.description if row.description else '' }}</div></td>
|
||||
<td>{{ row.min_age }}</td>
|
||||
<td>{{ row.max_age }}</td>
|
||||
<td>{{ row.application_fee }}</td>
|
||||
{%- if doc.enable_admission_application and frappe.utils.getdate(doc.admission_start_date) <= today -%}
|
||||
<td>
|
||||
<a class='btn btn-sm btn-primary' href='/student-applicant?new=1&student_admission={{doc.name}}&program={{row.program}}&academic_year={{academic_year}}'>
|
||||
{{ _("Apply Now") }}
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- if doc.enable_admission_application -%}
|
||||
<br>
|
||||
<p>
|
||||
<a class='btn btn-primary'
|
||||
href='/student-applicant?new=1&student_admission={{doc.name}}'>
|
||||
{{ _("Apply Now") }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div class="web-list-item">
|
||||
<div class="web-list-item transaction-list-item">
|
||||
{% set today = frappe.utils.getdate(frappe.utils.nowdate()) %}
|
||||
<a href = "{{ doc.route }}/">
|
||||
<a href = "{{ doc.route }}/" class="no-underline">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 text-left small bold" style="margin-top: -3px;"">
|
||||
<div class="col-sm-4 bold">
|
||||
<span class="indicator
|
||||
{% if frappe.utils.getdate(doc.admission_end_date) == today %}
|
||||
red
|
||||
@ -15,6 +15,14 @@
|
||||
{% endif %}
|
||||
">{{ doc.title }}</span>
|
||||
</div>
|
||||
<div class="col-sm-2 small">
|
||||
<span class="text-muted">
|
||||
Academic Year
|
||||
</span>
|
||||
<div class="text-muted bold">
|
||||
{{ doc.academic_year }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3 small">
|
||||
<span class="text-muted">
|
||||
Starts on
|
||||
@ -27,7 +35,7 @@
|
||||
<span class="text-muted">
|
||||
Ends on
|
||||
</span>
|
||||
<div class="bold">
|
||||
<div class=" text-muted bold">
|
||||
{{ frappe.format_date(doc.admission_end_date) }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@
|
||||
"program",
|
||||
"min_age",
|
||||
"max_age",
|
||||
"description",
|
||||
"column_break_4",
|
||||
"application_fee",
|
||||
"applicant_naming_series"
|
||||
@ -18,52 +19,47 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Program",
|
||||
"options": "Program",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Program"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "application_fee",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Application Fee",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Application Fee"
|
||||
},
|
||||
{
|
||||
"fieldname": "applicant_naming_series",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Naming Series (for Student Applicant)",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Naming Series (for Student Applicant)"
|
||||
},
|
||||
{
|
||||
"fieldname": "min_age",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Age",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Minimum Age"
|
||||
},
|
||||
{
|
||||
"fieldname": "max_age",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Maximum Age",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Maximum Age"
|
||||
},
|
||||
{
|
||||
"fetch_from": "program.description",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-10 23:06:30.037404",
|
||||
"modified": "2020-10-05 13:03:42.005985",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Admission Program",
|
||||
|
@ -168,6 +168,7 @@
|
||||
"fieldname": "student_email_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Student Email Address",
|
||||
"options": "Email",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
@ -261,7 +262,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-07 19:31:30.063563",
|
||||
"modified": "2020-10-05 13:59:45.631647",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Student Applicant",
|
||||
|
@ -70,19 +70,7 @@
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Image",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -95,7 +83,7 @@
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -107,6 +95,19 @@
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "academic_term",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Academic Term",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Academic Term",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "date_of_birth",
|
||||
@ -119,6 +120,19 @@
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 1,
|
||||
"fieldname": "gender",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Gender",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Gender",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "blood_group",
|
||||
@ -141,7 +155,7 @@
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
@ -206,19 +220,6 @@
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "guardians",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Guardians",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"options": "Student Guardian",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "siblings",
|
||||
|
@ -21,11 +21,16 @@ web_include_js = "assets/js/erpnext-web.min.js"
|
||||
web_include_css = "assets/css/erpnext-web.css"
|
||||
|
||||
doctype_js = {
|
||||
"Address": "public/js/address.js",
|
||||
"Communication": "public/js/communication.js",
|
||||
"Event": "public/js/event.js",
|
||||
"Newsletter": "public/js/newsletter.js"
|
||||
}
|
||||
|
||||
override_doctype_class = {
|
||||
'Address': 'erpnext.accounts.custom.address.ERPNextAddress'
|
||||
}
|
||||
|
||||
welcome_email = "erpnext.setup.utils.welcome_email"
|
||||
|
||||
# setup wizard
|
||||
@ -392,6 +397,9 @@ regional_overrides = {
|
||||
'Italy': {
|
||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data',
|
||||
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate',
|
||||
},
|
||||
'Germany': {
|
||||
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.germany.accounts_controller.validate_regional',
|
||||
}
|
||||
}
|
||||
user_privacy_documents = [
|
||||
|
@ -109,7 +109,6 @@
|
||||
"encashment_date",
|
||||
"exit_interview_details",
|
||||
"held_on",
|
||||
"reason_for_resignation",
|
||||
"new_workplace",
|
||||
"feedback",
|
||||
"lft",
|
||||
@ -682,7 +681,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "reason_for_leaving",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Reason for Leaving",
|
||||
"oldfieldname": "reason_for_leaving",
|
||||
"oldfieldtype": "Data"
|
||||
@ -696,6 +695,7 @@
|
||||
"options": "\nYes\nNo"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.leave_encashed ==\"Yes\"",
|
||||
"fieldname": "encashment_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Encashment Date",
|
||||
@ -705,7 +705,6 @@
|
||||
{
|
||||
"fieldname": "exit_interview_details",
|
||||
"fieldtype": "Column Break",
|
||||
"label": "Exit Interview Details",
|
||||
"oldfieldname": "col_brk6",
|
||||
"oldfieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
@ -713,18 +712,10 @@
|
||||
{
|
||||
"fieldname": "held_on",
|
||||
"fieldtype": "Date",
|
||||
"label": "Held On",
|
||||
"label": "Exit Interview Held On",
|
||||
"oldfieldname": "held_on",
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "reason_for_resignation",
|
||||
"fieldtype": "Select",
|
||||
"label": "Reason for Resignation",
|
||||
"oldfieldname": "reason_for_resignation",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nBetter Prospects\nHealth Concerns"
|
||||
},
|
||||
{
|
||||
"fieldname": "new_workplace",
|
||||
"fieldtype": "Data",
|
||||
@ -809,37 +800,29 @@
|
||||
"fieldname": "expense_approver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expense Approver",
|
||||
"options": "User",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "approvers_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Approvers",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Approvers"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_45",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "shift_request_approver",
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift Request Approver",
|
||||
"options": "User",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "User"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-07-28 01:36:04.109189",
|
||||
"modified": "2020-10-06 15:58:23.805489",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
@ -32,7 +33,7 @@ class HolidayList(Document):
|
||||
|
||||
|
||||
def validate_days(self):
|
||||
if self.from_date > self.to_date:
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
throw(_("To Date cannot be before From Date"))
|
||||
|
||||
for day in self.get("holidays"):
|
||||
|
@ -6,18 +6,28 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate,flt, cstr,random_string
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
|
||||
|
||||
class TestVehicleLog(unittest.TestCase):
|
||||
def setUp(self):
|
||||
employee_id = frappe.db.sql("""select name from `tabEmployee` where name='testdriver@example.com'""")
|
||||
self.employee_id = employee_id[0][0] if employee_id else None
|
||||
|
||||
if not self.employee_id:
|
||||
self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
|
||||
|
||||
self.license_plate = get_vehicle(self.employee_id)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc("Vehicle", self.license_plate, force=1)
|
||||
frappe.delete_doc("Employee", self.employee_id, force=1)
|
||||
|
||||
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
|
||||
employee_id = frappe.db.sql("""select name from `tabEmployee` where status='Active' order by modified desc limit 1""")
|
||||
employee_id = employee_id[0][0] if employee_id else None
|
||||
|
||||
license_plate = get_vehicle(employee_id)
|
||||
|
||||
vehicle_log = frappe.get_doc({
|
||||
"doctype": "Vehicle Log",
|
||||
"license_plate": cstr(license_plate),
|
||||
"employee":employee_id,
|
||||
"license_plate": cstr(self.license_plate),
|
||||
"employee": self.employee_id,
|
||||
"date":frappe.utils.nowdate(),
|
||||
"odometer":5010,
|
||||
"fuel_qty":frappe.utils.flt(50),
|
||||
@ -27,7 +37,7 @@ class TestVehicleLog(unittest.TestCase):
|
||||
vehicle_log.submit()
|
||||
|
||||
#checking value of vehicle odometer value on submit.
|
||||
vehicle = frappe.get_doc("Vehicle", license_plate)
|
||||
vehicle = frappe.get_doc("Vehicle", self.license_plate)
|
||||
self.assertEqual(vehicle.last_odometer, vehicle_log.odometer)
|
||||
|
||||
#checking value vehicle odometer on vehicle log cancellation.
|
||||
@ -40,6 +50,28 @@ class TestVehicleLog(unittest.TestCase):
|
||||
|
||||
self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
|
||||
|
||||
vehicle_log.delete()
|
||||
|
||||
def test_vehicle_log_fuel_expense(self):
|
||||
vehicle_log = frappe.get_doc({
|
||||
"doctype": "Vehicle Log",
|
||||
"license_plate": cstr(self.license_plate),
|
||||
"employee": self.employee_id,
|
||||
"date": frappe.utils.nowdate(),
|
||||
"odometer":5010,
|
||||
"fuel_qty":frappe.utils.flt(50),
|
||||
"price": frappe.utils.flt(500)
|
||||
})
|
||||
vehicle_log.save()
|
||||
vehicle_log.submit()
|
||||
|
||||
expense_claim = make_expense_claim(vehicle_log.name)
|
||||
fuel_expense = expense_claim.expenses[0].amount
|
||||
self.assertEqual(fuel_expense, 50*500)
|
||||
|
||||
vehicle_log.cancel()
|
||||
frappe.delete_doc("Expense Claim", expense_claim.name)
|
||||
frappe.delete_doc("Vehicle Log", vehicle_log.name)
|
||||
|
||||
def get_vehicle(employee_id):
|
||||
license_plate=random_string(10).upper()
|
||||
|
@ -32,7 +32,7 @@ def make_expense_claim(docname):
|
||||
vehicle_log = frappe.get_doc("Vehicle Log", docname)
|
||||
service_expense = sum([flt(d.expense_amount) for d in vehicle_log.service_detail])
|
||||
|
||||
claim_amount = service_expense + flt(vehicle_log.price)
|
||||
claim_amount = service_expense + (flt(vehicle_log.price) * flt(vehicle_log.fuel_qty) or 1)
|
||||
if not claim_amount:
|
||||
frappe.throw(_("No additional expenses has been added"))
|
||||
|
||||
|
@ -207,7 +207,6 @@ class TestBOM(unittest.TestCase):
|
||||
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
||||
self.assertEquals(bom_items, supplied_items)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
||||
|
@ -322,12 +322,13 @@ class ProductionPlan(Document):
|
||||
work_orders = []
|
||||
bom_data = {}
|
||||
|
||||
get_sub_assembly_items(item.get("bom_no"), bom_data)
|
||||
get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty"))
|
||||
|
||||
for key, data in bom_data.items():
|
||||
data.update({
|
||||
'qty': data.get("stock_qty") * item.get("qty"),
|
||||
'qty': data.get("stock_qty"),
|
||||
'production_plan': self.name,
|
||||
'use_multi_level_bom': item.get("use_multi_level_bom"),
|
||||
'company': self.company,
|
||||
'fg_warehouse': item.get("fg_warehouse"),
|
||||
'update_consumed_material_cost_in_project': 0
|
||||
@ -381,7 +382,6 @@ class ProductionPlan(Document):
|
||||
"transaction_date": nowdate(),
|
||||
"status": "Draft",
|
||||
"company": self.company,
|
||||
"requested_by": frappe.session.user,
|
||||
'material_request_type': material_request_type,
|
||||
'customer': item_doc.customer or ''
|
||||
})
|
||||
@ -782,7 +782,7 @@ def get_item_data(item_code):
|
||||
# "description": item_details.get("description")
|
||||
}
|
||||
|
||||
def get_sub_assembly_items(bom_no, bom_data):
|
||||
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty):
|
||||
data = get_children('BOM', parent = bom_no)
|
||||
for d in data:
|
||||
if d.expandable:
|
||||
@ -799,6 +799,6 @@ def get_sub_assembly_items(bom_no, bom_data):
|
||||
})
|
||||
|
||||
bom_item = bom_data.get(key)
|
||||
bom_item["stock_qty"] += d.stock_qty / d.parent_bom_qty
|
||||
bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
|
||||
|
||||
get_sub_assembly_items(bom_item.get("bom_no"), bom_data)
|
||||
get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"])
|
||||
|
@ -158,6 +158,46 @@ class TestProductionPlan(unittest.TestCase):
|
||||
self.assertTrue(mr.material_request_type, 'Customer Provided')
|
||||
self.assertTrue(mr.customer, '_Test Customer')
|
||||
|
||||
def test_production_plan_with_multi_level_bom(self):
|
||||
#|Item Code | Qty |
|
||||
#|Test BOM 1 | 1 |
|
||||
#| Test BOM 2 | 2 |
|
||||
#| Test BOM 3 | 3 |
|
||||
|
||||
for item_code in ["Test BOM 1", "Test BOM 2", "Test BOM 3", "Test RM BOM 1"]:
|
||||
create_item(item_code, is_stock_item=1)
|
||||
|
||||
# created bom upto 3 level
|
||||
if not frappe.db.get_value('BOM', {'item': "Test BOM 3"}):
|
||||
make_bom(item = "Test BOM 3", raw_materials = ["Test RM BOM 1"], rm_qty=3)
|
||||
|
||||
if not frappe.db.get_value('BOM', {'item': "Test BOM 2"}):
|
||||
make_bom(item = "Test BOM 2", raw_materials = ["Test BOM 3"], rm_qty=3)
|
||||
|
||||
if not frappe.db.get_value('BOM', {'item': "Test BOM 1"}):
|
||||
make_bom(item = "Test BOM 1", raw_materials = ["Test BOM 2"], rm_qty=2)
|
||||
|
||||
item_code = "Test BOM 1"
|
||||
pln = frappe.new_doc('Production Plan')
|
||||
pln.company = "_Test Company"
|
||||
pln.append("po_items", {
|
||||
"item_code": item_code,
|
||||
"bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
|
||||
"planned_qty": 3,
|
||||
"make_work_order_for_sub_assembly_items": 1
|
||||
})
|
||||
|
||||
pln.submit()
|
||||
pln.make_work_order()
|
||||
|
||||
#last level sub-assembly work order produce qty
|
||||
to_produce_qty = frappe.db.get_value("Work Order",
|
||||
{"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty")
|
||||
|
||||
self.assertEqual(to_produce_qty, 18.0)
|
||||
pln.cancel()
|
||||
frappe.delete_doc("Production Plan", pln.name)
|
||||
|
||||
def create_production_plan(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@ -205,12 +245,16 @@ def make_bom(**args):
|
||||
|
||||
bom.append('items', {
|
||||
'item_code': item,
|
||||
'qty': 1,
|
||||
'qty': args.rm_qty or 1.0,
|
||||
'uom': item_doc.stock_uom,
|
||||
'stock_uom': item_doc.stock_uom,
|
||||
'rate': item_doc.valuation_rate or args.rate,
|
||||
})
|
||||
|
||||
bom.insert(ignore_permissions=True)
|
||||
bom.submit()
|
||||
if not args.do_not_save:
|
||||
bom.insert(ignore_permissions=True)
|
||||
|
||||
if not args.do_not_submit:
|
||||
bom.submit()
|
||||
|
||||
return bom
|
@ -407,6 +407,49 @@ class TestWorkOrder(unittest.TestCase):
|
||||
ste1 = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1))
|
||||
self.assertEqual(len(ste1.items), 3)
|
||||
|
||||
def test_operation_time_with_batch_size(self):
|
||||
fg_item = "Test Batch Size Item For BOM"
|
||||
rm1 = "Test Batch Size Item RM 1 For BOM"
|
||||
|
||||
for item in ["Test Batch Size Item For BOM", "Test Batch Size Item RM 1 For BOM"]:
|
||||
make_item(item, {
|
||||
"include_item_in_manufacturing": 1,
|
||||
"is_stock_item": 1
|
||||
})
|
||||
|
||||
bom_name = frappe.db.get_value("BOM",
|
||||
{"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
|
||||
|
||||
if not bom_name:
|
||||
bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
|
||||
bom.with_operations = 1
|
||||
bom.append("operations", {
|
||||
"operation": "_Test Operation 1",
|
||||
"workstation": "_Test Workstation 1",
|
||||
"description": "Test Data",
|
||||
"operating_cost": 100,
|
||||
"time_in_mins": 40,
|
||||
"batch_size": 5
|
||||
})
|
||||
|
||||
bom.save()
|
||||
bom.submit()
|
||||
bom_name = bom.name
|
||||
|
||||
work_order = make_wo_order_test_record(item=fg_item,
|
||||
planned_start_date=now(), qty=1, do_not_save=True)
|
||||
|
||||
work_order.set_work_order_operations()
|
||||
work_order.save()
|
||||
self.assertEqual(work_order.operations[0].time_in_mins, 8.0)
|
||||
|
||||
work_order1 = make_wo_order_test_record(item=fg_item,
|
||||
planned_start_date=now(), qty=5, do_not_save=True)
|
||||
|
||||
work_order1.set_work_order_operations()
|
||||
work_order1.save()
|
||||
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
|
||||
|
||||
def get_scrap_item_details(bom_no):
|
||||
scrap_items = {}
|
||||
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
|
||||
|
@ -403,7 +403,7 @@ class WorkOrder(Document):
|
||||
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
|
||||
|
||||
for d in self.get("operations"):
|
||||
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * math.ceil(flt(self.qty) / flt(d.batch_size))
|
||||
d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * (flt(self.qty) / flt(d.batch_size))
|
||||
|
||||
self.calculate_operating_cost()
|
||||
|
||||
@ -434,7 +434,7 @@ class WorkOrder(Document):
|
||||
elif flt(d.completed_qty) <= max_allowed_qty_for_wo:
|
||||
d.status = "Completed"
|
||||
else:
|
||||
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
|
||||
frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'"))
|
||||
|
||||
def set_actual_dates(self):
|
||||
if self.get("operations"):
|
||||
|
@ -729,3 +729,4 @@ erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports
|
||||
erpnext.patches.v13_0.rename_issue_doctype_fields
|
||||
erpnext.patches.v13_0.change_default_pos_print_format
|
||||
erpnext.patches.v13_0.set_youtube_video_id
|
||||
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
10
erpnext/patches/v13_0/print_uom_after_quantity_patch.py
Normal file
10
erpnext/patches/v13_0/print_uom_after_quantity_patch.py
Normal file
@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from erpnext.setup.install import create_print_uom_after_qty_custom_field
|
||||
|
||||
def execute():
|
||||
create_print_uom_after_qty_custom_field()
|
@ -217,7 +217,7 @@
|
||||
"fieldname": "help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Help",
|
||||
"options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
|
||||
"options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condition. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -238,14 +238,13 @@
|
||||
"depends_on": "eval:doc.type == \"Deduction\"",
|
||||
"fieldname": "is_income_tax_component",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Income Tax Component",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Is Income Tax Component"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-flag",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 15:39:20.826565",
|
||||
"modified": "2020-10-07 20:38:33.795853",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Component",
|
||||
|
@ -117,7 +117,7 @@
|
||||
"depends_on": "eval:doc.is_flexible_benefit != 1",
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Condtion and formula"
|
||||
"label": "Condition and formula"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@ -206,38 +206,28 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Component properties and references ",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Component properties and references "
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_19",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-01 12:13:41.956495",
|
||||
"modified": "2020-10-07 20:39:41.619283",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Detail",
|
||||
|
@ -347,8 +347,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
# create additional salary of 150000
|
||||
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
|
||||
data["additional-1"] = create_additional_salary(employee, payroll_period, 50000)
|
||||
data["additional-2"] = create_additional_salary(employee, payroll_period, 100000)
|
||||
data["additional-1"] = create_additional_salary(employee, payroll_period, 150000)
|
||||
data["deducted_dates"] = create_salary_slips_for_payroll_period(employee,
|
||||
salary_structure.name, payroll_period)
|
||||
|
||||
|
@ -136,6 +136,7 @@ def get_timesheet_details(filters, timesheet_list):
|
||||
return timesheet_details_map
|
||||
|
||||
def get_billable_and_total_duration(activity, start_time, end_time):
|
||||
precision = frappe.get_precision("Timesheet Detail", "hours")
|
||||
activity_duration = time_diff_in_hours(end_time, start_time)
|
||||
billing_duration = 0.0
|
||||
if activity.billable:
|
||||
@ -143,4 +144,4 @@ def get_billable_and_total_duration(activity, start_time, end_time):
|
||||
if activity_duration != activity.billing_hours:
|
||||
billing_duration = activity_duration * activity.billing_hours / activity.hours
|
||||
|
||||
return flt(activity_duration, 2), flt(billing_duration, 2)
|
||||
return flt(activity_duration, precision), flt(billing_duration, precision)
|
25
erpnext/public/js/address.js
Normal file
25
erpnext/public/js/address.js
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2016, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Address", {
|
||||
is_your_company_address: function(frm) {
|
||||
frm.clear_table('links');
|
||||
if(frm.doc.is_your_company_address) {
|
||||
frm.add_child('links', {
|
||||
link_doctype: 'Company',
|
||||
link_name: frappe.defaults.get_user_default('Company')
|
||||
});
|
||||
frm.set_query('link_doctype', 'links', () => {
|
||||
return {
|
||||
filters: {
|
||||
name: 'Company'
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.refresh_field('links');
|
||||
}
|
||||
else {
|
||||
frm.trigger('refresh');
|
||||
}
|
||||
}
|
||||
});
|
@ -309,7 +309,6 @@ erpnext.setup.fiscal_years = {
|
||||
"Hong Kong": ["04-01", "03-31"],
|
||||
"India": ["04-01", "03-31"],
|
||||
"Iran": ["06-23", "06-22"],
|
||||
"Italy": ["07-01", "06-30"],
|
||||
"Myanmar": ["04-01", "03-31"],
|
||||
"New Zealand": ["04-01", "03-31"],
|
||||
"Pakistan": ["07-01", "06-30"],
|
||||
|
@ -703,9 +703,13 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
}
|
||||
|
||||
frappe.form.link_formatters['Item'] = function(value, doc) {
|
||||
if(doc && doc.item_name && doc.item_name !== value) {
|
||||
return value? value + ': ' + doc.item_name: doc.item_name;
|
||||
if (doc && value && doc.item_name && doc.item_name !== value) {
|
||||
return value + ': ' + doc.item_name;
|
||||
} else if (!value && doc.doctype && doc.item_name) {
|
||||
// format blank value in child table
|
||||
return doc.item_name;
|
||||
} else {
|
||||
// if value is blank in report view or item code and name are the same, return as is
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
|
||||
erpnext.utils.get_shipping_address = function(frm, callback){
|
||||
if (frm.doc.company) {
|
||||
frappe.call({
|
||||
method: "frappe.contacts.doctype.address.address.get_shipping_address",
|
||||
method: "erpnext.accounts.custom.address.get_shipping_address",
|
||||
args: {
|
||||
company: frm.doc.company,
|
||||
address: frm.doc.shipping_address
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user