Merge branch 'develop' of https://github.com/frappe/erpnext into project-link-for-all-accounts
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
new file mode 100644
index 0000000..8f67858
--- /dev/null
+++ b/.github/workflows/docker-release.yml
@@ -0,0 +1,14 @@
+name: Trigger Docker build on release
+on:
+ release:
+ types: [created]
+jobs:
+ curl:
+ runs-on: ubuntu-latest
+ container:
+ image: alpine:latest
+ steps:
+ - name: curl
+ run: |
+ apk add curl bash
+ curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
diff --git a/CODEOWNERS b/CODEOWNERS
index 5e1113d..7cf65a7 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,17 +3,16 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
-* @nabinhait
-manufacturing/ @rohitwaghchaure
+manufacturing/ @rohitwaghchaure @marination
accounts/ @deepeshgarg007 @nextchamp-saqib
-loan_management/ @deepeshgarg007
-pos* @nextchamp-saqib
-assets/ @nextchamp-saqib
+loan_management/ @deepeshgarg007 @rohitwaghchaure
+pos* @nextchamp-saqib @rohitwaghchaure
+assets/ @nextchamp-saqib @deepeshgarg007
stock/ @marination @rohitwaghchaure
-buying/ @marination @rohitwaghchaure
-hr/ @Anurag810
-projects/ @hrwX
-support/ @hrwX
-healthcare/ @ruchamahabal
-erpnext_integrations/ @Mangesh-Khairnar
+buying/ @marination @deepeshgarg007
+hr/ @Anurag810 @rohitwaghchaure
+projects/ @hrwX @nextchamp-saqib
+support/ @hrwX @marination
+healthcare/ @ruchamahabal @marination
+erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
requirements.txt @gavindsouza
diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py
index 421c86d..b2abffc 100644
--- a/erpnext/accounts/dashboard_fixtures.py
+++ b/erpnext/accounts/dashboard_fixtures.py
@@ -5,7 +5,22 @@
import json
from frappe.utils import nowdate, add_months, get_date_str
from frappe import _
-from erpnext.accounts.utils import get_fiscal_year, get_account_name
+from erpnext.accounts.utils import get_fiscal_year, get_account_name, FiscalYearError
+
+def _get_fiscal_year(date=None):
+ try:
+ fiscal_year = get_fiscal_year(date=nowdate(), as_dict=True)
+ return fiscal_year
+
+ except FiscalYearError:
+ #if no fiscal year for current date then get default fiscal year
+ try:
+ fiscal_year = get_fiscal_year(as_dict=True)
+ return fiscal_year
+
+ except FiscalYearError:
+ #if still no fiscal year found then no accounting data created, return
+ return None
def get_company_for_dashboards():
company = frappe.defaults.get_defaults().company
@@ -18,10 +33,16 @@
return None
def get_data():
+
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
return frappe._dict({
"dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards()
+ "charts": get_charts(fiscal_year),
+ "number_cards": get_number_cards(fiscal_year)
})
def get_dashboards():
@@ -46,10 +67,9 @@
]
}]
-def get_charts():
+def get_charts(fiscal_year):
company = frappe.get_doc("Company", get_company_for_dashboards())
bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
- fiscal_year = get_fiscal_year(date=nowdate())
default_cost_center = company.cost_center
return [
@@ -61,8 +81,8 @@
"filters_json": json.dumps({
"company": company.name,
"filter_based_on": "Fiscal Year",
- "from_fiscal_year": fiscal_year[0],
- "to_fiscal_year": fiscal_year[0],
+ "from_fiscal_year": fiscal_year.get('name'),
+ "to_fiscal_year": fiscal_year.get('name'),
"periodicity": "Monthly",
"include_default_book_entries": 1
}),
@@ -158,8 +178,8 @@
"report_name": "Budget Variance Report",
"filters_json": json.dumps({
"company": company.name,
- "from_fiscal_year": fiscal_year[0],
- "to_fiscal_year": fiscal_year[0],
+ "from_fiscal_year": fiscal_year.get('name'),
+ "to_fiscal_year": fiscal_year.get('name'),
"period": "Monthly",
"budget_against": "Cost Center"
}),
@@ -190,10 +210,10 @@
},
]
-def get_number_cards():
- fiscal_year = get_fiscal_year(date=nowdate())
- year_start_date = get_date_str(fiscal_year[1])
- year_end_date = get_date_str(fiscal_year[2])
+def get_number_cards(fiscal_year):
+
+ year_start_date = get_date_str(fiscal_year.get("year_start_date"))
+ year_end_date = get_date_str(fiscal_year.get("year_end_date"))
return [
{
"doctype": "Number Card",
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index f62d076..28b090b 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -14,6 +14,9 @@
on_change: function() {
var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value();
+ if (!company) {
+ frappe.throw(__("Please set a Company"));
+ }
frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company",
args: {
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 894ec5b..8834385 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -72,7 +72,11 @@
if doctype == "Budget":
add_dimension_to_budget_doctype(df, doc)
else:
- create_custom_field(doctype, df)
+ meta = frappe.get_meta(doctype, cached=False)
+ fieldnames = [d.fieldname for d in meta.get("fields")]
+
+ if df['fieldname'] not in fieldnames:
+ create_custom_field(doctype, df)
count += 1
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index 9e2f6ee..f341f78 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -14,7 +14,18 @@
is_group: 1
}
}
- })
+ });
+
+ frm.set_query("cost_center", "distributed_cost_center", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ is_group: 0,
+ enable_distributed_cost_center: 0,
+ name: ['!=', frm.doc.name]
+ }
+ };
+ });
},
refresh: function(frm) {
if (!frm.is_new()) {
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json
index 5013c92..e7fa954 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.json
+++ b/erpnext/accounts/doctype/cost_center/cost_center.json
@@ -16,6 +16,9 @@
"cb0",
"is_group",
"disabled",
+ "section_break_9",
+ "enable_distributed_cost_center",
+ "distributed_cost_center",
"lft",
"rgt",
"old_parent"
@@ -119,13 +122,31 @@
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_distributed_cost_center",
+ "fieldtype": "Check",
+ "label": "Enable Distributed Cost Center"
+ },
+ {
+ "depends_on": "eval:doc.is_group==0",
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "enable_distributed_cost_center",
+ "fieldname": "distributed_cost_center",
+ "fieldtype": "Table",
+ "label": "Distributed Cost Center",
+ "options": "Distributed Cost Center"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-04-29 16:09:30.025214",
+ "modified": "2020-06-17 16:09:30.025214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 0294e78..12094d4 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -19,6 +19,24 @@
def validate(self):
self.validate_mandatory()
self.validate_parent_cost_center()
+ self.validate_distributed_cost_center()
+
+ def validate_distributed_cost_center(self):
+ if cint(self.enable_distributed_cost_center):
+ if not self.distributed_cost_center:
+ frappe.throw(_("Please enter distributed cost center"))
+ if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
+ frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
+ if not self.get('__islocal'):
+ if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
+ and self.check_if_part_of_distributed_cost_center():
+ frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
+ if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
+ frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
+ if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
+ frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
+ else:
+ self.distributed_cost_center = []
def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center:
@@ -43,12 +61,15 @@
return 1
def convert_ledger_to_group(self):
+ if cint(self.enable_distributed_cost_center):
+ frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
+ if self.check_if_part_of_distributed_cost_center():
+ frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
if self.check_gle_exists():
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
- else:
- self.is_group = 1
- self.save()
- return 1
+ self.is_group = 1
+ self.save()
+ return 1
def check_gle_exists(self):
return frappe.db.get_value("GL Entry", {"cost_center": self.name})
@@ -57,6 +78,9 @@
return frappe.db.sql("select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2", self.name)
+ def check_if_part_of_distributed_cost_center(self):
+ return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
+
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
from erpnext.setup.doctype.company.company import get_name_with_abbr
@@ -100,3 +124,7 @@
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
return new_account
+
+def check_if_distributed_cost_center_enabled(cost_center_list):
+ value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
+ return next((True for x in value_list if x[0]), False)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index 8f23d90..b5fc7e3 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -22,6 +22,33 @@
self.assertRaises(frappe.ValidationError, cost_center.save)
+ def test_validate_distributed_cost_center(self):
+
+ if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
+ frappe.get_doc(test_records[0]).insert()
+
+ if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
+ frappe.get_doc(test_records[1]).insert()
+
+ invalid_distributed_cost_center = frappe.get_doc({
+ "company": "_Test Company",
+ "cost_center_name": "_Test Distributed Cost Center",
+ "doctype": "Cost Center",
+ "is_group": 0,
+ "parent_cost_center": "_Test Company - _TC",
+ "enable_distributed_cost_center": 1,
+ "distributed_cost_center": [{
+ "cost_center": "_Test Cost Center - _TC",
+ "percentage_allocation": 40
+ }, {
+ "cost_center": "_Test Cost Center 2 - _TC",
+ "percentage_allocation": 50
+ }
+ ]
+ })
+
+ self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
+
def create_cost_center(**args):
args = frappe._dict(args)
if args.cost_center_name:
diff --git a/erpnext/support/doctype/service_level/__init__.py b/erpnext/accounts/doctype/distributed_cost_center/__init__.py
similarity index 100%
copy from erpnext/support/doctype/service_level/__init__.py
copy to erpnext/accounts/doctype/distributed_cost_center/__init__.py
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
new file mode 100644
index 0000000..45b0e2d
--- /dev/null
+++ b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "creation": "2020-03-19 12:34:01.500390",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "cost_center",
+ "percentage_allocation"
+ ],
+ "fields": [
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Cost Center",
+ "options": "Cost Center",
+ "reqd": 1
+ },
+ {
+ "fieldname": "percentage_allocation",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Percentage Allocation",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-03-19 12:54:43.674655",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Distributed Cost Center",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
new file mode 100644
index 0000000..48c589f
--- /dev/null
+++ b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class DistributedCostCenter(Document):
+ pass
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 9d50639..af2aa65 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -191,6 +191,7 @@
{
"fieldname": "total_debit",
"fieldtype": "Currency",
+ "in_list_view": 1,
"label": "Total Debit",
"no_copy": 1,
"oldfieldname": "total_debit",
@@ -252,7 +253,6 @@
"fieldname": "total_amount",
"fieldtype": "Currency",
"hidden": 1,
- "in_list_view": 1,
"label": "Total Amount",
"no_copy": 1,
"options": "total_amount_currency",
@@ -503,7 +503,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-29 10:55:28.240916",
+ "modified": "2020-06-02 18:15:46.955697",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index 26c84a6..ff3533a 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -18,6 +18,7 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
+ "project",
"currency_section",
"account_currency",
"column_break_10",
@@ -32,7 +33,6 @@
"reference_type",
"reference_name",
"reference_due_date",
- "project",
"col_break3",
"is_advance",
"user_remark",
@@ -273,7 +273,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-25 01:47:49.060128",
+ "modified": "2020-06-18 14:06:54.833738",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cfcefff..bd15f76 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -319,7 +319,7 @@
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += reference.allocated_amount
- if not invoice_paid_amount_map.get(reference.reference_name):
+ if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
fields=['paid_amount', 'payment_amount', 'payment_term'])
for term in payment_schedule:
@@ -332,12 +332,14 @@
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
else:
- outstanding = invoice_paid_amount_map.get(key)['outstanding']
+ outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
+
if amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
- frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
- WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
+ if amount and outstanding:
+ frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
+ WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
def set_status(self):
if self.docstatus == 2:
@@ -1091,17 +1093,20 @@
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
- references.append({
- 'reference_doctype': dt,
- 'reference_name': dn,
- 'bill_no': doc.get('bill_no'),
- 'due_date': doc.get('due_date'),
- 'total_amount': grand_total,
- 'outstanding_amount': outstanding_amount,
- 'payment_term': payment_term.payment_term,
- 'allocated_amount': flt(payment_term.payment_amount - payment_term.paid_amount,
+ payment_term_outstanding = flt(payment_term.payment_amount - payment_term.paid_amount,
payment_term.precision('payment_amount'))
- })
+
+ if payment_term_outstanding:
+ references.append({
+ 'reference_doctype': dt,
+ 'reference_name': dn,
+ 'bill_no': doc.get('bill_no'),
+ 'due_date': doc.get('due_date'),
+ 'total_amount': grand_total,
+ 'outstanding_amount': outstanding_amount,
+ 'payment_term': payment_term.payment_term,
+ 'allocated_amount': payment_term_outstanding
+ })
return references
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 7508683..eef6be1 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -349,9 +349,10 @@
"read_only": 1
}
],
+ "in_create": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-08 10:23:02.815237",
+ "modified": "2020-05-29 17:38:49.392713",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index a80637e..eb1ccd9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -43,6 +43,8 @@
"col_break_address",
"shipping_address",
"shipping_address_display",
+ "billing_address",
+ "billing_address_display",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -169,7 +171,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -181,7 +185,9 @@
"options": "ACC-PINV-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier",
@@ -193,7 +199,9 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -205,7 +213,9 @@
"label": "Supplier Name",
"oldfieldname": "supplier_name",
"oldfieldtype": "Data",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fetch_from": "supplier.tax_id",
@@ -213,21 +223,27 @@
"fieldtype": "Read Only",
"label": "Tax Id",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date",
"oldfieldname": "due_date",
- "oldfieldtype": "Date"
+ "oldfieldtype": "Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "is_paid",
"fieldtype": "Check",
"label": "Is Paid",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -235,19 +251,25 @@
"fieldtype": "Check",
"label": "Is Return (Debit Note)",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -257,13 +279,17 @@
"label": "Company",
"options": "Company",
"print_hide": 1,
- "remember_last_selected_value": 1
+ "remember_last_selected_value": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center"
+ "options": "Cost Center",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Today",
@@ -275,7 +301,9 @@
"oldfieldtype": "Date",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "posting_time",
@@ -284,6 +312,8 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -292,7 +322,9 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -304,44 +336,58 @@
"oldfieldtype": "Link",
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.on_hold",
"fieldname": "sb_14",
"fieldtype": "Section Break",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "on_hold",
"fieldtype": "Check",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"description": "Once set, this invoice will be on hold till the set date",
"fieldname": "release_date",
"fieldtype": "Date",
- "label": "Release Date"
+ "label": "Release Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_17",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"fieldname": "hold_comment",
"fieldtype": "Small Text",
- "label": "Reason For Putting On Hold"
+ "label": "Reason For Putting On Hold",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "bill_no",
"fieldname": "supplier_invoice_details",
"fieldtype": "Section Break",
- "label": "Supplier Invoice Details"
+ "label": "Supplier Invoice Details",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_no",
@@ -349,11 +395,15 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_date",
@@ -361,13 +411,17 @@
"label": "Supplier Invoice Date",
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
- "label": "Returns"
+ "label": "Returns",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
@@ -377,26 +431,34 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
@@ -404,51 +466,67 @@
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -457,7 +535,9 @@
"oldfieldname": "currency",
"oldfieldtype": "Select",
"options": "Currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -466,18 +546,24 @@
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -485,14 +571,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -501,11 +591,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -513,7 +607,9 @@
"fieldtype": "Link",
"label": "Set Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -523,11 +619,15 @@
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -535,7 +635,9 @@
"fieldtype": "Select",
"label": "Raw Materials Supplied",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
@@ -546,25 +648,33 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -574,42 +684,56 @@
"oldfieldname": "entries",
"oldfieldtype": "Table",
"options": "Purchase Invoice Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_materials_supplied",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
"fieldtype": "Table",
"label": "Supplied Items",
"options": "Purchase Receipt Item Supplied",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_26",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -617,7 +741,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -627,18 +753,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_28",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -648,42 +780,56 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_49",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_51",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -692,7 +838,9 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -700,13 +848,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -715,13 +867,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -731,7 +887,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -741,7 +899,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -751,11 +911,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_40",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_added",
@@ -765,7 +929,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -775,7 +941,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -783,14 +951,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_44",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -798,7 +970,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -806,28 +980,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_46",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_49",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -837,7 +1021,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -846,7 +1032,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -856,7 +1044,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_in_words",
@@ -865,13 +1055,17 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break8",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -882,7 +1076,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounding_adjustment",
@@ -891,7 +1087,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -901,7 +1099,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -910,7 +1110,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_advance",
@@ -921,7 +1123,9 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "outstanding_amount",
@@ -932,14 +1136,18 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"depends_on": "grand_total",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -947,30 +1155,40 @@
"depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
- "label": "Payments"
+ "label": "Payments",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"label": "Mode of Payment",
"options": "Mode of Payment",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "clearance_date",
"fieldtype": "Date",
"hidden": 1,
- "label": "Clearance Date"
+ "label": "Clearance Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_br_payments",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_paid",
@@ -979,7 +1197,9 @@
"label": "Paid Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_paid_amount",
@@ -988,7 +1208,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -996,7 +1218,9 @@
"depends_on": "grand_total",
"fieldname": "write_off",
"fieldtype": "Section Break",
- "label": "Write Off"
+ "label": "Write Off",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "write_off_amount",
@@ -1004,7 +1228,9 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_write_off_amount",
@@ -1013,11 +1239,15 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_61",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1025,7 +1255,9 @@
"fieldtype": "Link",
"label": "Write Off Account",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1033,7 +1265,9 @@
"fieldtype": "Link",
"label": "Write Off Cost Center",
"options": "Cost Center",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1043,13 +1277,17 @@
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "allocate_advances_automatically",
"fieldtype": "Check",
- "label": "Set Advances and Allocate (FIFO)"
+ "label": "Set Advances and Allocate (FIFO)",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1057,7 +1295,9 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advances",
@@ -1067,20 +1307,26 @@
"oldfieldname": "advance_allocation_details",
"oldfieldtype": "Table",
"options": "Purchase Invoice Advance",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:(!doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -1088,7 +1334,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1096,25 +1344,33 @@
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"label": "Terms and Conditions",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
"fieldtype": "Link",
"label": "Terms",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
- "label": "Terms and Conditions1"
+ "label": "Terms and Conditions1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1122,7 +1378,9 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1130,11 +1388,15 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_112",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1146,14 +1408,18 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1162,7 +1428,9 @@
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "credit_to",
@@ -1173,7 +1441,9 @@
"options": "Account",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -1183,7 +1453,9 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -1193,7 +1465,9 @@
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "against_expense_account",
@@ -1203,11 +1477,15 @@
"no_copy": 1,
"oldfieldname": "against_expense_account",
"oldfieldtype": "Small Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_63",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -1216,14 +1494,18 @@
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_invoice_reference",
"fieldtype": "Link",
"label": "Inter Company Invoice Reference",
"options": "Sales Invoice",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
@@ -1232,14 +1514,18 @@
"no_copy": 1,
"oldfieldname": "remarks",
"oldfieldtype": "Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1248,7 +1534,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1257,11 +1545,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_114",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1270,24 +1562,32 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions "
+ "label": "Accounting Dimensions ",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1295,7 +1595,9 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "project",
@@ -1309,14 +1611,32 @@
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address",
+ "fieldtype": "Link",
+ "label": "Select Billing Address",
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address_display",
+ "fieldtype": "Small Text",
+ "label": "Billing Address",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-29 13:05:25.199832",
+ "modified": "2020-06-13 22:26:30.800199",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f60f87f..b2b4cb1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -582,14 +582,14 @@
def validate_item_code(self):
for d in self.get('items'):
- if not d.item_code:
+ if not d.item_code and self.is_opening == "No":
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
def validate_warehouse(self):
super(SalesInvoice, self).validate_warehouse()
for d in self.get_item_list():
- if not d.warehouse and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
+ if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
def validate_delivery_note(self):
@@ -1454,11 +1454,17 @@
parties = frappe.db.get_all("Supplier", fields=["name"], filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company})
company = frappe.get_cached_value("Customer", doc.customer, "represents_company")
+ if not parties:
+ frappe.throw(_('No Supplier found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
+
party = get_internal_party(parties, "Supplier", doc)
else:
parties = frappe.db.get_all("Customer", fields=["name"], filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company})
company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company")
+ if not parties:
+ frappe.throw(_('No Customer found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
+
party = get_internal_party(parties, "Customer", doc)
return {
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index a2f9040..8dbf0a8 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1772,53 +1772,6 @@
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
- def test_deferred_error_email(self):
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
-
- item = create_item("_Test Item for Deferred Accounting")
- item.enable_deferred_revenue = 1
- item.deferred_revenue_account = deferred_account
- item.no_of_months = 12
- item.save()
-
- si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
- si.items[0].enable_deferred_revenue = 1
- si.items[0].service_start_date = "2019-01-10"
- si.items[0].service_end_date = "2019-03-15"
- si.items[0].deferred_revenue_account = deferred_account
- si.save()
- si.submit()
-
- from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
-
- acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- acc_settings.acc_frozen_upto = '2019-01-31'
- acc_settings.save()
-
- pda = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date=nowdate(),
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Income",
- company="_Test Company"
- ))
-
- pda.insert()
- pda.submit()
-
- email = frappe.db.sql(""" select name from `tabEmail Queue`
- where message like %(txt)s """, {
- 'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name)
- })
-
- self.assertTrue(email)
-
- acc_settings.load_from_db()
- acc_settings.acc_frozen_upto = None
- acc_settings.save()
-
def test_inter_company_transaction(self):
if not frappe.db.exists("Customer", "_Test Internal Customer"):
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index b2638c7..d32a348 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -45,7 +45,9 @@
shipping_amount = 0.0
by_value = False
- self.validate_countries(doc)
+ if doc.get_shipping_address():
+ # validate country only if there is address
+ self.validate_countries(doc)
if self.calculate_based_on == 'Net Total':
value = doc.base_net_total
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index d40e58b..66aa180 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -169,9 +169,11 @@
def append_subtotal_row(self, party):
sub_total_row = self.total_row_map.get(party)
- self.data.append(sub_total_row)
- self.data.append({})
- self.update_sub_total_row(sub_total_row, 'Total')
+
+ if sub_total_row:
+ self.data.append(sub_total_row)
+ self.data.append({})
+ self.update_sub_total_row(sub_total_row, 'Total')
def get_voucher_balance(self, gle):
if self.filters.get("sales_person"):
@@ -232,7 +234,8 @@
if self.filters.get('group_by_party'):
self.append_subtotal_row(self.previous_party)
- self.data.append(self.total_row_map.get('Total'))
+ if self.data:
+ self.data.append(self.total_row_map.get('Total'))
def append_row(self, row):
self.allocate_future_payments(row)
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 05dc282..9c9ada8 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -29,37 +29,60 @@
for dimension in dimensions:
dimension_items = cam_map.get(dimension)
if dimension_items:
- for account, monthwise_data in iteritems(dimension_items):
- row = [dimension, account]
- totals = [0, 0, 0]
- for year in get_fiscal_years(filters):
- last_total = 0
- for relevant_months in period_month_ranges:
- period_data = [0, 0, 0]
- for month in relevant_months:
- if monthwise_data.get(year[0]):
- month_data = monthwise_data.get(year[0]).get(month, {})
- for i, fieldname in enumerate(["target", "actual", "variance"]):
- value = flt(month_data.get(fieldname))
- period_data[i] += value
- totals[i] += value
-
- period_data[0] += last_total
-
- if filters.get("show_cumulative"):
- last_total = period_data[0] - period_data[1]
-
- period_data[2] = period_data[0] - period_data[1]
- row += period_data
- totals[2] = totals[0] - totals[1]
- if filters["period"] != "Yearly":
- row += totals
- data.append(row)
+ data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
+ else:
+ DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(dimension)s
+ AND parent NOT IN %(dimension)s
+ GROUP BY parent''',{'dimension':[dimension]})
+ if DCC_allocation:
+ filters['budget_against_filter'] = [DCC_allocation[0][0]]
+ cam_map = get_dimension_account_month_map(filters)
+ dimension_items = cam_map.get(DCC_allocation[0][0])
+ if dimension_items:
+ data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
chart = get_chart_data(filters, columns, data)
return columns, data, None, chart
+def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
+
+ for account, monthwise_data in iteritems(dimension_items):
+ row = [dimension, account]
+ totals = [0, 0, 0]
+ for year in get_fiscal_years(filters):
+ last_total = 0
+ for relevant_months in period_month_ranges:
+ period_data = [0, 0, 0]
+ for month in relevant_months:
+ if monthwise_data.get(year[0]):
+ month_data = monthwise_data.get(year[0]).get(month, {})
+ for i, fieldname in enumerate(["target", "actual", "variance"]):
+ value = flt(month_data.get(fieldname))
+ period_data[i] += value
+ totals[i] += value
+
+ period_data[0] += last_total
+
+ if DCC_allocation:
+ period_data[0] = period_data[0]*(DCC_allocation/100)
+ period_data[1] = period_data[1]*(DCC_allocation/100)
+
+ if(filters.get("show_cumulative")):
+ last_total = period_data[0] - period_data[1]
+
+ period_data[2] = period_data[0] - period_data[1]
+ row += period_data
+ totals[2] = totals[0] - totals[1]
+ if filters["period"] != "Yearly" :
+ row += totals
+ data.append(row)
+
+ return data
+
+
def get_columns(filters):
columns = [
{
@@ -366,7 +389,7 @@
budget_values[i] += values[index]
actual_values[i] += values[index+1]
index += 3
-
+
return {
'data': {
'labels': labels,
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 4a35a66..533685d 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -56,9 +56,8 @@
to_date = add_months(start_date, months_to_add)
start_date = to_date
- if to_date == get_first_day(to_date):
- # if to_date is the first day, get the last day of previous month
- to_date = add_days(to_date, -1)
+ # Subtract one day from to_date, as it may be first day in next fiscal year or month
+ to_date = add_days(to_date, -1)
if to_date <= year_end_date:
# the normal case
@@ -387,11 +386,43 @@
key: value
})
+ distributed_cost_center_query = ""
+ if filters and filters.get('cost_center'):
+ distributed_cost_center_query = """
+ UNION ALL
+ SELECT posting_date,
+ account,
+ debit*(DCC_allocation.percentage_allocation/100) as debit,
+ credit*(DCC_allocation.percentage_allocation/100) as credit,
+ is_opening,
+ fiscal_year,
+ debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
+ credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency,
+ account_currency
+ FROM `tabGL Entry`,
+ (
+ SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(cost_center)s
+ AND parent NOT IN %(cost_center)s
+ AND is_cancelled = 0
+ GROUP BY parent
+ ) as DCC_allocation
+ WHERE company=%(company)s
+ {additional_conditions}
+ AND posting_date <= %(to_date)s
+ AND cost_center = DCC_allocation.parent
+ """.format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
+
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
- order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec
+ and is_cancelled = 0
+ {distributed_cost_center_query}
+ order by account, posting_date""".format(
+ additional_conditions=additional_conditions,
+ distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
if filters and filters.get('presentation_currency'):
convert_to_presentation_currency(gl_entries, get_currency(filters))
@@ -489,4 +520,4 @@
"width": 150
})
- return columns
+ return columns
\ No newline at end of file
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index f83a259..fcd36e4 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -128,18 +128,53 @@
filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book')
+ distributed_cost_center_query = ""
+ if filters and filters.get('cost_center'):
+ select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
+ credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
+
+ distributed_cost_center_query = """
+ UNION ALL
+ SELECT name as gl_entry,
+ posting_date,
+ account,
+ party_type,
+ party,
+ voucher_type,
+ voucher_no,
+ cost_center, project,
+ against_voucher_type,
+ against_voucher,
+ account_currency,
+ remarks, against,
+ is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
+ FROM `tabGL Entry`,
+ (
+ SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(cost_center)s
+ AND parent NOT IN %(cost_center)s
+ GROUP BY parent
+ ) as DCC_allocation
+ WHERE company=%(company)s
+ {conditions}
+ AND posting_date <= %(to_date)s
+ AND cost_center = DCC_allocation.parent
+ """.format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
+
gl_entries = frappe.db.sql(
"""
select
name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, cost_center, project,
against_voucher_type, against_voucher, account_currency,
- remarks, against, is_opening {select_fields}
+ remarks, against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
+ {distributed_cost_center_query}
{order_by_statement}
""".format(
- select_fields=select_fields, conditions=get_conditions(filters),
+ select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
order_by_statement=order_by_statement
),
filters, as_dict=1)
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index 9777ed1..3445df7 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -265,13 +265,6 @@
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
- },
- {
- 'fieldname': 'currency',
- 'label': _('Currency'),
- 'fieldtype': 'Currency',
- 'width': 80,
- 'hidden': 1
}
]
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index bb78ee2..a05dcd7 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -223,7 +223,7 @@
}
]
- if filters.get('group_by') != 'Terriotory':
+ if filters.get('group_by') != 'Territory':
columns.extend([
{
'label': _("Territory"),
@@ -304,13 +304,6 @@
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
- },
- {
- 'fieldname': 'currency',
- 'label': _('Currency'),
- 'fieldtype': 'Currency',
- 'width': 80,
- 'hidden': 1
}
]
@@ -536,6 +529,13 @@
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
+ },
+ {
+ 'fieldname': 'currency',
+ 'label': _('Currency'),
+ 'fieldtype': 'Currency',
+ 'width': 80,
+ 'hidden': 1
}
]
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 6e9b31f..60e675f 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -105,6 +105,7 @@
def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
data = []
+ new_accounts = accounts
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
for d in accounts:
@@ -118,6 +119,19 @@
"currency": company_currency,
"based_on": based_on
}
+ if based_on == 'cost_center':
+ cost_center_doc = frappe.get_doc("Cost Center",d.name)
+ if not cost_center_doc.enable_distributed_cost_center:
+ DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(cost_center)s
+ AND parent NOT IN %(cost_center)s
+ GROUP BY parent""",{'cost_center': [d.name]})
+ if DCC_allocation:
+ for account in new_accounts:
+ if account['name'] == DCC_allocation[0][0]:
+ for value in value_fields:
+ d[value] += account[value]*(DCC_allocation[0][1]/100)
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 4ac0f65..a9fb237 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -111,7 +111,7 @@
# {"purchase_invoice": list of dict of all gle created for this invoice}
gle_map = {}
gle = frappe.db.get_all('GL Entry',\
- {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]]},
+ {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
for d in gle:
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 58cd570..830bed2 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -57,6 +57,9 @@
frappe.cache().hset("fiscal_years", company, fiscal_years)
+ if not transaction_date and not fiscal_year:
+ return fiscal_years
+
if transaction_date:
transaction_date = getdate(transaction_date)
@@ -79,6 +82,23 @@
if verbose==1: frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)
+@frappe.whitelist()
+def get_fiscal_year_filter_field(company=None):
+ field = {
+ "fieldtype": "Select",
+ "options": [],
+ "operator": "Between",
+ "query_value": True
+ }
+ fiscal_years = get_fiscal_years(company=company)
+ for fiscal_year in fiscal_years:
+ field["options"].append({
+ "label": fiscal_year.name,
+ "value": fiscal_year.name,
+ "query_value": [fiscal_year.year_start_date.strftime("%Y-%m-%d"), fiscal_year.year_end_date.strftime("%Y-%m-%d")]
+ })
+ return field
+
def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
years = [f[0] for f in get_fiscal_years(date, label=_(label), company=company)]
if fiscal_year not in years:
@@ -935,4 +955,4 @@
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
- return gl_entries
\ No newline at end of file
+ return gl_entries
diff --git a/erpnext/assets/dashboard_fixtures.py b/erpnext/assets/dashboard_fixtures.py
index 9af45d1..7f3c1de 100644
--- a/erpnext/assets/dashboard_fixtures.py
+++ b/erpnext/assets/dashboard_fixtures.py
@@ -5,14 +5,23 @@
import json
from frappe.utils import nowdate, add_months, get_date_str
from frappe import _
-from erpnext.accounts.utils import get_fiscal_year
-
+from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
+from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
def get_data():
+
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ year_start_date = get_date_str(fiscal_year.get('year_start_date'))
+ year_end_date = get_date_str(fiscal_year.get('year_end_date'))
+
return frappe._dict({
"dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
+ "charts": get_charts(fiscal_year, year_start_date, year_end_date),
+ "number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
})
def get_dashboards():
@@ -31,12 +40,7 @@
]
}]
-fiscal_year = get_fiscal_year(date=nowdate())
-year_start_date = get_date_str(fiscal_year[1])
-year_end_date = get_date_str(fiscal_year[2])
-
-
-def get_charts():
+def get_charts(fiscal_year, year_start_date, year_end_date):
company = get_company_for_dashboards()
return [
{
@@ -55,8 +59,8 @@
"company": company,
"status": "In Location",
"filter_based_on": "Fiscal Year",
- "from_fiscal_year": fiscal_year[0],
- "to_fiscal_year": fiscal_year[0],
+ "from_fiscal_year": fiscal_year.get('name'),
+ "to_fiscal_year": fiscal_year.get('name'),
"period_start_date": year_start_date,
"period_end_date": year_end_date,
"date_based_on": "Purchase Date",
@@ -134,7 +138,7 @@
}
]
-def get_number_cards():
+def get_number_cards(fiscal_year, year_start_date, year_end_date):
return [
{
"name": "Total Assets",
@@ -172,14 +176,4 @@
"filters_json": "[]",
"doctype": "Number Card"
}
- ]
-
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
\ No newline at end of file
+ ]
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index d6adde6..1869a29 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -41,7 +41,7 @@
team_member = frappe.db.get_value('User', assign_to_member, "email")
args = {
'doctype' : 'Asset Maintenance',
- 'assign_to' : team_member,
+ 'assign_to' : [team_member],
'name' : asset_maintenance_name,
'description' : maintenance_task,
'date' : next_due_date
diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py
index 186bfb2..172c936 100644
--- a/erpnext/buying/dashboard_fixtures.py
+++ b/erpnext/buying/dashboard_fixtures.py
@@ -5,13 +5,24 @@
import json
from frappe import _
from frappe.utils import nowdate
-from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
def get_data():
+
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ fiscal_year_name = fiscal_year.get("name")
+ start_date = str(fiscal_year.get("year_start_date"))
+ end_date = str(fiscal_year.get("year_end_date"))
+
return frappe._dict({
"dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
+ "charts": get_charts(company, fiscal_year_name, start_date, end_date),
+ "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
})
def get_company_for_dashboards():
@@ -24,12 +35,6 @@
return company_list[0].name
return None
-company = frappe.get_doc("Company", get_company_for_dashboards())
-fiscal_year = get_fiscal_year(nowdate(), as_dict=1)
-fiscal_year_name = fiscal_year.get("name")
-start_date = str(fiscal_year.get("year_start_date"))
-end_date = str(fiscal_year.get("year_end_date"))
-
def get_dashboards():
return [{
"name": "Buying",
@@ -48,7 +53,7 @@
]
}]
-def get_charts():
+def get_charts(company, fiscal_year_name, start_date, end_date):
return [
{
"name": "Purchase Order Analysis",
@@ -139,7 +144,7 @@
}
]
-def get_number_cards():
+def get_number_cards(company, fiscal_year_name, start_date, end_date):
return [
{
"name": "Annual Purchase",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index a4f60fb..13f5cb0 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -38,6 +38,8 @@
"col_break_address",
"shipping_address",
"shipping_address_display",
+ "billing_address",
+ "billing_address_display",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -135,7 +137,9 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user"
+ "options": "fa fa-user",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -145,7 +149,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -157,7 +163,9 @@
"options": "PUR-ORD-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -170,14 +178,18 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
"description": "Fetch items based on Default Supplier.",
"fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button",
- "label": "Get Items from Open Material Requests"
+ "label": "Get Items from Open Material Requests",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -186,7 +198,9 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company",
@@ -198,13 +212,17 @@
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -216,27 +234,35 @@
"oldfieldname": "transaction_date",
"oldfieldtype": "Date",
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "schedule_date",
"fieldtype": "Date",
- "label": "Required By"
+ "label": "Required By",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.docstatus===1",
"fieldname": "order_confirmation_no",
"fieldtype": "Data",
- "label": "Order Confirmation No"
+ "label": "Order Confirmation No",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.order_confirmation_no",
"fieldname": "order_confirmation_date",
"fieldtype": "Date",
- "label": "Order Confirmation Date"
+ "label": "Order Confirmation Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -248,19 +274,25 @@
"oldfieldtype": "Data",
"options": "Purchase Order",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "drop_ship",
"fieldtype": "Section Break",
- "label": "Drop Ship"
+ "label": "Drop Ship",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -268,31 +300,41 @@
"fieldtype": "Data",
"label": "Customer Name",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_19",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_person",
"fieldtype": "Link",
"label": "Customer Contact",
- "options": "Contact"
+ "options": "Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_display",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_mobile",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Mobile No",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_email",
@@ -300,46 +342,60 @@
"hidden": 1,
"label": "Customer Contact Email",
"options": "Email",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
@@ -347,32 +403,42 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -382,7 +448,9 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -392,18 +460,24 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_price_list",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -411,14 +485,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -427,11 +505,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Sets 'Warehouse' in each row of the Items table.",
@@ -439,11 +521,15 @@
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -452,25 +538,33 @@
"in_standard_filter": 1,
"label": "Supply Raw Materials",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -480,26 +574,34 @@
"oldfieldname": "po_details",
"oldfieldtype": "Table",
"options": "Purchase Order Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_break_48",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Purchase Order Pricing Rule",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_material_details",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
@@ -509,17 +611,23 @@
"oldfieldtype": "Table",
"options": "Purchase Order Item Supplied",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sb_last_purchase",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -527,7 +635,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -538,18 +648,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_26",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -559,20 +675,26 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -581,22 +703,30 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_52",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -604,13 +734,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -619,13 +753,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -635,7 +773,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -645,7 +785,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -656,11 +798,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_39",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_added",
@@ -670,7 +816,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -680,7 +828,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -688,14 +838,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "discount_section",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -703,7 +857,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -711,28 +867,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_45",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals_section",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -743,7 +909,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -752,7 +920,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "In Words will be visible once you save the Purchase Order.",
@@ -762,7 +932,9 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounded_total",
@@ -772,12 +944,16 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
- "oldfieldtype": "Column Break"
+ "oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "grand_total",
@@ -787,7 +963,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounding_adjustment",
@@ -796,20 +974,26 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -818,7 +1002,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advance_paid",
@@ -827,19 +1013,25 @@
"no_copy": 1,
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -847,7 +1039,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -856,7 +1050,9 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
@@ -865,21 +1061,27 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor"
+ "oldfieldtype": "Text Editor",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
- "oldfieldtype": "Section Break"
+ "oldfieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -894,7 +1096,9 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "ref_sq",
@@ -905,7 +1109,9 @@
"oldfieldname": "ref_sq",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -915,18 +1121,24 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_order_reference",
"fieldtype": "Link",
"label": "Inter Company Order Reference",
"options": "Sales Order",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_74",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -936,7 +1148,9 @@
"label": "% Received",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -946,7 +1160,9 @@
"label": "% Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -956,6 +1172,8 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -966,7 +1184,9 @@
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -978,11 +1198,15 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_86",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -990,19 +1214,25 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
- "label": "Subscription Section"
+ "label": "Subscription Section",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1010,7 +1240,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1018,11 +1250,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1031,44 +1267,72 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
- "options": "Tax Category"
+ "options": "Tax Category",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "supplied_items",
"fieldname": "set_reserve_warehouse",
"fieldtype": "Link",
"label": "Set Reserve Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "tracking_section",
"fieldtype": "Section Break",
- "label": "Tracking"
+ "label": "Tracking",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_75",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address",
+ "fieldtype": "Link",
+ "label": "Select Billing Address",
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address_display",
+ "fieldtype": "Small Text",
+ "label": "Billing Address",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-24 12:13:14.186280",
+ "modified": "2020-06-13 22:25:47.333850",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1112,6 +1376,12 @@
"read": 1,
"role": "Purchase Manager",
"write": 1
+ },
+ {
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Accounts User"
}
],
"search_fields": "status, transaction_date, supplier,grand_total",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index f62df20..c7efb8a 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -71,6 +71,15 @@
"compare_fields": [["project", "="], ["item_code", "="],
["uom", "="], ["conversion_factor", "="]],
"is_child_table": True
+ },
+ "Material Request": {
+ "ref_dn_field": "material_request",
+ "compare_fields": [["company", "="]],
+ },
+ "Material Request Item": {
+ "ref_dn_field": "material_request_item",
+ "compare_fields": [["project", "="], ["item_code", "="]],
+ "is_child_table": True
}
})
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 1712369..813286f 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -118,7 +118,7 @@
self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
-
+
def test_add_new_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -144,7 +144,7 @@
self.assertEquals(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
-
+
def test_remove_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -185,6 +185,23 @@
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
+ def test_update_child_qty_rate_perm(self):
+ po = create_purchase_order(item_code= "_Test Item", qty=4)
+
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Accounts User")
+ frappe.set_user(user)
+
+ # update qty
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+
+ # add new item
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+ frappe.set_user("Administrator")
+
def test_update_qty(self):
po = create_purchase_order()
@@ -689,7 +706,7 @@
po.save()
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
-
+
def test_po_optional_blanket_order(self):
"""
Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 95db33b..dfdb487 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -25,6 +25,7 @@
self.validate_duplicate_supplier()
self.validate_supplier_list()
validate_for_items(self)
+ super(RequestforQuotation, self).set_qty_as_per_stock_uom()
self.update_email_id()
def validate_duplicate_supplier(self):
@@ -278,6 +279,7 @@
"description": data.description,
"qty": data.qty,
"rate": data.rate,
+ "conversion_factor": data.conversion_factor if data.conversion_factor else None,
"supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
"warehouse": data.warehouse or '',
"request_for_quotation_item": data.name,
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index dbd9f02..3de9526 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -6,12 +6,14 @@
import unittest
import frappe
-from erpnext.templates.pages.rfq import check_supplier_has_docname_access
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 create_supplier_quotation
class TestRequestforQuotation(unittest.TestCase):
def test_quote_status(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
rfq = make_request_for_quotation()
self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Pending')
@@ -31,7 +33,6 @@
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote')
def test_make_supplier_quotation(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
rfq = make_request_for_quotation()
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
@@ -51,15 +52,13 @@
self.assertEqual(sq1.get('items')[0].qty, 5)
def test_make_supplier_quotation_with_special_characters(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
-
frappe.delete_doc_if_exists("Supplier", "_Test Supplier '1", force=1)
supplier = frappe.new_doc("Supplier")
supplier.supplier_name = "_Test Supplier '1"
supplier.supplier_group = "_Test Supplier Group"
supplier.insert()
- rfq = make_request_for_quotation(supplier_wt_appos)
+ rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier"))
sq.submit()
@@ -76,7 +75,6 @@
frappe.form_dict.name = None
def test_make_supplier_quotation_from_portal(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
rfq = make_request_for_quotation()
rfq.get('items')[0].rate = 100
rfq.supplier = rfq.suppliers[0].supplier
@@ -90,12 +88,34 @@
self.assertEqual(supplier_quotation_doc.get('items')[0].qty, 5)
self.assertEqual(supplier_quotation_doc.get('items')[0].amount, 500)
+ def test_make_multi_uom_supplier_quotation(self):
+ item_code = "_Test Multi UOM RFQ Item"
+ if not frappe.db.exists('Item', item_code):
+ item = make_item(item_code, {'stock_uom': '_Test UOM'})
+ row = item.append('uoms', {
+ 'uom': 'Kg',
+ 'conversion_factor': 2
+ })
+ row.db_update()
-def make_request_for_quotation(supplier_data=None):
+ rfq = make_request_for_quotation(item_code="_Test Multi UOM RFQ Item", uom="Kg", conversion_factor=2)
+ rfq.get('items')[0].rate = 100
+ rfq.supplier = rfq.suppliers[0].supplier
+
+ self.assertEqual(rfq.items[0].stock_qty, 10)
+
+ supplier_quotation_name = create_supplier_quotation(rfq)
+ supplier_quotation = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
+
+ self.assertEqual(supplier_quotation.items[0].qty, 5)
+ self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
+
+def make_request_for_quotation(**args):
"""
:param supplier_data: List containing supplier data
"""
- supplier_data = supplier_data if supplier_data else get_supplier_data()
+ args = frappe._dict(args)
+ supplier_data = args.get("supplier_data") if args.get("supplier_data") else get_supplier_data()
rfq = frappe.new_doc('Request for Quotation')
rfq.transaction_date = nowdate()
rfq.status = 'Draft'
@@ -106,11 +126,13 @@
rfq.append('suppliers', data)
rfq.append("items", {
- "item_code": "_Test Item",
+ "item_code": args.item_code or "_Test Item",
"description": "_Test Item",
- "uom": "_Test UOM",
- "qty": 5,
- "warehouse": "_Test Warehouse - _TC",
+ "uom": args.uom or "_Test UOM",
+ "stock_uom": args.stock_uom or "_Test UOM",
+ "qty": args.qty or 5,
+ "conversion_factor": args.conversion_factor or 1.0,
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
"schedule_date": nowdate()
})
diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
index 0159df9..408f49f 100644
--- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
+++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2016-02-25 08:04:02.452958",
"doctype": "DocType",
@@ -9,6 +10,7 @@
"supplier_part_no",
"column_break_3",
"item_name",
+ "schedule_date",
"section_break_5",
"description",
"item_group",
@@ -18,9 +20,11 @@
"image_view",
"quantity",
"qty",
+ "stock_uom",
"col_break2",
- "schedule_date",
"uom",
+ "conversion_factor",
+ "stock_qty",
"warehouse_and_reference",
"warehouse",
"project_name",
@@ -33,7 +37,7 @@
"fields": [
{
"bold": 1,
- "columns": 3,
+ "columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
@@ -98,7 +102,7 @@
{
"fieldname": "quantity",
"fieldtype": "Section Break",
- "label": "Quantity"
+ "label": "Quantity & Stock"
},
{
"bold": 1,
@@ -129,12 +133,12 @@
{
"fieldname": "uom",
"fieldtype": "Link",
+ "in_list_view": 1,
"label": "UOM",
"oldfieldname": "uom",
"oldfieldtype": "Link",
"options": "UOM",
"print_width": "100px",
- "read_only": 1,
"reqd": 1,
"width": "100px"
},
@@ -144,7 +148,7 @@
"label": "Warehouse and Reference"
},
{
- "columns": 3,
+ "columns": 2,
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
@@ -202,6 +206,7 @@
},
{
"allow_on_submit": 1,
+ "default": "0",
"fieldname": "page_break",
"fieldtype": "Check",
"label": "Page Break",
@@ -219,10 +224,36 @@
{
"fieldname": "section_break_23",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "UOM Conversion Factor",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "stock_qty",
+ "fieldtype": "Float",
+ "label": "Qty as per Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"istable": 1,
- "modified": "2019-05-01 17:50:23.703801",
+ "links": [],
+ "modified": "2020-06-12 19:10:36.333441",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Item",
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
index 3966879..88a865f 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import flt
def execute(filters=None):
columns = get_columns(filters)
@@ -54,15 +55,16 @@
"width": 140
},
{
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 200
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150
},
{
"label": _("Quantity"),
"fieldname": "quantity",
- "fieldtype": "Int",
+ "fieldtype": "Float",
"width": 140
},
{
@@ -118,7 +120,7 @@
},
{
"label": _("Purchase Order Amount(Company Currency)"),
- "fieldname": "purchase_order_amt_usd",
+ "fieldname": "purchase_order_amt_in_company_currency",
"fieldtype": "Float",
"width": 140
},
@@ -175,17 +177,17 @@
"requesting_site": po.warehouse,
"requestor": po.owner,
"material_request_no": po.material_request,
- "description": po.description,
- "quantity": po.qty,
+ "item_code": po.item_code,
+ "quantity": flt(po.qty),
"unit_of_measurement": po.stock_uom,
"status": po.status,
"purchase_order_date": po.transaction_date,
"purchase_order": po.parent,
"supplier": po.supplier,
- "estimated_cost": mr_record.get('amount'),
- "actual_cost": pi_records.get(po.name),
- "purchase_order_amt": po.amount,
- "purchase_order_amt_in_company_currency": po.base_amount,
+ "estimated_cost": flt(mr_record.get('amount')),
+ "actual_cost": flt(pi_records.get(po.name)),
+ "purchase_order_amt": flt(po.amount),
+ "purchase_order_amt_in_company_currency": flt(po.base_amount),
"expected_delivery_date": po.schedule_date,
"actual_delivery_date": pr_records.get(po.name)
}
@@ -198,9 +200,14 @@
SELECT
par.transaction_date,
par.per_ordered,
+ par.owner,
child.name,
child.parent,
- child.amount
+ child.amount,
+ child.qty,
+ child.item_code,
+ child.uom,
+ par.status
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
WHERE
par.per_ordered>=0
@@ -217,7 +224,15 @@
procurement_record_details = dict(
material_request_date=record.transaction_date,
material_request_no=record.parent,
- estimated_cost=record.amount
+ requestor=record.owner,
+ item_code=record.item_code,
+ estimated_cost=flt(record.amount),
+ quantity=flt(record.qty),
+ unit_of_measurement=record.uom,
+ status=record.status,
+ actual_cost=0,
+ purchase_order_amt=0,
+ purchase_order_amt_in_company_currency=0
)
procurement_record_against_mr.append(procurement_record_details)
return mr_records, procurement_record_against_mr
@@ -259,7 +274,7 @@
child.warehouse,
child.material_request,
child.material_request_item,
- child.description,
+ child.item_code,
child.stock_uom,
child.qty,
child.amount,
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index 06da336..44ab767 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -37,8 +37,6 @@
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
po.get("items")[0].cost_center = "Main - _TPC"
- po.items[0].rate = 500.0
- po.save()
po.submit()
pr = make_purchase_receipt(po.name)
pr.get("items")[0].cost_center = "Main - _TPC"
@@ -55,7 +53,7 @@
"requesting_site": "_Test Procurement Warehouse - _TPC",
"requestor": "Administrator",
"material_request_no": mr.name,
- "description": '_Test Item 1',
+ "item_code": '_Test Item',
"quantity": 10.0,
"unit_of_measurement": "_Test UOM",
"status": "To Bill",
@@ -63,7 +61,7 @@
"purchase_order": po.name,
"supplier": "_Test Supplier",
"estimated_cost": 0.0,
- "actual_cost": None,
+ "actual_cost": 0.0,
"purchase_order_amt": po.net_total,
"purchase_order_amt_in_company_currency": po.base_net_total,
"expected_delivery_date": date_obj,
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index eecb143..f54b593 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1137,8 +1137,8 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
- child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@@ -1157,8 +1157,8 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
- child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
return child_item
@@ -1190,6 +1190,26 @@
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
+ def check_permissions(doc, perm_type='create'):
+ try:
+ doc.check_permission(perm_type)
+ except:
+ action = "add" if perm_type == 'create' else "update"
+ frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
+
+ def get_new_child_item(item_row):
+ if parent_doctype == "Sales Order":
+ return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+ if parent_doctype == "Purchase Order":
+ return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+
+ def validate_quantity(child_item, d):
+ if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
+ frappe.throw(_("Cannot set quantity less than delivered quantity"))
+
+ if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
+ frappe.throw(_("Cannot set quantity less than received quantity"))
+
data = json.loads(trans_items)
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
@@ -1201,20 +1221,29 @@
new_child_flag = False
if not d.get("docname"):
new_child_flag = True
- if parent_doctype == "Sales Order":
- child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
- if parent_doctype == "Purchase Order":
- child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
+ check_permissions(parent, 'create')
+ child_item = get_new_child_item(d)
else:
+ check_permissions(parent, 'write')
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
- if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
+
+ prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
+ prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
+ prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
+
+ if parent_doctype == 'Sales Order':
+ prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
+ elif parent_doctype == 'Purchase Order':
+ prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
+
+ rate_unchanged = prev_rate == new_rate
+ qty_unchanged = prev_qty == new_qty
+ conversion_factor_unchanged = prev_con_fac == new_con_fac
+ date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
+ if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
continue
- if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
- frappe.throw(_("Cannot set quantity less than delivered quantity"))
-
- if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
- frappe.throw(_("Cannot set quantity less than received quantity"))
+ validate_quantity(child_item, d)
child_item.qty = flt(d.get("qty"))
precision = child_item.precision("rate") or 2
@@ -1225,6 +1254,18 @@
else:
child_item.rate = flt(d.get("rate"))
+ if d.get("conversion_factor"):
+ if child_item.stock_uom == child_item.uom:
+ child_item.conversion_factor = 1
+ else:
+ child_item.conversion_factor = flt(d.get('conversion_factor'))
+
+ if d.get("delivery_date") and parent_doctype == 'Sales Order':
+ child_item.delivery_date = d.get('delivery_date')
+
+ if d.get("schedule_date") and parent_doctype == 'Purchase Order':
+ child_item.schedule_date = d.get('schedule_date')
+
if flt(child_item.price_list_rate):
if flt(child_item.rate) > flt(child_item.price_list_rate):
# if rate is greater than price_list_rate, set margin
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 608e537..89b48f0 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -349,7 +349,7 @@
})
if not rm.rate:
- rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
+ rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company=self.company)
rm.amount = qty * flt(rm.rate)
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 29f8dd5..1f95e00 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -70,7 +70,7 @@
else:
attributes_list = attribute_values.get(attribute.lower(), [])
- validate_item_attribute_value(attributes_list, attribute, value, item.name)
+ validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
def validate_is_incremental(numeric_attribute, attribute, value, item):
from_range = numeric_attribute.from_range
@@ -93,13 +93,20 @@
.format(attribute, from_range, to_range, increment, item),
InvalidItemAttributeValueError, title=_('Invalid Attribute'))
-def validate_item_attribute_value(attributes_list, attribute, attribute_value, item):
+def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
if allow_rename_attribute_value:
pass
elif attribute_value not in attributes_list:
- frappe.throw(_("The value {0} is already assigned to an exisiting Item {2}.").format(
- attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Rename Not Allowed'))
+ if from_variant:
+ frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
+ frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
+ else:
+ msg = _("The value {0} is already assigned to an existing Item {1}.").format(
+ frappe.bold(attribute_value), frappe.bold(item))
+ msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
+
+ frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
def get_attribute_values(item):
if not frappe.flags.attribute_values:
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 83139b1..d098c1c 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -19,7 +19,8 @@
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
- self.validate_inspection()
+ if not self.get('is_return'):
+ self.validate_inspection()
self.validate_serialized_batch()
self.validate_customer_provided_item()
@@ -228,7 +229,9 @@
def check_expense_account(self, item):
if not item.get("expense_account"):
- frappe.throw(_("Expense Account not set for Item {0}. Please set an Expense Account for the item in the Items table").format(item.item_code))
+ frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
+ Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
+ title=_("Expense Account Missing"))
else:
is_expense_account = frappe.db.get_value("Account",
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 6fef0c4..f5f8b4e 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -6,6 +6,7 @@
"creation": "2013-04-10 11:45:37",
"doctype": "DocType",
"document_type": "Document",
+ "email_append_to": 1,
"engine": "InnoDB",
"field_order": [
"organization_lead",
@@ -448,7 +449,7 @@
"idx": 5,
"image_field": "image",
"links": [],
- "modified": "2020-05-11 20:27:45.868960",
+ "modified": "2020-06-18 14:39:41.835416",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
@@ -508,8 +509,10 @@
}
],
"search_fields": "lead_name,lead_owner,status",
+ "sender_field": "email_id",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "subject_field": "title",
"title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
index 9ed616a..68f0104 100644
--- a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
+++ b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
@@ -3,11 +3,7 @@
def get_data():
return {
- 'fieldname': 'prevdoc_docname',
- 'non_standard_fieldnames': {
- 'Supplier Quotation': 'opportunity',
- 'Quotation': 'opportunity'
- },
+ 'fieldname': 'opportunity',
'transactions': [
{
'items': ['Quotation', 'Supplier Quotation']
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.json b/erpnext/crm/doctype/social_media_post/social_media_post.json
index 2601c14..0a00dca 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.json
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.json
@@ -30,24 +30,32 @@
"fieldname": "text",
"fieldtype": "Small Text",
"label": "Tweet",
- "mandatory_depends_on": "eval:doc.twitter ==1"
+ "mandatory_depends_on": "eval:doc.twitter ==1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
- "label": "Image"
+ "label": "Image",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "twitter",
"fieldtype": "Check",
- "label": "Twitter"
+ "label": "Twitter",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "linkedin",
"fieldtype": "Check",
- "label": "LinkedIn"
+ "label": "LinkedIn",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -56,13 +64,17 @@
"no_copy": 1,
"options": "Social Media Post",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.twitter ==1",
"fieldname": "content",
"fieldtype": "Section Break",
- "label": "Twitter"
+ "label": "Twitter",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -70,7 +82,9 @@
"fieldtype": "Select",
"label": "Post Status",
"options": "\nScheduled\nPosted\nError",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -78,7 +92,9 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Twitter Post Id",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -86,68 +102,89 @@
"fieldtype": "Data",
"hidden": 1,
"label": "LinkedIn Post Id",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "campaign_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Campaign",
- "options": "Campaign"
+ "options": "Campaign",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
- "label": "Share On"
+ "label": "Share On",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_14",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tweet_preview",
- "fieldtype": "HTML"
+ "fieldtype": "HTML",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"depends_on": "eval:doc.linkedin==1",
"fieldname": "linkedin_section",
"fieldtype": "Section Break",
- "label": "LinkedIn"
+ "label": "LinkedIn",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "attachments_section",
"fieldtype": "Section Break",
- "label": "Attachments"
+ "label": "Attachments",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "linkedin_post",
"fieldtype": "Text",
"label": "Post",
- "mandatory_depends_on": "eval:doc.linkedin ==1"
+ "mandatory_depends_on": "eval:doc.linkedin ==1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "scheduled_time",
"fieldtype": "Datetime",
"label": "Scheduled Time",
- "read_only_depends_on": "eval:doc.post_status == \"Posted\""
+ "read_only_depends_on": "eval:doc.post_status == \"Posted\"",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-21 15:10:04.953713",
+ "modified": "2020-06-14 10:31:33.961381",
"modified_by": "Administrator",
"module": "CRM",
"name": "Social Media Post",
"owner": "Administrator",
"permissions": [
{
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -157,6 +194,35 @@
"report": 1,
"role": "System Manager",
"share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
],
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js
index 7606565..f09d2ef 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.js
+++ b/erpnext/education/doctype/fee_structure/fee_structure.js
@@ -9,6 +9,14 @@
},
onload: function(frm) {
+ frm.set_query("academic_term", function() {
+ return {
+ "filters": {
+ "academic_year": frm.doc.academic_year
+ }
+ };
+ });
+
frm.set_query("receivable_account", function(doc) {
return {
filters: {
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.json b/erpnext/education/doctype/fee_structure/fee_structure.json
index 8ff6851..67e4637 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.json
+++ b/erpnext/education/doctype/fee_structure/fee_structure.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
@@ -11,8 +12,8 @@
"program",
"student_category",
"column_break_2",
- "academic_term",
"academic_year",
+ "academic_term",
"section_break_4",
"components",
"section_break_6",
@@ -157,7 +158,8 @@
],
"icon": "fa fa-flag",
"is_submittable": 1,
- "modified": "2019-05-26 09:04:17.765758",
+ "links": [],
+ "modified": "2020-06-16 15:34:57.295010",
"modified_by": "Administrator",
"module": "Education",
"name": "Fee Structure",
diff --git a/erpnext/education/doctype/program_course/program_course.json b/erpnext/education/doctype/program_course/program_course.json
index a24e88a..940358e 100644
--- a/erpnext/education/doctype/program_course/program_course.json
+++ b/erpnext/education/doctype/program_course/program_course.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2015-09-07 14:37:01.886859",
"doctype": "DocType",
"editable_grid": 1,
@@ -16,26 +17,33 @@
"in_list_view": 1,
"label": "Course",
"options": "Course",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
- {
+ {
+ "fetch_from": "course.course_name",
"fieldname": "course_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Course Name",
- "fetch_from": "course.course_name",
- "read_only":1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "required",
"fieldtype": "Check",
"in_list_view": 1,
- "label": "Mandatory"
+ "label": "Mandatory",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"istable": 1,
- "modified": "2019-06-12 12:42:12.845972",
+ "links": [],
+ "modified": "2020-06-09 18:56:10.213241",
"modified_by": "Administrator",
"module": "Education",
"name": "Program Course",
@@ -45,4 +53,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_admission/student_admission.json b/erpnext/education/doctype/student_admission/student_admission.json
index b3c10d4..1096888 100644
--- a/erpnext/education/doctype/student_admission/student_admission.json
+++ b/erpnext/education/doctype/student_admission/student_admission.json
@@ -1,398 +1,119 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 1,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "",
- "beta": 0,
- "creation": "2016-09-13 03:05:27.154713",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
+ "actions": [],
+ "allow_guest_to_view": 1,
+ "allow_rename": 1,
+ "creation": "2016-09-13 03:05:27.154713",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "route",
+ "column_break_3",
+ "academic_year",
+ "admission_start_date",
+ "admission_end_date",
+ "published",
+ "enable_admission_application",
+ "section_break_5",
+ "program_details",
+ "introduction"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "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": "Title",
- "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,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "route",
- "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": "Route",
- "length": 0,
- "no_copy": 1,
- "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,
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route",
+ "no_copy": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "application_form_route",
- "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": "Application Form Route",
- "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,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "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,
- "unique": 0
- },
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Academic Year",
+ "no_copy": 1,
+ "options": "Academic Year",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 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": 1,
- "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,
- "unique": 0
- },
+ "fieldname": "admission_start_date",
+ "fieldtype": "Date",
+ "label": "Admission Start Date",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_start_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": "Admission Start Date",
- "length": 0,
- "no_copy": 1,
- "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,
- "unique": 0
- },
+ "fieldname": "admission_end_date",
+ "fieldtype": "Date",
+ "label": "Admission End Date",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_end_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": "Admission End Date",
- "length": 0,
- "no_copy": 1,
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "published",
+ "fieldtype": "Check",
+ "label": "Publish on website"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "published",
- "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": "Publish on website",
- "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,
- "unique": 0
- },
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break",
+ "label": "Eligibility and Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "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": "Eligibility and Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "program_details",
+ "fieldtype": "Table",
+ "label": "Eligibility and Details",
+ "options": "Student Admission Program"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "program_details",
- "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": "Eligibility and Details",
- "length": 0,
- "no_copy": 0,
- "options": "Student Admission 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "introduction",
+ "fieldtype": "Text Editor",
+ "label": "Introduction"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "introduction",
- "fieldtype": "Text Editor",
- "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": "Introduction",
- "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,
- "unique": 0
+ "default": "0",
+ "fieldname": "enable_admission_application",
+ "fieldtype": "Check",
+ "label": "Enable Admission Application"
}
- ],
- "has_web_view": 1,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_published_field": "published",
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-11-10 18:57:34.570376",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Admission",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "has_web_view": 1,
+ "is_published_field": "published",
+ "links": [],
+ "modified": "2020-06-15 20:18:38.591626",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Admission",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Academics User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Academics User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "route": "admissions",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "restrict_to_domain": "Education",
+ "route": "admissions",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_admission/templates/student_admission.html b/erpnext/education/doctype/student_admission/templates/student_admission.html
index 25afaca..e5a9ead 100644
--- a/erpnext/education/doctype/student_admission/templates/student_admission.html
+++ b/erpnext/education/doctype/student_admission/templates/student_admission.html
@@ -43,8 +43,8 @@
<thead>
<tr class="active">
<th style="width: 90px">Program/Std.</th>
- <th style="width: 170px">Minumum Age(DOB)</th>
- <th style="width: 170px">Maximum Age(DOB)</th>
+ <th style="width: 170px">Minumum Age</th>
+ <th style="width: 170px">Maximum Age</th>
<th style="width: 100px">Application Fee</th>
</tr>
</thead>
@@ -52,8 +52,8 @@
{% for row in program_details %}
<tr>
<td>{{ row.program }}</td>
- <td>{{ row.minimum_age }}</td>
- <td>{{ row.maximum_age }}</td>
+ <td>{{ row.min_age }}</td>
+ <td>{{ row.max_age }}</td>
<td>{{ row.application_fee }}</td>
</tr>
{% endfor %}
@@ -61,12 +61,11 @@
</table>
</div>
{% endif %}
-
- {%- if application_form_route -%}
+ {%- if doc.enable_admission_application -%}
<br>
<p>
<a class='btn btn-primary'
- href='/{{ doc.application_form_route }}?new=1'>
+ href='/student-applicant?new=1&student_admission={{doc.name}}'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
diff --git a/erpnext/education/doctype/student_admission/test_student_admission.js b/erpnext/education/doctype/student_admission/test_student_admission.js
index ed794b2..3a0bb0b 100644
--- a/erpnext/education/doctype/student_admission/test_student_admission.js
+++ b/erpnext/education/doctype/student_admission/test_student_admission.js
@@ -11,7 +11,7 @@
{admission_start_date: '2016-04-20'},
{admission_end_date: '2016-05-31'},
{title: '2016-17 Admissions'},
- {application_form_route: 'student-applicant'},
+ {enable_admission_application: 1},
{introduction: 'Test intro'},
{program_details: [
[
@@ -28,7 +28,7 @@
assert.ok(cur_frm.doc.admission_start_date == '2016-04-20');
assert.ok(cur_frm.doc.admission_end_date == '2016-05-31');
assert.ok(cur_frm.doc.title == '2016-17 Admissions');
- assert.ok(cur_frm.doc.application_form_route == 'student-applicant');
+ assert.ok(cur_frm.doc.enable_admission_application == 1);
assert.ok(cur_frm.doc.introduction == 'Test intro');
assert.ok(cur_frm.doc.program_details[0].program == 'Standard Test', 'Program correctly selected');
assert.ok(cur_frm.doc.program_details[0].application_fee == 1000);
diff --git a/erpnext/education/doctype/student_admission_program/student_admission_program.json b/erpnext/education/doctype/student_admission_program/student_admission_program.json
index 97b1bba..e9f041e 100644
--- a/erpnext/education/doctype/student_admission_program/student_admission_program.json
+++ b/erpnext/education/doctype/student_admission_program/student_admission_program.json
@@ -1,237 +1,77 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2017-09-15 12:59:43.207923",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-09-15 12:59:43.207923",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "program",
+ "min_age",
+ "max_age",
+ "column_break_4",
+ "application_fee",
+ "applicant_naming_series"
+ ],
"fields": [
{
- "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": 0,
- "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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "program",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Program",
+ "options": "Program",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "minimum_age",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Minimum Age",
- "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
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maximum_age",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Maximum Age",
- "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
- },
+ "fieldname": "application_fee",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Application Fee",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "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
- },
+ "fieldname": "applicant_naming_series",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Naming Series (for Student Applicant)",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "application_fee",
- "fieldtype": "Currency",
- "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": "Application Fee",
- "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
- },
+ "fieldname": "min_age",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Minimum Age",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "applicant_naming_series",
- "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": "Naming Series (for Student Applicant)",
- "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
+ "fieldname": "max_age",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Maximum Age",
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "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-11-04 03:37:17.408427",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Admission Program",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-10 23:06:30.037404",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Admission Program",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_applicant/student_applicant.py b/erpnext/education/doctype/student_applicant/student_applicant.py
index ab94780..2113482 100644
--- a/erpnext/education/doctype/student_applicant/student_applicant.py
+++ b/erpnext/education/doctype/student_applicant/student_applicant.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import getdate
+from frappe.utils import getdate, add_years, nowdate, date_diff
class StudentApplicant(Document):
def autoname(self):
@@ -31,6 +31,7 @@
def validate(self):
self.validate_dates()
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
+
if self.student_admission and self.program and self.date_of_birth:
self.validation_from_student_admission()
@@ -48,16 +49,16 @@
frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant"))
def validation_from_student_admission(self):
+
student_admission = get_student_admission_data(self.student_admission, self.program)
- # different validation for minimum and maximum age so that either min/max can also work independently.
- if student_admission and student_admission.minimum_age and \
- getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+ if student_admission and student_admission.min_age and \
+ date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age)) < 0:
+ frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
- if student_admission and student_admission.maximum_age and \
- getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+ if student_admission and student_admission.max_age and \
+ date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age)) > 0:
+ frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
def on_payment_authorized(self, *args, **kwargs):
@@ -65,10 +66,12 @@
def get_student_admission_data(student_admission, program):
+
student_admission = frappe.db.sql("""select sa.admission_start_date, sa.admission_end_date,
- sap.program, sap.minimum_age, sap.maximum_age, sap.applicant_naming_series
+ sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series
from `tabStudent Admission` sa, `tabStudent Admission Program` sap
where sa.name = sap.parent and sa.name = %s and sap.program = %s""", (student_admission, program), as_dict=1)
+
if student_admission:
return student_admission[0]
else:
diff --git a/erpnext/education/web_form/student_applicant/student_applicant.json b/erpnext/education/web_form/student_applicant/student_applicant.json
index b1ad754..1810f07 100644
--- a/erpnext/education/web_form/student_applicant/student_applicant.json
+++ b/erpnext/education/web_form/student_applicant/student_applicant.json
@@ -1,200 +1,248 @@
{
- "accept_payment": 0,
- "allow_comments": 0,
- "allow_delete": 0,
- "allow_edit": 1,
- "allow_incomplete": 0,
- "allow_multiple": 1,
- "allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
- "creation": "2016-09-22 13:10:10.792735",
- "doc_type": "Student Applicant",
- "docstatus": 0,
- "doctype": "Web Form",
- "idx": 0,
- "is_standard": 1,
- "login_required": 1,
- "max_attachment_size": 0,
- "modified": "2017-02-21 05:44:46.022738",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "student-applicant",
- "owner": "Administrator",
- "payment_button_label": "Buy Now",
- "published": 1,
- "route": "student-applicant",
- "show_sidebar": 1,
- "sidebar_items": [],
- "success_url": "/student-applicant",
- "title": "Student Applicant",
+ "accept_payment": 0,
+ "allow_comments": 0,
+ "allow_delete": 0,
+ "allow_edit": 1,
+ "allow_incomplete": 0,
+ "allow_multiple": 1,
+ "allow_print": 0,
+ "amount": 0.0,
+ "amount_based_on_field": 0,
+ "creation": "2016-09-22 13:10:10.792735",
+ "doc_type": "Student Applicant",
+ "docstatus": 0,
+ "doctype": "Web Form",
+ "idx": 0,
+ "is_standard": 1,
+ "login_required": 1,
+ "max_attachment_size": 0,
+ "modified": "2020-06-11 22:53:45.875310",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "student-applicant",
+ "owner": "Administrator",
+ "payment_button_label": "Buy Now",
+ "published": 1,
+ "route": "student-applicant",
+ "route_to_success_link": 0,
+ "show_attachments": 0,
+ "show_in_grid": 0,
+ "show_sidebar": 1,
+ "sidebar_items": [],
+ "success_url": "/student-applicant",
+ "title": "Student Applicant",
"web_form_fields": [
{
- "fieldname": "first_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "First Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 1
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "First Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 1,
+ "show_in_filter": 0
+ },
{
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Middle Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "middle_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Middle Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "last_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Last Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Last Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "image",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Image",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 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
+ },
{
- "fieldname": "program",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Program",
- "max_length": 0,
- "max_value": 0,
- "options": "Program",
- "read_only": 0,
- "reqd": 1
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "program",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Program",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Program",
+ "read_only": 0,
+ "reqd": 1,
+ "show_in_filter": 0
+ },
{
- "fieldname": "academic_year",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Academic Year",
- "max_length": 0,
- "max_value": 0,
- "options": "Academic Year",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Academic Year",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Academic Year",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "date_of_birth",
- "fieldtype": "Date",
- "hidden": 0,
- "label": "Date of Birth",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "date_of_birth",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "label": "Date of Birth",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "blood_group",
- "fieldtype": "Select",
- "hidden": 0,
- "label": "Blood Group",
- "max_length": 0,
- "max_value": 0,
- "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "blood_group",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "label": "Blood Group",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "student_email_id",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Student Email ID",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_email_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Student Email ID",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "student_mobile_number",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Student Mobile Number",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_mobile_number",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Student Mobile Number",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "default": "INDIAN",
- "fieldname": "nationality",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Nationality",
- "max_length": 0,
- "max_value": 0,
- "options": "",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "default": "INDIAN",
+ "fieldname": "nationality",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Nationality",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "address_line_1",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Address Line 1",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "address_line_1",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Address Line 1",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "address_line_2",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Address Line 2",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "address_line_2",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Address Line 2",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "pincode",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Pincode",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "pincode",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Pincode",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "guardians",
- "fieldtype": "Table",
- "hidden": 0,
- "label": "Guardians",
- "max_length": 0,
- "max_value": 0,
- "options": "Student Guardian",
- "read_only": 0,
- "reqd": 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
+ },
{
- "fieldname": "siblings",
- "fieldtype": "Table",
- "hidden": 0,
- "label": "Siblings",
- "max_length": 0,
- "max_value": 0,
- "options": "Student Sibling",
- "read_only": 0,
- "reqd": 0
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "siblings",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "label": "Siblings",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Sibling",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_admission",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Student Admission",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Admission",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
}
]
}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index 7046038..d59f909 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -241,14 +241,17 @@
return taxes
def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
+ """Shipping lines represents the shipping details,
+ each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines:
- taxes.append({
- "charge_type": _("Actual"),
- "account_head": get_tax_account_head(shipping_charge),
- "description": shipping_charge["title"],
- "tax_amount": shipping_charge["price"],
- "cost_center": shopify_settings.cost_center
- })
+ for tax in shipping_charge.get("tax_lines"):
+ taxes.append({
+ "charge_type": _("Actual"),
+ "account_head": get_tax_account_head(tax),
+ "description": tax["title"],
+ "tax_amount": tax["price"],
+ "cost_center": shopify_settings.cost_center
+ })
return taxes
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 1b0c9f6..6dedaa8 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -73,10 +73,16 @@
if customer_exists:
frappe.rename_doc("Customer", old_name, customer_name)
- billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
- shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
- rename_address(billing_address, customer)
- rename_address(shipping_address, customer)
+ for address_type in ("Billing", "Shipping",):
+ try:
+ address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type})
+ rename_address(address, customer)
+ except (
+ frappe.DoesNotExistError,
+ frappe.DuplicateEntryError,
+ frappe.ValidationError,
+ ):
+ pass
else:
create_address(raw_billing_data, customer, "Billing")
create_address(raw_shipping_data, customer, "Shipping")
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index 64c3b2d..25ffd28 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -8,6 +8,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_request_session
+from requests.exceptions import HTTPError
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.erpnext_integrations.utils import get_webhook_address
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
@@ -29,19 +30,24 @@
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2019-04/webhooks.json', self)
+ url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
- d = session.post(url, data=json.dumps({
+ res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
"format": "json"
}
}), headers=get_header(self))
- d.raise_for_status()
- self.update_webhook_table(method, d.json())
+ res.raise_for_status()
+ self.update_webhook_table(method, res.json())
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
make_shopify_log(status="Warning", exception=e, rollback=True)
@@ -50,13 +56,18 @@
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
deleted_webhooks.append(d)
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
- frappe.log_error(message=frappe.get_traceback(), title=e)
+ frappe.log_error(message=e, title='Shopify Webhooks Issue')
for d in deleted_webhooks:
self.remove(d)
@@ -125,4 +136,3 @@
}
create_custom_fields(custom_fields)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index bde1011..f9f0bb3 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2019-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+ url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
index d84c823..fd16d1e 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
@@ -1,7 +1,9 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Tally Migration', {
+frappe.provide("erpnext.tally_migration");
+
+frappe.ui.form.on("Tally Migration", {
onload: function (frm) {
let reload_status = true;
frappe.realtime.on("tally_migration_progress_update", function (data) {
@@ -35,7 +37,17 @@
}
});
},
+
refresh: function (frm) {
+ frm.trigger("show_logs_preview");
+ erpnext.tally_migration.failed_import_log = JSON.parse(frm.doc.failed_import_log);
+ erpnext.tally_migration.fixed_errors_log = JSON.parse(frm.doc.fixed_errors_log);
+
+ ["default_round_off_account", "default_warehouse", "default_cost_center"].forEach(account => {
+ frm.toggle_reqd(account, frm.doc.is_master_data_imported === 1)
+ frm.toggle_enable(account, frm.doc.is_day_book_data_processed != 1)
+ })
+
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
if (frm.doc.is_master_data_processed) {
if (frm.doc.status != "Importing Master Data") {
@@ -47,6 +59,7 @@
}
}
}
+
if (frm.doc.day_book_data && !frm.doc.is_day_book_data_imported) {
if (frm.doc.is_day_book_data_processed) {
if (frm.doc.status != "Importing Day Book Data") {
@@ -59,6 +72,17 @@
}
}
},
+
+ erpnext_company: function (frm) {
+ frappe.db.exists("Company", frm.doc.erpnext_company).then(exists => {
+ if (exists) {
+ frappe.msgprint(
+ __("Company {0} already exists. Continuing will overwrite the Company and Chart of Accounts", [frm.doc.erpnext_company]),
+ );
+ }
+ });
+ },
+
add_button: function (frm, label, method) {
frm.add_custom_button(
label,
@@ -71,5 +95,255 @@
frm.reload_doc();
}
);
+ },
+
+ render_html_table(frm, shown_logs, hidden_logs, field) {
+ if (shown_logs && shown_logs.length > 0) {
+ frm.toggle_display(field, true);
+ } else {
+ frm.toggle_display(field, false);
+ return
+ }
+ let rows = erpnext.tally_migration.get_html_rows(shown_logs, field);
+ let rows_head, table_caption;
+
+ let table_footer = (hidden_logs && (hidden_logs.length > 0)) ? `<tr class="text-muted">
+ <td colspan="4">And ${hidden_logs.length} more others</td>
+ </tr>`: "";
+
+ if (field === "fixed_error_log_preview") {
+ rows_head = `<th width="75%">${__("Meta Data")}</th>
+ <th width="10%">${__("Unresolve")}</th>`
+ table_caption = "Resolved Issues"
+ } else {
+ rows_head = `<th width="75%">${__("Error Message")}</th>
+ <th width="10%">${__("Create")}</th>`
+ table_caption = "Error Log"
+ }
+
+ frm.get_field(field).$wrapper.html(`
+ <table class="table table-bordered">
+ <caption>${table_caption}</caption>
+ <tr class="text-muted">
+ <th width="5%">${__("#")}</th>
+ <th width="10%">${__("DocType")}</th>
+ ${rows_head}
+ </tr>
+ ${rows}
+ ${table_footer}
+ </table>
+ `);
+ },
+
+ show_error_summary(frm) {
+ let summary = erpnext.tally_migration.failed_import_log.reduce((summary, row) => {
+ if (row.doc) {
+ if (summary[row.doc.doctype]) {
+ summary[row.doc.doctype] += 1;
+ } else {
+ summary[row.doc.doctype] = 1;
+ }
+ }
+ return summary
+ }, {});
+ console.table(summary);
+ },
+
+ show_logs_preview(frm) {
+ let empty = "[]";
+ let import_log = frm.doc.failed_import_log || empty;
+ let completed_log = frm.doc.fixed_errors_log || empty;
+ let render_section = !(import_log === completed_log && import_log === empty);
+
+ frm.toggle_display("import_log_section", render_section);
+ if (render_section) {
+ frm.trigger("show_error_summary");
+ frm.trigger("show_errored_import_log");
+ frm.trigger("show_fixed_errors_log");
+ }
+ },
+
+ show_errored_import_log(frm) {
+ let import_log = erpnext.tally_migration.failed_import_log;
+ let logs = import_log.slice(0, 20);
+ let hidden_logs = import_log.slice(20);
+
+ frm.events.render_html_table(frm, logs, hidden_logs, "failed_import_preview");
+ },
+
+ show_fixed_errors_log(frm) {
+ let completed_log = erpnext.tally_migration.fixed_errors_log;
+ let logs = completed_log.slice(0, 20);
+ let hidden_logs = completed_log.slice(20);
+
+ frm.events.render_html_table(frm, logs, hidden_logs, "fixed_error_log_preview");
}
});
+
+erpnext.tally_migration.getError = (traceback) => {
+ /* Extracts the Error Message from the Python Traceback or Solved error */
+ let is_multiline = traceback.trim().indexOf("\n") != -1;
+ let message;
+
+ if (is_multiline) {
+ let exc_error_idx = traceback.trim().lastIndexOf("\n") + 1
+ let error_line = traceback.substr(exc_error_idx)
+ let split_str_idx = (error_line.indexOf(':') > 0) ? error_line.indexOf(':') + 1 : 0;
+ message = error_line.slice(split_str_idx).trim();
+ } else {
+ message = traceback;
+ }
+
+ return message
+}
+
+erpnext.tally_migration.cleanDoc = (obj) => {
+ /* Strips all null and empty values of your JSON object */
+ let temp = obj;
+ $.each(temp, function(key, value){
+ if (value === "" || value === null){
+ delete obj[key];
+ } else if (Object.prototype.toString.call(value) === '[object Object]') {
+ erpnext.tally_migration.cleanDoc(value);
+ } else if ($.isArray(value)) {
+ $.each(value, function (k,v) { erpnext.tally_migration.cleanDoc(v); });
+ }
+ });
+ return temp;
+}
+
+erpnext.tally_migration.unresolve = (document) => {
+ /* Mark document migration as unresolved ie. move to failed error log */
+ let frm = cur_frm;
+ let failed_log = erpnext.tally_migration.failed_import_log;
+ let fixed_log = erpnext.tally_migration.fixed_errors_log;
+
+ let modified_fixed_log = fixed_log.filter(row => {
+ if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
+ return row
+ }
+ });
+
+ failed_log.push({ doc: document, exc: `Marked unresolved on ${Date()}` });
+
+ frm.doc.failed_import_log = JSON.stringify(failed_log);
+ frm.doc.fixed_errors_log = JSON.stringify(modified_fixed_log);
+
+ frm.dirty();
+ frm.save();
+}
+
+erpnext.tally_migration.resolve = (document) => {
+ /* Mark document migration as resolved ie. move to fixed error log */
+ let frm = cur_frm;
+ let failed_log = erpnext.tally_migration.failed_import_log;
+ let fixed_log = erpnext.tally_migration.fixed_errors_log;
+
+ let modified_failed_log = failed_log.filter(row => {
+ if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
+ return row
+ }
+ });
+ fixed_log.push({ doc: document, exc: `Solved on ${Date()}` });
+
+ frm.doc.failed_import_log = JSON.stringify(modified_failed_log);
+ frm.doc.fixed_errors_log = JSON.stringify(fixed_log);
+
+ frm.dirty();
+ frm.save();
+}
+
+erpnext.tally_migration.create_new_doc = (document) => {
+ /* Mark as resolved and create new document */
+ erpnext.tally_migration.resolve(document);
+ return frappe.call({
+ type: "POST",
+ method: 'erpnext.erpnext_integrations.doctype.tally_migration.tally_migration.new_doc',
+ args: {
+ document
+ },
+ freeze: true,
+ callback: function(r) {
+ if(!r.exc) {
+ frappe.model.sync(r.message);
+ frappe.get_doc(r.message.doctype, r.message.name).__run_link_triggers = true;
+ frappe.set_route("Form", r.message.doctype, r.message.name);
+ }
+ }
+ });
+}
+
+erpnext.tally_migration.get_html_rows = (logs, field) => {
+ let index = 0;
+ let rows = logs
+ .map(({ doc, exc }) => {
+ let id = frappe.dom.get_unique_id();
+ let traceback = exc;
+
+ let error_message = erpnext.tally_migration.getError(traceback);
+ index++;
+
+ let show_traceback = `
+ <button class="btn btn-default btn-xs m-3" type="button" data-toggle="collapse" data-target="#${id}-traceback" aria-expanded="false" aria-controls="${id}-traceback">
+ ${__("Show Traceback")}
+ </button>
+ <div class="collapse margin-top" id="${id}-traceback">
+ <div class="well">
+ <pre style="font-size: smaller;">${traceback}</pre>
+ </div>
+ </div>`;
+
+ let show_doc = `
+ <button class='btn btn-default btn-xs m-3' type='button' data-toggle='collapse' data-target='#${id}-doc' aria-expanded='false' aria-controls='${id}-doc'>
+ ${__("Show Document")}
+ </button>
+ <div class="collapse margin-top" id="${id}-doc">
+ <div class="well">
+ <pre style="font-size: smaller;">${JSON.stringify(erpnext.tally_migration.cleanDoc(doc), null, 1)}</pre>
+ </div>
+ </div>`;
+
+ let create_button = `
+ <button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.create_new_doc(${JSON.stringify(doc)})'>
+ ${__("Create Document")}
+ </button>`
+
+ let mark_as_unresolved = `
+ <button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.unresolve(${JSON.stringify(doc)})'>
+ ${__("Mark as unresolved")}
+ </button>`
+
+ if (field === "fixed_error_log_preview") {
+ return `<tr>
+ <td>${index}</td>
+ <td>
+ <div>${doc.doctype}</div>
+ </td>
+ <td>
+ <div>${error_message}</div>
+ <div>${show_doc}</div>
+ </td>
+ <td>
+ <div>${mark_as_unresolved}</div>
+ </td>
+ </tr>`;
+ } else {
+ return `<tr>
+ <td>${index}</td>
+ <td>
+ <div>${doc.doctype}</div>
+ </td>
+ <td>
+ <div>${error_message}</div>
+ <div>${show_traceback}</div>
+ <div>${show_doc}</div>
+ </td>
+ <td>
+ <div>${create_button}</div>
+ </td>
+ </tr>`;
+ }
+ }).join("");
+
+ return rows
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
index dc6f093..417d943 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
@@ -28,14 +28,19 @@
"vouchers",
"accounts_section",
"default_warehouse",
- "round_off_account",
+ "default_round_off_account",
"column_break_21",
"default_cost_center",
"day_book_section",
"day_book_data",
"column_break_27",
"is_day_book_data_processed",
- "is_day_book_data_imported"
+ "is_day_book_data_imported",
+ "import_log_section",
+ "failed_import_log",
+ "fixed_errors_log",
+ "failed_import_preview",
+ "fixed_error_log_preview"
],
"fields": [
{
@@ -57,6 +62,7 @@
"fieldname": "tally_creditors_account",
"fieldtype": "Data",
"label": "Tally Creditors Account",
+ "read_only_depends_on": "eval:doc.is_master_data_processed==1",
"reqd": 1
},
{
@@ -69,6 +75,7 @@
"fieldname": "tally_debtors_account",
"fieldtype": "Data",
"label": "Tally Debtors Account",
+ "read_only_depends_on": "eval:doc.is_master_data_processed==1",
"reqd": 1
},
{
@@ -92,7 +99,7 @@
"fieldname": "erpnext_company",
"fieldtype": "Data",
"label": "ERPNext Company",
- "read_only_depends_on": "eval:doc.is_master_data_processed == 1"
+ "read_only_depends_on": "eval:doc.is_master_data_processed==1"
},
{
"fieldname": "processed_files_section",
@@ -136,6 +143,7 @@
},
{
"depends_on": "is_master_data_imported",
+ "description": "The accounts are set by the system automatically but do confirm these defaults",
"fieldname": "accounts_section",
"fieldtype": "Section Break",
"label": "Accounts"
@@ -147,12 +155,6 @@
"options": "Warehouse"
},
{
- "fieldname": "round_off_account",
- "fieldtype": "Link",
- "label": "Round Off Account",
- "options": "Account"
- },
- {
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
@@ -212,11 +214,47 @@
"fieldname": "default_uom",
"fieldtype": "Link",
"label": "Default UOM",
- "options": "UOM"
+ "options": "UOM",
+ "read_only_depends_on": "eval:doc.is_master_data_imported==1"
+ },
+ {
+ "default": "[]",
+ "fieldname": "failed_import_log",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "options": "JSON"
+ },
+ {
+ "fieldname": "failed_import_preview",
+ "fieldtype": "HTML",
+ "label": "Failed Import Log"
+ },
+ {
+ "fieldname": "import_log_section",
+ "fieldtype": "Section Break",
+ "label": "Import Log"
+ },
+ {
+ "fieldname": "default_round_off_account",
+ "fieldtype": "Link",
+ "label": "Default Round Off Account",
+ "options": "Account"
+ },
+ {
+ "default": "[]",
+ "fieldname": "fixed_errors_log",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "options": "JSON"
+ },
+ {
+ "fieldname": "fixed_error_log_preview",
+ "fieldtype": "HTML",
+ "label": "Fixed Error Log"
}
],
"links": [],
- "modified": "2020-04-16 13:03:28.894919",
+ "modified": "2020-04-28 00:29:18.039826",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Tally Migration",
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 13474e1..462685f 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -6,6 +6,7 @@
import json
import re
+import sys
import traceback
import zipfile
from decimal import Decimal
@@ -15,18 +16,34 @@
import frappe
from erpnext import encode_company_abbr
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
+from erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer import unset_existing_data
+
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document
from frappe.model.naming import getseries, revert_series_if_last
from frappe.utils.data import format_datetime
-
PRIMARY_ACCOUNT = "Primary"
VOUCHER_CHUNK_SIZE = 500
+@frappe.whitelist()
+def new_doc(document):
+ document = json.loads(document)
+ doctype = document.pop("doctype")
+ document.pop("name", None)
+ doc = frappe.new_doc(doctype)
+ doc.update(document)
+
+ return doc
+
class TallyMigration(Document):
+ def validate(self):
+ failed_import_log = json.loads(self.failed_import_log)
+ sorted_failed_import_log = sorted(failed_import_log, key=lambda row: row["doc"]["creation"])
+ self.failed_import_log = json.dumps(sorted_failed_import_log)
+
def autoname(self):
if not self.name:
self.name = "Tally Migration on " + format_datetime(self.creation)
@@ -65,9 +82,17 @@
"attached_to_name": self.name,
"content": json.dumps(value),
"is_private": True
- }).insert()
+ })
+ try:
+ f.insert()
+ except frappe.DuplicateEntryError:
+ pass
setattr(self, key, f.file_url)
+ def set_account_defaults(self):
+ self.default_cost_center, self.default_round_off_account = frappe.db.get_value("Company", self.erpnext_company, ["cost_center", "round_off_account"])
+ self.default_warehouse = frappe.db.get_value("Stock Settings", "Stock Settings", "default_warehouse")
+
def _process_master_data(self):
def get_company_name(collection):
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
@@ -84,7 +109,11 @@
children, parents = get_children_and_parent_dict(accounts)
group_set = [acc[1] for acc in accounts if acc[2]]
children, customers, suppliers = remove_parties(parents, children, group_set)
- coa = traverse({}, children, roots, roots, group_set)
+
+ try:
+ coa = traverse({}, children, roots, roots, group_set)
+ except RecursionError:
+ self.log(_("Error occured while parsing Chart of Accounts: Please make sure that no two accounts have the same name"))
for account in coa:
coa[account]["root_type"] = root_type_map[account]
@@ -126,14 +155,18 @@
def remove_parties(parents, children, group_set):
customers, suppliers = set(), set()
for account in parents:
+ found = False
if self.tally_creditors_account in parents[account]:
- children.pop(account, None)
+ found = True
if account not in group_set:
suppliers.add(account)
- elif self.tally_debtors_account in parents[account]:
- children.pop(account, None)
+ if self.tally_debtors_account in parents[account]:
+ found = True
if account not in group_set:
customers.add(account)
+ if found:
+ children.pop(account, None)
+
return children, customers, suppliers
def traverse(tree, children, accounts, roots, group_set):
@@ -151,6 +184,7 @@
parties, addresses = [], []
for account in collection.find_all("LEDGER"):
party_type = None
+ links = []
if account.NAME.string.strip() in customers:
party_type = "Customer"
parties.append({
@@ -161,7 +195,9 @@
"territory": "All Territories",
"customer_type": "Individual",
})
- elif account.NAME.string.strip() in suppliers:
+ links.append({"link_doctype": party_type, "link_name": account["NAME"]})
+
+ if account.NAME.string.strip() in suppliers:
party_type = "Supplier"
parties.append({
"doctype": party_type,
@@ -170,6 +206,8 @@
"supplier_group": "All Supplier Groups",
"supplier_type": "Individual",
})
+ links.append({"link_doctype": party_type, "link_name": account["NAME"]})
+
if party_type:
address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
addresses.append({
@@ -183,7 +221,7 @@
"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
- "links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
+ "links": links
})
return parties, addresses
@@ -242,12 +280,18 @@
def create_company_and_coa(coa_file_url):
coa_file = frappe.get_doc("File", {"file_url": coa_file_url})
frappe.local.flags.ignore_chart_of_accounts = True
- company = frappe.get_doc({
- "doctype": "Company",
- "company_name": self.erpnext_company,
- "default_currency": "INR",
- "enable_perpetual_inventory": 0,
- }).insert()
+
+ try:
+ company = frappe.get_doc({
+ "doctype": "Company",
+ "company_name": self.erpnext_company,
+ "default_currency": "INR",
+ "enable_perpetual_inventory": 0,
+ }).insert()
+ except frappe.DuplicateEntryError:
+ company = frappe.get_doc("Company", self.erpnext_company)
+ unset_existing_data(self.erpnext_company)
+
frappe.local.flags.ignore_chart_of_accounts = False
create_charts(company.name, custom_chart=json.loads(coa_file.get_content()))
company.create_default_warehouses()
@@ -256,36 +300,35 @@
parties_file = frappe.get_doc("File", {"file_url": parties_file_url})
for party in json.loads(parties_file.get_content()):
try:
- frappe.get_doc(party).insert()
+ party_doc = frappe.get_doc(party)
+ party_doc.insert()
except:
- self.log(party)
+ self.log(party_doc)
addresses_file = frappe.get_doc("File", {"file_url": addresses_file_url})
for address in json.loads(addresses_file.get_content()):
try:
- frappe.get_doc(address).insert(ignore_mandatory=True)
+ address_doc = frappe.get_doc(address)
+ address_doc.insert(ignore_mandatory=True)
except:
- try:
- gstin = address.pop("gstin", None)
- frappe.get_doc(address).insert(ignore_mandatory=True)
- self.log({"address": address, "message": "Invalid GSTIN: {}. Address was created without GSTIN".format(gstin)})
- except:
- self.log(address)
+ self.log(address_doc)
def create_items_uoms(items_file_url, uoms_file_url):
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
for uom in json.loads(uoms_file.get_content()):
if not frappe.db.exists(uom):
try:
- frappe.get_doc(uom).insert()
+ uom_doc = frappe.get_doc(uom)
+ uom_doc.insert()
except:
- self.log(uom)
+ self.log(uom_doc)
items_file = frappe.get_doc("File", {"file_url": items_file_url})
for item in json.loads(items_file.get_content()):
try:
- frappe.get_doc(item).insert()
+ item_doc = frappe.get_doc(item)
+ item_doc.insert()
except:
- self.log(item)
+ self.log(item_doc)
try:
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
@@ -299,10 +342,13 @@
self.publish("Import Master Data", _("Done"), 4, 4)
+ self.set_account_defaults()
self.is_master_data_imported = 1
+ frappe.db.commit()
except:
self.publish("Import Master Data", _("Process Failed"), -1, 5)
+ frappe.db.rollback()
self.log()
finally:
@@ -323,7 +369,9 @@
processed_voucher = function(voucher)
if processed_voucher:
vouchers.append(processed_voucher)
+ frappe.db.commit()
except:
+ frappe.db.rollback()
self.log(voucher)
return vouchers
@@ -349,6 +397,7 @@
journal_entry = {
"doctype": "Journal Entry",
"tally_guid": voucher.GUID.string.strip(),
+ "tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
"posting_date": voucher.DATE.string.strip(),
"company": self.erpnext_company,
"accounts": accounts,
@@ -377,6 +426,7 @@
"doctype": doctype,
party_field: voucher.PARTYNAME.string.strip(),
"tally_guid": voucher.GUID.string.strip(),
+ "tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
"posting_date": voucher.DATE.string.strip(),
"due_date": voucher.DATE.string.strip(),
"items": get_voucher_items(voucher, doctype),
@@ -468,14 +518,21 @@
oldest_year = new_year
def create_custom_fields(doctypes):
- for doctype in doctypes:
- df = {
- "fieldtype": "Data",
- "fieldname": "tally_guid",
- "read_only": 1,
- "label": "Tally GUID"
- }
- create_custom_field(doctype, df)
+ tally_guid_df = {
+ "fieldtype": "Data",
+ "fieldname": "tally_guid",
+ "read_only": 1,
+ "label": "Tally GUID"
+ }
+ tally_voucher_no_df = {
+ "fieldtype": "Data",
+ "fieldname": "tally_voucher_no",
+ "read_only": 1,
+ "label": "Tally Voucher Number"
+ }
+ for df in [tally_guid_df, tally_voucher_no_df]:
+ for doctype in doctypes:
+ create_custom_field(doctype, df)
def create_price_list():
frappe.get_doc({
@@ -490,7 +547,7 @@
try:
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
- frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
+ frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.default_round_off_account)
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
vouchers = json.loads(vouchers_file.get_content())
@@ -521,11 +578,14 @@
for index, voucher in enumerate(chunk, start=start):
try:
- doc = frappe.get_doc(voucher).insert()
- doc.submit()
+ voucher_doc = frappe.get_doc(voucher)
+ voucher_doc.insert()
+ voucher_doc.submit()
self.publish("Importing Vouchers", _("{} of {}").format(index, total), index, total)
+ frappe.db.commit()
except:
- self.log(voucher)
+ frappe.db.rollback()
+ self.log(voucher_doc)
if is_last:
self.status = ""
@@ -551,9 +611,22 @@
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
def log(self, data=None):
- data = data or self.status
- message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
- return frappe.log_error(title="Tally Migration Error", message=message)
+ if isinstance(data, frappe.model.document.Document):
+ if sys.exc_info()[1].__class__ != frappe.DuplicateEntryError:
+ failed_import_log = json.loads(self.failed_import_log)
+ doc = data.as_dict()
+ failed_import_log.append({
+ "doc": doc,
+ "exc": traceback.format_exc()
+ })
+ self.failed_import_log = json.dumps(failed_import_log, separators=(',', ':'))
+ self.save()
+ frappe.db.commit()
+
+ else:
+ data = data or self.status
+ message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
+ return frappe.log_error(title="Tally Migration Error", message=message)
def set_status(self, status=""):
self.status = status
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 0c13b6a..3dc7c1e 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -70,6 +70,7 @@
if frappe.db.get_value('Item', item, 'is_stock_item'):
frappe.throw(_(msg))
+@frappe.whitelist()
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ['name', 'practitioner_name', 'mobile_phone']
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index 4c2d3f6..2bef5fb 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -15,6 +15,7 @@
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
+ ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record"))
self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status"))
@@ -26,7 +27,7 @@
self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
# Discharge
- schedule_discharge(patient=patient)
+ schedule_discharge(frappe.as_json({'patient': patient}))
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
@@ -44,8 +45,10 @@
patient = create_patient()
ip_record = create_inpatient(patient)
+ ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
ip_record_new = create_inpatient(patient)
+ ip_record_new.expected_length_of_stay = 0
self.assertRaises(frappe.ValidationError, ip_record_new.save)
service_unit = get_healthcare_service_unit()
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index ab161aa..2a69589 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -41,7 +41,7 @@
notification_config = "erpnext.startup.notifications.get_notification_config"
get_help_messages = "erpnext.utilities.activation.get_help_messages"
leaderboards = "erpnext.startup.leaderboard.get_leaderboards"
-
+filters_config = "erpnext.startup.filters.get_filters_config"
on_session_creation = [
"erpnext.portal.utils.create_customer_or_supplier",
@@ -238,6 +238,9 @@
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"on_trash": "erpnext.regional.check_deletion_permission"
},
+ "Purchase Invoice": {
+ "on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries"
+ },
"Payment Entry": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
"on_trash": "erpnext.regional.check_deletion_permission"
@@ -320,8 +323,7 @@
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans"
],
"monthly_long": [
- "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",
- "erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense",
+ "erpnext.accounts.deferred_revenue.process_deferred_accounting",
"erpnext.hr.utils.allocate_earned_leaves",
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans"
]
diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json
index 7ac000b..12548d4 100644
--- a/erpnext/hr/desk_page/hr/hr.json
+++ b/erpnext/hr/desk_page/hr/hr.json
@@ -18,7 +18,7 @@
{
"hidden": 0,
"label": "Leaves",
- "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Application\",\n \"name\": \"Leave Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Allocation\",\n \"name\": \"Leave Allocation\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Type\"\n ],\n \"label\": \"Leave Policy\",\n \"name\": \"Leave Policy\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Period\",\n \"name\": \"Leave Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Type\",\n \"name\": \"Leave Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Holiday List\",\n \"name\": \"Holiday List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Compensatory Leave Request\",\n \"name\": \"Compensatory Leave Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Encashment\",\n \"name\": \"Leave Encashment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Block List\",\n \"name\": \"Leave Block List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Application\"\n ],\n \"doctype\": \"Leave Application\",\n \"is_query_report\": true,\n \"label\": \"Employee Leave Balance\",\n \"name\": \"Employee Leave Balance\",\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Application\",\n \"name\": \"Leave Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Allocation\",\n \"name\": \"Leave Allocation\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Type\"\n ],\n \"label\": \"Leave Policy\",\n \"name\": \"Leave Policy\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Period\",\n \"name\": \"Leave Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Type\",\n \"name\": \"Leave Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Holiday List\",\n \"name\": \"Holiday List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Compensatory Leave Request\",\n \"name\": \"Compensatory Leave Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Encashment\",\n \"name\": \"Leave Encashment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Block List\",\n \"name\": \"Leave Block List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Application\"\n ],\n \"doctype\": \"Leave Application\",\n \"is_query_report\": true,\n \"label\": \"Employee Leave Balance\",\n \"name\": \"Employee Leave Balance\",\n \"type\": \"report\"\n }\n]"
},
{
"hidden": 0,
@@ -93,7 +93,7 @@
"idx": 0,
"is_standard": 1,
"label": "HR",
- "modified": "2020-05-28 13:36:07.710600",
+ "modified": "2020-06-16 19:20:50.976045",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
@@ -126,7 +126,7 @@
},
{
"label": "Salary Structure",
- "link_to": "Payroll Entry",
+ "link_to": "Salary Structure",
"type": "DocType"
},
{
diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json
index 906f6f7..a656a7e 100644
--- a/erpnext/hr/doctype/attendance/attendance.json
+++ b/erpnext/hr/doctype/attendance/attendance.json
@@ -19,11 +19,15 @@
"attendance_date",
"company",
"department",
- "shift",
"attendance_request",
- "amended_from",
+ "details_section",
+ "shift",
+ "in_time",
+ "out_time",
+ "column_break_18",
"late_entry",
- "early_exit"
+ "early_exit",
+ "amended_from"
],
"fields": [
{
@@ -172,13 +176,36 @@
"fieldname": "early_exit",
"fieldtype": "Check",
"label": "Early Exit"
+ },
+ {
+ "fieldname": "details_section",
+ "fieldtype": "Section Break",
+ "label": "Details"
+ },
+ {
+ "depends_on": "shift",
+ "fieldname": "in_time",
+ "fieldtype": "Datetime",
+ "label": "In Time",
+ "read_only": 1
+ },
+ {
+ "depends_on": "shift",
+ "fieldname": "out_time",
+ "fieldtype": "Datetime",
+ "label": "Out Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-ok",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-11 11:40:14.319496",
+ "modified": "2020-05-29 13:51:37.177231",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index df0f75a..d4c118f 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -19,7 +19,7 @@
approvers = []
department_details = {}
department_list = []
- employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
+ employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver"], as_dict=True)
employee_department = filters.get("department") or employee.department
if employee_department:
@@ -33,10 +33,16 @@
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
+ if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
+ approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name']))
+
+
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
+ field_name = "Leave Approver"
else:
parentfield = "expense_approvers"
+ field_name = "Expense Approver"
if department_list:
for d in department_list:
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
@@ -46,4 +52,12 @@
and approver.parentfield = %s
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
+ if len(approvers) == 0:
+ frappe.throw(_("Please set {0} for the Employee or for Department: {1}").
+ format(
+ field_name, frappe.bold(employee_department),
+ frappe.bold(employee.name)
+ ),
+ title=_(field_name + " Missing"))
+
return set(tuple(approver) for approver in approvers)
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index f575765..7dacacf1 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -62,6 +62,7 @@
"salary_mode",
"payroll_cost_center",
"column_break_52",
+ "expense_approver",
"bank_name",
"bank_ac_no",
"health_insurance_section",
@@ -205,7 +206,7 @@
"label": "Status",
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nActive\nLeft",
+ "options": "Active\nLeft",
"reqd": 1,
"search_index": 1
},
@@ -667,6 +668,7 @@
"oldfieldtype": "Date"
},
{
+ "depends_on": "eval:doc.status == \"Left\"",
"fieldname": "relieving_date",
"fieldtype": "Date",
"label": "Relieving Date",
@@ -797,13 +799,21 @@
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "expense_approver",
+ "fieldtype": "Link",
+ "label": "Expense Approver",
+ "options": "User",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2020-05-05 18:51:03.152503",
+ "modified": "2020-06-18 18:01:27.223535",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index 6cc49cf..cba8ee9 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -139,13 +139,13 @@
employee: function (frm) {
if (frm.doc.employee) {
return frappe.call({
- method: "erpnext.hr.doctype.employee_advance.employee_advance.get_due_advance_amount",
+ method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
args: {
"employee": frm.doc.employee,
"posting_date": frm.doc.posting_date
},
callback: function(r) {
- frm.set_value("due_advance_amount",r.message);
+ frm.set_value("pending_amount",r.message);
}
});
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 8c5ce42..0d90913 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -19,7 +19,7 @@
"column_break_11",
"advance_amount",
"paid_amount",
- "due_advance_amount",
+ "pending_amount",
"claimed_amount",
"return_amount",
"section_break_7",
@@ -103,14 +103,6 @@
"read_only": 1
},
{
- "depends_on": "eval:cur_frm.doc.employee",
- "fieldname": "due_advance_amount",
- "fieldtype": "Currency",
- "label": "Due Advance Amount",
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
"fieldname": "claimed_amount",
"fieldtype": "Currency",
"label": "Claimed Amount",
@@ -177,11 +169,19 @@
"fieldname": "repay_unclaimed_amount_from_salary",
"fieldtype": "Check",
"label": "Repay unclaimed amount from salary"
+ },
+ {
+ "depends_on": "eval:cur_frm.doc.employee",
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "label": "Pending Amount",
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-06 15:11:33.747535",
+ "modified": "2020-06-12 12:42:39.833818",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index c5a9fce..b290bcc 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -95,7 +95,7 @@
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist()
-def get_due_advance_amount(employee, posting_date):
+def get_pending_amount(employee, posting_date):
employee_due_amount = frappe.get_all("Employee Advance", \
filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \
fields = ["advance_amount", "paid_amount"])
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index 8670512..15fbd4e 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -72,7 +72,7 @@
return doc
-def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None):
+def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None):
"""Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@@ -100,7 +100,9 @@
'company': employee_doc.company,
'shift': shift,
'late_entry': late_entry,
- 'early_exit': early_exit
+ 'early_exit': early_exit,
+ 'in_time': in_time,
+ 'out_time': out_time
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index fb23103..6bb9af9 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -243,7 +243,6 @@
},
update_employee_advance_claimed_amount: function(frm) {
- console.log("update_employee_advance_claimed_amount")
let amount_to_be_allocated = frm.doc.grand_total;
$.each(frm.doc.advances || [], function(i, advance){
if (amount_to_be_allocated >= advance.unclaimed_amount){
@@ -295,6 +294,16 @@
frm.events.get_advances(frm);
},
+ cost_center: function(frm) {
+ frm.events.set_child_cost_center(frm);
+ },
+ set_child_cost_center: function(frm){
+ (frm.doc.expenses || []).forEach(function(d) {
+ if (!d.cost_center){
+ d.cost_center = frm.doc.cost_center;
+ }
+ });
+ },
get_taxes: function(frm) {
if(frm.doc.taxes) {
frappe.call({
@@ -338,8 +347,7 @@
frappe.ui.form.on("Expense Claim Detail", {
expenses_add: function(frm, cdt, cdn) {
- var row = frappe.get_doc(cdt, cdn);
- frm.script_manager.copy_from_first_row("expenses", row, ["cost_center"]);
+ frm.events.set_child_cost_center(frm);
},
amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index 96baaab..fa28470 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -66,6 +66,7 @@
"fieldname": "employee",
"fieldtype": "Link",
"in_global_search": 1,
+ "in_standard_filter": 1,
"label": "From Employee",
"oldfieldname": "employee",
"oldfieldtype": "Link",
@@ -164,6 +165,7 @@
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
+ "in_standard_filter": 1,
"label": "Posting Date",
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
@@ -236,6 +238,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
+ "in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
@@ -368,7 +371,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2019-12-14 23:52:05.388458",
+ "modified": "2020-06-15 12:43:04.099803",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 1f50e27..fb1f2c0 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -38,11 +38,15 @@
},
validate: function(frm) {
+ if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){
+ frm.doc.half_day_date = frm.doc.from_date;
+ }
frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
},
make_dashboard: function(frm) {
var leave_details;
+ let lwps;
if (frm.doc.employee) {
frappe.call({
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details",
@@ -58,6 +62,7 @@
if (!r.exc && r.message['leave_approver']) {
frm.set_value('leave_approver', r.message['leave_approver']);
}
+ lwps = r.message["lwps"];
}
});
$("div").remove(".form-dashboard-section.custom");
@@ -67,6 +72,18 @@
})
);
frm.dashboard.show();
+ let allowed_leave_types = Object.keys(leave_details);
+
+ // lwps should be allowed, lwps don't have any allocation
+ allowed_leave_types = allowed_leave_types.concat(lwps);
+
+ frm.set_query('leave_type', function(){
+ return {
+ filters : [
+ ['leave_type_name', 'in', allowed_leave_types]
+ ]
+ };
+ });
}
},
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index f2968bc..0423824 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -19,7 +19,6 @@
from frappe.model.document import Document
class LeaveApplication(Document):
-
def get_feed(self):
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
@@ -33,6 +32,7 @@
self.validate_block_days()
self.validate_salary_processed_days()
self.validate_attendance()
+ self.set_half_day_date()
if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
self.validate_optional_leave()
self.validate_applicable_after()
@@ -131,8 +131,6 @@
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
date = dt.strftime("%Y-%m-%d")
status = "Half Day" if getdate(date) == getdate(self.half_day_date) else "On Leave"
- print("-------->>>", status)
- # frappe.throw("Hello")
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
attendance_date = date, docstatus = ('!=', 2)))
@@ -292,6 +290,10 @@
frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
day = add_days(day, 1)
+ def set_half_day_date(self):
+ if self.from_date == self.to_date and self.half_day == 1:
+ self.half_day_date = self.from_date
+
def notify_employee(self):
employee = frappe.get_doc("Employee", self.employee)
if not employee.user_id:
@@ -442,6 +444,7 @@
total_allocated_leaves = frappe.db.get_value('Leave Allocation', {
'from_date': ('<=', date),
'to_date': ('>=', date),
+ 'employee': employee,
'leave_type': allocation.leave_type,
}, 'SUM(total_leaves_allocated)') or 0
@@ -459,9 +462,14 @@
"pending_leaves": leaves_pending,
"remaining_leaves": remaining_leaves}
+ #is used in set query
+ lwps = frappe.get_list("Leave Type", filters = {"is_lwp": 1})
+ lwps = [lwp.name for lwp in lwps]
+
ret = {
'leave_allocation': leave_allocation,
- 'leave_approver': get_leave_approver(employee)
+ 'leave_approver': get_leave_approver(employee),
+ 'lwps': lwps
}
return ret
diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html
index 295f3b4..d30e3b9 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html
+++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html
@@ -1,5 +1,5 @@
-{% if data %}
+{% if not jQuery.isEmptyObject(data) %}
<h5 style="margin-top: 20px;"> {{ __("Allocated Leaves") }} </h5>
<table class="table table-bordered small">
<thead>
@@ -11,7 +11,6 @@
<th style="width: 16%" class="text-right">{{ __("Pending Leaves") }}</th>
<th style="width: 16%" class="text-right">{{ __("Available Leaves") }}</th>
</tr>
-
</thead>
<tbody>
{% for(const [key, value] of Object.entries(data)) { %}
@@ -26,6 +25,6 @@
{% } %}
</tbody>
</table>
-{% } else { %}
+{% else %}
<p style="margin-top: 30px;"> No Leaves have been allocated. </p>
-{% } %}
\ No newline at end of file
+{% endif %}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py
index d56080e..1973564 100644
--- a/erpnext/hr/doctype/shift_type/shift_type.py
+++ b/erpnext/hr/doctype/shift_type/shift_type.py
@@ -28,13 +28,14 @@
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
single_shift_logs = list(group)
- attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs)
- mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name)
+ attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
+ mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
for employee in self.get_assigned_employee(self.process_attendance_after, True):
self.mark_absent_for_dates_with_no_attendance(employee)
def get_attendance(self, logs):
- """Return attendance_status, working_hours for a set of logs belonging to a single shift.
+ """Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time
+ for a set of logs belonging to a single shift.
Assumtion:
1. These logs belongs to an single shift, single employee and is not in a holiday date.
2. Logs are in chronological order
@@ -48,10 +49,10 @@
early_exit = True
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
- return 'Absent', total_working_hours, late_entry, early_exit
+ return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
- return 'Half Day', total_working_hours, late_entry, early_exit
- return 'Present', total_working_hours, late_entry, early_exit
+ return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
+ return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
def mark_absent_for_dates_with_no_attendance(self, employee):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index cd12510..8d95924 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -73,11 +73,11 @@
def assign_task_to_users(self, task, users):
for user in users:
args = {
- 'assign_to' : user,
- 'doctype' : task.doctype,
- 'name' : task.name,
- 'description' : task.description or task.subject,
- 'notify': self.notify_users_by_email
+ 'assign_to': [user],
+ 'doctype': task.doctype,
+ 'name': task.name,
+ 'description': task.description or task.subject,
+ 'notify': self.notify_users_by_email
}
assign_to.add(args)
diff --git a/erpnext/loan_management/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json
index d79860a..48193b0 100644
--- a/erpnext/loan_management/desk_page/loan/loan.json
+++ b/erpnext/loan_management/desk_page/loan/loan.json
@@ -23,7 +23,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Loan Repayment\"\n ],\n \"doctype\": \"Loan Repayment\",\n \"incomplete_dependencies\": [\n \"Loan Repayment\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"doctype\": \"Loan Security Pledge\",\n \"incomplete_dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
+ "links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
}
],
"category": "Modules",
@@ -34,11 +34,10 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Loan",
- "modified": "2020-05-28 13:37:42.017709",
+ "modified": "2020-06-07 19:42:14.947902",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index c9e36a8..d44088b 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -27,6 +27,7 @@
def on_cancel(self):
self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ['GL Entry']
def set_missing_values(self):
if not self.disbursement_date:
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 094b9c6..e6ceb55 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -31,6 +31,7 @@
self.update_is_accrued()
self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ['GL Entry']
def update_is_accrued(self):
frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0)
@@ -176,21 +177,23 @@
return term_loans
def make_loan_interest_accrual_entry(args):
- loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
- loan_interest_accrual.loan = args.loan
- loan_interest_accrual.applicant_type = args.applicant_type
- loan_interest_accrual.applicant = args.applicant
- loan_interest_accrual.interest_income_account = args.interest_income_account
- loan_interest_accrual.loan_account = args.loan_account
- loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, 2)
- loan_interest_accrual.interest_amount = flt(args.interest_amount, 2)
- loan_interest_accrual.posting_date = args.posting_date or nowdate()
- loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
- loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
- loan_interest_accrual.payable_principal_amount = args.payable_principal
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
- loan_interest_accrual.save()
- loan_interest_accrual.submit()
+ loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
+ loan_interest_accrual.loan = args.loan
+ loan_interest_accrual.applicant_type = args.applicant_type
+ loan_interest_accrual.applicant = args.applicant
+ loan_interest_accrual.interest_income_account = args.interest_income_account
+ loan_interest_accrual.loan_account = args.loan_account
+ loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
+ loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
+ loan_interest_accrual.posting_date = args.posting_date or nowdate()
+ loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
+ loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
+ loan_interest_accrual.payable_principal_amount = args.payable_principal
+
+ loan_interest_accrual.save()
+ loan_interest_accrual.submit()
def get_no_of_days_for_interest_accural(loan, posting_date):
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 2ab668a..c28994e 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -6,7 +6,7 @@
import frappe, erpnext
import json
from frappe import _
-from frappe.utils import flt, getdate
+from frappe.utils import flt, getdate, cint
from six import iteritems
from frappe.model.document import Document
from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime
@@ -29,8 +29,11 @@
def on_cancel(self):
self.mark_as_unpaid()
self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ['GL Entry']
def set_missing_values(self, amounts):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
+
if not self.posting_date:
self.posting_date = get_datetime()
@@ -38,24 +41,26 @@
self.cost_center = erpnext.get_default_cost_center(self.company)
if not self.interest_payable:
- self.interest_payable = flt(amounts['interest_amount'], 2)
+ self.interest_payable = flt(amounts['interest_amount'], precision)
if not self.penalty_amount:
- self.penalty_amount = flt(amounts['penalty_amount'], 2)
+ self.penalty_amount = flt(amounts['penalty_amount'], precision)
if not self.pending_principal_amount:
- self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
+ self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision)
if not self.payable_principal_amount and self.is_term_loan:
- self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
+ self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision)
if not self.payable_amount:
- self.payable_amount = flt(amounts['payable_amount'], 2)
+ self.payable_amount = flt(amounts['payable_amount'], precision)
if amounts.get('due_date'):
self.due_date = amounts.get('due_date')
def validate_amount(self):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
+
if not self.amount_paid:
frappe.throw(_("Amount paid cannot be zero"))
@@ -63,11 +68,13 @@
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
frappe.throw(msg)
- if self.payment_type == "Loan Closure" and flt(self.amount_paid, 2) < flt(self.payable_amount, 2):
+ if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision):
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
frappe.throw(msg)
def update_paid_amount(self):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
+
loan = frappe.get_doc("Loan", self.against_loan)
for payment in self.repayment_details:
@@ -75,9 +82,9 @@
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
- (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
+ (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual))
- if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
+ if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision):
if loan.is_secured_loan:
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
else:
@@ -253,6 +260,7 @@
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
def get_amounts(amounts, against_loan, posting_date, payment_type):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
against_loan_doc = frappe.get_doc("Loan", against_loan)
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
@@ -282,8 +290,8 @@
payable_principal_amount += entry.payable_principal_amount
pending_accrual_entries.setdefault(entry.name, {
- 'interest_amount': flt(entry.interest_amount),
- 'payable_principal_amount': flt(entry.payable_principal_amount)
+ 'interest_amount': flt(entry.interest_amount, precision),
+ 'payable_principal_amount': flt(entry.payable_principal_amount, precision)
})
if not final_due_date:
@@ -301,11 +309,11 @@
per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365
total_pending_interest += (pending_days * per_day_interest)
- amounts["pending_principal_amount"] = pending_principal_amount
- amounts["payable_principal_amount"] = payable_principal_amount
- amounts["interest_amount"] = total_pending_interest
- amounts["penalty_amount"] = penalty_amount
- amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
+ amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
+ amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
+ amounts["interest_amount"] = flt(total_pending_interest, precision)
+ amounts["penalty_amount"] = flt(penalty_amount, precision)
+ amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
amounts["pending_accrual_entries"] = pending_accrual_entries
if final_due_date:
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 51c5cb9..1dd3710 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -41,6 +41,7 @@
"options": "Company:company:default_currency"
},
{
+ "default": "0",
"fieldname": "rate_of_interest",
"fieldtype": "Percent",
"label": "Rate of Interest (%) Yearly",
@@ -143,7 +144,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-15 00:24:43.259963",
+ "modified": "2020-06-07 18:55:59.346292",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Type",
diff --git a/erpnext/loan_management/report/loan_security_status/loan_security_status.py b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
index ea6a2ee6..1951855 100644
--- a/erpnext/loan_management/report/loan_security_status/loan_security_status.py
+++ b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
@@ -76,7 +76,8 @@
"fieldtype": "Link",
"fieldname": "currency",
"options": "Currency",
- "width": 50
+ "width": 50,
+ "hidden": 1
}
]
@@ -84,17 +85,13 @@
def get_data(filters):
- loan_security_price_map = frappe._dict(frappe.get_all("Loan Security",
- fields=["name", "loan_security_price"], as_list=1
- ))
-
data = []
conditions = get_conditions(filters)
loan_security_pledges = frappe.db.sql("""
SELECT
p.name, p.applicant, p.loan, p.status, p.pledge_time,
- c.loan_security, c.qty
+ c.loan_security, c.qty, c.loan_security_price, c.amount
FROM
`tabLoan Security Pledge` p, `tabPledge` c
WHERE
@@ -115,8 +112,8 @@
row["pledge_time"] = pledge.pledge_time
row["loan_security"] = pledge.loan_security
row["qty"] = pledge.qty
- row["loan_security_price"] = loan_security_price_map.get(pledge.loan_security)
- row["loan_security_value"] = row["loan_security_price"] * pledge.qty
+ row["loan_security_price"] = pledge.loan_security_price
+ row["loan_security_value"] = pledge.amount
row["currency"] = default_currency
data.append(row)
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
index 4a17fd0..64e4bc6 100644
--- a/erpnext/manufacturing/dashboard_fixtures.py
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -4,7 +4,6 @@
import frappe, erpnext, json
from frappe import _
from frappe.utils import nowdate, get_first_day, get_last_day, add_months
-from erpnext.accounts.utils import get_fiscal_year
def get_data():
return frappe._dict({
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 1bdac57..7d31a1c 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -112,8 +112,16 @@
if self.routing:
self.set("operations", [])
for d in frappe.get_all("BOM Operation", fields = ["*"],
- filters = {'parenttype': 'Routing', 'parent': self.routing}):
- child = self.append('operations', d)
+ filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
+ child = self.append('operations', {
+ "operation": d.operation,
+ "workstation": d.workstation,
+ "description": d.description,
+ "time_in_mins": d.time_in_mins,
+ "batch_size": d.batch_size,
+ "operating_cost": d.operating_cost,
+ "idx": d.idx
+ })
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
def set_bom_material_details(self):
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 3ca851d..0350e2c 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -78,6 +78,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_hour_rate",
"fieldtype": "Currency",
"label": "Base Hour Rate(Company Currency)",
@@ -87,6 +88,7 @@
},
{
"default": "5",
+ "depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
"label": "Operating Cost(Company Currency)",
@@ -108,12 +110,12 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-07-16 22:35:55.374037",
- "modified_by": "govindsmenokee@gmail.com",
+ "modified": "2020-06-16 17:01:11.128420",
+ "modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 6cfd0ba..d7589fa 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -44,7 +44,6 @@
name: d.workstation
},
callback: function (data) {
- frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
frm.events.calculate_operating_cost(frm, d);
}
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index cac8067..2ca9f16 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -217,6 +217,8 @@
}
def get_summary_data(self):
+ if not self.data: return
+
return [
{
"value": sum(self.total_demand),
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 571f87a..d1294cc 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -77,6 +77,7 @@
customer = frappe.new_doc("Customer")
customer.customer_name = user_details.fullname
customer.customer_type = "Individual"
+ customer.flags.ignore_mandatory = True
customer.insert(ignore_permissions=True)
try:
@@ -91,7 +92,11 @@
"link_name": customer.name
})
- contact.insert()
+ contact.save()
+
+ except frappe.DuplicateEntryError:
+ return customer.name
+
except Exception as e:
frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed"))
pass
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index df19995..7a0caed 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -62,11 +62,26 @@
'subscription_id': subscription_id,
'email_id': email
}, order_by="creation desc")
- return frappe.get_doc("Member", members[0]['name'])
+ try:
+ return frappe.get_doc("Member", members[0]['name'])
+ except:
+ return None
+
+def verify_signature(data):
+ signature = frappe.request.headers.get('X-Razorpay-Signature')
+
+ settings = frappe.get_doc("Membership Settings")
+ key = settings.get_webhook_secret()
+
+ controller = frappe.get_doc("Razorpay Settings")
+
+ controller.verify_signature(data, signature, key)
+
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs):
- data = frappe.request.get_data()
+ data = frappe.request.get_data(as_text=True)
+ verify_signature(data)
if isinstance(data, six.string_types):
data = json.loads(data)
@@ -84,7 +99,10 @@
except Exception as e:
error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed"))
notify_failure(error_log)
- raise e
+ return False
+
+ if not member:
+ return False
if data.event == "subscription.activated":
member.customer_id = payment.customer_id
@@ -113,7 +131,6 @@
return True
-
def notify_failure(log):
try:
content = """Dear System Manager,
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
index c01a0b2..8c0e3a4 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
@@ -1,8 +1,30 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Membership Settings', {
+frappe.ui.form.on("Membership Settings", {
refresh: function(frm) {
+ if (frm.doc.webhook_secret) {
+ frm.add_custom_button(__("Revoke <Key></Key>"), () => {
+ frm.call("revoke_key").then(() => {
+ frm.refresh();
+ })
+ });
+ }
+ frm.trigger("add_generate_button");
+ },
- }
+ add_generate_button: function(frm) {
+ let label;
+
+ if (frm.doc.webhook_secret) {
+ label = __("Regenerate Webhook Secret");
+ } else {
+ label = __("Generate Webhook Secret");
+ }
+ frm.add_custom_button(label, () => {
+ frm.call("generate_webhook_key").then(() => {
+ frm.refresh();
+ });
+ });
+ },
});
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 56b8eac..52b9d01 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -8,7 +8,8 @@
"enable_razorpay",
"razorpay_settings_section",
"billing_cycle",
- "billing_frequency"
+ "billing_frequency",
+ "webhook_secret"
],
"fields": [
{
@@ -34,11 +35,17 @@
"fieldname": "billing_frequency",
"fieldtype": "Int",
"label": "Billing Frequency"
+ },
+ {
+ "fieldname": "webhook_secret",
+ "fieldtype": "Password",
+ "label": "Webhook Secret",
+ "read_only": 1
}
],
"issingle": 1,
"links": [],
- "modified": "2020-04-07 18:42:51.496807",
+ "modified": "2020-05-22 12:38:27.103759",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership Settings",
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
index 2b8e37f..f3b2eee 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.py
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
@@ -4,11 +4,27 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.integrations.utils import get_payment_gateway_controller
from frappe.model.document import Document
class MembershipSettings(Document):
- pass
+ def generate_webhook_key(self):
+ key = frappe.generate_hash(length=20)
+ self.webhook_secret = key
+ self.save()
+
+ frappe.msgprint(
+ _("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
+ _("Webhook Secret")
+ );
+
+ def revoke_key(self):
+ self.webhook_secret = None;
+ self.save()
+
+ def get_webhook_secret(self):
+ return self.get_password(fieldname="webhook_secret", raise_exception=False)
@frappe.whitelist()
def get_plans_for_membership(*args, **kwargs):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7125abf..e9ef6c0 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -680,6 +680,8 @@
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
+execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
+erpnext.patches.v12_0.move_due_advance_amount_to_pending_amount
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
@@ -695,4 +697,7 @@
erpnext.patches.v12_0.update_uom_conversion_factor
erpnext.patches.v13_0.delete_old_purchase_reports
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
-erpnext.patches.v12_0.unhide_cost_center_field
\ No newline at end of file
+erpnext.patches.v12_0.unhide_cost_center_field
+erpnext.patches.v13_0.update_sla_enhancements
+erpnext.patches.v12_0.update_address_template_for_india
+erpnext.patches.v12_0.set_multi_uom_in_rfq
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index 3c9758e..1ddbae6 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -4,7 +4,7 @@
def execute():
frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
- if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account'):
+ if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
frappe.db.sql("""
UPDATE `tabBank` b, `tabBank Account` ba
SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
@@ -12,4 +12,4 @@
""")
frappe.reload_doc('accounts', 'doctype', 'bank_account')
- frappe.reload_doc('accounts', 'doctype', 'payment_request')
\ No newline at end of file
+ frappe.reload_doc('accounts', 'doctype', 'payment_request')
diff --git a/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
new file mode 100644
index 0000000..6013eaa
--- /dev/null
+++ b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ ''' Move from due_advance_amount to pending_amount '''
+
+ if frappe.db.has_column("Employee Advance", "due_advance_amount"):
+ frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
diff --git a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
new file mode 100644
index 0000000..70ca6b2
--- /dev/null
+++ b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import flt
+from erpnext.stock.get_item_details import get_conversion_factor
+
+def execute():
+ frappe.reload_doc('buying', 'doctype', 'request_for_quotation_item')
+
+ frappe.db.sql("""UPDATE `tabRequest for Quotation Item`
+ SET
+ stock_uom = uom,
+ conversion_factor = 1,
+ stock_qty = qty""")
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/update_address_template_for_india.py b/erpnext/patches/v12_0/update_address_template_for_india.py
new file mode 100644
index 0000000..0d582da
--- /dev/null
+++ b/erpnext/patches/v12_0/update_address_template_for_india.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.address_template.setup import set_up_address_templates
+
+def execute():
+ if frappe.db.get_value('Company', {'country': 'India'}, 'name'):
+ address_template = frappe.db.get_value('Address Template', 'India', 'template')
+ if not address_template or "gstin" not in address_template:
+ set_up_address_templates(default_country='India')
diff --git a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
index ec94cd0..5ade8ca 100644
--- a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
+++ b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
@@ -14,15 +14,21 @@
frappe.reload_doc("hr", "doctype", doctype)
+ standard_tax_exemption_amount_exists = frappe.db.has_column("Payroll Period", "standard_tax_exemption_amount")
+
+ select_fields = "name, start_date, end_date"
+ if standard_tax_exemption_amount_exists:
+ select_fields = "name, start_date, end_date, standard_tax_exemption_amount"
+
for company in frappe.get_all("Company"):
payroll_periods = frappe.db.sql("""
SELECT
- name, start_date, end_date, standard_tax_exemption_amount
+ {0}
FROM
`tabPayroll Period`
WHERE company=%s
ORDER BY start_date DESC
- """, company.name, as_dict = 1)
+ """.format(select_fields), company.name, as_dict = 1)
for i, period in enumerate(payroll_periods):
income_tax_slab = frappe.new_doc("Income Tax Slab")
@@ -36,7 +42,8 @@
income_tax_slab.effective_from = period.start_date
income_tax_slab.company = company.name
income_tax_slab.allow_tax_exemption = 1
- income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount
+ if standard_tax_exemption_amount_exists:
+ income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount
income_tax_slab.flags.ignore_mandatory = True
income_tax_slab.submit()
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
index a7d4c66..be5e30f 100644
--- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -7,4 +7,4 @@
for entry in doctypes:
if frappe.db.exists('DocType', entry):
frappe.reload_doc('Healthcare', 'doctype', entry)
- frappe.db.sql("update `tab{dt}` set company = '{company}' where ifnull(company, '') = ''".format(dt=entry, company=company))
+ frappe.db.sql("update `tab{dt}` set company = {company} where ifnull(company, '') = ''".format(dt=entry, company=frappe.db.escape(company)))
diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py
new file mode 100644
index 0000000..c156ba9
--- /dev/null
+++ b/erpnext/patches/v13_0/update_sla_enhancements.py
@@ -0,0 +1,93 @@
+# Copyright (c) 2018, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ # add holiday list and employee group fields in SLA
+ # change response and resolution time in priorities child table
+ if frappe.db.exists('DocType', 'Service Level Agreement'):
+ sla_details = frappe.db.get_all('Service Level Agreement', fields=['name', 'service_level'])
+ priorities = frappe.db.get_all('Service Level Priority', fields=['*'], filters={
+ 'parenttype': ('in', ['Service Level Agreement', 'Service Level'])
+ })
+
+ frappe.reload_doc('support', 'doctype', 'service_level_agreement')
+ frappe.reload_doc('support', 'doctype', 'pause_sla_on_status')
+ frappe.reload_doc('support', 'doctype', 'service_level_priority')
+ frappe.reload_doc('support', 'doctype', 'service_day')
+
+ for entry in sla_details:
+ values = frappe.db.get_value('Service Level', entry.service_level, ['holiday_list', 'employee_group'])
+ if values:
+ holiday_list = values[0]
+ employee_group = values[1]
+ frappe.db.set_value('Service Level Agreement', entry.name, {
+ 'holiday_list': holiday_list,
+ 'employee_group': employee_group
+ })
+
+ priority_dict = {}
+
+ for priority in priorities:
+ if priority.parenttype == 'Service Level Agreement':
+ response_time = convert_to_seconds(priority.response_time, priority.response_time_period)
+ resolution_time = convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
+ frappe.db.set_value('Service Level Priority', priority.name, {
+ 'response_time': response_time,
+ 'resolution_time': resolution_time
+ })
+ if priority.parenttype == 'Service Level':
+ if not priority.parent in priority_dict:
+ priority_dict[priority.parent] = []
+ priority_dict[priority.parent].append(priority)
+
+
+ # copy Service Levels to Service Level Agreements
+ sl = [entry.service_level for entry in sla_details]
+ if frappe.db.exists('DocType', 'Service Level'):
+ service_levels = frappe.db.get_all('Service Level', filters={'service_level': ('not in', sl)}, fields=['*'])
+ for entry in service_levels:
+ sla = frappe.new_doc('Service Level Agreement')
+ sla.service_level = entry.service_level
+ sla.holiday_list = entry.holiday_list
+ sla.employee_group = entry.employee_group
+ sla.flags.ignore_validate = True
+ sla = sla.insert(ignore_mandatory=True)
+
+ frappe.db.sql("""
+ UPDATE
+ `tabService Day`
+ SET
+ parent = %(new_parent)s , parentfield = 'support_and_resolution', parenttype = 'Service Level Agreement'
+ WHERE
+ parent = %(old_parent)s
+ """, {'new_parent': sla.name, 'old_parent': entry.name}, as_dict = 1)
+
+ priority_list = priority_dict.get(entry.name)
+ if priority_list:
+ sla = frappe.get_doc('Service Level Agreement', sla.name)
+ for priority in priority_list:
+ row = sla.append('priorities', {
+ 'priority': priority.priority,
+ 'default_priority': priority.default_priority,
+ 'response_time': convert_to_seconds(priority.response_time, priority.response_time_period),
+ 'resolution_time': convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
+ })
+ row.db_update()
+ sla.db_update()
+
+ frappe.delete_doc_if_exists('DocType', 'Service Level')
+
+
+def convert_to_seconds(value, unit):
+ seconds = 0
+ if unit == "Hour":
+ seconds = value * 3600
+ if unit == "Day":
+ seconds = value * 3600 * 24
+ if unit == "Week":
+ seconds = value * 3600 * 24 * 7
+ return seconds
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 0993e69..6b6b8c5 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -1,4 +1,5 @@
import frappe
+from frappe.utils import cint
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
def get_field_filter_data():
@@ -243,6 +244,8 @@
else:
product_info = None
+ product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
+
return {
'next_attribute': next_attribute,
'valid_options_for_attributes': valid_options_for_attributes,
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 5862963..3570a0f 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -18,7 +18,7 @@
};
},
onload: function (frm) {
- var so = frm.get_docfield("Project", "sales_order");
+ var so = frappe.meta.get_docfield("Project", "sales_order");
so.get_route_options_for_new_doc = function (field) {
if (frm.is_new()) return;
return {
@@ -135,4 +135,4 @@
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
-}
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index bd33694..47a28fd 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -64,7 +64,7 @@
def assign():
from frappe.desk.form import assign_to
assign_to.add({
- "assign_to": "test@example.com",
+ "assign_to": ["test@example.com"],
"doctype": task.doctype,
"name": task.name,
"description": "Close this task"
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 32f0428..cbc624c 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -13,7 +13,7 @@
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.hr.doctype.salary_structure.test_salary_structure \
import make_salary_structure, create_salary_structure_assignment
-
+from erpnext.hr.doctype.employee.test_employee import make_employee
class TestTimesheet(unittest.TestCase):
def setUp(self):
@@ -25,8 +25,10 @@
def test_timesheet_billing_amount(self):
- make_salary_structure_for_timesheet("_T-Employee-00001")
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
+ emp = make_employee("test_employee_6@salary.com")
+
+ make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate=True, billable=1)
self.assertEqual(timesheet.total_hours, 2)
self.assertEqual(timesheet.total_billable_hours, 2)
@@ -35,8 +37,10 @@
self.assertEqual(timesheet.total_billable_amount, 100)
def test_timesheet_billing_amount_not_billable(self):
- make_salary_structure_for_timesheet("_T-Employee-00001")
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=0)
+ emp = make_employee("test_employee_6@salary.com")
+
+ make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate=True, billable=0)
self.assertEqual(timesheet.total_hours, 2)
self.assertEqual(timesheet.total_billable_hours, 0)
@@ -45,8 +49,10 @@
self.assertEqual(timesheet.total_billable_amount, 0)
def test_salary_slip_from_timesheet(self):
- salary_structure = make_salary_structure_for_timesheet("_T-Employee-00001")
- timesheet = make_timesheet("_T-Employee-00001", simulate = True, billable=1)
+ emp = make_employee("test_employee_6@salary.com")
+
+ salary_structure = make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate = True, billable=1)
salary_slip = make_salary_slip(timesheet.name)
salary_slip.submit()
@@ -65,7 +71,9 @@
self.assertEqual(timesheet.status, 'Submitted')
def test_sales_invoice_from_timesheet(self):
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
+ emp = make_employee("test_employee_6@salary.com")
+
+ timesheet = make_timesheet(emp, simulate=True, billable=1)
sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
sales_invoice.due_date = nowdate()
sales_invoice.submit()
@@ -80,7 +88,9 @@
self.assertEqual(item.rate, 50.00)
def test_timesheet_billing_based_on_project(self):
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1, project = '_Test Project', company='_Test Company')
+ emp = make_employee("test_employee_6@salary.com")
+
+ timesheet = make_timesheet(emp, simulate=True, billable=1, project = '_Test Project', company='_Test Company')
sales_invoice = create_sales_invoice(do_not_save=True)
sales_invoice.project = '_Test Project'
sales_invoice.submit()
@@ -90,6 +100,8 @@
self.assertEqual(ts.time_logs[0].sales_invoice, sales_invoice.name)
def test_timesheet_time_overlap(self):
+ emp = make_employee("test_employee_6@salary.com")
+
settings = frappe.get_single('Projects Settings')
initial_setting = settings.ignore_employee_time_overlap
settings.ignore_employee_time_overlap = 0
@@ -97,7 +109,7 @@
update_activity_type("_Test Activity Type")
timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = "_T-Employee-00001"
+ timesheet.employee = emp
timesheet.append(
'time_logs',
{
@@ -129,12 +141,14 @@
settings.save()
def test_timesheet_std_working_hours(self):
+ emp = make_employee("test_employee_6@salary.com")
+
company = frappe.get_doc('Company', "_Test Company")
company.standard_working_hours = 8
company.save()
timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = "_T-Employee-00001"
+ timesheet.employee = emp
timesheet.company = '_Test Company'
timesheet.append(
'time_logs',
@@ -156,7 +170,7 @@
company.save()
timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = "_T-Employee-00001"
+ timesheet.employee = emp
timesheet.company = '_Test Company'
timesheet.append(
'time_logs',
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 3eea390..defc18b 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -258,7 +258,12 @@
var update_billing_hours = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
- if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
+ if(!child.billable) {
+ frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
+ } else {
+ // bill all hours by default
+ frappe.model.set_value(cdt, cdn, "billing_hours", child.hours);
+ }
};
var update_time_rates = function(frm, cdt, cdn){
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 9c56189..a4cc68b 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -73,6 +73,8 @@
me.frm.set_query('contact_person', erpnext.queries.contact_query);
me.frm.set_query('supplier_address', erpnext.queries.address_query);
+ me.frm.set_query('billing_address', erpnext.queries.company_address_query);
+
if(this.frm.fields_dict.supplier) {
this.frm.set_query("supplier", function() {
return{ query: "erpnext.controllers.queries.supplier_query" }});
@@ -283,6 +285,11 @@
"shipping_address_display", true);
},
+ billing_address: function() {
+ erpnext.utils.get_address_display(this.frm, "billing_address",
+ "billing_address_display", true);
+ },
+
tc_name: function() {
this.get_terms();
},
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 524a958..ca897dd 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -552,7 +552,8 @@
if (show_batch_dialog)
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
.then((r) => {
- if(r.message.has_batch_no || r.message.has_serial_no) {
+ if (r.message &&
+ (r.message.has_batch_no || r.message.has_serial_no)) {
frappe.flags.hide_serial_batch_dialog = false;
}
});
@@ -917,7 +918,7 @@
shipping_rule: function() {
var me = this;
- if(this.frm.doc.shipping_rule && this.frm.doc.shipping_address) {
+ if(this.frm.doc.shipping_rule) {
return this.frm.call({
doc: this.frm.doc,
method: "apply_shipping_rule",
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 2cd79b5..bcab0d8 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -487,7 +487,14 @@
fieldtype: 'Date',
fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
in_list_view: 1,
- label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date")
+ label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
+ reqd: 1
+ })
+ fields.splice(3, 0, {
+ fieldtype: 'Float',
+ fieldname: "conversion_factor",
+ in_list_view: 1,
+ label: __("Conversion Factor")
})
}
@@ -536,6 +543,7 @@
"item_code": d.item_code,
"delivery_date": d.delivery_date,
"schedule_date": d.schedule_date,
+ "conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
});
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 735b417..617e916 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -81,4 +81,10 @@
.place-order-container {
text-align: right;
+}
+
+.kb-card {
+ .card-body > .card-title {
+ line-height: 1.3;
+ }
}
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
index ded3a51..cf2644e 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
@@ -2,4 +2,13 @@
// For license information, please see license.txt
frappe.ui.form.on('Quality Procedure', {
+ refresh: function(frm) {
+ frm.set_query("procedure","processes", (frm) =>{
+ return {
+ filters: {
+ name: ["not in", [frm.parent_quality_procedure, frm.name]]
+ }
+ };
+ });
+ }
});
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
index 6df116c..b3c0d94 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_rename": 1,
"autoname": "format:PRC-{quality_procedure_name}",
"creation": "2018-10-06 00:06:29.756804",
"doctype": "DocType",
@@ -72,7 +73,7 @@
],
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:09:29.371627",
+ "modified": "2020-06-17 17:25:03.434953",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure",
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
index d29710d..1952e57 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
@@ -11,18 +11,18 @@
nsm_parent_field = 'parent_quality_procedure'
def before_save(self):
- for process in self.processes:
- if process.procedure:
- doc = frappe.get_doc("Quality Procedure", process.procedure)
- if doc.parent_quality_procedure:
- frappe.throw(_("{0} already has a Parent Procedure {1}.").format(process.procedure, doc.parent_quality_procedure))
- self.is_group = 1
+ self.check_for_incorrect_child()
def on_update(self):
self.set_parent()
def after_insert(self):
self.set_parent()
+ #if Child is Added through Tree View.
+ if self.parent_quality_procedure:
+ parent_quality_procedure = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
+ parent_quality_procedure.append("processes", {"procedure": self.name})
+ parent_quality_procedure.save()
def on_trash(self):
if self.parent_quality_procedure:
@@ -43,10 +43,20 @@
def set_parent(self):
for process in self.processes:
+ # Set parent for only those children who don't have a parent
+ parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
+ if not parent_quality_procedure and process.procedure:
+ frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name)
+
+ def check_for_incorrect_child(self):
+ for process in self.processes:
if process.procedure:
- doc = frappe.get_doc("Quality Procedure", process.procedure)
- doc.parent_quality_procedure = self.name
- doc.save(ignore_permissions=True)
+ # Check if any child process belongs to another parent.
+ parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
+ if parent_quality_procedure and parent_quality_procedure != self.name:
+ frappe.throw(_("{0} already has a Parent Procedure {1}.".format(frappe.bold(process.procedure), frappe.bold(parent_quality_procedure))),
+ title=_("Invalid Child Procedure"))
+ self.is_group = 1
@frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
index 6df6f65..ef48ab6 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
@@ -16,6 +16,7 @@
},
],
breadcrumb: "Setup",
+ disable_add_node: true,
root_label: "All Quality Procedures",
get_tree_root: false,
menu_items: [
diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
index 0a67fa5..3925dbb 100644
--- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
+++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
@@ -1,6 +1,8 @@
{
+ "actions": [],
"creation": "2019-05-26 00:10:00.248885",
"doctype": "DocType",
+ "editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"process_description",
@@ -23,7 +25,8 @@
}
],
"istable": 1,
- "modified": "2019-05-26 22:05:49.007189",
+ "links": [],
+ "modified": "2020-06-17 15:44:38.937915",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure Process",
diff --git a/erpnext/regional/address_template/templates/india.html b/erpnext/regional/address_template/templates/india.html
index ffb9d05..5d2329e 100644
--- a/erpnext/regional/address_template/templates/india.html
+++ b/erpnext/regional/address_template/templates/india.html
@@ -1,7 +1,7 @@
{{ address_line1 }}<br>{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}{{ city }}<br>
{% if gst_state %}{{ gst_state }}{% endif -%}
{% if gst_state_number %}, State Code: {{ gst_state_number }}<br>{% endif -%}
-{% if pincode %}PIN: {{ pincode }}<br>{% endif -%}
+{% if pincode %}Postal Code: {{ pincode }}<br>{% endif -%}
{{ country }}<br>
{% if phone %}Phone: {{ phone }}<br>{% endif -%}
{% if fax %}Fax: {{ fax }}<br>{% endif -%}
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
index 35f9cf6..888b2da 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -52,7 +52,7 @@
<td class="disabled"></td>
<td class="disabled"></td>
<tr>
- <td>(d) {{__("Inward Supplies(liable to reverse charge")}}</td>
+ <td>(d) {{__("Inward Supplies(liable to reverse charge)")}}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.txval, 2) }}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.iamt, 2) }}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.camt, 2) }}</td>
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 9e7a023..619734f 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -158,7 +158,7 @@
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
- self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y")
+ self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
self.set_itc_details(itc_details)
@@ -192,31 +192,27 @@
for d in self.report_dict["itc_elg"]["itc_avl"]:
itc_type = itc_type_map.get(d["ty"])
- gst_category = "Registered Regular"
+ gst_category = ["Registered Regular"]
if d["ty"] == 'ISRC':
reverse_charge = "Y"
+ itc_type = 'All Other ITC'
+ gst_category = ['Unregistered', 'Overseas']
else:
reverse_charge = "N"
for account_head in self.account_heads:
+ for category in gst_category:
+ for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+ d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
- d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2)
- d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2)
- d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2)
- d["csamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2)
-
- net_itc["iamt"] += flt(d["iamt"], 2)
- net_itc["camt"] += flt(d["camt"], 2)
- net_itc["samt"] += flt(d["samt"], 2)
- net_itc["csamt"] += flt(d["csamt"], 2)
+ for key in ['iamt', 'camt', 'samt', 'csamt']:
+ net_itc[key] += flt(d[key], 2)
for account_head in self.account_heads:
itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
- itc_inelg["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2)
- itc_inelg["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2)
- itc_inelg["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2)
- itc_inelg["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2)
+ for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+ itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
@@ -274,17 +270,16 @@
""" #nosec
.format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
- def get_itc_details(self, reverse_charge='N'):
-
+ def get_itc_details(self):
itc_amount = frappe.db.sql("""
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
- where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
+ where s.docstatus = 1 and t.parent = s.name
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
and s.company_gstin = %s
group by t.account_head, s.gst_category, s.eligibility_for_itc
""",
- (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
itc_details = {}
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
index 6b9567c..31a7545 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -64,7 +64,8 @@
"buying_price_list": self.default_buying_price_list
}
- if not invoices_args.get("invoice_no", ''): return
+ if not invoices_args.get("bill_no", ''):
+ frappe.throw(_("Numero has not set in the XML file"))
supp_dict = get_supplier_details(file_content)
invoices_args["destination_code"] = get_destination_code_from_file(file_content)
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 732780a..9fe29eb 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -9,6 +9,8 @@
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.regional.india import number_state_mapping
from six import string_types
+from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.utils import get_account_currency
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -615,8 +617,9 @@
data.transDocDate = frappe.utils.formatdate(doc.lr_date, 'dd/mm/yyyy')
if doc.gst_transporter_id:
- validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID')
- data.transporterId = doc.gst_transporter_id
+ if doc.gst_transporter_id[0:2] != "88":
+ validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID')
+ data.transporterId = doc.gst_transporter_id
return data
@@ -657,5 +660,53 @@
elif val:
gst_accounts[val] = acc
-
return gst_accounts
+
+def make_reverse_charge_entries(doc, method):
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'India':
+ return
+
+ if doc.reverse_charge == 'Y':
+ gl_entries = []
+ gst_accounts = get_gst_accounts(doc.company)
+ gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ + gst_accounts.get('igst_account')
+
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
+ account_currency = get_account_currency(tax.account_head)
+
+ gl_entries.append(doc.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "posting_date": doc.posting_date,
+ "against": doc.supplier,
+ "credit": tax.base_tax_amount_after_discount_amount,
+ "credits_in_account_currency": tax.base_tax_amount_after_discount_amount \
+ if account_currency==doc.company_currency \
+ else tax.tax_amount_after_discount_amount
+ }, account_currency, item=tax)
+ )
+
+ gl_entries.append(doc.get_gl_dict(
+ {
+ "account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to,
+ "cost_center": doc.cost_center,
+ "posting_date": doc.posting_date,
+ "party_type": 'Supplier',
+ "party": doc.supplier,
+ "against": tax.account_head,
+ "debit": tax.base_tax_amount_after_discount_amount,
+ "debit_in_account_currency": tax.base_tax_amount_after_discount_amount \
+ if account_currency==doc.company_currency \
+ else tax.tax_amount_after_discount_amount
+ }, account_currency, item=doc)
+ )
+
+ make_gl_entries(gl_entries)
\ No newline at end of file
diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py
index 6d34402..cae28be 100644
--- a/erpnext/regional/united_states/setup.py
+++ b/erpnext/regional/united_states/setup.py
@@ -9,14 +9,14 @@
make_custom_fields()
add_print_formats()
-def make_custom_fields():
+def make_custom_fields(update=True):
custom_fields = {
'Supplier': [
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
label='Is IRS 1099 reporting required for supplier?')
]
}
- create_custom_fields(custom_fields)
+ create_custom_fields(custom_fields, update=update)
def add_print_formats():
frappe.reload_doc("regional", "print_format", "irs_1099_form")
diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json
index c32a7c8..9ec6343 100644
--- a/erpnext/selling/desk_page/selling/selling.json
+++ b/erpnext/selling/desk_page/selling/selling.json
@@ -29,7 +29,7 @@
"category": "Modules",
"charts": [
{
- "chart_name": "Income",
+ "chart_name": "Incoming Bills (Purchase Invoice)",
"label": "Income"
}
],
@@ -43,7 +43,7 @@
"idx": 0,
"is_standard": 1,
"label": "Selling",
- "modified": "2020-05-28 13:46:08.314240",
+ "modified": "2020-06-03 13:23:24.861706",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index a6889e0..682dfed 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -3,16 +3,19 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe.model.naming import set_name_by_naming_series
-from frappe import _, msgprint, throw
+from frappe import _, msgprint
import frappe.defaults
-from frappe.utils import flt, cint, cstr, today
+from frappe.utils import flt, cint, cstr, today, get_formatted_email
from frappe.desk.reportview import build_match_conditions, get_filters_cond
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address
from frappe.model.rename_doc import update_linked_doctypes
from frappe.model.mapper import get_mapped_doc
+from frappe.utils.user import get_users_with_role
+
class Customer(TransactionBase):
def get_feed(self):
@@ -336,6 +339,7 @@
return lp_details
+@frappe.whitelist()
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
from erpnext.controllers.queries import get_fields
@@ -378,10 +382,45 @@
.format(customer, customer_outstanding, credit_limit))
# If not authorized person raise exception
- credit_controller = frappe.db.get_value('Accounts Settings', None, 'credit_controller')
- if not credit_controller or credit_controller not in frappe.get_roles():
- throw(_("Please contact to the user who have Sales Master Manager {0} role")
- .format(" / " + credit_controller if credit_controller else ""))
+ credit_controller_role = frappe.db.get_single_value('Accounts Settings', 'credit_controller')
+ if not credit_controller_role or credit_controller_role not in frappe.get_roles():
+ # form a list of emails for the credit controller users
+ credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager")
+
+ # form a list of emails and names to show to the user
+ credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})]
+ credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list]
+
+ if not credit_controller_users:
+ frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
+
+ message = """Please contact any of the following users to extend the credit limits for {0}:
+ <br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users))
+
+ # if the current user does not have permissions to override credit limit,
+ # prompt them to send out an email to the controller users
+ frappe.msgprint(message,
+ title="Notify",
+ raise_exception=1,
+ primary_action={
+ 'label': 'Send Email',
+ 'server_action': 'erpnext.selling.doctype.customer.customer.send_emails',
+ 'args': {
+ 'customer': customer,
+ 'customer_outstanding': customer_outstanding,
+ 'credit_limit': credit_limit,
+ 'credit_controller_users_list': credit_controller_users_list
+ }
+ }
+ )
+
+@frappe.whitelist()
+def send_emails(args):
+ args = json.loads(args)
+ subject = (_("Credit limit reached for customer {0}").format(args.get('customer')))
+ message = (_("Credit limit has been crossed for customer {0} ({1}/{2})")
+ .format(args.get('customer'), args.get('customer_outstanding'), args.get('credit_limit')))
+ frappe.sendmail(recipients=[args.get('credit_controller_users_list')], subject=subject, message=message)
def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None):
# Outstanding based on GL Entries
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 05e4aa8..ffb6635 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -868,7 +868,8 @@
],
"field_no_map": [
"rate",
- "price_list_rate"
+ "price_list_rate",
+ "item_tax_template"
],
"postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.qty and doc.supplier == supplier and doc.item_code in selected_items
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b8b0d40..74e742f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -400,6 +400,23 @@
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ def test_update_child_qty_rate_perm(self):
+ so = make_sales_order(item_code= "_Test Item", qty=4)
+
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Accounts User")
+ frappe.set_user(user)
+
+ # update qty
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+
+ # add new item
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ frappe.set_user("Administrator")
+
def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index e593499..eff17f8 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -769,22 +769,23 @@
"label": "Against Blanket Order"
},
{
- "fieldname": "bom_no",
- "fieldtype": "Link",
- "label": "BOM No",
- "no_copy": 1,
- "options": "BOM",
- "print_hide": 1
- },
- {
- "fieldname": "manufacturing_section_section",
- "fieldtype": "Section Break",
- "label": "Manufacturing Section"
- }
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "manufacturing_section_section",
+ "fieldtype": "Section Break",
+ "label": "Manufacturing Section"
+ }
],
"idx": 1,
"istable": 1,
- "modified": "2020-05-15 18:13:43.006493",
+ "links": [],
+ "modified": "2020-05-29 20:54:32.309460",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index 88bd9c1..f15f63d 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -5,183 +5,183 @@
import calendar
import frappe
from frappe import _
-from frappe.utils import cint, cstr
+from frappe.utils import cint, cstr, getdate
def execute(filters=None):
- common_columns = [
- {
- 'label': _('New Customers'),
- 'fieldname': 'new_customers',
- 'fieldtype': 'Int',
- 'default': 0,
- 'width': 125
- },
- {
- 'label': _('Repeat Customers'),
- 'fieldname': 'repeat_customers',
- 'fieldtype': 'Int',
- 'default': 0,
- 'width': 125
- },
- {
- 'label': _('Total'),
- 'fieldname': 'total',
- 'fieldtype': 'Int',
- 'default': 0,
- 'width': 100
- },
- {
- 'label': _('New Customer Revenue'),
- 'fieldname': 'new_customer_revenue',
- 'fieldtype': 'Currency',
- 'default': 0.0,
- 'width': 175
- },
- {
- 'label': _('Repeat Customer Revenue'),
- 'fieldname': 'repeat_customer_revenue',
- 'fieldtype': 'Currency',
- 'default': 0.0,
- 'width': 175
- },
- {
- 'label': _('Total Revenue'),
- 'fieldname': 'total_revenue',
- 'fieldtype': 'Currency',
- 'default': 0.0,
- 'width': 175
- }
- ]
- if filters.get('view_type') == 'Monthly':
- return get_data_by_time(filters, common_columns)
- else:
- return get_data_by_territory(filters, common_columns)
+ common_columns = [
+ {
+ 'label': _('New Customers'),
+ 'fieldname': 'new_customers',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 125
+ },
+ {
+ 'label': _('Repeat Customers'),
+ 'fieldname': 'repeat_customers',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 125
+ },
+ {
+ 'label': _('Total'),
+ 'fieldname': 'total',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 100
+ },
+ {
+ 'label': _('New Customer Revenue'),
+ 'fieldname': 'new_customer_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ },
+ {
+ 'label': _('Repeat Customer Revenue'),
+ 'fieldname': 'repeat_customer_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ },
+ {
+ 'label': _('Total Revenue'),
+ 'fieldname': 'total_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ }
+ ]
+ if filters.get('view_type') == 'Monthly':
+ return get_data_by_time(filters, common_columns)
+ else:
+ return get_data_by_territory(filters, common_columns)
def get_data_by_time(filters, common_columns):
- # key yyyy-mm
- columns = [
- {
- 'label': _('Year'),
- 'fieldname': 'year',
- 'fieldtype': 'Data',
- 'width': 100
- },
- {
- 'label': _('Month'),
- 'fieldname': 'month',
- 'fieldtype': 'Data',
- 'width': 100
- },
- ]
- columns += common_columns
+ # key yyyy-mm
+ columns = [
+ {
+ 'label': _('Year'),
+ 'fieldname': 'year',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ {
+ 'label': _('Month'),
+ 'fieldname': 'month',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ ]
+ columns += common_columns
- customers_in = get_customer_stats(filters)
+ customers_in = get_customer_stats(filters)
- # time series
- from_year, from_month, temp = filters.get('from_date').split('-')
- to_year, to_month, temp = filters.get('to_date').split('-')
+ # time series
+ from_year, from_month, temp = filters.get('from_date').split('-')
+ to_year, to_month, temp = filters.get('to_date').split('-')
- from_year, from_month, to_year, to_month = \
- cint(from_year), cint(from_month), cint(to_year), cint(to_month)
+ from_year, from_month, to_year, to_month = \
+ cint(from_year), cint(from_month), cint(to_year), cint(to_month)
- out = []
- for year in range(from_year, to_year+1):
- for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
- key = '{year}-{month:02d}'.format(year=year, month=month)
- data = customers_in.get(key)
- new = data['new'] if data else [0, 0.0]
- repeat = data['repeat'] if data else [0, 0.0]
- out.append({
- 'year': cstr(year),
- 'month': calendar.month_name[month],
- 'new_customers': new[0],
- 'repeat_customers': repeat[0],
- 'total': new[0] + repeat[0],
- 'new_customer_revenue': new[1],
- 'repeat_customer_revenue': repeat[1],
- 'total_revenue': new[1] + repeat[1]
- })
- return columns, out
+ out = []
+ for year in range(from_year, to_year+1):
+ for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
+ key = '{year}-{month:02d}'.format(year=year, month=month)
+ data = customers_in.get(key)
+ new = data['new'] if data else [0, 0.0]
+ repeat = data['repeat'] if data else [0, 0.0]
+ out.append({
+ 'year': cstr(year),
+ 'month': calendar.month_name[month],
+ 'new_customers': new[0],
+ 'repeat_customers': repeat[0],
+ 'total': new[0] + repeat[0],
+ 'new_customer_revenue': new[1],
+ 'repeat_customer_revenue': repeat[1],
+ 'total_revenue': new[1] + repeat[1]
+ })
+ return columns, out
def get_data_by_territory(filters, common_columns):
- columns = [{
- 'label': 'Territory',
- 'fieldname': 'territory',
- 'fieldtype': 'Link',
- 'options': 'Territory',
- 'width': 150
- }]
- columns += common_columns
+ columns = [{
+ 'label': 'Territory',
+ 'fieldname': 'territory',
+ 'fieldtype': 'Link',
+ 'options': 'Territory',
+ 'width': 150
+ }]
+ columns += common_columns
- customers_in = get_customer_stats(filters, tree_view=True)
+ customers_in = get_customer_stats(filters, tree_view=True)
- territory_dict = {}
- for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
- territory_dict.update({
- t.name: {
- 'parent': t.parent_territory,
- 'is_group': t.is_group
- }
- })
+ territory_dict = {}
+ for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
+ territory_dict.update({
+ t.name: {
+ 'parent': t.parent_territory,
+ 'is_group': t.is_group
+ }
+ })
- depth_map = frappe._dict()
- for name, info in territory_dict.items():
- default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
- depth_map.setdefault(name, default)
+ depth_map = frappe._dict()
+ for name, info in territory_dict.items():
+ default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
+ depth_map.setdefault(name, default)
- data = []
- for name, indent in depth_map.items():
- condition = customers_in.get(name)
- new = customers_in[name]['new'] if condition else [0, 0.0]
- repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
- temp = {
- 'territory': name,
- 'parent_territory': territory_dict[name]['parent'],
- 'indent': indent,
- 'new_customers': new[0],
- 'repeat_customers': repeat[0],
- 'total': new[0] + repeat[0],
- 'new_customer_revenue': new[1],
- 'repeat_customer_revenue': repeat[1],
- 'total_revenue': new[1] + repeat[1],
- 'bold': 0 if indent else 1
- }
- data.append(temp)
+ data = []
+ for name, indent in depth_map.items():
+ condition = customers_in.get(name)
+ new = customers_in[name]['new'] if condition else [0, 0.0]
+ repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
+ temp = {
+ 'territory': name,
+ 'parent_territory': territory_dict[name]['parent'],
+ 'indent': indent,
+ 'new_customers': new[0],
+ 'repeat_customers': repeat[0],
+ 'total': new[0] + repeat[0],
+ 'new_customer_revenue': new[1],
+ 'repeat_customer_revenue': repeat[1],
+ 'total_revenue': new[1] + repeat[1],
+ 'bold': 0 if indent else 1
+ }
+ data.append(temp)
- loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
+ loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
- for ld in loop_data:
- if ld['parent_territory']:
- parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
- for key in parent_data.keys():
- if key not in ['indent', 'territory', 'parent_territory', 'bold']:
- parent_data[key] += ld[key]
+ for ld in loop_data:
+ if ld['parent_territory']:
+ parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
+ for key in parent_data.keys():
+ if key not in ['indent', 'territory', 'parent_territory', 'bold']:
+ parent_data[key] += ld[key]
- return columns, data, None, None, None, 1
+ return columns, data, None, None, None, 1
def get_customer_stats(filters, tree_view=False):
- """ Calculates number of new and repeated customers. """
- company_condition = ''
- if filters.get('company'):
- company_condition = ' and company=%(company)s'
+ """ Calculates number of new and repeated customers and revenue. """
+ company_condition = ''
+ if filters.get('company'):
+ company_condition = ' and company=%(company)s'
- customers = []
- customers_in = {}
+ customers = []
+ customers_in = {}
- for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
- where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s
- {company_condition} order by posting_date'''.format(company_condition=company_condition),
- filters, as_dict=1):
+ for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
+ where docstatus=1 and posting_date <= %(to_date)s
+ {company_condition} order by posting_date'''.format(company_condition=company_condition),
+ filters, as_dict=1):
- key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
- customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
+ key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+ new_or_repeat = 'new' if si.customer not in customers else 'repeat'
+ customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
- if not si.customer in customers:
- customers_in[key]['new'][0] += 1
- customers_in[key]['new'][1] += si.base_grand_total
- customers.append(si.customer)
- else:
- customers_in[key]['repeat'][0] += 1
- customers_in[key]['repeat'][1] += si.base_grand_total
+ # if filters.from_date <= si.posting_date.strftime('%Y-%m-%d'):
+ if getdate(filters.from_date) <= getdate(si.posting_date):
+ customers_in[key][new_or_repeat][0] += 1
+ customers_in[key][new_or_repeat][1] += si.base_grand_total
+ if new_or_repeat == 'new':
+ customers.append(si.customer)
- return customers_in
+ return customers_in
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 405004e..08a98ba 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -96,7 +96,7 @@
"label": _("Customer Group"),
"fieldtype": "Link",
"fieldname": "customer_group",
- "options": "customer Group",
+ "options": "Customer Group",
"width": 120
},
{
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 4d2d540..7c0be3b 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -101,8 +101,7 @@
if not context.purchase_order_list:
frappe.throw(_("No items to be received are overdue"))
- if not (context.events or context.todo_list or context.notifications or context.cards
- or context.purchase_orders_items_overdue_list):
+ if not context:
return None
frappe.flags.ignore_account_permission = False
diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
index aba6a79..28d1d16 100644
--- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
+++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@@ -49,7 +50,7 @@
"fieldname": "terms_and_conditions_help",
"fieldtype": "HTML",
"label": "Terms and Conditions Help",
- "options": "<h4>Standard Terms and Conditions Example</h4>\n\n<pre>Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
+ "options": "<h4>Standard Terms and Conditions Example</h4>\n\n<pre>Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
},
{
"fieldname": "applicable_modules_section",
@@ -81,7 +82,8 @@
],
"icon": "icon-legal",
"idx": 1,
- "modified": "2019-07-04 13:31:30.393425",
+ "links": [],
+ "modified": "2020-06-16 22:54:38.094844",
"modified_by": "Administrator",
"module": "Setup",
"name": "Terms and Conditions",
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index e666a41..74ff0ec 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -105,3 +105,4 @@
"ref_doctype": "Company"
})
settings.save()
+
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index d04c8c2..a7e8388 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -42,9 +42,9 @@
return {
"doc": decorate_quotation_doc(doc),
- "shipping_addresses": [{"name": address.name, "display": address.display}
+ "shipping_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
for address in addresses if address.address_type == "Shipping"],
- "billing_addresses": [{"name": address.name, "display": address.display}
+ "billing_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
for address in addresses if address.address_type == "Billing"],
"shipping_rules": get_applicable_shipping_rules(party),
"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
@@ -78,8 +78,10 @@
if is_stock_item:
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
+ if not cint(item_stock.in_stock):
+ throw(_("{1} Not in Stock").format(item.item_code))
if item.qty > item_stock.stock_qty[0][0]:
- throw(_("Only {0} in stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
+ throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
sales_order.flags.ignore_permissions = True
sales_order.insert()
@@ -337,21 +339,17 @@
def _set_price_list(cart_settings, quotation=None):
"""Set price list based on customer or shopping cart default"""
from erpnext.accounts.party import get_default_price_list
-
- # check if customer price list exists
+ party_name = quotation.get("party_name") if quotation else get_party().get("name")
selling_price_list = None
- if quotation and quotation.get("party_name"):
- selling_price_list = frappe.db.get_value('Customer', quotation.get("party_name"), 'default_price_list')
- # else check for territory based price list
+ # check if default customer price list exists
+ if party_name:
+ selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name))
+
+ # check default price list in shopping cart
if not selling_price_list:
selling_price_list = cart_settings.price_list
- party_name = quotation.get("party_name") if quotation else get_party().get("name")
-
- if not selling_price_list and party_name:
- selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name))
-
if quotation:
quotation.selling_price_list = selling_price_list
diff --git a/erpnext/startup/filters.py b/erpnext/startup/filters.py
new file mode 100644
index 0000000..a99e49b
--- /dev/null
+++ b/erpnext/startup/filters.py
@@ -0,0 +1,14 @@
+
+import frappe
+
+def get_filters_config():
+ filters_config = {
+ "fiscal year": {
+ "label": "Fiscal Year",
+ "get_field": "erpnext.accounts.utils.get_fiscal_year_filter_field",
+ "valid_for_fieldtypes": ["Date", "Datetime", "DateRange"],
+ "depends_on": "company",
+ }
+ }
+
+ return filters_config
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_fixtures.py b/erpnext/stock/dashboard_fixtures.py
index 0f1fd12..7625b1a 100644
--- a/erpnext/stock/dashboard_fixtures.py
+++ b/erpnext/stock/dashboard_fixtures.py
@@ -5,31 +5,26 @@
import json
from frappe import _
from frappe.utils import nowdate
-from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
+from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
def get_data():
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ fiscal_year_name = fiscal_year.get("name")
+ start_date = str(fiscal_year.get("year_start_date"))
+ end_date = str(fiscal_year.get("year_end_date"))
+
return frappe._dict({
"dashboards": get_dashboards(),
- "charts": get_charts(),
- "number_cards": get_number_cards(),
+ "charts": get_charts(company, fiscal_year_name, start_date, end_date),
+ "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
})
-def get_company_for_dashboards():
- company = frappe.defaults.get_defaults().company
- if company:
- return company
- else:
- company_list = frappe.get_list("Company")
- if company_list:
- return company_list[0].name
- return None
-
-company = frappe.get_doc("Company", get_company_for_dashboards())
-fiscal_year = get_fiscal_year(nowdate(), as_dict=1)
-fiscal_year_name = fiscal_year.get("name")
-start_date = str(fiscal_year.get("year_start_date"))
-end_date = str(fiscal_year.get("year_end_date"))
-
def get_dashboards():
return [{
"name": "Stock",
@@ -48,7 +43,7 @@
]
}]
-def get_charts():
+def get_charts(company, fiscal_year_name, start_date, end_date):
return [
{
"doctype": "Dashboard Chart",
@@ -133,7 +128,7 @@
}
]
-def get_number_cards():
+def get_number_cards(company, fiscal_year_name, start_date, end_date):
return [
{
"name": "Total Active Items",
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 71b998f..2f75bbd 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -34,7 +34,7 @@
if self.numeric_values:
validate_is_incremental(self, self.name, item.value, item.name)
else:
- validate_item_attribute_value(attributes_list, self.name, item.value, item.name)
+ validate_item_attribute_value(attributes_list, self.name, item.value, item.name, from_variant=False)
def validate_numeric(self):
if self.numeric_values:
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 3562181..3a8deb6 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -18,7 +18,7 @@
// formatter for material request item
frm.set_indicator_formatter('item_code',
- function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; });
+ function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; });
frm.set_query("item_code", "items", function() {
return {
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 93b29c8..4b8b594 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -119,11 +119,13 @@
if item_location.serial_no:
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
+ auto_set_serial_no = frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo")
+
locations.append(frappe._dict({
'qty': qty,
'stock_qty': stock_qty,
'warehouse': item_location.warehouse,
- 'serial_no': serial_nos,
+ 'serial_no': serial_nos if auto_set_serial_no else item_doc.serial_no,
'batch_no': item_location.batch_no
}))
@@ -206,6 +208,7 @@
sle.batch_no = batch.name
and sle.`item_code`=%(item_code)s
and sle.`company` = %(company)s
+ and batch.disabled = 0
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
@@ -471,4 +474,4 @@
item.material_request = location.material_request
item.serial_no = location.serial_no
item.batch_no = location.batch_no
- item.material_request_item = location.material_request_item
\ No newline at end of file
+ item.material_request_item = location.material_request_item
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index e9568ee..50c18f6 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -25,7 +25,7 @@
frm.custom_make_buttons = {
'Stock Entry': 'Return',
- 'Purchase Invoice': 'Invoice'
+ 'Purchase Invoice': 'Purchase Invoice'
};
frm.set_query("expense_account", "items", function() {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 02f72e2..3b92dac 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -31,6 +31,8 @@
"col_break_address",
"shipping_address",
"shipping_address_display",
+ "billing_address",
+ "billing_address_display",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -130,13 +132,17 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user"
+ "options": "fa fa-user",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -147,7 +153,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -159,7 +167,9 @@
"options": "MAT-PRE-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -174,6 +184,8 @@
"print_width": "150px",
"reqd": 1,
"search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -184,18 +196,24 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_delivery_note",
"fieldtype": "Data",
- "label": "Supplier Delivery Note"
+ "label": "Supplier Delivery Note",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -210,6 +228,8 @@
"print_width": "100px",
"reqd": 1,
"search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -223,6 +243,8 @@
"print_hide": 1,
"print_width": "100px",
"reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -231,7 +253,9 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company",
@@ -245,6 +269,8 @@
"print_width": "150px",
"remember_last_selected_value": 1,
"reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -254,7 +280,9 @@
"label": "Is Return",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_return",
@@ -264,46 +292,60 @@
"no_copy": 1,
"options": "Purchase Receipt",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
@@ -311,32 +353,42 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -346,7 +398,9 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Rate at which supplier's currency is converted to company's base currency",
@@ -357,13 +411,17 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -371,7 +429,9 @@
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "buying_price_list",
@@ -380,7 +440,9 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "buying_price_list",
@@ -388,7 +450,9 @@
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -397,11 +461,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
@@ -409,7 +477,9 @@
"fieldtype": "Link",
"label": "Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Sets 'Rejected Warehouse' in each row of the items table.",
@@ -420,11 +490,15 @@
"oldfieldname": "rejected_warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -434,7 +508,9 @@
"oldfieldname": "is_subcontracted",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
@@ -447,13 +523,17 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -463,20 +543,26 @@
"oldfieldname": "purchase_receipt_details",
"oldfieldtype": "Table",
"options": "Purchase Receipt Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "supplied_items",
@@ -485,7 +571,9 @@
"label": "Get Current Stock",
"oldfieldtype": "Button",
"options": "get_current_stock",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -496,7 +584,9 @@
"oldfieldtype": "Section Break",
"options": "fa fa-table",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
@@ -507,18 +597,24 @@
"oldfieldtype": "Table",
"options": "Purchase Receipt Item Supplied",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break0",
"fieldtype": "Section Break",
- "oldfieldtype": "Section Break"
+ "oldfieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -526,7 +622,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -539,18 +637,24 @@
"print_width": "150px",
"read_only": 1,
"reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
"fieldname": "column_break_27",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -560,42 +664,56 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Add / Edit Taxes and Charges",
"fieldname": "taxes_charges_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_col",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
- "options": "Shipping Rule"
+ "options": "Shipping Rule",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -604,7 +722,9 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -612,13 +732,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -627,13 +751,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -643,7 +771,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -653,7 +783,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -663,12 +795,16 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break3",
"fieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -679,7 +815,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -689,7 +827,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -697,14 +837,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_42",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -712,7 +856,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -720,28 +866,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_44",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_46",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -751,7 +907,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -760,7 +918,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_in_words",
@@ -769,7 +929,9 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounded_total",
@@ -779,11 +941,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "grand_total",
@@ -793,7 +959,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounding_adjustment",
@@ -802,7 +970,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -812,7 +982,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -821,13 +993,17 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -836,7 +1012,9 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
@@ -845,14 +1023,18 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor"
+ "oldfieldtype": "Text Editor",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_no",
@@ -861,7 +1043,9 @@
"label": "Bill No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_date",
@@ -870,7 +1054,9 @@
"label": "Bill Date",
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -878,7 +1064,9 @@
"fieldtype": "Section Break",
"label": "More Information",
"oldfieldtype": "Section Break",
- "options": "fa fa-file-text"
+ "options": "fa fa-file-text",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -895,6 +1083,8 @@
"read_only": 1,
"reqd": 1,
"search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -910,6 +1100,8 @@
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -919,7 +1111,9 @@
"label": "Range",
"oldfieldname": "range",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break4",
@@ -927,6 +1121,8 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -941,12 +1137,16 @@
"label": "% Amount Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "subscription_detail",
"fieldtype": "Section Break",
- "label": "Auto Repeat Detail"
+ "label": "Auto Repeat Detail",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -955,13 +1155,17 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -969,7 +1173,9 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -981,13 +1187,17 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -995,11 +1205,15 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_details",
@@ -1010,6 +1224,8 @@
"options": "<div class=\"columnHeading\">Other Details</div>",
"print_hide": 1,
"print_width": "30%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "30%"
},
{
@@ -1017,13 +1233,17 @@
"fieldtype": "Small Text",
"label": "Instructions",
"oldfieldname": "instructions",
- "oldfieldtype": "Text"
+ "oldfieldtype": "Text",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
"fieldtype": "Small Text",
"label": "Remarks",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1031,19 +1251,25 @@
"fieldname": "transporter_info",
"fieldtype": "Section Break",
"label": "Transporter Details",
- "options": "fa fa-truck"
+ "options": "fa fa-truck",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "transporter_name",
"fieldtype": "Data",
"label": "Transporter Name",
"oldfieldname": "transporter_name",
- "oldfieldtype": "Data"
+ "oldfieldtype": "Data",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break5",
"fieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -1054,6 +1280,8 @@
"oldfieldname": "lr_no",
"oldfieldtype": "Data",
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -1064,6 +1292,8 @@
"oldfieldname": "lr_date",
"oldfieldtype": "Date",
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -1072,26 +1302,48 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_reference",
"fieldtype": "Link",
"label": "Inter Company Reference",
"options": "Delivery Note",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address",
+ "fieldtype": "Link",
+ "label": "Select Billing Address",
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address_display",
+ "fieldtype": "Small Text",
+ "label": "Billing Address",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-18 18:02:18.020763",
+ "modified": "2020-06-13 22:26:03.600092",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
@@ -1158,4 +1410,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 18d6853..5fbd512 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -500,7 +500,7 @@
if raw_material_cost and self.purpose == "Manufacture":
d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
- elif self.purpose == "Repack" and total_fg_qty:
+ elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually:
d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
d.basic_amount = d.basic_rate * d.qty
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index c16a41c..7b9c129 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -23,6 +23,7 @@
"image",
"image_view",
"quantity_and_rate",
+ "set_basic_rate_manually",
"qty",
"basic_rate",
"basic_amount",
@@ -491,12 +492,21 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
+ "fieldname": "set_basic_rate_manually",
+ "fieldtype": "Check",
+ "label": "Set Basic Rate Manually",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-23 19:19:28.539769",
+ "modified": "2020-06-08 12:57:03.172887",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 9a97210..5df3fa8 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -8,7 +8,7 @@
def execute(filters=None):
if not filters: filters = {}
- float_preceision = frappe.db.get_default("float_preceision")
+ float_precision = frappe.db.get_default("float_precision")
condition = get_condition(filters)
@@ -25,7 +25,7 @@
data = []
for item in items:
total_outgoing = flt(consumed_item_map.get(item.name, 0)) + flt(delivered_item_map.get(item.name,0))
- avg_daily_outgoing = flt(total_outgoing / diff, float_preceision)
+ avg_daily_outgoing = flt(total_outgoing / diff, float_precision)
reorder_level = (avg_daily_outgoing * flt(item.lead_time_days)) + flt(item.safety_stock)
data.append([item.name, item.item_name, item.item_group, item.brand, item.description,
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index af99780..723ed5c 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -180,10 +180,10 @@
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
- if 0 < batch[0] <= qty_to_pop:
+ if 0 < flt(batch[0]) <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
- qty_to_pop -= batch[0]
+ qty_to_pop -= flt(batch[0])
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
else:
# all from current batch
@@ -262,4 +262,4 @@
]
},
"type" : "bar"
- }
\ No newline at end of file
+ }
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index f21dc3f..11e758f 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -230,12 +230,12 @@
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
- if qty >= 0:
+ if flt(qty) >= 0:
total = sum(f[0] for f in previous_stock_queue)
return sum(flt(f[0]) * flt(f[1]) for f in previous_stock_queue) / flt(total) if total else 0.0
else:
available_qty_for_outgoing, outgoing_cost = 0, 0
- qty_to_pop = abs(qty)
+ qty_to_pop = abs(flt(qty))
while qty_to_pop and previous_stock_queue:
batch = previous_stock_queue[0]
if 0 < batch[0] <= qty_to_pop:
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
index a3fe72d..b1ad7c8 100644
--- a/erpnext/support/desk_page/support/support.json
+++ b/erpnext/support/desk_page/support/support.json
@@ -13,7 +13,7 @@
{
"hidden": 0,
"label": "Service Level Agreement",
- "links": "[\n {\n \"description\": \"Service Level.\",\n \"label\": \"Service Level\",\n \"name\": \"Service Level\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -43,7 +43,7 @@
"idx": 0,
"is_standard": 1,
"label": "Support",
- "modified": "2020-05-28 13:51:23.869954",
+ "modified": "2020-06-04 11:54:56.124219",
"modified_by": "Administrator",
"module": "Support",
"name": "Support",
@@ -65,8 +65,8 @@
"type": "DocType"
},
{
- "label": "Service Level",
- "link_to": "Service Level",
+ "label": "Service Level Agreement",
+ "link_to": "Service Level Agreement",
"type": "DocType"
}
]
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index bad40cc..e7e5bd3 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -38,10 +38,35 @@
},
refresh: function (frm) {
-
if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") {
if (frm.doc.service_level_agreement) {
- set_time_to_resolve_and_response(frm);
+ frappe.call({
+ 'method': 'frappe.client.get',
+ args: {
+ doctype: 'Service Level Agreement',
+ name: frm.doc.service_level_agreement
+ },
+ callback: function(data) {
+ let statuses = data.message.pause_sla_on;
+ const hold_statuses = [];
+ $.each(statuses, (_i, entry) => {
+ hold_statuses.push(entry.status);
+ });
+ if (hold_statuses.includes(frm.doc.status)) {
+ frm.dashboard.clear_headline();
+ let message = {"indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)])};
+ frm.dashboard.set_headline_alert(
+ '<div class="row">' +
+ '<div class="col-xs-12">' +
+ '<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
+ '</div>' +
+ '</div>'
+ );
+ } else {
+ set_time_to_resolve_and_response(frm);
+ }
+ }
+ });
}
frm.add_custom_button(__("Close"), function () {
@@ -55,6 +80,7 @@
frm: frm
});
}, __("Make"));
+
} else {
if (frm.doc.service_level_agreement) {
frm.dashboard.clear_headline();
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index c12cef4..6525ab2 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -31,9 +31,13 @@
"resolution_by",
"resolution_by_variance",
"service_level_agreement_creation",
+ "on_hold_since",
+ "total_hold_time",
"response",
"mins_to_first_response",
"first_responded_on",
+ "column_break_26",
+ "avg_response_time",
"additional_info",
"lead",
"contact",
@@ -50,7 +54,9 @@
"resolution_date",
"content_type",
"attachment",
- "via_customer_portal"
+ "via_customer_portal",
+ "resolution_time",
+ "user_resolution_time"
],
"fields": [
{
@@ -114,7 +120,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Open\nReplied\nHold\nClosed",
+ "options": "Open\nReplied\nHold\nResolved\nClosed",
"search_index": 1
},
{
@@ -161,6 +167,7 @@
"options": "Service Level Agreement"
},
{
+ "depends_on": "eval: doc.status != 'Replied';",
"fieldname": "response_by",
"fieldtype": "Datetime",
"label": "Response By",
@@ -174,6 +181,7 @@
"read_only": 1
},
{
+ "depends_on": "eval: doc.status != 'Replied';",
"fieldname": "resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
@@ -328,7 +336,7 @@
"read_only": 1
},
{
- "depends_on": "eval: doc.service_level_agreement",
+ "depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
"description": "in hours",
"fieldname": "response_by_variance",
"fieldtype": "Float",
@@ -336,7 +344,7 @@
"read_only": 1
},
{
- "depends_on": "eval: doc.service_level_agreement",
+ "depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
"description": "in hours",
"fieldname": "resolution_by_variance",
"fieldtype": "Float",
@@ -362,12 +370,48 @@
"label": "Issue Split From",
"options": "Issue",
"read_only": 1
+ },
+ {
+ "fieldname": "column_break_26",
+ "fieldtype": "Column Break"
+ },
+ {
+ "bold": 1,
+ "fieldname": "avg_response_time",
+ "fieldtype": "Duration",
+ "label": "Average Response Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "resolution_time",
+ "fieldtype": "Duration",
+ "label": "Resolution Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "user_resolution_time",
+ "fieldtype": "Duration",
+ "label": "User Resolution Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "on_hold_since",
+ "fieldtype": "Datetime",
+ "hidden": 1,
+ "label": "On Hold Since",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_hold_time",
+ "fieldtype": "Duration",
+ "label": "Total Hold Time",
+ "read_only": 1
}
],
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
- "modified": "2020-03-13 02:19:49.477928",
+ "modified": "2020-06-10 12:47:37.146914",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",
@@ -395,4 +439,4 @@
"title_field": "subject",
"track_changes": 1,
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 117267f..883e603 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -7,7 +7,7 @@
from frappe import _
from frappe import utils
from frappe.model.document import Document
-from frappe.utils import now, time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime
+from frappe.utils import time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime, time_diff_in_seconds, time_diff
from datetime import datetime, timedelta
from frappe.model.mapper import get_mapped_doc
from frappe.utils.user import is_website_user
@@ -47,8 +47,8 @@
self.contact = frappe.db.get_value("Contact", {"email_id": email_id})
if self.contact:
- contact = frappe.get_doc('Contact', self.contact)
- self.customer = contact.get_link_for('Customer')
+ contact = frappe.get_doc("Contact", self.contact)
+ self.customer = contact.get_link_for("Customer")
if not self.company:
self.company = frappe.db.get_value("Lead", self.lead, "company") or \
@@ -56,18 +56,70 @@
def update_status(self):
status = frappe.db.get_value("Issue", self.name, "status")
- if self.status!="Open" and status =="Open" and not self.first_responded_on:
+ if self.status != "Open" and status == "Open" and not self.first_responded_on:
self.first_responded_on = frappe.flags.current_time or now_datetime()
- if self.status=="Closed" and status !="Closed":
+ if self.status in ["Closed", "Resolved"] and status not in ["Resolved", "Closed"]:
self.resolution_date = frappe.flags.current_time or now_datetime()
if frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing":
set_service_level_agreement_variance(issue=self.name)
self.update_agreement_status()
+ set_resolution_time(issue=self)
+ set_user_resolution_time(issue=self)
- if self.status=="Open" and status !="Open":
+ if self.status == "Open" and status != "Open":
# if no date, it should be set as None and not a blank string "", as per mysql strict config
self.resolution_date = None
+ self.reset_issue_metrics()
+ # enable SLA and variance on Reopen
+ self.agreement_fulfilled = "Ongoing"
+ set_service_level_agreement_variance(issue=self.name)
+
+ self.handle_hold_time(status)
+
+ def handle_hold_time(self, status):
+ if self.service_level_agreement:
+ # set response and resolution variance as None as the issue is on Hold for status as Replied
+ pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"],
+ filters={"parent": self.service_level_agreement})
+ hold_statuses = [entry.status for entry in pause_sla_on]
+ update_values = {}
+
+ if self.status in hold_statuses and status not in hold_statuses:
+ update_values['on_hold_since'] = frappe.flags.current_time or now_datetime()
+ if not self.first_responded_on:
+ update_values['response_by'] = None
+ update_values['response_by_variance'] = 0
+ update_values['resolution_by'] = None
+ update_values['resolution_by_variance'] = 0
+
+ # calculate hold time when status is changed from Replied to any other status
+ if self.status not in hold_statuses and status in hold_statuses:
+ hold_time = self.total_hold_time if self.total_hold_time else 0
+ now_time = frappe.flags.current_time or now_datetime()
+ update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since)
+
+ # re-calculate SLA variables after issue changes from Replied to Open
+ # add hold time to SLA variables
+ if self.status == "Open" and status in hold_statuses:
+ start_date_time = get_datetime(self.service_level_agreement_creation)
+ priority = get_priority(self)
+ now_time = frappe.flags.current_time or now_datetime()
+ hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
+
+ if not self.first_responded_on:
+ response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+ update_values['response_by'] = add_to_date(response_by, seconds=round(hold_time))
+ response_by_variance = round(time_diff_in_hours(self.response_by, now_time))
+ update_values['response_by_variance'] = response_by_variance + (hold_time // 3600)
+
+ resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
+ update_values['resolution_by'] = add_to_date(resolution_by, seconds=round(hold_time))
+ resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_time))
+ update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600)
+ update_values['on_hold_since'] = None
+
+ self.db_set(update_values)
def update_agreement_status(self):
if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
@@ -128,6 +180,7 @@
replicated_issue.response_by_variance = None
replicated_issue.resolution_by = None
replicated_issue.resolution_by_variance = None
+ replicated_issue.reset_issue_metrics()
frappe.get_doc(replicated_issue).insert()
@@ -137,7 +190,7 @@
communications = frappe.get_all("Communication",
filters={"reference_doctype": "Issue",
"reference_name": comm_to_split_from.reference_name,
- "creation": ('>=', comm_to_split_from.creation)})
+ "creation": (">=", comm_to_split_from.creation)})
for communication in communications:
doc = frappe.get_doc("Communication", communication.name)
@@ -173,20 +226,15 @@
self.service_level_agreement = service_level_agreement.name
self.priority = service_level_agreement.default_priority if not priority else priority
- service_level_agreement = frappe.get_doc("Service Level Agreement", service_level_agreement.name)
- priority = service_level_agreement.get_service_level_agreement_priority(self.priority)
- priority.update({
- "support_and_resolution": service_level_agreement.support_and_resolution,
- "holiday_list": service_level_agreement.holiday_list
- })
+ priority = get_priority(self)
if not self.creation:
self.creation = now_datetime()
self.service_level_agreement_creation = now_datetime()
start_date_time = get_datetime(self.service_level_agreement_creation)
- self.response_by = get_expected_time_for(parameter='response', service_level=priority, start_date_time=start_date_time)
- self.resolution_by = get_expected_time_for(parameter='resolution', service_level=priority, start_date_time=start_date_time)
+ self.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+ self.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
self.response_by_variance = round(time_diff_in_hours(self.response_by, now_datetime()))
self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()))
@@ -221,36 +269,41 @@
self.agreement_fulfilled = "Ongoing"
self.save()
+ def reset_issue_metrics(self):
+ self.db_set("resolution_time", None)
+ self.db_set("user_resolution_time", None)
+
+
+def get_priority(issue):
+ service_level_agreement = frappe.get_doc("Service Level Agreement", issue.service_level_agreement)
+ priority = service_level_agreement.get_service_level_agreement_priority(issue.priority)
+ priority.update({
+ "support_and_resolution": service_level_agreement.support_and_resolution,
+ "holiday_list": service_level_agreement.holiday_list
+ })
+ return priority
+
+
def get_expected_time_for(parameter, service_level, start_date_time):
current_date_time = start_date_time
expected_time = current_date_time
start_time = None
end_time = None
- # lets assume response time is in days by default
- if parameter == 'response':
- allotted_days = service_level.get("response_time")
- time_period = service_level.get("response_time_period")
- elif parameter == 'resolution':
- allotted_days = service_level.get("resolution_time")
- time_period = service_level.get("resolution_time_period")
+ if parameter == "response":
+ allotted_seconds = service_level.get("response_time")
+ elif parameter == "resolution":
+ allotted_seconds = service_level.get("resolution_time")
else:
frappe.throw(_("{0} parameter is invalid").format(parameter))
- allotted_hours = 0
- if time_period == 'Hour':
- allotted_hours = allotted_days
- allotted_days = 0
- elif time_period == 'Week':
- allotted_days *= 7
-
- expected_time_is_set = 1 if allotted_days == 0 and time_period in ['Day', 'Week'] else 0
+ expected_time_is_set = 0
support_days = {}
for service in service_level.get("support_and_resolution"):
support_days[service.workday] = frappe._dict({
- 'start_time': service.start_time,
- 'end_time': service.end_time,
+ "start_time": service.start_time,
+ "end_time": service.end_time,
})
holidays = get_holidays(service_level.get("holiday_list"))
@@ -264,25 +317,22 @@
if getdate(current_date_time) == getdate(start_date_time) and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time \
else support_days[current_weekday].start_time
end_time = support_days[current_weekday].end_time
- time_left_today = time_diff_in_hours(end_time, start_time)
+ time_left_today = time_diff_in_seconds(end_time, start_time)
# no time left for support today
- if time_left_today < 0: pass
- elif time_period == 'Hour':
- if time_left_today >= allotted_hours:
+ if time_left_today <= 0: pass
+ elif allotted_seconds:
+ if time_left_today >= allotted_seconds:
expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
- expected_time = add_to_date(expected_time, hours=allotted_hours)
+ expected_time = add_to_date(expected_time, seconds=allotted_seconds)
expected_time_is_set = 1
else:
- allotted_hours = allotted_hours - time_left_today
- else:
- allotted_days -= 1
- expected_time_is_set = allotted_days <= 0
+ allotted_seconds = allotted_seconds - time_left_today
if not expected_time_is_set:
current_date_time = add_to_date(current_date_time, days=1)
- if end_time and time_period != 'Hour':
+ if end_time and allotted_seconds >= 86400:
current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
else:
current_date_time = expected_time
@@ -311,6 +361,36 @@
if variance < 0:
frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False)
+
+def set_resolution_time(issue):
+ # total time taken from issue creation to closing
+ resolution_time = time_diff_in_seconds(issue.resolution_date, issue.creation)
+ issue.db_set("resolution_time", resolution_time)
+
+
+def set_user_resolution_time(issue):
+ # total time taken by a user to close the issue apart from wait_time
+ communications = frappe.get_list("Communication", filters={
+ "reference_doctype": issue.doctype,
+ "reference_name": issue.name
+ },
+ fields=["sent_or_received", "name", "creation"],
+ order_by="creation"
+ )
+
+ pending_time = []
+ for i in range(len(communications)):
+ if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
+ wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
+ if wait_time > 0:
+ pending_time.append(wait_time)
+
+ total_pending_time = sum(pending_time)
+ resolution_time_in_secs = time_diff_in_seconds(issue.resolution_date, issue.creation)
+ user_resolution_time = resolution_time_in_secs - total_pending_time
+ issue.db_set("user_resolution_time", user_resolution_time)
+
+
def get_list_context(context=None):
return {
"title": _("Issues"),
@@ -318,7 +398,7 @@
"row_template": "templates/includes/issue_row.html",
"show_sidebar": True,
"show_search": True,
- 'no_breadcrumbs': True
+ "no_breadcrumbs": True
}
@@ -326,12 +406,12 @@
from frappe.www.list import get_list
user = frappe.session.user
- contact = frappe.db.get_value('Contact', {'user': user}, 'name')
+ contact = frappe.db.get_value("Contact", {"user": user}, "name")
customer = None
if contact:
- contact_doc = frappe.get_doc('Contact', contact)
- customer = contact_doc.get_link_for('Customer')
+ contact_doc = frappe.get_doc("Contact", contact)
+ customer = contact_doc.get_link_for("Customer")
ignore_permissions = False
if is_website_user():
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 7a5e3e3..fb8ceb5 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -5,15 +5,18 @@
import frappe
import unittest
from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
-from frappe.utils import now_datetime, get_datetime
+from frappe.utils import now_datetime, get_datetime, flt
import datetime
from datetime import timedelta
class TestIssue(unittest.TestCase):
- def test_response_time_and_resolution_time_based_on_different_sla(self):
+ def setUp(self):
+ frappe.db.sql("delete from `tabService Level Agreement`")
+ frappe.db.sql("delete from `tabEmployee`")
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
create_service_level_agreements_for_issues()
+ def test_response_time_and_resolution_time_based_on_different_sla(self):
creation = datetime.datetime(2019, 3, 4, 12, 0)
# make issue with customer specific SLA
@@ -72,8 +75,67 @@
self.assertEqual(issue.agreement_fulfilled, 'Fulfilled')
-def make_issue(creation=None, customer=None, index=0):
+ def test_issue_metrics(self):
+ creation = datetime.datetime(2020, 3, 4, 4, 0)
+ issue = make_issue(creation, index=1)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 4, 15)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 5, 0)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 5, 5)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
+ issue.reload()
+ issue.status = 'Closed'
+ issue.save()
+
+ self.assertEqual(issue.avg_response_time, 600)
+ self.assertEqual(issue.resolution_time, 3900)
+ self.assertEqual(issue.user_resolution_time, 1200)
+
+ def test_hold_time_on_replied(self):
+ creation = datetime.datetime(2020, 3, 4, 4, 0)
+
+ issue = make_issue(creation, index=1)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 4, 15)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
+ issue.reload()
+ issue.status = 'Replied'
+ issue.save()
+
+ self.assertEqual(issue.on_hold_since, frappe.flags.current_time)
+
+ creation = datetime.datetime(2020, 3, 4, 5, 0)
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 0)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ issue.reload()
+ self.assertEqual(flt(issue.total_hold_time, 2), 2700)
+ self.assertEqual(issue.resolution_by, datetime.datetime(2020, 3, 4, 16, 45))
+
+ creation = datetime.datetime(2020, 3, 4, 5, 5)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
+ issue.reload()
+ issue.status = 'Closed'
+ issue.save()
+
+ issue.reload()
+ self.assertEqual(flt(issue.total_hold_time, 2), 2700)
+
+
+def make_issue(creation=None, customer=None, index=0):
issue = frappe.get_doc({
"doctype": "Issue",
"subject": "Service Level Agreement Issue {0}".format(index),
@@ -86,6 +148,7 @@
return issue
+
def create_customer(name, customer_group, territory):
create_customer_group(customer_group)
@@ -99,6 +162,7 @@
"territory": territory
}).insert(ignore_permissions=True)
+
def create_customer_group(customer_group):
if not frappe.db.exists("Customer Group", {"customer_group_name": customer_group}):
@@ -107,6 +171,7 @@
"customer_group_name": customer_group
}).insert(ignore_permissions=True)
+
def create_territory(territory):
if not frappe.db.exists("Territory", {"territory_name": territory}):
@@ -114,3 +179,21 @@
"doctype": "Territory",
"territory_name": territory,
}).insert(ignore_permissions=True)
+
+
+def create_communication(reference_name, sender, sent_or_received, creation):
+ issue = frappe.get_doc({
+ "doctype": "Communication",
+ "communication_type": "Communication",
+ "communication_medium": "Email",
+ "sent_or_received": sent_or_received,
+ "email_status": "Open",
+ "subject": "Test Issue",
+ "sender": sender,
+ "content": "Test",
+ "status": "Linked",
+ "reference_doctype": "Issue",
+ "creation": creation,
+ "reference_name": reference_name
+ })
+ issue.save()
diff --git a/erpnext/support/doctype/service_level/__init__.py b/erpnext/support/doctype/pause_sla_on_status/__init__.py
similarity index 100%
rename from erpnext/support/doctype/service_level/__init__.py
rename to erpnext/support/doctype/pause_sla_on_status/__init__.py
diff --git a/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json
new file mode 100644
index 0000000..5b03f25
--- /dev/null
+++ b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json
@@ -0,0 +1,33 @@
+{
+ "actions": [],
+ "creation": "2020-06-05 13:59:43.265588",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "status"
+ ],
+ "fields": [
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-05 15:15:29.986608",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "Pause SLA On Status",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.py b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.py
new file mode 100644
index 0000000..a3b547e
--- /dev/null
+++ b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class PauseSLAOnStatus(Document):
+ pass
diff --git a/erpnext/support/doctype/service_level/service_level.js b/erpnext/support/doctype/service_level/service_level.js
deleted file mode 100644
index abe254b..0000000
--- a/erpnext/support/doctype/service_level/service_level.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Service Level', {
-
-});
diff --git a/erpnext/support/doctype/service_level/service_level.json b/erpnext/support/doctype/service_level/service_level.json
deleted file mode 100644
index dced3aa..0000000
--- a/erpnext/support/doctype/service_level/service_level.json
+++ /dev/null
@@ -1,111 +0,0 @@
-{
- "autoname": "field:service_level",
- "creation": "2018-11-19 12:44:30.407502",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "service_level",
- "employee_group",
- "column_break_2",
- "holiday_list",
- "default_priority",
- "response_and_resoution_time",
- "priorities",
- "section_break_01",
- "support_and_resolution"
- ],
- "fields": [
- {
- "fieldname": "service_level",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Level",
- "reqd": 1,
- "unique": 1
- },
- {
- "fieldname": "column_break_2",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "holiday_list",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Holiday List (ignored during SLA calculation)",
- "options": "Holiday List",
- "reqd": 1
- },
- {
- "fieldname": "employee_group",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Employee Group",
- "options": "Employee Group"
- },
- {
- "fieldname": "response_and_resoution_time",
- "fieldtype": "Section Break",
- "label": "Response and Resoution Time"
- },
- {
- "fieldname": "section_break_01",
- "fieldtype": "Section Break",
- "label": "Support Hours"
- },
- {
- "fieldname": "support_and_resolution",
- "fieldtype": "Table",
- "label": "Support and Resolution",
- "options": "Service Day",
- "reqd": 1
- },
- {
- "fieldname": "priorities",
- "fieldtype": "Table",
- "label": "Priorities",
- "options": "Service Level Priority",
- "reqd": 1
- },
- {
- "fieldname": "default_priority",
- "fieldtype": "Link",
- "label": "Default Priority",
- "options": "Issue Priority",
- "read_only": 1
- }
- ],
- "modified": "2019-06-06 12:58:03.464056",
- "modified_by": "Administrator",
- "module": "Support",
- "name": "Service Level",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "All",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC"
-}
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level/service_level.py b/erpnext/support/doctype/service_level/service_level.py
deleted file mode 100644
index 89fa25c..0000000
--- a/erpnext/support/doctype/service_level/service_level.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from datetime import datetime
-from frappe.utils import get_weekdays
-
-class ServiceLevel(Document):
-
- def validate(self):
- self.check_priorities()
- self.check_support_and_resolution()
-
- def check_priorities(self):
- default_priority = []
- priorities = []
-
- for priority in self.priorities:
- # Check if response and resolution time is set for every priority
- if not (priority.response_time or priority.resolution_time):
- frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx))
-
- priorities.append(priority.priority)
-
- if priority.default_priority:
- default_priority.append(priority.default_priority)
-
- if priority.response_time_period == "Hour":
- response = priority.response_time * 0.0416667
- elif priority.response_time_period == "Day":
- response = priority.response_time
- elif priority.response_time_period == "Week":
- response = priority.response_time * 7
-
- if priority.resolution_time_period == "Hour":
- resolution = priority.resolution_time * 0.0416667
- elif priority.resolution_time_period == "Day":
- resolution = priority.resolution_time
- elif priority.resolution_time_period == "Week":
- resolution = priority.resolution_time * 7
-
- if response > resolution:
- frappe.throw(_("Response Time for {0} at index {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
-
- # Check if repeated priority
- if not len(set(priorities)) == len(priorities):
- repeated_priority = get_repeated(priorities)
- frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
-
- # Check if repeated default priority
- if not len(set(default_priority)) == len(default_priority):
- frappe.throw(_("Select only one Priority as Default."))
-
- # set default priority from priorities
- try:
- self.default_priority = next(d.priority for d in self.priorities if d.default_priority)
- except Exception:
- frappe.throw(_("Select a Default Priority."))
-
- def check_support_and_resolution(self):
- week = get_weekdays()
- support_days = []
-
- for support_and_resolution in self.support_and_resolution:
- # Check if start and end time is set for every support day
- if not (support_and_resolution.start_time or support_and_resolution.end_time):
- frappe.throw(_("Set Start Time and End Time for \
- Support Day {0} at index {1}.".format(support_and_resolution.workday, support_and_resolution.idx)))
-
- support_days.append(support_and_resolution.workday)
- support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
-
- if support_and_resolution.start_time >= support_and_resolution.end_time:
- frappe.throw(_("Start Time can't be greater than or equal to End Time \
- for {0}.".format(support_and_resolution.workday)))
-
- # Check for repeated workday
- if not len(set(support_days)) == len(support_days):
- repeated_days = get_repeated(support_days)
- frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
-
-def get_repeated(values):
- unique_list = []
- diff = []
- for value in values:
- if value not in unique_list:
- unique_list.append(str(value))
- else:
- if value not in diff:
- diff.append(str(value))
- return " ".join(diff)
diff --git a/erpnext/support/doctype/service_level/service_level_dashboard.py b/erpnext/support/doctype/service_level/service_level_dashboard.py
deleted file mode 100644
index 393095e..0000000
--- a/erpnext/support/doctype/service_level/service_level_dashboard.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from frappe import _
-
-def get_data():
- return {
- 'fieldname': 'service_level',
- 'transactions': [
- {
- 'label': _('Service Level Agreement'),
- 'items': ['Service Level Agreement']
- }
- ]
- }
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level/test_service_level.py b/erpnext/support/doctype/service_level/test_service_level.py
deleted file mode 100644
index 09577df..0000000
--- a/erpnext/support/doctype/service_level/test_service_level.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
-from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
-
-import frappe
-import unittest
-
-class TestServiceLevel(unittest.TestCase):
-
- def test_service_level(self):
- employee_group = make_employee_group()
- make_holiday_list()
- make_priorities()
-
- # Default Service Level
- test_make_service_level = create_service_level("__Test Service Level", "__Test Holiday List", employee_group, 4, 6)
- get_make_service_level = get_service_level("__Test Service Level")
-
- self.assertEqual(test_make_service_level.name, get_make_service_level.name)
- self.assertEqual(test_make_service_level.holiday_list, get_make_service_level.holiday_list)
- self.assertEqual(test_make_service_level.employee_group, get_make_service_level.employee_group)
-
- # Service Level
- test_make_service_level = create_service_level("_Test Service Level", "__Test Holiday List", employee_group, 2, 3)
- get_make_service_level = get_service_level("_Test Service Level")
-
- self.assertEqual(test_make_service_level.name, get_make_service_level.name)
- self.assertEqual(test_make_service_level.holiday_list, get_make_service_level.holiday_list)
- self.assertEqual(test_make_service_level.employee_group, get_make_service_level.employee_group)
-
-
-def create_service_level(service_level, holiday_list, employee_group, response_time, resolution_time):
- sl = frappe.get_doc({
- "doctype": "Service Level",
- "service_level": service_level,
- "holiday_list": holiday_list,
- "employee_group": employee_group,
- "priorities": [
- {
- "priority": "Low",
- "response_time": response_time,
- "response_time_period": "Hour",
- "resolution_time": resolution_time,
- "resolution_time_period": "Hour",
- },
- {
- "priority": "Medium",
- "response_time": response_time,
- "default_priority": 1,
- "response_time_period": "Hour",
- "resolution_time": resolution_time,
- "resolution_time_period": "Hour",
- },
- {
- "priority": "High",
- "response_time": response_time,
- "response_time_period": "Hour",
- "resolution_time": resolution_time,
- "resolution_time_period": "Hour",
- }
- ],
- "support_and_resolution": [
- {
- "workday": "Monday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Tuesday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Wednesday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Thursday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Friday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Saturday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Sunday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- }
- ]
- })
-
- sl_exists = frappe.db.exists("Service Level", {"service_level": service_level})
-
- if not sl_exists:
- sl.insert()
- return sl
- else:
- return frappe.get_doc("Service Level", {"service_level": service_level})
-
-def get_service_level(service_level):
- return frappe.get_doc("Service Level", service_level)
-
-def make_holiday_list():
- holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
- if not holiday_list:
- now = frappe.utils.now_datetime()
- holiday_list = frappe.get_doc({
- "doctype": "Holiday List",
- "holiday_list_name": "__Test Holiday List",
- "from_date": "2019-01-01",
- "to_date": "2019-12-31",
- "holidays": [
- {
- "description": "Test Holiday 1",
- "holiday_date": "2019-03-05"
- },
- {
- "description": "Test Holiday 2",
- "holiday_date": "2019-03-07"
- },
- {
- "description": "Test Holiday 3",
- "holiday_date": "2019-02-11"
- },
- ]
- }).insert()
-
-def create_service_level_for_sla():
- employee_group = make_employee_group()
- make_holiday_list()
- make_priorities()
-
- # Default Service Level
- create_service_level("__Test Service Level", "__Test Holiday List", employee_group, 4, 6)
-
- # Service Level
- create_service_level("_Test Service Level", "__Test Holiday List", employee_group, 2, 3)
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index 1d486f4..5346195 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -2,28 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on('Service Level Agreement', {
- service_level: function(frm) {
- frm.fields_dict.support_and_resolution.grid.remove_all();
- frappe.call({
- "method": "frappe.client.get",
- args: {
- doctype: "Service Level",
- name: frm.doc.service_level
- },
- callback: function(data){
- let count = Math.max(data.message.priorities.length, data.message.support_and_resolution.length);
- let i = 0;
- while (i < count){
- if (data.message.priorities[i]) {
- frm.add_child("priorities", data.message.priorities[i]);
- }
- if (data.message.support_and_resolution[i]) {
- frm.add_child("support_and_resolution", data.message.support_and_resolution[i]);
- }
- i++;
- }
- frm.refresh();
- }
+ setup: function(frm) {
+ let allow_statuses = [];
+ const exclude_statuses = ['Open', 'Closed', 'Resolved'];
+
+ frappe.model.with_doctype('Issue', () => {
+ let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
+ statuses = statuses.split('\n');
+ allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
+ frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses);
});
- },
-});
+ }
+});
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
index 9a83ca7..939c199 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "format:SLA-{service_level}-{####}",
"creation": "2018-12-26 21:08:15.448812",
"doctype": "DocType",
@@ -6,12 +7,13 @@
"engine": "InnoDB",
"field_order": [
"enable",
+ "section_break_2",
"service_level",
+ "default_priority",
"default_service_level_agreement",
- "holiday_list",
"column_break_2",
"employee_group",
- "default_priority",
+ "holiday_list",
"entity_section",
"entity_type",
"column_break_10",
@@ -21,6 +23,8 @@
"active",
"column_break_7",
"end_date",
+ "section_break_18",
+ "pause_sla_on",
"response_and_resolution_time_section",
"priorities",
"support_and_resolution_section_break",
@@ -28,42 +32,31 @@
],
"fields": [
{
- "default": "0",
- "depends_on": "eval: !doc.customer;",
- "fieldname": "default_service_level_agreement",
- "fieldtype": "Check",
- "label": "Default Service Level Agreement"
- },
- {
"fieldname": "service_level",
- "fieldtype": "Link",
+ "fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Service Level",
- "options": "Service Level",
"reqd": 1
},
{
- "fetch_from": "service_level.holiday_list",
"fieldname": "holiday_list",
"fieldtype": "Link",
"label": "Holiday List",
"options": "Holiday List",
- "read_only": 1
+ "reqd": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
- "fetch_from": "service_level.employee_group",
"fieldname": "employee_group",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee Group",
- "options": "Employee Group",
- "read_only": 1
+ "options": "Employee Group"
},
{
"fieldname": "agreement_details_section",
@@ -103,21 +96,15 @@
"fieldname": "support_and_resolution",
"fieldtype": "Table",
"label": "Support and Resolution",
- "options": "Service Day"
+ "options": "Service Day",
+ "reqd": 1
},
{
"fieldname": "priorities",
"fieldtype": "Table",
"label": "Priorities",
- "options": "Service Level Priority"
- },
- {
- "fetch_from": "service_level.default_priority",
- "fieldname": "default_priority",
- "fieldtype": "Link",
- "label": "Default Priority",
- "options": "Issue Priority",
- "read_only": 1
+ "options": "Service Level Priority",
+ "reqd": 1
},
{
"default": "1",
@@ -156,9 +143,38 @@
"fieldname": "enable",
"fieldtype": "Check",
"label": "Enable"
+ },
+ {
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "default_service_level_agreement",
+ "fieldtype": "Check",
+ "label": "Default Service Level Agreement"
+ },
+ {
+ "fieldname": "default_priority",
+ "fieldtype": "Link",
+ "label": "Default Priority",
+ "options": "Issue Priority",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_18",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "pause_sla_on",
+ "fieldtype": "Table",
+ "label": "Pause SLA On",
+ "options": "Pause SLA On Status"
}
],
- "modified": "2019-07-09 17:22:16.402939",
+ "links": [],
+ "modified": "2020-06-10 12:30:15.050785",
"modified_by": "Administrator",
"module": "Support",
"name": "Service Level Agreement",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index a399c58..c692315 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -6,11 +6,73 @@
import frappe
from frappe.model.document import Document
from frappe import _
-from frappe.utils import getdate
+from frappe.utils import getdate, get_weekdays
class ServiceLevelAgreement(Document):
def validate(self):
+ self.validate_doc()
+ self.check_priorities()
+ self.check_support_and_resolution()
+
+ def check_priorities(self):
+ default_priority = []
+ priorities = []
+
+ for priority in self.priorities:
+ # Check if response and resolution time is set for every priority
+ if not (priority.response_time or priority.resolution_time):
+ frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx))
+
+ priorities.append(priority.priority)
+
+ if priority.default_priority:
+ default_priority.append(priority.default_priority)
+
+ response = priority.response_time
+ resolution = priority.resolution_time
+
+ if response > resolution:
+ frappe.throw(_("Response Time for {0} at index {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
+
+ # Check if repeated priority
+ if not len(set(priorities)) == len(priorities):
+ repeated_priority = get_repeated(priorities)
+ frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
+
+ # Check if repeated default priority
+ if not len(set(default_priority)) == len(default_priority):
+ frappe.throw(_("Select only one Priority as Default."))
+
+ # set default priority from priorities
+ try:
+ self.default_priority = next(d.priority for d in self.priorities if d.default_priority)
+ except Exception:
+ frappe.throw(_("Select a Default Priority."))
+
+ def check_support_and_resolution(self):
+ week = get_weekdays()
+ support_days = []
+
+ for support_and_resolution in self.support_and_resolution:
+ # Check if start and end time is set for every support day
+ if not (support_and_resolution.start_time or support_and_resolution.end_time):
+ frappe.throw(_("Set Start Time and End Time for \
+ Support Day {0} at index {1}.".format(support_and_resolution.workday, support_and_resolution.idx)))
+
+ support_days.append(support_and_resolution.workday)
+ support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
+
+ if support_and_resolution.start_time >= support_and_resolution.end_time:
+ frappe.throw(_("Start Time can't be greater than or equal to End Time \
+ for {0}.".format(support_and_resolution.workday)))
+
+ # Check for repeated workday
+ if not len(set(support_days)) == len(support_days):
+ repeated_days = get_repeated(support_days)
+ frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
+
+ def validate_doc(self):
if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
frappe.throw(_("Service Level Agreement tracking is not enabled."))
@@ -35,9 +97,7 @@
return frappe._dict({
"priority": priority.priority,
"response_time": priority.response_time,
- "response_time_period": priority.response_time_period,
- "resolution_time": priority.resolution_time,
- "resolution_time_period": priority.resolution_time_period
+ "resolution_time": priority.resolution_time
})
def check_agreement_status():
@@ -110,4 +170,15 @@
return {
"priority": [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])],
"service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters)]
- }
\ No newline at end of file
+ }
+
+def get_repeated(values):
+ unique_list = []
+ diff = []
+ for value in values:
+ if value not in unique_list:
+ unique_list.append(str(value))
+ else:
+ if value not in diff:
+ diff.append(str(value))
+ return " ".join(diff)
diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
index 4a741ea..07ef368 100644
--- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
@@ -5,19 +5,20 @@
import frappe
import unittest
-from erpnext.support.doctype.service_level.test_service_level import create_service_level_for_sla
+from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
+from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
class TestServiceLevelAgreement(unittest.TestCase):
-
- def test_service_level_agreement(self):
+ def setUp(self):
+ frappe.db.sql("delete from `tabService Level Agreement`")
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
- create_service_level_for_sla()
-
+ def test_service_level_agreement(self):
# Default Service Level Agreement
create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
- service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type=None, entity=None, response_time=4, resolution_time=6)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+
get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
@@ -28,8 +29,8 @@
# Service Level Agreement for Customer
customer = create_customer()
create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer", entity=customer, response_time=2, resolution_time=3)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type="Customer", entity=customer, response_time=7200, resolution_time=10800)
get_customer_service_level_agreement = get_service_level_agreement(entity_type="Customer", entity=customer)
self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
@@ -40,8 +41,8 @@
# Service Level Agreement for Customer Group
customer_group = create_customer_group()
create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer Group", entity=customer_group, response_time=2, resolution_time=3)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type="Customer Group", entity=customer_group, response_time=7200, resolution_time=10800)
get_customer_group_service_level_agreement = get_service_level_agreement(entity_type="Customer Group", entity=customer_group)
self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
@@ -52,8 +53,8 @@
# Service Level Agreement for Territory
territory = create_territory()
create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Territory", entity=territory, response_time=2, resolution_time=3)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type="Territory", entity=territory, response_time=7200, resolution_time=10800)
get_territory_service_level_agreement = get_service_level_agreement(entity_type="Territory", entity=territory)
self.assertEqual(create_territory_service_level_agreement.name, get_territory_service_level_agreement.name)
@@ -71,14 +72,19 @@
service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
return service_level_agreement
-def create_service_level_agreement(default_service_level_agreement, service_level, holiday_list, employee_group,
+def create_service_level_agreement(default_service_level_agreement, holiday_list, employee_group,
response_time, entity_type, entity, resolution_time):
+ employee_group = make_employee_group()
+ make_holiday_list()
+ make_priorities()
+
service_level_agreement = frappe.get_doc({
"doctype": "Service Level Agreement",
"enable": 1,
+ "service_level": "__Test Service Level",
"default_service_level_agreement": default_service_level_agreement,
- "service_level": service_level,
+ "default_priority": "Medium",
"holiday_list": holiday_list,
"employee_group": employee_group,
"entity_type": entity_type,
@@ -109,6 +115,11 @@
"resolution_time_period": "Hour",
}
],
+ "pause_sla_on": [
+ {
+ "status": "Replied"
+ }
+ ],
"support_and_resolution": [
{
"workday": "Monday",
@@ -167,6 +178,7 @@
else:
return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
+
def create_customer():
customer = frappe.get_doc({
"doctype": "Customer",
@@ -206,23 +218,41 @@
return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})
def create_service_level_agreements_for_issues():
- create_service_level_for_sla()
-
- create_service_level_agreement(default_service_level_agreement=1,
- service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type=None, entity=None, response_time=4, resolution_time=6)
+ create_service_level_agreement(default_service_level_agreement=1, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
create_customer()
- create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer", entity="_Test Customer", response_time=2, resolution_time=3)
+ create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
create_customer_group()
- create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=2, resolution_time=3)
+ create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
create_territory()
- create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Territory", entity="_Test SLA Territory", response_time=2, resolution_time=3)
+ create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
+
+def make_holiday_list():
+ holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
+ if not holiday_list:
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "holiday_list_name": "__Test Holiday List",
+ "from_date": "2019-01-01",
+ "to_date": "2019-12-31",
+ "holidays": [
+ {
+ "description": "Test Holiday 1",
+ "holiday_date": "2019-03-05"
+ },
+ {
+ "description": "Test Holiday 2",
+ "holiday_date": "2019-03-07"
+ },
+ {
+ "description": "Test Holiday 3",
+ "holiday_date": "2019-02-11"
+ },
+ ]
+ }).insert()
diff --git a/erpnext/support/doctype/service_level_priority/service_level_priority.json b/erpnext/support/doctype/service_level_priority/service_level_priority.json
index cd87a1c..65d5169 100644
--- a/erpnext/support/doctype/service_level_priority/service_level_priority.json
+++ b/erpnext/support/doctype/service_level_priority/service_level_priority.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2019-05-04 05:54:03.658991",
"doctype": "DocType",
"editable_grid": 1,
@@ -9,10 +10,8 @@
"default_priority",
"sb_00",
"response_time",
- "response_time_period",
"cb_00",
- "resolution_time",
- "resolution_time_period"
+ "resolution_time"
],
"fields": [
{
@@ -28,16 +27,11 @@
"fieldtype": "Section Break"
},
{
- "columns": 1,
- "fieldname": "response_time",
- "fieldtype": "Int",
- "in_list_view": 1,
- "label": "Response Time"
- },
- {
- "columns": 1,
+ "columns": 2,
"fieldname": "resolution_time",
- "fieldtype": "Int",
+ "fieldtype": "Duration",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "Resolution Time"
},
@@ -46,35 +40,30 @@
"fieldtype": "Column Break"
},
{
- "columns": 2,
- "fieldname": "response_time_period",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Response Time Period",
- "options": "Hour\nDay\nWeek"
- },
- {
- "columns": 2,
- "fieldname": "resolution_time_period",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Resolution Time Period",
- "options": "Hour\nDay\nWeek"
- },
- {
"fieldname": "cb_01",
"fieldtype": "Column Break"
},
{
+ "columns": 1,
"default": "0",
"fieldname": "default_priority",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Default Priority"
+ },
+ {
+ "columns": 2,
+ "fieldname": "response_time",
+ "fieldtype": "Duration",
+ "hide_days": 1,
+ "hide_seconds": 1,
+ "in_list_view": 1,
+ "label": "First Response Time"
}
],
"istable": 1,
- "modified": "2019-05-21 06:54:42.674377",
+ "links": [],
+ "modified": "2020-06-10 12:45:47.545915",
"modified_by": "Administrator",
"module": "Support",
"name": "Service Level Priority",
@@ -84,4 +73,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/support/doctype/support_settings/support_settings.js b/erpnext/support/doctype/support_settings/support_settings.js
index 1d1069d..78adca8 100644
--- a/erpnext/support/doctype/support_settings/support_settings.js
+++ b/erpnext/support/doctype/support_settings/support_settings.js
@@ -3,6 +3,6 @@
frappe.ui.form.on('Support Settings', {
refresh: function(frm) {
-
+ //
}
});
diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json
index be9e064..5d3d3ac 100644
--- a/erpnext/support/doctype/support_settings/support_settings.json
+++ b/erpnext/support/doctype/support_settings/support_settings.json
@@ -1,4 +1,5 @@
{
+ "actions": "",
"creation": "2017-02-17 13:07:35.686409",
"doctype": "DocType",
"editable_grid": 1,
@@ -21,6 +22,10 @@
"post_description_key",
"post_route_key",
"post_route_string",
+ "greetings_section_section",
+ "greeting_title",
+ "column_break_19",
+ "greeting_subtitle",
"search_apis_sb",
"search_apis"
],
@@ -122,13 +127,44 @@
},
{
"default": "0",
+ "depends_on": "eval:doc.track_service_level_agreement;",
"fieldname": "allow_resetting_service_level_agreement",
"fieldtype": "Check",
"label": "Allow Resetting Service Level Agreement"
+ },
+ {
+ "default": "We're here to help",
+ "fieldname": "greeting_title",
+ "fieldtype": "Data",
+ "label": "Greeting Title",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "Browse help topics",
+ "fieldname": "greeting_subtitle",
+ "fieldtype": "Data",
+ "label": "Greeting Subtitle",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "greetings_section_section",
+ "fieldtype": "Section Break",
+ "label": "Greetings Section",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"issingle": 1,
- "modified": "2019-07-10 22:52:39.663873",
+ "links": [],
+ "modified": "2020-06-11 13:08:38.473616",
"modified_by": "Administrator",
"module": "Support",
"name": "Support Settings",
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
index 5fd9011..163c955 100644
--- a/erpnext/templates/generators/item/item_configure.js
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -193,14 +193,17 @@
filtered_items_count === 1 ?
filtered_items[0] : '';
+ // Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock.
+ const in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock;
+ const add_to_cart = `<a href data-action="btn_add_to_cart" data-item-code="${one_item}">${__('Add to cart')}</a>`;
+ const product_action = in_stock ? add_to_cart : `<a style="color:#74808b;">${__('Not in Stock')}</a>`;
+
const item_add_to_cart = one_item ? `
<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
<div>
<div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
</div>
- <a href data-action="btn_add_to_cart" data-item-code="${one_item}">
- ${__('Add to cart')}
- </a>
+ ${product_action}
</div>
`: '';
diff --git a/erpnext/templates/generators/student_admission.html b/erpnext/templates/generators/student_admission.html
index ae70df8..8b15344 100644
--- a/erpnext/templates/generators/student_admission.html
+++ b/erpnext/templates/generators/student_admission.html
@@ -14,12 +14,12 @@
{%- if introduction -%}
<div>{{ introduction }}</div>
-{% endif %}
+{% endif %}
-{%- if application_form_route -%}
+{%- if doc.enable_admission_application -%}
<p>
<a class='btn btn-primary'
- href='/{{ doc.application_form_route }}'>
+ href='/student-applicant'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
index c91723e..646210e 100644
--- a/erpnext/templates/includes/cart/address_card.html
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -3,7 +3,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
</div>
<div class="card-body">
- <h5 class="card-title">{{ address.name }}</h5>
+ <h5 class="card-title">{{ address.title }}</h5>
<p class="card-text text-muted">
{{ address.display }}
</p>
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 60de3af..aa25c88 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -109,7 +109,7 @@
reqd: 1
},
{
- label: __('Pin Code'),
+ label: __('Postal Code'),
fieldname: 'pincode',
fieldtype: 'Data'
},
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index cf7661e..4274ba1 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -10,9 +10,17 @@
'Agriculture': '/agriculture',
'Hospitality': ''
} %}
+
{% set link = '' %}
+{% set label = domains[0].domain %}
{% if domains %}
- {% set link = links[domains[0].domain] %}
+ {% set link = links[label] %}
{% endif %}
-<a href="https://erpnext.com{{ link }}?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + domains[0].domain + ' Companies') if domains else '' }}</a>
+{% if label == "Services" %}
+ {% set label = "Service" %}
+{% endif %}
+
+
+
+<a href="https://erpnext.com{{ link }}?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + label + ' Companies') if domains else '' }}</a>
diff --git a/erpnext/support/doctype/service_level/__init__.py b/erpnext/www/support/__init__.py
similarity index 100%
copy from erpnext/support/doctype/service_level/__init__.py
copy to erpnext/www/support/__init__.py
diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html
new file mode 100644
index 0000000..93da503
--- /dev/null
+++ b/erpnext/www/support/index.html
@@ -0,0 +1,58 @@
+{% extends "templates/web.html" %}
+
+{% block content %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <div class="hero-content">
+ <h1 class="hero-title">{{ greeting_title or _("We're here to help!") }}</h1>
+ {% if greeting_subtitle %}
+ <p class="hero-subtitle">{{ greeting_subtitle }}</p>
+ {% endif %}
+ </div>
+ </div>
+</section>
+
+{% if favorite_article_list %}
+<section class="section section-padding-top section-padding-bottom bg-light">
+ <div class='container'>
+ <h2>{{ _("Frequently Read Articles") }}</h2>
+ <div class="row">
+ {% for favorite_article in favorite_article_list %}
+ <div class="mt-4 col-12 col-sm-6 col-lg-4">
+ <div class="card card-md h-100 kb-card">
+ <div class="card-body">
+ <h6 class="card-subtitle mb-2 text-uppercase small text-muted">
+ {{ favorite_article['category'] }}</h6>
+ <h3 class="card-title">{{ favorite_article['title'] }}</h3>
+ <p class="card-text">{{ favorite_article['description'] }}</p>
+ </div>
+ <a href="{{ favorite_article['route'] }}" class="stretched-link"></a>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% if help_article_list %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <h2>{{ _("Help Articles") }}</h2>
+ <div class="row">
+ {% for item in help_article_list %}
+ <div class="mt-5 col-12 col-sm-6 col-lg-4">
+ <h5>{{ item['category'].name }}</h5>
+ <div>
+ {% for article in item['articles'] %}
+ <a href="{{ article.route }}" class="mt-2 d-block">{{ article.title }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py
new file mode 100644
index 0000000..5d26743
--- /dev/null
+++ b/erpnext/www/support/index.py
@@ -0,0 +1,74 @@
+from __future__ import unicode_literals
+import frappe
+
+def get_context(context):
+ context.no_cache = 1
+ context.align_greeting = ''
+ setting = frappe.get_doc("Support Settings")
+
+ context.greeting_title = setting.greeting_title
+ context.greeting_subtitle = setting.greeting_subtitle
+
+ # Support content
+ favorite_articles = get_favorite_articles_by_page_view()
+ if len(favorite_articles) < 6:
+ name_list = []
+ if favorite_articles:
+ for article in favorite_articles:
+ name_list.append(article.name)
+ for record in (frappe.get_all("Help Article",
+ fields=["title", "content", "route", "category"],
+ filters={"name": ['not in', tuple(name_list)], "published": 1},
+ order_by="creation desc", limit=(6-len(favorite_articles)))):
+ favorite_articles.append(record)
+
+ context.favorite_article_list = get_favorite_articles(favorite_articles)
+ context.help_article_list = get_help_article_list()
+
+def get_favorite_articles_by_page_view():
+ return frappe.db.sql(
+ """
+ SELECT
+ t1.name as name,
+ t1.title as title,
+ t1.content as content,
+ t1.route as route,
+ t1.category as category,
+ count(t1.route) as count
+ FROM `tabHelp Article` AS t1
+ INNER JOIN
+ `tabWeb Page View` AS t2
+ ON t1.route = t2.path
+ WHERE t1.published = 1
+ GROUP BY route
+ ORDER BY count DESC
+ LIMIT 6;
+ """, as_dict=True)
+
+def get_favorite_articles(favorite_articles):
+ favorite_article_list=[]
+ for article in favorite_articles:
+ description = frappe.utils.strip_html(article.content)
+ if len(description) > 120:
+ description = description[:120] + '...'
+ favorite_article_dict = {
+ 'title': article.title,
+ 'description': description,
+ 'route': article.route,
+ 'category': article.category,
+ }
+ favorite_article_list.append(favorite_article_dict)
+ return favorite_article_list
+
+def get_help_article_list():
+ help_article_list=[]
+ category_list = frappe.get_all("Help Category", fields="name")
+ for category in category_list:
+ help_articles = frappe.get_all("Help Article", fields="*", filters={"category": category.name, "published": 1}, order_by="modified desc", limit=5)
+ if help_articles:
+ help_aricles_per_caetgory = {
+ 'category': category,
+ 'articles': help_articles,
+ }
+ help_article_list.append(help_aricles_per_caetgory)
+ return help_article_list
\ No newline at end of file