Merge pull request #22227 from frappe/opportunity-dashboard
fix: Links in opportunity dashboard
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
new file mode 100644
index 0000000..d36b115
--- /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.org/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/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/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..c9bbbab 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,6 +122,24 @@
"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",
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/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/distributed_cost_center/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index d2245d6..15e51bb 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/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 57dc179..8b5d4d1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1450,11 +1450,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/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/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
index 80bccaf..5001ad9 100644
--- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
+++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
@@ -93,7 +93,7 @@
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
- ifnull(sum(case when ds.schedule_date < %(from_date)s then
+ ifnull(sum(case when ds.schedule_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
ds.depreciation_amount
else
0
@@ -115,9 +115,7 @@
group by a.asset_category
union
SELECT a.asset_category,
- ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
- and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s)
- then
+ ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
0
else
a.opening_accumulated_depreciation
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/assets/doctype/asset_maintenance/asset_maintenance.json b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
index efb2de3..c0c2566 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
@@ -1,559 +1,140 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:asset_name",
- "beta": 0,
- "creation": "2017-10-19 16:50:22.879545",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "field:asset_name",
+ "creation": "2017-10-19 16:50:22.879545",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "asset_name",
+ "asset_category",
+ "company",
+ "column_break_3",
+ "item_code",
+ "item_name",
+ "section_break_6",
+ "maintenance_team",
+ "column_break_9",
+ "maintenance_manager",
+ "maintenance_manager_name",
+ "section_break_8",
+ "asset_maintenance_tasks"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_name",
- "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": "Asset Name",
- "length": 0,
- "no_copy": 0,
- "options": "Asset",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "asset_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Asset Name",
+ "options": "Asset",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "asset_name.asset_category",
- "fieldname": "asset_category",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Asset Category",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "asset_category",
+ "fieldtype": "Read Only",
+ "label": "Asset Category"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "asset_name.item_code",
- "fieldname": "item_code",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_code",
+ "fieldtype": "Read Only",
+ "label": "Item Code"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "asset_name.item_name",
- "fieldname": "item_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Read Only",
+ "label": "Item Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "select_serial_no",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Select Serial No",
- "length": 0,
- "no_copy": 0,
- "options": "Serial No",
- "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": "section_break_6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Serial No",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_team",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Maintenance Team",
+ "options": "Asset Maintenance Team",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "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_9",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintenance_team",
- "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": "Maintenance Team",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance Team",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "maintenance_team.maintenance_manager",
- "fieldname": "maintenance_manager",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Manager",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_manager",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Maintenance Manager",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "maintenance_team.maintenance_manager_name",
- "fieldname": "maintenance_manager_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Manager Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_manager_name",
+ "fieldtype": "Read Only",
+ "label": "Maintenance Manager Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_8",
- "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": "Tasks",
- "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": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Tasks"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_maintenance_tasks",
- "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": "Maintenance Tasks",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance Task",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "asset_maintenance_tasks",
+ "fieldtype": "Table",
+ "label": "Maintenance Tasks",
+ "options": "Asset Maintenance Task",
+ "reqd": 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": 0,
- "max_attachments": 0,
- "modified": "2018-05-22 17:20:54.711885",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Maintenance",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2020-05-28 20:28:32.993823",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Maintenance",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Quality Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Quality Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ 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 3f04611..d6adde6 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -38,7 +38,7 @@
@frappe.whitelist()
def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, next_due_date):
- team_member = frappe.get_doc('User', assign_to_member).email
+ team_member = frappe.db.get_value('User', assign_to_member, "email")
args = {
'doctype' : 'Asset Maintenance',
'assign_to' : team_member,
@@ -77,7 +77,7 @@
def update_maintenance_log(asset_maintenance, item_code, item_name, task):
asset_maintenance_log = frappe.get_value("Asset Maintenance Log", {"asset_maintenance": asset_maintenance,
- "task": task.maintenance_task, "maintenance_status": ('in',['Planned','Overdue'])})
+ "task": task.name, "maintenance_status": ('in',['Planned','Overdue'])})
if not asset_maintenance_log:
asset_maintenance_log = frappe.get_doc({
@@ -86,7 +86,7 @@
"asset_name": asset_maintenance,
"item_code": item_code,
"item_name": item_name,
- "task": task.maintenance_task,
+ "task": task.name,
"has_certificate": task.certificate_required,
"description": task.description,
"assign_to_name": task.assign_to_name,
diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
index 42aecdf..7395bec 100644
--- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
+++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
@@ -1,819 +1,210 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2017-10-23 16:58:44.424309",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2017-10-23 16:58:44.424309",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "asset_maintenance",
+ "naming_series",
+ "asset_name",
+ "column_break_2",
+ "item_code",
+ "item_name",
+ "section_break_5",
+ "task",
+ "task_name",
+ "maintenance_type",
+ "periodicity",
+ "assign_to_name",
+ "column_break_6",
+ "due_date",
+ "completion_date",
+ "maintenance_status",
+ "section_break_12",
+ "has_certificate",
+ "certificate_attachement",
+ "section_break_6",
+ "description",
+ "column_break_9",
+ "actions_performed",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_maintenance",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Asset Maintenance",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance",
- "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": "asset_maintenance",
+ "fieldtype": "Link",
+ "label": "Asset Maintenance",
+ "options": "Asset Maintenance"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Series",
- "length": 0,
- "no_copy": 0,
- "options": "ACC-AML-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "options": "ACC-AML-.YYYY.-",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "asset_maintenance.asset_name",
- "fieldname": "asset_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Asset Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "asset_maintenance.asset_name",
+ "fieldname": "asset_name",
+ "fieldtype": "Read Only",
+ "label": "Asset Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "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": "column_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "asset_maintenance.item_code",
- "fieldname": "item_code",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "asset_maintenance.item_code",
+ "fieldname": "item_code",
+ "fieldtype": "Read Only",
+ "label": "Item Code"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "asset_maintenance.item_name",
- "fieldname": "item_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "asset_maintenance.item_name",
+ "fieldname": "item_name",
+ "fieldtype": "Read Only",
+ "label": "Item Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 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,
- "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": "section_break_5",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "task",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Task",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance Task",
- "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": "task",
+ "fieldtype": "Link",
+ "label": "Task",
+ "options": "Asset Maintenance Task"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.maintenance_type",
- "fieldname": "maintenance_type",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Type",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.maintenance_type",
+ "fieldname": "maintenance_type",
+ "fieldtype": "Read Only",
+ "label": "Maintenance Type"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.periodicity",
- "fieldname": "periodicity",
- "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": "Periodicity",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.periodicity",
+ "fieldname": "periodicity",
+ "fieldtype": "Data",
+ "label": "Periodicity",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.assign_to_name",
- "fieldname": "assign_to_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Assign To",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.assign_to_name",
+ "fieldname": "assign_to_name",
+ "fieldtype": "Read Only",
+ "label": "Assign To"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_6",
- "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": "column_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.next_due_date",
- "fieldname": "due_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Due Date",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.next_due_date",
+ "fieldname": "due_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Due Date",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "completion_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Completion Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "completion_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Completion Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintenance_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Maintenance Status",
- "length": 0,
- "no_copy": 0,
- "options": "Planned\nCompleted\nCancelled\nOverdue",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Maintenance Status",
+ "options": "Planned\nCompleted\nCancelled\nOverdue",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_12",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_12",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.certificate_required",
- "fieldname": "has_certificate",
- "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": "Has Certificate ",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fetch_from": "task.certificate_required",
+ "fieldname": "has_certificate",
+ "fieldtype": "Check",
+ "label": "Has Certificate "
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.has_certificate",
- "fieldname": "certificate_attachement",
- "fieldtype": "Attach",
- "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": "Certificate",
- "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
- },
+ "depends_on": "eval:doc.has_certificate",
+ "fieldname": "certificate_attachement",
+ "fieldtype": "Attach",
+ "label": "Certificate"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "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": "section_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.description",
- "fieldname": "description",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.description",
+ "fieldname": "description",
+ "fieldtype": "Read Only",
+ "label": "Description",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_9",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "actions_performed",
- "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": "Actions performed",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "fieldname": "actions_performed",
+ "fieldtype": "Text Editor",
+ "label": "Actions performed"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Asset Maintenance Log",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Asset Maintenance Log",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fetch_from": "task.maintenance_task",
+ "fieldname": "task_name",
+ "fieldtype": "Data",
+ "in_preview": 1,
+ "label": "Task Name",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-21 14:44:51.457835",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Maintenance Log",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-05-28 20:51:48.238397",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Maintenance Log",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "",
- "track_changes": 1,
- "track_seen": 1,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 1
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py
index d98cc31..2a5666d 100644
--- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py
+++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py
@@ -7,5 +7,4 @@
from frappe.model.document import Document
class AssetMaintenanceTask(Document):
- def autoname(self):
- self.name = self.maintenance_task
+ pass
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/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 29f8dd5..50b17ab 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 exisiting 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/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json
index c43fcae..44d672a 100644
--- a/erpnext/crm/module_onboarding/crm/crm.json
+++ b/erpnext/crm/module_onboarding/crm/crm.json
@@ -16,7 +16,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
"idx": 0,
"is_complete": 0,
- "modified": "2020-05-28 13:59:33.693420",
+ "modified": "2020-05-28 21:07:41.278784",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
@@ -35,8 +35,8 @@
"step": "Create and Send Quotation"
}
],
- "subtitle": "Lead, Opportunity, Customer and more",
- "success_message": "CRM Module is all setup!",
- "title": "Let's Set Up Your CRM",
+ "subtitle": "Lead, Opportunity, Customer and more.",
+ "success_message": "CRM Module is all Set Up!",
+ "title": "Let's Set Up Your CRM.",
"user_can_dismiss": 1
}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
index 9201d77..78f7e4d 100644
--- a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
+++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
@@ -8,7 +8,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-27 11:30:28.237263",
+ "modified": "2020-05-28 21:07:11.461172",
"modified_by": "Administrator",
"name": "Create and Send Quotation",
"owner": "Administrator",
diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json
index 6ff0bd6..c45e8b0 100644
--- a/erpnext/crm/onboarding_step/create_lead/create_lead.json
+++ b/erpnext/crm/onboarding_step/create_lead/create_lead.json
@@ -8,7 +8,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-27 11:30:59.493720",
+ "modified": "2020-05-28 21:07:01.373403",
"modified_by": "Administrator",
"name": "Create Lead",
"owner": "Administrator",
diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
index 545a756..fa26921a 100644
--- a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
+++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
@@ -8,7 +8,7 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
- "modified": "2020-05-27 11:28:07.452857",
+ "modified": "2020-05-14 17:28:16.448676",
"modified_by": "Administrator",
"name": "Introduction to CRM",
"owner": "Administrator",
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/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/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index a45c6b1..c3371ed 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -141,12 +141,12 @@
result += new_bank_transaction(transaction)
if result:
- end_date = frappe.db.get_value('Bank Transaction', result.pop(), 'date')
+ last_transaction_date = frappe.db.get_value('Bank Transaction', result.pop(), 'date')
frappe.logger().info("Plaid added {} new Bank Transactions from '{}' between {} and {}".format(
len(result), bank_account, start_date, end_date))
- frappe.db.set_value("Bank Account", bank_account, "last_integration_date", end_date)
+ frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
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/hooks.py b/erpnext/hooks.py
index ab161aa..9d7cdc2 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -320,8 +320,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/dashboard_fixtures.py b/erpnext/hr/dashboard_fixtures.py
index 004477c..6e042ac 100644
--- a/erpnext/hr/dashboard_fixtures.py
+++ b/erpnext/hr/dashboard_fixtures.py
@@ -143,7 +143,7 @@
number_cards.append(
get_number_cards_doc("Employee", "Employees Left (Last year)", filters_json = json.dumps([
- ["Employee", "modified", "Previous", "1 year"],
+ ["Employee", "relieving_date", "Previous", "1 year"],
["Employee", "status", "=", "Left"]
])
)
diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json
index 7ac000b..1c24444 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-10 12:41:41.695669",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
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/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 db39eff..a49dfcf 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/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/onboarding_step/create_holiday_list/create_holiday_list.json b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
index 208e394..32472b4 100644
--- a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
+++ b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
@@ -1,6 +1,6 @@
{
"action": "Create Entry",
- "creation": "2020-05-27 11:47:34.700174",
+ "creation": "2020-05-28 11:47:34.700174",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@@ -14,6 +14,6 @@
"owner": "Administrator",
"reference_document": "Holiday List",
"show_full_form": 1,
- "title": "Create Holiday list",
+ "title": "Create Holiday List",
"validate_action": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
index a8c96fb..0a1d0ba 100644
--- a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
+++ b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
@@ -1,6 +1,6 @@
{
"action": "Update Settings",
- "creation": "2020-05-14 13:13:52.427711",
+ "creation": "2020-05-28 13:13:52.427711",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
@@ -14,6 +14,6 @@
"owner": "Administrator",
"reference_document": "HR Settings",
"show_full_form": 0,
- "title": "HR settings",
+ "title": "HR Settings",
"validate_action": 0
}
\ No newline at end of file
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/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 1bdac57..2543eec 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -112,8 +112,15 @@
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,
+ "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/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 b0421f4..a0707b7 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
@@ -694,4 +696,4 @@
execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
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
\ No newline at end of file
+erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
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..f1ffaf9
--- /dev/null
+++ b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
@@ -0,0 +1,9 @@
+# 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 '''
+ frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
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/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/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/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/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..3085a31 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -615,8 +615,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
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..ac3bc20 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):
@@ -378,10 +381,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/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..e78d0ff 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
@@ -8,180 +8,180 @@
from frappe.utils import cint, cstr
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. """
+ 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')
+ 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 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
- return customers_in
+ return customers_in
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index d04c8c2..7096c17 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -337,21 +337,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/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/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: