[Resolved] merge conflicts
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index d234e1e..90e5821 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '10.1.46'
+__version__ = '10.1.47'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.js b/erpnext/hr/doctype/employee_loan/employee_loan.js
new file mode 100644
index 0000000..e089e29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_loan/employee_loan.js
@@ -0,0 +1,121 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Employee Loan', {
+ onload: function (frm) {
+ frm.set_query("employee_loan_application", function () {
+ return {
+ "filters": {
+ "employee": frm.doc.employee,
+ "docstatus": 1,
+ "status": "Approved"
+ }
+ };
+ });
+
+ frm.set_query("interest_income_account", function () {
+ return {
+ "filters": {
+ "company": frm.doc.company,
+ "root_type": "Income",
+ "is_group": 0
+ }
+ };
+ });
+
+ frm.set_query("employee", function() {
+ return {
+ "filters": {
+ "company": frm.doc.company,
+ }
+ };
+ });
+
+ $.each(["payment_account", "employee_loan_account"], function (i, field) {
+ frm.set_query(field, function () {
+ return {
+ "filters": {
+ "company": frm.doc.company,
+ "root_type": "Asset",
+ "is_group": 0
+ }
+ };
+ });
+ })
+ },
+
+ refresh: function (frm) {
+ if (frm.doc.docstatus == 1 && (frm.doc.status == "Sanctioned" || frm.doc.status == "Partially Disbursed")) {
+ frm.add_custom_button(__('Make Disbursement Entry'), function () {
+ frm.trigger("make_jv");
+ })
+ }
+ frm.trigger("toggle_fields");
+ },
+
+ make_jv: function (frm) {
+ frappe.call({
+ args: {
+ "employee_loan": frm.doc.name,
+ "company": frm.doc.company,
+ "employee_loan_account": frm.doc.employee_loan_account,
+ "employee": frm.doc.employee,
+ "loan_amount": frm.doc.loan_amount,
+ "payment_account": frm.doc.payment_account
+ },
+ method: "erpnext.hr.doctype.employee_loan.employee_loan.make_jv_entry",
+ callback: function (r) {
+ if (r.message)
+ var doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ })
+ },
+
+ mode_of_payment: function (frm) {
+ if (frm.doc.mode_of_payment && frm.doc.company) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
+ args: {
+ "mode_of_payment": frm.doc.mode_of_payment,
+ "company": frm.doc.company
+ },
+ callback: function (r, rt) {
+ if (r.message) {
+ frm.set_value("payment_account", r.message.account);
+ }
+ }
+ });
+ }
+ },
+
+ employee_loan_application: function (frm) {
+ if(frm.doc.employee_loan_application){
+ return frappe.call({
+ method: "erpnext.hr.doctype.employee_loan.employee_loan.get_employee_loan_application",
+ args: {
+ "employee_loan_application": frm.doc.employee_loan_application
+ },
+ callback: function (r) {
+ if (!r.exc && r.message) {
+ frm.set_value("loan_type", r.message.loan_type);
+ frm.set_value("loan_amount", r.message.loan_amount);
+ frm.set_value("repayment_method", r.message.repayment_method);
+ frm.set_value("monthly_repayment_amount", r.message.repayment_amount);
+ frm.set_value("repayment_periods", r.message.repayment_periods);
+ frm.set_value("rate_of_interest", r.message.rate_of_interest);
+ }
+ }
+ });
+ }
+ },
+
+ repayment_method: function (frm) {
+ frm.trigger("toggle_fields")
+ },
+
+ toggle_fields: function (frm) {
+ frm.toggle_enable("monthly_repayment_amount", frm.doc.repayment_method == "Repay Fixed Amount per Period")
+ frm.toggle_enable("repayment_periods", frm.doc.repayment_method == "Repay Over Number of Periods")
+ }
+});
diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py
new file mode 100644
index 0000000..b6c6502
--- /dev/null
+++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, math
+from frappe import _
+from frappe.utils import flt, rounded
+from frappe.model.mapper import get_mapped_doc
+from frappe.model.document import Document
+
+from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method
+
+class EmployeeLoanApplication(Document):
+ def validate(self):
+ check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
+ self.validate_loan_amount()
+ self.get_repayment_details()
+
+ def validate_loan_amount(self):
+ maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount')
+ if maximum_loan_limit and self.loan_amount > maximum_loan_limit:
+ frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit))
+
+ def get_repayment_details(self):
+ if self.repayment_method == "Repay Over Number of Periods":
+ self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
+
+ if self.repayment_method == "Repay Fixed Amount per Period":
+ monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
+ if monthly_interest_rate:
+ monthly_interest_amount = self.loan_amount * monthly_interest_rate
+ if monthly_interest_amount >= self.repayment_amount:
+ frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}").
+ format(self.repayment_amount, monthly_interest_amount))
+
+ self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
+ math.log(self.repayment_amount - (monthly_interest_amount))) /
+ (math.log(1 + monthly_interest_rate)))
+ else:
+ self.repayment_periods = self.loan_amount / self.repayment_amount
+
+ self.calculate_payable_amount()
+
+ def calculate_payable_amount(self):
+ balance_amount = self.loan_amount
+ self.total_payable_amount = 0
+ self.total_payable_interest = 0
+
+ while(balance_amount > 0):
+ interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
+ balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
+
+ self.total_payable_interest += interest_amount
+
+ self.total_payable_amount = self.loan_amount + self.total_payable_interest
+
+@frappe.whitelist()
+def make_employee_loan(source_name, target_doc = None):
+ doclist = get_mapped_doc("Employee Loan Application", source_name, {
+ "Employee Loan Application": {
+ "doctype": "Employee Loan",
+ "validation": {
+ "docstatus": ["=", 1]
+ }
+ }
+ }, target_doc)
+
+ return doclist
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 2383804..d4beec3 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -63,7 +63,6 @@
self.validate_weights()
self.sync_tasks()
self.tasks = []
- self.load_tasks()
self.send_welcome_email()
def validate_project_name(self):
@@ -86,6 +85,9 @@
def sync_tasks(self):
"""sync tasks and remove table"""
+ if not hasattr(self, "deleted_task_list"):
+ self.set("deleted_task_list", [])
+
if self.flags.dont_sync_tasks: return
task_names = []
@@ -134,7 +136,7 @@
# delete
for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
- frappe.delete_doc("Task", t.name)
+ self.deleted_task_list.append(t.name)
def update_costing_and_percentage_complete(self):
self.update_percent_complete()
@@ -143,8 +145,14 @@
def is_row_updated(self, row, existing_task_data):
if self.get("__islocal") or not existing_task_data: return True
+ project_task_custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task"}, "fieldname")
+
d = existing_task_data.get(row.task_id)
+ for field in project_task_custom_fields:
+ if row.get(field) != d.get(field):
+ return True
+
if (d and (row.title != d.title or row.status != d.status
or getdate(row.start_date) != getdate(d.start_date) or getdate(row.end_date) != getdate(d.end_date)
or row.description != d.description or row.task_weight != d.task_weight)):
@@ -272,9 +280,19 @@
user.welcome_email_sent = 1
def on_update(self):
+ self.delete_task()
+ self.load_tasks()
self.update_costing_and_percentage_complete()
self.update_dependencies_on_duplicated_project()
+ def delete_task(self):
+ if not self.get('deleted_task_list'): return
+
+ for d in self.get('deleted_task_list'):
+ frappe.delete_doc("Task", d)
+
+ self.deleted_task_list = []
+
def update_dependencies_on_duplicated_project(self):
if self.flags.dont_sync_tasks: return
if not self.copied_from:
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 2822ae8..0ecf6e1 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -26,7 +26,8 @@
};
});
- if (this.frm.doc.__islocal) {
+ if (this.frm.doc.__islocal
+ && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) {
this.frm.set_value("disable_rounded_total", cint(frappe.sys_defaults.disable_rounded_total));
}