diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 471da9e8f0..05a3c11d91 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -337,13 +337,18 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup def validate_party_frozen_disabled(party_type, party_name): if party_type and party_name: - party = frappe.db.get_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True) - if party.disabled: - frappe.throw(_("{0} {1} is disabled").format(party_type, party_name), PartyDisabled) - elif party.is_frozen: - frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') - if not frozen_accounts_modifier in frappe.get_roles(): - frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) + if party_type in ("Customer", "Supplier"): + party = frappe.db.get_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True) + if party.disabled: + frappe.throw(_("{0} {1} is disabled").format(party_type, party_name), PartyDisabled) + elif party.get("is_frozen"): + frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') + if not frozen_accounts_modifier in frappe.get_roles(): + frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) + + elif party_type == "Employee": + if frappe.db.get_value("Employee", party_name, "status") == "Left": + frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), PartyDisabled, alert=True) def get_timeline_data(doctype, name): '''returns timeline data for the past one year''' diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index dc79d1fb5a..ab7ad499c9 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -47,6 +47,12 @@ frappe.query_reports["General Ledger"] = { "label": __("Voucher No"), "fieldtype": "Data", }, + { + "fieldname":"project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" + }, { "fieldtype": "Break", }, diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 00c17e8e98..d09ac707c4 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -149,6 +149,9 @@ def get_conditions(filters): if not (filters.get("account") or filters.get("party") or filters.get("group_by_account")): conditions.append("posting_date >=%(from_date)s") + if filters.get("project"): + conditions.append("project=%(project)s") + from frappe.desk.reportview import build_match_conditions match_conditions = build_match_conditions("GL Entry") if match_conditions: conditions.append(match_conditions) diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.js b/erpnext/buying/doctype/purchase_common/purchase_common.js index 720a1dc208..c03ccc80da 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.js +++ b/erpnext/buying/doctype/purchase_common/purchase_common.js @@ -21,20 +21,12 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if(this.frm.get_field('shipping_address')) { this.frm.set_query("shipping_address", function(){ if(me.frm.doc.customer){ - return{ - filters:{ - "customer": me.frm.doc.customer - } - } - } - else{ - return{ - filters:{ - "is_your_company_address": 1, - "company": me.frm.doc.company - } - } - } + return { + query: 'frappe.geo.doctype.address.address.address_query', + filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } + }; + } else + return erpnext.queries.company_address_query(me.frm.doc) }); } }, diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index ce6f4511dd..7e5e045812 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -26,11 +26,11 @@ frappe.ui.form.on("Supplier", { if(frm.doc.__islocal){ hide_field(['address_html','contact_html']); - erpnext.utils.clear_address_and_contact(frm); + frappe.geo.clear_address_and_contact(frm); } else { unhide_field(['address_html','contact_html']); - erpnext.utils.render_address_and_contact(frm); + frappe.geo.render_address_and_contact(frm); // custom buttons frm.add_custom_button(__('Accounting Ledger'), function() { diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 9eaaad1e61..7f92767279 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -6,7 +6,7 @@ import frappe import frappe.defaults from frappe import msgprint, _ from frappe.model.naming import make_autoname -from erpnext.utilities.address_and_contact import (load_address_and_contact, +from frappe.geo.address_and_contact import (load_address_and_contact, delete_contact_and_address) from erpnext.utilities.transaction_base import TransactionBase @@ -61,14 +61,6 @@ class Supplier(TransactionBase): validate_party_accounts(self) self.status = get_party_status(self) - def get_contacts(self,nm): - if nm: - contact_details =frappe.db.convert_to_lists(frappe.db.sql("select name, CONCAT(IFNULL(first_name,''),' ',IFNULL(last_name,'')),contact_no,email_id from `tabContact` where supplier = %s", nm)) - - return contact_details - else: - return '' - def on_trash(self): delete_contact_and_address('Supplier', self.name) diff --git a/erpnext/buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.json b/erpnext/buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.json deleted file mode 100644 index 128548f82a..0000000000 --- a/erpnext/buying/report/supplier_addresses_and_contacts/supplier_addresses_and_contacts.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "apply_user_permissions": 1, - "creation": "2013-10-09 10:38:40", - "docstatus": 0, - "doctype": "Report", - "idx": 1, - "is_standard": "Yes", - "modified": "2014-09-11 08:53:17.358554", - "modified_by": "Administrator", - "module": "Buying", - "name": "Supplier Addresses and Contacts", - "owner": "Administrator", - "query": "SELECT\n `tabSupplier`.name as \"Supplier:Link/Supplier:120\",\n\t`tabSupplier`.supplier_name as \"Supplier Name::120\",\n\t`tabSupplier`.supplier_type as \"Supplier Type:Link/Supplier Type:120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2), \n\t\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\n concat_ws(', ', `tabContact`.first_name, `tabContact`.last_name) as \"Contact Name::180\",\n\t`tabContact`.phone as \"Phone\",\n\t`tabContact`.mobile_no as \"Mobile No\",\n\t`tabContact`.email_id as \"Email Address::120\",\n\t`tabContact`.is_primary_contact as \"Is Primary Contact::120\"\nFROM\n\t`tabSupplier`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.supplier=`tabSupplier`.name\n\t)\n\tleft join `tabContact` on (\n\t\t`tabContact`.supplier=`tabSupplier`.name\n\t)\nWHERE\n\t`tabSupplier`.docstatus<2\nORDER BY\n\t`tabSupplier`.name asc", - "ref_doctype": "Supplier", - "report_name": "Supplier Addresses and Contacts", - "report_type": "Query Report" -} \ No newline at end of file diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py index e246266b29..990ca7a8ba 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -172,8 +172,12 @@ def get_data(): { "type": "report", "is_query_report": True, - "name": "Supplier Addresses and Contacts", - "doctype": "Supplier" + "name": "Addresses And Contacts", + "label": "Supplier Addresses And Contacts", + "doctype": "Address", + "route_options": { + "party_type": "Supplier" + } }, ] }, diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py index 3a18a27422..366d771bb0 100644 --- a/erpnext/config/hr.py +++ b/erpnext/config/hr.py @@ -265,6 +265,26 @@ def get_data(): ] }, + { + "label": _("Employee Loan Management"), + "icon": "icon-list", + "items": [ + { + "type": "doctype", + "name": "Loan Type", + "description": _("Define various loan types") + }, + { + "type": "doctype", + "name": "Employee Loan Application", + "description": _("Employee Loan Application") + }, + { + "type": "doctype", + "name": "Employee Loan" + }, + ] + }, { "label": _("Help"), "icon": "fa fa-facetime-video", diff --git a/erpnext/config/schools.py b/erpnext/config/schools.py index 903f54b411..a4c5744385 100644 --- a/erpnext/config/schools.py +++ b/erpnext/config/schools.py @@ -159,23 +159,6 @@ def get_data(): } ] }, - { - "label": _("LMS"), - "items": [ - { - "type": "doctype", - "name": "Announcement" - }, - { - "type": "doctype", - "name": "Topic" - }, - { - "type": "doctype", - "name": "Discussion" - } - ] - }, { "label": _("Setup"), "items": [ diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py index c406d09b1d..bbae245551 100644 --- a/erpnext/config/selling.py +++ b/erpnext/config/selling.py @@ -117,6 +117,16 @@ def get_data(): "link": "Tree/Sales Person", "description": _("Manage Sales Person Tree."), }, + { + "type": "report", + "is_query_report": True, + "name": "Addresses And Contacts", + "label": "Sales Partner Addresses And Contacts", + "doctype": "Address", + "route_options": { + "party_type": "Sales Partner" + } + }, { "type": "report", "is_query_report": True, @@ -215,8 +225,12 @@ def get_data(): { "type": "report", "is_query_report": True, - "name": "Customer Addresses And Contacts", - "doctype": "Contact" + "name": "Addresses And Contacts", + "label": "Customer Addresses And Contacts", + "doctype": "Address", + "route_options": { + "party_type": "Customer" + } }, { "type": "report", diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index be3cd82e7f..7e221254ff 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -25,6 +25,7 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({ refresh: function() { var doc = this.frm.doc; erpnext.toggle_naming_series(); + frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'name', doctype: 'Lead'} if(!this.frm.doc.__islocal && this.frm.doc.__onload && !this.frm.doc.__onload.is_customer) { this.frm.add_custom_button(__("Customer"), this.create_customer, __("Make")); @@ -34,9 +35,9 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({ } if(!this.frm.doc.__islocal) { - erpnext.utils.render_address_and_contact(cur_frm); + frappe.geo.render_address_and_contact(cur_frm); } else { - erpnext.utils.clear_address_and_contact(cur_frm); + frappe.geo.clear_address_and_contact(cur_frm); } }, diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 4b2899d08f..411f59730e 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -8,7 +8,7 @@ from frappe.utils import cstr, validate_email_add, cint, comma_and, has_gravatar from frappe.model.mapper import get_mapped_doc from erpnext.controllers.selling_controller import SellingController -from erpnext.utilities.address_and_contact import load_address_and_contact +from frappe.geo.address_and_contact import load_address_and_contact from erpnext.accounts.party import set_taxes sender_field = "email_id" @@ -127,7 +127,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False): @frappe.whitelist() def make_opportunity(source_name, target_doc=None): - target_doc = get_mapped_doc("Lead", source_name, + target_doc = get_mapped_doc("Lead", source_name, {"Lead": { "doctype": "Opportunity", "field_map": { diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 4bb6765ac8..301dc825e0 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -106,30 +106,6 @@ class Opportunity(TransactionBase): lead_name, company_name = frappe.db.get_value("Lead", self.lead, ["lead_name", "company_name"]) self.customer_name = company_name or lead_name - def get_cust_address(self,name): - details = frappe.db.sql("""select customer_name, address, territory, customer_group - from `tabCustomer` where name = %s and docstatus != 2""", (name), as_dict = 1) - if details: - ret = { - 'customer_name': details and details[0]['customer_name'] or '', - 'address' : details and details[0]['address'] or '', - 'territory' : details and details[0]['territory'] or '', - 'customer_group' : details and details[0]['customer_group'] or '' - } - # ********** get primary contact details (this is done separately coz. , in case there is no primary contact thn it would not be able to fetch customer details in case of join query) - - contact_det = frappe.db.sql("""select contact_name, contact_no, email_id - from `tabContact` where customer = %s and is_customer = 1 - and is_primary_contact = 'Yes' and docstatus != 2""", name, as_dict = 1) - - ret['contact_person'] = contact_det and contact_det[0]['contact_name'] or '' - ret['contact_no'] = contact_det and contact_det[0]['contact_no'] or '' - ret['email_id'] = contact_det and contact_det[0]['email_id'] or '' - - return ret - else: - frappe.throw(_("Customer {0} does not exist").format(name), frappe.DoesNotExistError) - def on_update(self): self.add_calendar_event() diff --git a/erpnext/docs/assets/img/human-resources/employee-loan-application.png b/erpnext/docs/assets/img/human-resources/employee-loan-application.png new file mode 100644 index 0000000000..317de4f093 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/employee-loan-application.png differ diff --git a/erpnext/docs/assets/img/human-resources/employee-loan.png b/erpnext/docs/assets/img/human-resources/employee-loan.png new file mode 100644 index 0000000000..19f3181ec2 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/employee-loan.png differ diff --git a/erpnext/docs/assets/img/human-resources/loan-repayment-salary-slip.png b/erpnext/docs/assets/img/human-resources/loan-repayment-salary-slip.png new file mode 100644 index 0000000000..4e5f340083 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/loan-repayment-salary-slip.png differ diff --git a/erpnext/docs/assets/img/human-resources/loan-type.png b/erpnext/docs/assets/img/human-resources/loan-type.png new file mode 100644 index 0000000000..9146b89daa Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/loan-type.png differ diff --git a/erpnext/docs/assets/img/human-resources/repayment-info.png b/erpnext/docs/assets/img/human-resources/repayment-info.png new file mode 100644 index 0000000000..5b8bdf76f6 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/repayment-info.png differ diff --git a/erpnext/docs/assets/img/human-resources/repayment-schedule.png b/erpnext/docs/assets/img/human-resources/repayment-schedule.png new file mode 100644 index 0000000000..f2ddf02fab Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/repayment-schedule.png differ diff --git a/erpnext/docs/user/manual/en/human-resources/employee-loan-management.md b/erpnext/docs/user/manual/en/human-resources/employee-loan-management.md new file mode 100644 index 0000000000..b76a2260c7 --- /dev/null +++ b/erpnext/docs/user/manual/en/human-resources/employee-loan-management.md @@ -0,0 +1,56 @@ +# Employee Loan Management +This module enables companies which provides employee loans to define and manage employee loans. +Employees can request loans, which are then reviewed and approved. For the approved loans, +repayment schedule for the entire loan cycle can be generated and automatic deduction from salary can also be set up. + +### Loan Type +To create a new Loan Type go to: + +> Human Resources > Employee Loan Management > Loan Type > New Loan Type + +Configure Loan limit and Rate of interest. + +Loan Type + +### Employee Loan Application + +Employee can apply for loan by going to: + +> Human Resources > Employee Loan Management > Employee Loan Application > New Employee Loan Application + +Employee Loan Application + +#### In the Employee Loan Application, + + * Enter Employee details and Loan details + * Select the repayment method, and based on your selection enter Repayment Period in Months or repayment Amount + +On save, Employee can see Repayment Information and make changes if required before submitting. + +Employee Loan Application + +### Employee Loan + +Once the Loan is approved, Manager can create Employee Loan record for the Employee. + +> Human Resources > Employee Loan Management > Employee Loan > New Employee Loan + +Employee Loan Application + +#### In the Employee Loan, + + * Enter Employee and Loan Application + * Check "Repay from Salary" if the loan repayment will be deducted from the salary + * Enter Disbursement Date and Account Info + * As soon as you hit save, the repayment schedule is generated. + +repayment Schedule + +#### Loan repayment deduction from Salary + +To auto deduct the Loan repayment from Salary, check "Repay from Salary" in Employee Loan. It will appear as Loan repayment in Salary Slip. + +Salary Slip + + + \ No newline at end of file diff --git a/erpnext/docs/user/manual/en/human-resources/index.txt b/erpnext/docs/user/manual/en/human-resources/index.txt index f91edfaa3e..673efdb838 100644 --- a/erpnext/docs/user/manual/en/human-resources/index.txt +++ b/erpnext/docs/user/manual/en/human-resources/index.txt @@ -15,4 +15,5 @@ holiday-list human-resource-setup daily-work-summary fleet-management +employee-loan-management articles diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8443cdf7e1..66acdb1b8b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -107,7 +107,6 @@ portal_menu_items = [ {"title": _("Shipments"), "route": "/shipments", "reference_doctype": "Delivery Note", "role":"Customer"}, {"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role":"Customer"}, {"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"}, - {"title": _("Announcements"), "route": "/announcement", "reference_doctype": "Announcement"}, {"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"} ] @@ -122,18 +121,7 @@ has_website_permission = { "Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission", "Supplier Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", - "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", - "Discussion": "erpnext.schools.web_form.discussion.discussion.has_website_permission" -} - -permission_query_conditions = { - "Contact": "erpnext.utilities.address_and_contact.get_permission_query_conditions_for_contact", - "Address": "erpnext.utilities.address_and_contact.get_permission_query_conditions_for_address" -} - -has_permission = { - "Contact": "erpnext.utilities.address_and_contact.has_permission", - "Address": "erpnext.utilities.address_and_contact.has_permission" + "Issue": "erpnext.support.doctype.issue.issue.has_website_permission" } dump_report_map = "erpnext.startup.report_data_map.data_map" @@ -153,7 +141,7 @@ doc_events = { "after_insert": "frappe.email.doctype.contact.contact.update_contact", "validate": "erpnext.hr.doctype.employee.employee.validate_employee_role", "on_update": "erpnext.hr.doctype.employee.employee.update_user_permissions", - "on_update": "erpnext.utilities.address_and_contact.set_default_role" + "on_update": "frappe.geo.address_and_contact.set_default_role" }, ("Sales Taxes and Charges Template", 'Price List'): { "on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings" diff --git a/erpnext/buying/report/supplier_addresses_and_contacts/__init__.py b/erpnext/hr/doctype/employee_loan/__init__.py similarity index 100% rename from erpnext/buying/report/supplier_addresses_and_contacts/__init__.py rename to erpnext/hr/doctype/employee_loan/__init__.py 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 0000000000..a03dbda4b1 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/employee_loan.js @@ -0,0 +1,88 @@ +// 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" + } + }; + }); + + $.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 + } + }; + }); + }) + }, + + mode_of_payment: function(frm){ + 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); + } + } + }); + }, + + refresh: function(frm) { + frm.trigger("toggle_fields"); + + if(frm.doc.docstatus==1) { + frm.add_custom_button(__('Ledger'), function() { + frappe.route_options = { + "voucher_no": frm.doc.name, + "from_date": frm.doc.posting_date, + "to_date": frm.doc.posting_date, + "company": frm.doc.company, + group_by_voucher: 0 + }; + frappe.set_route("query-report", "General Ledger"); + }, "fa fa-table"); + } + }, + + employee_loan_application: function(frm) { + return frm.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/employee_loan.json b/erpnext/hr/doctype/employee_loan/employee_loan.json new file mode 100644 index 0000000000..560515fde9 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/employee_loan.json @@ -0,0 +1,892 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "ELN.####", + "beta": 0, + "creation": "2016-12-02 10:11:49.673604", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_loan_application", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Loan Application", + "length": 0, + "no_copy": 0, + "options": "Employee Loan Application", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Loan Type", + "length": 0, + "no_copy": 0, + "options": "Loan Type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "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_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Posting Date", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 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": 1, + "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": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Loan Unpaid\nLoan Paid\nEMI in progress\nLoan Repaid", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repay_from_salary", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repay from Salary", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "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_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Details", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Rate of Interest (%) / Year", + "length": 0, + "no_copy": 0, + "options": "loan_type.rate_of_interest", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "disbursement_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Disbursement Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Repay Over Number of Periods", + "fieldname": "repayment_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Method", + "length": 0, + "no_copy": 0, + "options": "\nRepay Fixed Amount per Period\nRepay Over Number of Periods", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "", + "fieldname": "repayment_periods", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Period in Months", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "", + "fieldname": "monthly_repayment_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Account Info", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Mode of Payment", + "length": 0, + "no_copy": 0, + "options": "Mode of Payment", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "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_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_loan_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Loan Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Schedule", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repayment_schedule", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Schedule", + "length": 0, + "no_copy": 1, + "options": "Repayment Schedule", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_17", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Totals", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "total_payment", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Payment", + "length": 0, + "no_copy": 0, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_19", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "total_interest_payable", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Interest Payable", + "length": 0, + "no_copy": 0, + "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, + "unique": 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_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Employee Loan", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-17 07:13:10.704520", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Loan", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "posting_date", + "sort_field": "creation", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.py b/erpnext/hr/doctype/employee_loan/employee_loan.py new file mode 100644 index 0000000000..4d7a9f38df --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/employee_loan.py @@ -0,0 +1,128 @@ +# -*- 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 +import erpnext +from frappe import _ +from frappe.utils import flt, rounded, add_months, nowdate +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.accounts.general_ledger import make_gl_entries + +class EmployeeLoan(AccountsController): + def validate(self): + check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods) + if not self.company: + self.company = erpnext.get_default_company() + if not self.posting_date: + self.posting_date = nowdate() + if self.loan_type and not self.rate_of_interest: + self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest") + if self.repayment_method == "Repay Over Number of Periods": + self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods) + + self.make_repayment_schedule() + self.set_repayment_period() + self.calculate_totals() + + def on_submit(self): + self.make_gl_entries() + + def on_cancel(self): + self.make_gl_entries() + + def make_gl_entries(self): + gl_entries = [] + # Gl entries for employee loan account + gl_entries.append( + self.get_gl_dict({ + "account": self.employee_loan_account, + "party_type": "Employee", + "party": self.employee, + "debit": self.loan_amount, + "debit_in_account_currency": self.loan_amount + }) + ) + # Gl entries for payment account + gl_entries.append( + self.get_gl_dict({ + "account": self.payment_account, + "credit": self.loan_amount, + "credit_in_account_currency": self.loan_amount + }) + ) + make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) + + def make_repayment_schedule(self): + self.repayment_schedule = [] + payment_date = self.disbursement_date + balance_amount = self.loan_amount + + while(balance_amount > 0): + interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) + principal_amount = self.monthly_repayment_amount - interest_amount + balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount) + + if balance_amount < 0: + principal_amount += balance_amount + balance_amount = 0.0 + + total_payment = principal_amount + interest_amount + + self.append("repayment_schedule", { + "payment_date": payment_date, + "principal_amount": principal_amount, + "interest_amount": interest_amount, + "total_payment": total_payment, + "balance_loan_amount": balance_amount + }) + + next_payment_date = add_months(payment_date, 1) + payment_date = next_payment_date + + def set_repayment_period(self): + if self.repayment_method == "Repay Fixed Amount per Period": + repayment_periods = len(self.repayment_schedule) + + self.repayment_periods = repayment_periods + + def calculate_totals(self): + self.total_payment = 0 + self.total_interest_payable = 0 + for data in self.repayment_schedule: + self.total_payment += data.total_payment + self.total_interest_payable +=data.interest_amount + + def update_status(self): + if self.disbursement_date: + self.status = "Loan Paid" + if len(self.repayment_schedule)>0: + self.status = "repayment in progress" + +def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods): + if repayment_method == "Repay Over Number of Periods" and not repayment_periods: + frappe.throw(_("Please enter Repayment Periods")) + + if repayment_method == "Repay Fixed Amount per Period": + if not monthly_repayment_amount: + frappe.throw(_("Please enter repayment Amount")) + if monthly_repayment_amount > loan_amount: + frappe.throw(_("Monthly Repayment Amount cannot be greater than Loan Amount")) + +def get_monthly_repayment_amount(repayment_method, loan_amount, rate_of_interest, repayment_periods): + if repayment_method == "Repay Over Number of Periods": + if rate_of_interest: + monthly_interest_rate = flt(rate_of_interest) / (12 *100) + monthly_repayment_amount = math.ceil((loan_amount * monthly_interest_rate * + (1 + monthly_interest_rate)**repayment_periods) \ + / ((1 + monthly_interest_rate)**repayment_periods - 1)) + else: + monthly_repayment_amount = math.ceil(flt(loan_amount) / repayment_periods) + return monthly_repayment_amount + +@frappe.whitelist() +def get_employee_loan_application(employee_loan_application): + employee_loan = frappe.get_doc("Employee Loan Application", employee_loan_application) + if employee_loan: + return employee_loan \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan/test_employee_loan.py b/erpnext/hr/doctype/employee_loan/test_employee_loan.py new file mode 100644 index 0000000000..c1c6ba24a9 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/test_employee_loan.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import erpnext +import unittest +from frappe.utils import nowdate +from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee + +class TestEmployeeLoan(unittest.TestCase): + def setUp(self): + create_loan_type("Personal Loan", 500000, 8.4) + self.employee = make_employee("robert_loan@loan.com") + create_employee_loan(self.employee, "Personal Loan", 280000, "Repay Over Number of Periods", 20) + + def test_employee_loan(self): + employee_loan = frappe.get_doc("Employee Loan", {"employee":self.employee}) + self.assertEquals(employee_loan.monthly_repayment_amount, 15052) + self.assertEquals(employee_loan.total_interest_payable, 21034) + self.assertEquals(employee_loan.total_payment, 301034) + + schedule = employee_loan.repayment_schedule + + self.assertEquals(len(schedule), 20) + + for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: + self.assertEquals(schedule[idx].principal_amount, principal_amount) + self.assertEquals(schedule[idx].interest_amount, interest_amount) + self.assertEquals(schedule[idx].balance_loan_amount, balance_loan_amount) + + employee_loan.repayment_method = "Repay Fixed Amount per Period" + employee_loan.monthly_repayment_amount = 14000 + employee_loan.save() + + self.assertEquals(len(employee_loan.repayment_schedule), 22) + self.assertEquals(employee_loan.total_interest_payable, 22712) + self.assertEquals(employee_loan.total_payment, 302712) + + +def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest): + if not frappe.db.get_value("Loan Type", loan_name): + frappe.get_doc({ + "doctype": "Loan Type", + "loan_name": loan_name, + "maximum_loan_amount": maximum_loan_amount, + "rate_of_interest": rate_of_interest + }).insert() + +def create_employee_loan(employee, loan_type, loan_amount, repayment_method, repayment_periods): + if not frappe.db.get_value("Employee Loan", {"employee":employee}): + employee_loan = frappe.new_doc("Employee Loan") + employee_loan.update({ + "employee": employee, + "loan_type": loan_type, + "loan_amount": loan_amount, + "repayment_method": repayment_method, + "repayment_periods": repayment_periods, + "disbursement_date": nowdate(), + "mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'), + "payment_account": frappe.db.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name"), + "employee_loan_account": frappe.db.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") + }) + employee_loan.insert() + return employee_loan + else: + return frappe.get_doc("Employee Loan", {"employee":employee}) \ No newline at end of file diff --git a/erpnext/schools/doctype/announcement/__init__.py b/erpnext/hr/doctype/employee_loan_application/__init__.py similarity index 100% rename from erpnext/schools/doctype/announcement/__init__.py rename to erpnext/hr/doctype/employee_loan_application/__init__.py diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.js b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.js new file mode 100644 index 0000000000..6958beabfa --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.js @@ -0,0 +1,16 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Loan Application', { + refresh: function(frm) { + frm.trigger("toggle_fields") + }, + repayment_method: function(frm) { + frm.trigger("toggle_fields") + }, + + toggle_fields: function(frm) { + frm.toggle_enable("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.json b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.json new file mode 100644 index 0000000000..e30dc5a001 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.json @@ -0,0 +1,681 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "ELA/.#####", + "beta": 0, + "creation": "2016-12-02 12:35:56.046811", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Posting 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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "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, + "unique": 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_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Open\nApproved\nRejected", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "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_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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Info", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Type", + "length": 0, + "no_copy": 0, + "options": "Loan Type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "required_by_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Required by 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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reason", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repayment_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Info", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repayment_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Method", + "length": 0, + "no_copy": 0, + "options": "\nRepay Fixed Amount per Period\nRepay Over Number of Periods", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Rate of Interest", + "length": 0, + "no_copy": 0, + "options": "loan_type.rate_of_interest", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_payable_interest", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Payable Interest", + "length": 0, + "no_copy": 0, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "repayment_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "repayment_periods", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Period in Months", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_payable_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Payable Amount", + "length": 0, + "no_copy": 0, + "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, + "unique": 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_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Employee Loan Application", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-09 12:02:36.562577", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Loan Application", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "employee, employee_name, loan_type, loan_amount", + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee", + "title_field": "employee_name", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file 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 0000000000..15dbd4e5e4 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py @@ -0,0 +1,34 @@ +# -*- 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 +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 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) + self.repayment_periods = math.ceil((math.log(self.repayment_amount) - math.log(self.repayment_amount - \ + (self.loan_amount*monthly_interest_rate)))/(math.log(1+monthly_interest_rate))) + + self.total_payable_amount = self.repayment_amount * self.repayment_periods + self.total_payable_interest = self.total_payable_amount - self.loan_amount \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.py new file mode 100644 index 0000000000..1d157d6d81 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee + +class TestEmployeeLoanApplication(unittest.TestCase): + def setUp(self): + self.create_loan_type() + self.employee = make_employee("kate_loan@loan.com") + self.create_loan_application() + + def create_loan_type(self): + if not frappe.db.get_value("Loan Type", "Home Loan"): + frappe.get_doc({ + "doctype": "Loan Type", + "loan_name": "Home Loan", + "maximum_loan_amount": 500000, + "rate_of_interest": 9.2 + }).insert() + + def create_loan_application(self): + if not frappe.db.get_value("Employee Loan Application", {"employee":self.employee}, "name"): + loan_application = frappe.new_doc("Employee Loan Application") + loan_application.update({ + "employee": self.employee, + "loan_type": "Home Loan", + "rate_of_interest": 9.2, + "loan_amount": 250000, + "repayment_method": "Repay Over Number of Periods", + "repayment_periods": 24 + }) + loan_application.insert() + + + def test_loan_totals(self): + loan_application = frappe.get_doc("Employee Loan Application", {"employee":self.employee}) + self.assertEquals(loan_application.repayment_amount, 11445) + self.assertEquals(loan_application.total_payable_interest, 24680) + self.assertEquals(loan_application.total_payable_amount, 274680) + + loan_application.repayment_method = "Repay Fixed Amount per Period" + loan_application.repayment_amount = 15000 + loan_application.save() + + self.assertEquals(loan_application.repayment_periods, 18) + self.assertEquals(loan_application.total_payable_interest, 20000) + self.assertEquals(loan_application.total_payable_amount, 270000) \ No newline at end of file diff --git a/erpnext/schools/doctype/discussion/__init__.py b/erpnext/hr/doctype/loan_type/__init__.py similarity index 100% rename from erpnext/schools/doctype/discussion/__init__.py rename to erpnext/hr/doctype/loan_type/__init__.py diff --git a/erpnext/hr/doctype/loan_type/loan_type.js b/erpnext/hr/doctype/loan_type/loan_type.js new file mode 100644 index 0000000000..72f5775add --- /dev/null +++ b/erpnext/hr/doctype/loan_type/loan_type.js @@ -0,0 +1,7 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Loan Type', { + refresh: function(frm) { + } +}); diff --git a/erpnext/schools/doctype/discussion/discussion.json b/erpnext/hr/doctype/loan_type/loan_type.json similarity index 58% rename from erpnext/schools/doctype/discussion/discussion.json rename to erpnext/hr/doctype/loan_type/loan_type.json index 57ebfbaceb..f9441ea1a2 100644 --- a/erpnext/schools/doctype/discussion/discussion.json +++ b/erpnext/hr/doctype/loan_type/loan_type.json @@ -1,15 +1,15 @@ { "allow_copy": 0, - "allow_import": 1, + "allow_import": 0, "allow_rename": 0, - "autoname": "Discussion.####", + "autoname": "field:loan_name", "beta": 0, - "creation": "2016-06-13 07:57:38.326925", + "creation": "2016-12-02 10:41:40.732843", "custom": 0, "docstatus": 0, "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "document_type": "", + "editable_grid": 1, "engine": "InnoDB", "fields": [ { @@ -17,7 +17,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "subject", + "fieldname": "loan_name", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, @@ -25,7 +25,7 @@ "in_filter": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Subject", + "label": "Loan Name", "length": 0, "no_copy": 0, "permlevel": 0, @@ -36,7 +36,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -45,18 +45,17 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "course", - "fieldtype": "Link", + "fieldname": "maximum_loan_amount", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Course", + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maximum Loan Amount", "length": 0, "no_copy": 0, - "options": "Course", "permlevel": 0, "precision": "", "print_hide": 0, @@ -65,7 +64,92 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Rate of Interest (%) Yearly", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "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_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "disabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Disabled", + "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, "unique": 0 }, @@ -96,34 +180,6 @@ "search_index": 0, "set_only_once": 0, "unique": 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_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Discussion", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 } ], "hide_heading": 0, @@ -132,14 +188,14 @@ "image_view": 0, "in_create": 0, "in_dialog": 0, - "is_submittable": 1, + "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-11-07 05:28:34.032169", + "modified": "2016-12-29 15:54:17.716285", "modified_by": "Administrator", - "module": "Schools", - "name": "Discussion", + "module": "HR", + "name": "Loan Type", "name_case": "", "owner": "Administrator", "permissions": [ @@ -158,18 +214,17 @@ "print": 1, "read": 1, "report": 1, - "role": "Academics User", + "role": "HR Manager", "set_user_permissions": 0, "share": 1, - "submit": 1, + "submit": 0, "write": 1 } ], - "quick_entry": 1, + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "subject", - "track_seen": 1 + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/loan_type/loan_type.py b/erpnext/hr/doctype/loan_type/loan_type.py new file mode 100644 index 0000000000..2714e206d8 --- /dev/null +++ b/erpnext/hr/doctype/loan_type/loan_type.py @@ -0,0 +1,10 @@ +# -*- 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 +from frappe.model.document import Document + +class LoanType(Document): + pass diff --git a/erpnext/hr/doctype/loan_type/test_loan_type.py b/erpnext/hr/doctype/loan_type/test_loan_type.py new file mode 100644 index 0000000000..078e11e262 --- /dev/null +++ b/erpnext/hr/doctype/loan_type/test_loan_type.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Loan Type') + +class TestLoanType(unittest.TestCase): + pass diff --git a/erpnext/schools/doctype/topic/__init__.py b/erpnext/hr/doctype/repayment_schedule/__init__.py similarity index 100% rename from erpnext/schools/doctype/topic/__init__.py rename to erpnext/hr/doctype/repayment_schedule/__init__.py diff --git a/erpnext/schools/doctype/topic/topic.json b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json similarity index 59% rename from erpnext/schools/doctype/topic/topic.json rename to erpnext/hr/doctype/repayment_schedule/repayment_schedule.json index 0a69f88222..3a0dfd3d55 100644 --- a/erpnext/schools/doctype/topic/topic.json +++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json @@ -1,60 +1,30 @@ { "allow_copy": 0, "allow_import": 0, - "allow_rename": 1, - "autoname": "Topic.####", + "allow_rename": 0, "beta": 0, - "creation": "2016-06-28 07:06:38.749398", + "creation": "2016-12-20 15:32:25.078334", "custom": 0, "docstatus": 0, "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "document_type": "", + "editable_grid": 1, "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, - "fieldname": "course", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Course", - "length": 0, - "no_copy": 0, - "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "topic_name", - "fieldtype": "Data", + "columns": 2, + "fieldname": "payment_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Topic Name", + "label": "Payment Date", "length": 0, "no_copy": 0, "permlevel": 0, @@ -64,7 +34,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -73,26 +43,26 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, - "fieldname": "introduction", - "fieldtype": "Text", + "columns": 2, + "fieldname": "principal_amount", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, - "label": "Introduction", + "label": "Principal Amount", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -101,26 +71,82 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, - "fieldname": "content", - "fieldtype": "Text Editor", + "columns": 2, + "fieldname": "interest_amount", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, - "label": "Content", + "label": "Interest Amount", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "total_payment", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Total Payment", "length": 0, "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "balance_loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Balance Loan Amount", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -134,42 +160,20 @@ "in_dialog": 0, "is_submittable": 0, "issingle": 0, - "istable": 0, + "istable": 1, "max_attachments": 0, - "modified": "2016-11-07 05:29:20.531725", + "modified": "2017-01-09 12:00:10.818772", "modified_by": "Administrator", - "module": "Schools", - "name": "Topic", + "module": "HR", + "name": "Repayment Schedule", "name_case": "", "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "course", + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.py b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.py new file mode 100644 index 0000000000..8abee5e089 --- /dev/null +++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.py @@ -0,0 +1,10 @@ +# -*- 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 +from frappe.model.document import Document + +class RepaymentSchedule(Document): + pass diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js index 8b0dd1655c..0262259855 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.js +++ b/erpnext/hr/doctype/salary_slip/salary_slip.js @@ -110,6 +110,7 @@ cur_frm.cscript.depends_on_lwp = function(doc,dt,dn){ calculate_earning_total(doc, dt, dn, true); calculate_ded_total(doc, dt, dn, true); calculate_net_pay(doc, dt, dn); + refresh_many(['amount','gross_pay', 'rounded_total', 'net_pay', 'loan_repayment']); }; // Calculate earning total diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json index e558d73bcf..7d5dbe9c9a 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.json +++ b/erpnext/hr/doctype/salary_slip/salary_slip.json @@ -1266,7 +1266,35 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "Gross Pay + Arrear Amount +Encashment Amount - Total Deduction", + "fieldname": "loan_repayment", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Repayment", + "length": 0, + "no_copy": 0, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Gross Pay + Arrear Amount + Encashment Amount - Total Deduction - Loan Repayment", "fieldname": "net_pay", "fieldtype": "Currency", "hidden": 0, @@ -1362,7 +1390,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-12-14 08:26:31.400930", + "modified": "2017-01-09 12:37:03.802501", "modified_by": "Administrator", "module": "HR", "name": "Salary Slip", @@ -1413,7 +1441,7 @@ }, { "amend": 0, - "apply_user_permissions": 0, + "apply_user_permissions": 1, "cancel": 0, "create": 0, "delete": 0, @@ -1430,6 +1458,7 @@ "set_user_permissions": 0, "share": 0, "submit": 0, + "user_permission_doctypes": "[\"Employee\"]", "write": 0 } ], @@ -1440,5 +1469,6 @@ "sort_order": "DESC", "timeline_field": "employee", "title_field": "employee_name", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index eeec6e8e76..de205e3bdd 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -329,11 +329,21 @@ class SalarySlip(TransactionBase): self.sum_components('earnings', 'gross_pay') self.sum_components('deductions', 'total_deduction') + + self.set_loan_repayment() - self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) + self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.loan_repayment)) self.rounded_total = rounded(self.net_pay, self.precision("net_pay") if disable_rounded_total else 0) + def set_loan_repayment(self): + employee_loan = frappe.db.sql("""select sum(total_payment) as loan_repayment from `tabRepayment Schedule` + where payment_date between %s and %s and parent in (select name from `tabEmployee Loan` + where employee = %s and repay_from_salary = 1 and docstatus = 1)""", + (self.start_date, self.end_date, self.employee), as_dict=True) + if employee_loan: + self.loan_repayment = employee_loan[0].loan_repayment + def on_submit(self): if self.net_pay < 0: frappe.throw(_("Net Pay cannot be less than 0")) diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 3d119c3e38..9999f1ec08 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -7,7 +7,7 @@ import frappe import erpnext import calendar from erpnext.accounts.utils import get_fiscal_year -from frappe.utils import getdate, nowdate, add_days +from frappe.utils import getdate, nowdate, add_days, flt from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.hr.doctype.process_payroll.test_process_payroll import get_salary_component_account from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details @@ -24,7 +24,6 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") - def tearDown(self): frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.set_user("Administrator") @@ -40,12 +39,12 @@ class TestSalarySlip(unittest.TestCase): self.assertEquals(ss.total_working_days, no_of_days[0]) self.assertEquals(ss.payment_days, no_of_days[0]) - self.assertEquals(ss.earnings[0].amount, 5000) + self.assertEquals(ss.earnings[0].amount, 25000) self.assertEquals(ss.earnings[1].amount, 3000) self.assertEquals(ss.deductions[0].amount, 5000) - self.assertEquals(ss.deductions[1].amount, 2500) - self.assertEquals(ss.gross_pay, 10500) - self.assertEquals(ss.net_pay, 3000) + self.assertEquals(ss.deductions[1].amount, 5000) + self.assertEquals(ss.gross_pay, 40500) + self.assertEquals(ss.net_pay, 29918) def test_salary_slip_with_holidays_excluded(self): no_of_days = self.get_no_of_days() @@ -58,13 +57,13 @@ class TestSalarySlip(unittest.TestCase): self.assertEquals(ss.total_working_days, no_of_days[0] - no_of_days[1]) self.assertEquals(ss.payment_days, no_of_days[0] - no_of_days[1]) - self.assertEquals(ss.earnings[0].amount, 5000) - self.assertEquals(ss.earnings[0].default_amount, 5000) + self.assertEquals(ss.earnings[0].amount, 25000) + self.assertEquals(ss.earnings[0].default_amount, 25000) self.assertEquals(ss.earnings[1].amount, 3000) self.assertEquals(ss.deductions[0].amount, 5000) - self.assertEquals(ss.deductions[1].amount, 2500) - self.assertEquals(ss.gross_pay, 10500) - self.assertEquals(ss.net_pay, 3000) + self.assertEquals(ss.deductions[1].amount, 5000) + self.assertEquals(ss.gross_pay, 40500) + self.assertEquals(ss.net_pay, 29918) def test_payment_days(self): no_of_days = self.get_no_of_days() @@ -130,9 +129,23 @@ class TestSalarySlip(unittest.TestCase): ss = frappe.get_doc("Salary Slip", self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) ss.submit() + email_queue = frappe.db.sql("""select name from `tabEmail Queue`""") self.assertTrue(email_queue) + def test_loan_repayment_salary_slip(self): + from erpnext.hr.doctype.employee_loan.test_employee_loan import create_loan_type, create_employee_loan + employee = self.make_employee("test_employee@salary.com") + create_loan_type("Car Loan", 500000, 6.4) + employee_loan = create_employee_loan(employee, "Car Loan", 11000, "Repay Over Number of Periods", 20) + employee_loan.repay_from_salary = 1 + employee_loan.submit() + ss = frappe.get_doc("Salary Slip", + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) + ss.submit() + self.assertEquals(ss.loan_repayment, 582) + self.assertEquals(ss.net_pay, (flt(ss.gross_pay) - (flt(ss.total_deduction) + flt(ss.loan_repayment)))) + def test_payroll_frequency(self): fiscal_year = get_fiscal_year(nowdate(), company="_Test Company")[0] month = "%02d" % getdate(nowdate()).month @@ -167,7 +180,7 @@ class TestSalarySlip(unittest.TestCase): }).insert() if not frappe.db.get_value("Employee", {"user_id": user}): - frappe.get_doc({ + employee = frappe.get_doc({ "doctype": "Employee", "naming_series": "EMP-", "employee_name": user, @@ -183,6 +196,9 @@ class TestSalarySlip(unittest.TestCase): "status": "Active", "employment_type": "Intern" }).insert() + return employee.name + else: + return frappe.get_value("Employee", {"employee_name":user}, "name") def make_holiday_list(self): fiscal_year = get_fiscal_year(nowdate(), company="_Test Company") @@ -278,7 +294,7 @@ def make_salary_structure(sal_struct, payroll_frequency, employee): def get_employee_details(employee): return [{"employee": employee, - "base": 25000, + "base": 50000, "variable": 5000 } ] @@ -289,14 +305,14 @@ def get_earnings_component(): "salary_component": 'Basic Salary', "abbr":'BS', "condition": 'base > 10000', - "formula": 'base*.2', + "formula": 'base*.5', "idx": 1 }, { "salary_component": 'Basic Salary', "abbr":'BS', "condition": 'base < 10000', - "formula": 'base*.1', + "formula": 'base*.2', "idx": 2 }, { @@ -320,13 +336,13 @@ def get_deductions_component(): "salary_component": 'Professional Tax', "abbr":'PT', "condition": 'base > 10000', - "formula": 'base*.2', + "formula": 'base*.1', "idx": 1 }, { "salary_component": 'TDS', "abbr":'T', - "formula": 'base*.5', + "formula": 'base*.1', "idx": 2 }, { diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index fe88d9afcf..36e50d319a 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -19,8 +19,8 @@ class TestSalaryStructure(unittest.TestCase): frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List") make_earning_salary_component(["Basic Salary", "Allowance", "HRA"]) make_deduction_salary_component(["Professional Tax", "TDS"]) - self.make_employee("test_employee@salary.com") - self.make_employee("test_employee_2@salary.com") + make_employee("test_employee@salary.com") + make_employee("test_employee_2@salary.com") def make_holiday_list(self): if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): @@ -33,37 +33,6 @@ class TestSalaryStructure(unittest.TestCase): }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() - - def make_employee(self, user): - if not frappe.db.get_value("User", user): - frappe.get_doc({ - "doctype": "User", - "email": user, - "first_name": user, - "new_password": "password", - "user_roles": [{"doctype": "UserRole", "role": "Employee"}] - }).insert() - - - if not frappe.db.get_value("Employee", {"user_id": user}): - emp = frappe.get_doc({ - "doctype": "Employee", - "naming_series": "EMP-", - "employee_name": user, - "company": erpnext.get_default_company(), - "user_id": user, - "date_of_birth": "1990-05-08", - "date_of_joining": "2013-01-01", - "relieving_date": "", - "department": frappe.get_all("Department", fields="name")[0].name, - "gender": "Female", - "company_email": user, - "status": "Active", - "employment_type": "Intern" - }).insert() - return emp.name - else: - return frappe.get_value("Employee", {"employee_name":user}, "name") def test_amount_totals(self): sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"}) @@ -76,6 +45,36 @@ class TestSalaryStructure(unittest.TestCase): self.assertEquals(sal_slip.get("total_deduction"), 7500) self.assertEquals(sal_slip.get("net_pay"), 7500) +def make_employee(user): + if not frappe.db.get_value("User", user): + frappe.get_doc({ + "doctype": "User", + "email": user, + "first_name": user, + "new_password": "password", + "user_roles": [{"doctype": "UserRole", "role": "Employee"}] + }).insert() + + + if not frappe.db.get_value("Employee", {"user_id": user}): + emp = frappe.get_doc({ + "doctype": "Employee", + "naming_series": "EMP-", + "employee_name": user, + "company": erpnext.get_default_company(), + "user_id": user, + "date_of_birth": "1990-05-08", + "date_of_joining": "2013-01-01", + "relieving_date": "", + "department": frappe.get_all("Department", fields="name")[0].name, + "gender": "Female", + "company_email": user, + "status": "Active", + "employment_type": "Intern" + }).insert() + return emp.name + else: + return frappe.get_value("Employee", {"employee_name":user}, "name") def make_salary_slip_from_salary_structure(employee): sal_struct = make_salary_structure('Salary Structure Sample') diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 7f1c442a10..8105e1a963 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -30,28 +30,31 @@ def get_columns(leave_types): return columns def get_data(filters, leave_types): - + user = frappe.session.user allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date) active_employees = frappe.get_all("Employee", filters = { "status": "Active", "company": filters.company}, - fields = ["name", "employee_name", "department"]) + fields = ["name", "employee_name", "department", "user_id"]) data = [] for employee in active_employees: - row = [employee.name, employee.employee_name, employee.department] + leave_approvers = [l.leave_approver for l in frappe.db.sql("""select leave_approver from `tabEmployee Leave Approver` where parent = %s""", + (employee.name),as_dict=True)] + if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)): + row = [employee.name, employee.employee_name, employee.department] - for leave_type in leave_types: - # leaves taken - leaves_taken = get_approved_leaves_for_period(employee.name, leave_type, - filters.from_date, filters.to_date) + for leave_type in leave_types: + # leaves taken + leaves_taken = get_approved_leaves_for_period(employee.name, leave_type, + filters.from_date, filters.to_date) - # closing balance - closing = get_leave_balance_on(employee.name, leave_type, filters.to_date, - allocation_records_based_on_to_date.get(employee.name, frappe._dict())) + # closing balance + closing = get_leave_balance_on(employee.name, leave_type, filters.to_date, + allocation_records_based_on_to_date.get(employee.name, frappe._dict())) - row += [leaves_taken, closing] + row += [leaves_taken, closing] - data.append(row) + data.append(row) return data \ No newline at end of file diff --git a/erpnext/patches/v7_0/set_portal_settings.py b/erpnext/patches/v7_0/set_portal_settings.py index f7ee20597b..54a17dc221 100644 --- a/erpnext/patches/v7_0/set_portal_settings.py +++ b/erpnext/patches/v7_0/set_portal_settings.py @@ -8,7 +8,7 @@ from erpnext.setup.setup_wizard import domainify def execute(): frappe.reload_doctype('Role') - for dt in ("assessment", "announcement", "course", "fees"): + for dt in ("assessment", "course", "fees"): frappe.reload_doc("schools", "doctype", dt) frappe.reload_doc('website', 'doctype', 'portal_menu_item') diff --git a/erpnext/patches/v7_2/update_assessment_modules.py b/erpnext/patches/v7_2/update_assessment_modules.py index 2ea9f6eea9..ac04294cb3 100644 --- a/erpnext/patches/v7_2/update_assessment_modules.py +++ b/erpnext/patches/v7_2/update_assessment_modules.py @@ -20,7 +20,8 @@ def execute(): frappe.reload_doc("schools", "doctype", "evaluation_criteria") - for assessment in frappe.get_all("Assessment Plan", fields=["name", "grading_scale"]): + for assessment in frappe.get_all("Assessment Plan", fields=["name", "grading_scale"], filters = [["docstatus", "!=", 2 ]]): + print assessment for stud_result in frappe.db.sql("select * from `tabAssessment Result` where parent= %s", assessment.name, as_dict=True): if stud_result.result: assessment_result = frappe.new_doc("Assessment Result") @@ -30,6 +31,7 @@ def execute(): assessment_result.grading_scale = assessment.grading_scale assessment_result.total_score = stud_result.result assessment_result.flags.ignore_validate = True + assessment_result.flags.ignore_mandatory = True assessment_result.save() frappe.db.sql("""delete from `tabAssessment Result` where parent != '' or parent is not null""") \ No newline at end of file diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 9cd2aa8072..7433222538 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -1,7 +1,7 @@ { "allow_copy": 0, "allow_import": 1, - "allow_rename": 0, + "allow_rename": 1, "autoname": "TASK.#####", "beta": 0, "creation": "2013-01-29 19:25:50", @@ -944,7 +944,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2016-11-07 05:12:23.294476", + "modified": "2017-01-27 01:26:24.985167", "modified_by": "Administrator", "module": "Projects", "name": "Task", @@ -960,7 +960,6 @@ "export": 0, "if_owner": 0, "import": 0, - "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -979,5 +978,6 @@ "sort_order": "DESC", "timeline_field": "project", "title_field": "subject", + "track_changes": 0, "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 16385e1b10..711520530a 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -31,7 +31,8 @@ erpnext.financial_statements = { "account": data.account, "company": frappe.query_report_filters_by_name.company.get_value(), "from_date": data.from_date || data.year_start_date, - "to_date": data.to_date || data.year_end_date + "to_date": data.to_date || data.year_end_date, + "project": $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; })[0].$input.val() }; frappe.set_route("query-report", "General Ledger"); }, diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index 1141f99623..f3167bb620 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -36,7 +36,7 @@ $.extend(erpnext.queries, { customer_filter: function(doc) { if(!doc.customer) { - frappe.throw(__("Please set {0}", __(frappe.meta.get_label(doc.doctype, "customer", doc.name)))); + frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "customer", doc.name))])); } return { filters: { customer: doc.customer } }; @@ -45,8 +45,8 @@ $.extend(erpnext.queries, { contact_query: function(doc) { if(frappe.dynamic_link) { if(!doc[frappe.dynamic_link.fieldname]) { - frappe.throw(__("Please set {0}", __(frappe.meta.get_label(doc.doctype, - frappe.dynamic_link.fieldname, doc.name)))); + frappe.throw(__("Please set {0}", + [__(frappe.meta.get_label(doc.doctype, frappe.dynamic_link.fieldname, doc.name))])); } return { @@ -58,8 +58,8 @@ $.extend(erpnext.queries, { address_query: function(doc) { if(frappe.dynamic_link) { if(!doc[frappe.dynamic_link.fieldname]) { - frappe.throw(__("Please set {0}", __(frappe.meta.get_label(doc.doctype, - frappe.dynamic_link.fieldname, doc.name)))); + frappe.throw(__("Please set {0}", + [__(frappe.meta.get_label(doc.doctype, frappe.dynamic_link.fieldname, doc.name))])); } return { @@ -68,9 +68,16 @@ $.extend(erpnext.queries, { } }, + company_address_query: function(doc) { + return { + query: 'frappe.geo.doctype.address.address.address_query', + filters: { is_your_company_address: 1, link_doctype: 'Company', link_name: doc.company || '' } + }; + }, + supplier_filter: function(doc) { if(!doc.supplier) { - frappe.throw(__("Please set {0}", __(frappe.meta.get_label(doc.doctype, "supplier", doc.name)))); + frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "supplier", doc.name))])); } return { filters: { supplier: doc.supplier } }; @@ -78,8 +85,8 @@ $.extend(erpnext.queries, { lead_filter: function(doc) { if(!doc.lead) { - frappe.throw(__("Please specify a") + " " + - __(frappe.meta.get_label(doc.doctype, "lead", doc.name))); + frappe.throw(__("Please specify a {0}", + [__(frappe.meta.get_label(doc.doctype, "lead", doc.name))])); } return { filters: { lead: doc.lead } }; diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 74e9fb655c..551ea51feb 100644 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -82,32 +82,6 @@ $.extend(erpnext, { $.extend(erpnext.utils, { - clear_address_and_contact: function(frm) { - $(frm.fields_dict['address_html'].wrapper).html(""); - frm.fields_dict['contact_html'] && $(frm.fields_dict['contact_html'].wrapper).html(""); - }, - - render_address_and_contact: function(frm) { - // render address - $(frm.fields_dict['address_html'].wrapper) - .html(frappe.render_template("address_list", - cur_frm.doc.__onload)) - .find(".btn-address").on("click", function() { - frappe.new_doc("Address"); - }); - - // render contact - if(frm.fields_dict['contact_html']) { - $(frm.fields_dict['contact_html'].wrapper) - .html(frappe.render_template("contact_list", - cur_frm.doc.__onload)) - .find(".btn-contact").on("click", function() { - frappe.new_doc("Contact"); - } - ); - } - }, - set_party_dashboard_indicators: function(frm) { if(frm.doc.__onload && frm.doc.__onload.dashboard_info) { var info = frm.doc.__onload.dashboard_info; diff --git a/erpnext/schools/doctype/announcement/announcement.js b/erpnext/schools/doctype/announcement/announcement.js deleted file mode 100644 index 5b1d1c0898..0000000000 --- a/erpnext/schools/doctype/announcement/announcement.js +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2016, Frappe and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Announcement', { - onload: function(frm) { - frm.add_fetch('instructor', 'instructor_name' , 'posted_by'); - } -}); - diff --git a/erpnext/schools/doctype/announcement/announcement.json b/erpnext/schools/doctype/announcement/announcement.json deleted file mode 100644 index 831b71f973..0000000000 --- a/erpnext/schools/doctype/announcement/announcement.json +++ /dev/null @@ -1,315 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "announcement.#####", - "beta": 0, - "creation": "2016-06-23 05:37:33.996289", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "", - "fieldname": "receiver", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Receiver", - "length": 0, - "no_copy": 0, - "options": "Student\nStudent Group\nAll Students", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "instructor", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Instructor", - "length": 0, - "no_copy": 0, - "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval: doc.receiver == \"Student\"", - "fieldname": "student", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Student", - "length": 0, - "no_copy": 0, - "options": "Student", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "depends_on": "eval: doc.receiver == \"Student Group\"", - "fieldname": "student_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Student Group", - "length": 0, - "no_copy": 0, - "options": "Student Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "posted_by", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Posted By", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "subject", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Subject", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Announcement", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-07-21 06:30:12.825629", - "modified_by": "r@r.com", - "module": "Schools", - "name": "Announcement", - "name_case": "", - "owner": "demo@erpnext.com", - "permissions": [ - { - "amend": 1, - "apply_user_permissions": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "subject", - "track_seen": 1 -} \ No newline at end of file diff --git a/erpnext/schools/doctype/announcement/announcement.py b/erpnext/schools/doctype/announcement/announcement.py deleted file mode 100644 index 1a3fe75d82..0000000000 --- a/erpnext/schools/doctype/announcement/announcement.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe import _ - -class Announcement(Document): - def validate(self): - self.validate_receiver() - self.set_posted_by() - - def validate_receiver(self): - if self.receiver == "Student": - if not self.student: - frappe.throw(_("Please select a Student")) - self.student_group = None - elif self.receiver == "Student Group": - if not self.student_group: - frappe.throw(_("Please select a Student Group")) - self.student = None - else: - self.student_group = None - self.student = None - - def set_posted_by(self): - if self.instructor: - self.posted_by = frappe.db.get_value("Instructor", self.instructor, "instructor_name") - else: - self.posted_by = frappe.session.user - - - - -def get_message_list(doctype, txt, filters, limit_start, limit_page_length=20): - user = frappe.session.user - student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user) - if student: - sg_list = frappe.db.sql("""select parent from `tabStudent Group Student` as sgs - where sgs.student = %s """,(student)) - - data = frappe.db.sql("""select name, receiver, subject, description, posted_by, modified, - student, student_group - from `tabAnnouncement` as announce - where (announce.receiver = "Student" and announce.student = %s) - or (announce.receiver = "Student Group" and announce.student_group in %s) - or announce.receiver = "All Students" - and announce.docstatus = 1 - order by announce.idx asc limit {0} , {1}""" - .format(limit_start, limit_page_length), (student,sg_list), as_dict = True) - - for announcement in data: - try: - num_attachments = frappe.db.sql(""" select count(file_url) from tabFile as file - where file.attached_to_name=%s - and file.attached_to_doctype=%s""",(announcement.name,"Announcement")) - - except IOError or frappe.DoesNotExistError: - pass - frappe.local.message_log.pop() - - announcement.num_attachments = num_attachments[0][0] - - return data - -def get_list_context(context=None): - return { - "show_sidebar": True, - 'no_breadcrumbs': True, - "title": _("Announcements"), - "get_list": get_message_list, - "row_template": "templates/includes/announcement/announcement_row.html" - } \ No newline at end of file diff --git a/erpnext/schools/doctype/announcement/test_announcement.py b/erpnext/schools/doctype/announcement/test_announcement.py deleted file mode 100644 index b0200a238c..0000000000 --- a/erpnext/schools/doctype/announcement/test_announcement.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -# test_records = frappe.get_test_records('Announcement') - -class TestAnnouncement(unittest.TestCase): - pass diff --git a/erpnext/schools/doctype/assessment_plan/assessment_plan.py b/erpnext/schools/doctype/assessment_plan/assessment_plan.py index aa84ae342d..1b4b671b87 100644 --- a/erpnext/schools/doctype/assessment_plan/assessment_plan.py +++ b/erpnext/schools/doctype/assessment_plan/assessment_plan.py @@ -13,7 +13,7 @@ class AssessmentPlan(Document): frappe.throw(_("Please select Student Group or Student Batch")) self.validate_student_batch() self.validate_overlap() - + self.validate_max_score() def validate_overlap(self): """Validates overlap for Student Group/Student Batch, Instructor, Room""" @@ -42,4 +42,11 @@ class AssessmentPlan(Document): def validate_student_batch(self): if self.student_group: - self.student_batch = frappe.db.get_value("Student Group", self.student_group, "student_batch") \ No newline at end of file + self.student_batch = frappe.db.get_value("Student Group", self.student_group, "student_batch") + + def validate_max_score(self): + max_score = 0 + for d in self.evaluation_criterias: + max_score += d.maximum_score + if self.maximum_assessment_score != max_score: + frappe.throw(_("Sum of Scores of Evaluation Criterias needs to be {0}.".format(self.maximum_assessment_score))) \ No newline at end of file diff --git a/erpnext/schools/doctype/discussion/discussion.js b/erpnext/schools/doctype/discussion/discussion.js deleted file mode 100644 index df3c2b8a29..0000000000 --- a/erpnext/schools/doctype/discussion/discussion.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Frappe and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Discussion', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/schools/doctype/discussion/discussion.py b/erpnext/schools/doctype/discussion/discussion.py deleted file mode 100644 index 96732e3b40..0000000000 --- a/erpnext/schools/doctype/discussion/discussion.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.model.document import Document - -class Discussion(Document): - def validate(self): - if not self.owner== frappe.session.user: - frappe.throw(_("Not Permitted")) - -def get_discussions(doctype, txt, filters, limit_start, limit_page_length=20): - from frappe.www.list import get_list - if not filters: - filters = [] - filters.append(("Discussion", "course", "=", frappe.form_dict.course)) - return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=True) - -def get_list_context(context=None): - course_name = frappe.form_dict.course - portal_items = [{'reference_doctype': u'Topic', 'route': u"/topic?course=" + str(course_name), 'show_always': 0L, 'title': u'Topics'}, - {'reference_doctype': u'Discussion', 'route': u"/discussion?course=" + str(course_name), 'show_always': 0L, 'title': u'Discussions'}, - - ] - sidebar_title = course_name - return { - "show_sidebar": True, - 'no_breadcrumbs': True, - "get_list" : get_discussions, - "title": _("Discussions"), - "sidebar_items" : portal_items, - "sidebar_title" : sidebar_title, - "row_template": "templates/includes/discussion/discussion_row.html" - } \ No newline at end of file diff --git a/erpnext/schools/doctype/discussion/test_discussion.py b/erpnext/schools/doctype/discussion/test_discussion.py deleted file mode 100644 index 31799f06e1..0000000000 --- a/erpnext/schools/doctype/discussion/test_discussion.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -# test_records = frappe.get_test_records('Discussion') - -class TestDiscussion(unittest.TestCase): - pass diff --git a/erpnext/schools/doctype/topic/test_topic.py b/erpnext/schools/doctype/topic/test_topic.py deleted file mode 100644 index 1d2974ed7f..0000000000 --- a/erpnext/schools/doctype/topic/test_topic.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -# test_records = frappe.get_test_records('Topic') - -class TestTopic(unittest.TestCase): - pass diff --git a/erpnext/schools/doctype/topic/topic.js b/erpnext/schools/doctype/topic/topic.js deleted file mode 100644 index bd9379dfad..0000000000 --- a/erpnext/schools/doctype/topic/topic.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Frappe and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Topic', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/schools/doctype/topic/topic.py b/erpnext/schools/doctype/topic/topic.py deleted file mode 100644 index 5dba561681..0000000000 --- a/erpnext/schools/doctype/topic/topic.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe import _ - -class Topic(Document): - pass - -def get_topic_list(doctype, txt, filters, limit_start, limit_page_length=20): - user = frappe.session.user - student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user) - if student: - data = frappe. db.sql('''select name, course, modified,topic_name, introduction, content from `tabTopic` as topic - where topic.course = %s - order by idx asc limit {0} , {1}'''.format(limit_start, limit_page_length),filters.course,as_dict = True) - - for topic in data: - try: - num_attachments = frappe.db.sql(""" select count(file_url) from tabFile as file - where file.attached_to_name=%s - and file.attached_to_doctype=%s""",(topic.name,"Topic")) - - except IOError or frappe.DoesNotExistError: - pass - frappe.local.message_log.pop() - - topic.num_attachments = num_attachments[0][0] - - return data - -def get_list_context(context=None): - course = frappe.get_doc('Course', frappe.form_dict.course) - portal_items = [{'reference_doctype': u'Topic', 'route': u"/topic?course=" + str(course.name), 'show_always': 0L, 'title': u'Topics'}, - {'reference_doctype': u'Discussion', 'route': u"/discussion?course=" + str(course.name), 'show_always': 0L, 'title': u'Discussions'}, - - ] - return { - "show_sidebar": True, - "title": _("Topic"), - 'no_breadcrumbs': True, - "sidebar_items" : portal_items, - "sidebar_title" : course.name, - "get_list": get_topic_list, - "row_template": "templates/includes/topic/topic_row.html" - } \ No newline at end of file diff --git a/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py b/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py index cd2c82c37b..01cee58350 100644 --- a/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py +++ b/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py @@ -61,7 +61,7 @@ def get_attendance_list(from_date, to_date, student_batch, students_list): students_with_leave_application = get_students_with_leave_application(from_date, to_date, students_list) for d in attendance_list: att_map.setdefault(d.student, frappe._dict()).setdefault(d.date, "") - if students_with_leave_application and d.student in students_with_leave_application.get(d.date,[]): + if students_with_leave_application and d.student in students_with_leave_application.get(d.date): att_map[d.student][d.date] = "Present" else: att_map[d.student][d.date] = d.status diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index 747b31e13d..540ec28c94 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -37,7 +37,7 @@ frappe.ui.form.on("Customer", { frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); if(!frm.doc.__islocal) { - erpnext.utils.render_address_and_contact(frm); + frappe.geo.render_address_and_contact(frm); // custom buttons frm.add_custom_button(__('Accounting Ledger'), function() { @@ -53,7 +53,7 @@ frappe.ui.form.on("Customer", { erpnext.utils.set_party_dashboard_indicators(frm); } else { - erpnext.utils.clear_address_and_contact(frm); + frappe.geo.clear_address_and_contact(frm); } var grid = cur_frm.get_field("sales_team").grid; diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d8011af20a..e14cde07db 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -9,7 +9,7 @@ import frappe.defaults from frappe.utils import flt, cint, cstr from frappe.desk.reportview import build_match_conditions from erpnext.utilities.transaction_base import TransactionBase -from erpnext.utilities.address_and_contact import load_address_and_contact, delete_contact_and_address +from frappe.geo.address_and_contact import load_address_and_contact, delete_contact_and_address from erpnext.accounts.party import validate_party_accounts, get_timeline_data # keep this from erpnext.accounts.party_status import get_party_status from erpnext import get_default_currency @@ -95,9 +95,14 @@ class Customer(TransactionBase): def create_lead_address_contact(self): if self.lead_name: # assign lead address to customer (if already not set) - address_name = frappe.get_value('Dynamic Link', dict(parenttype='Address', link_doctype='Lead', link_name=self.name)) - if address_name: - address = frappe.get_doc('Address', address_name) + address_names = frappe.get_all('Dynamic Link', filters={ + "parenttype":"Address", + "link_doctype":"Lead", + "link_name":self.lead_name + }, fields=["parent as name"]) + + for address_name in address_names: + address = frappe.get_doc('Address', address_name.get('name')) if not address.has_link('Customer', self.name): address.append('links', dict(link_doctype='Customer', link_name=self.name)) address.save() @@ -105,17 +110,17 @@ class Customer(TransactionBase): lead = frappe.db.get_value("Lead", self.lead_name, ["lead_name", "email_id", "phone", "mobile_no"], as_dict=True) # create contact from lead - c = frappe.new_doc('Contact') - c.first_name = lead.lead_name - c.email_id = lead.email_id - c.phone = lead.phone - c.mobile_no = lead.mobile_no - c.is_primary_contact = 1 - c.append('links', dict(link_doctype='Customer', link_name=self.name)) - c.flags.ignore_permissions = self.flags.ignore_permissions - c.autoname() - if not frappe.db.exists("Contact", c.name): - c.insert() + contact = frappe.new_doc('Contact') + contact.first_name = lead.lead_name + contact.email_id = lead.email_id + contact.phone = lead.phone + contact.mobile_no = lead.mobile_no + contact.is_primary_contact = 1 + contact.append('links', dict(link_doctype='Customer', link_name=self.name)) + contact.flags.ignore_permissions = self.flags.ignore_permissions + contact.autoname() + if not frappe.db.exists("Contact", contact.name): + contact.insert() def validate_name_with_customer_group(self): if frappe.db.exists("Customer Group", self.name): @@ -133,7 +138,7 @@ class Customer(TransactionBase): def on_trash(self): delete_contact_and_address('Customer', self.name) if self.lead_name: - frappe.db.sql("update `tabLead` set status='Interested' where name=%s",self.lead_name) + frappe.db.sql("update `tabLead` set status='Interested' where name=%s", self.lead_name) def after_rename(self, olddn, newdn, merge=False): if frappe.defaults.get_global_default('cust_master_name') == 'Customer Name': diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py index 4d12efd99b..3c4d528bd3 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.py +++ b/erpnext/selling/page/sales_funnel/sales_funnel.py @@ -12,9 +12,9 @@ def get_funnel_data(from_date, to_date): where (date(`modified`) between %s and %s) and status != "Do Not Contact" """, (from_date, to_date))[0][0] - active_leads += frappe.db.sql("""select count(distinct customer) from `tabContact` - where (date(`modified`) between %s and %s) - and status != "Passive" """, (from_date, to_date))[0][0] + active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact + left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer' + and (date(contact.modified) between %s and %s) and status != "Passive" """, (from_date, to_date))[0][0] opportunities = frappe.db.sql("""select count(*) from `tabOpportunity` where (date(`creation`) between %s and %s) diff --git a/erpnext/selling/report/customer_addresses_and_contacts/__init__.py b/erpnext/selling/report/customer_addresses_and_contacts/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/selling/report/customer_addresses_and_contacts/customer_addresses_and_contacts.json b/erpnext/selling/report/customer_addresses_and_contacts/customer_addresses_and_contacts.json deleted file mode 100644 index 1f6707beb9..0000000000 --- a/erpnext/selling/report/customer_addresses_and_contacts/customer_addresses_and_contacts.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2012-10-04 18:45:27", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 1, - "is_standard": "Yes", - "modified": "2015-08-24 11:44:00.711112", - "modified_by": "Administrator", - "module": "Selling", - "name": "Customer Addresses And Contacts", - "owner": "Administrator", - "query": "SELECT\n\t`tabCustomer`.name as \"Customer ID:Link/Customer\",\n\t`tabCustomer`.customer_name as \"Customer Name\",\n\t`tabCustomer`.customer_group as \"Customer Group:Link/Customer Group\",\n\t`tabAddress`.address_line1 as \"Address Line 1\",\n\t`tabAddress`.address_line2 as \"Address Line 2\",\n\t`tabAddress`.city as \"City\",\n\t`tabAddress`.state as \"State\",\n\t`tabAddress`.pincode as \"Postal Code\",\n\t`tabAddress`.country as \"Country\",\n\t`tabAddress`.is_primary_address as \"Is Primary Address:Check\", \n\t`tabContact`.first_name as \"First Name\",\n\t`tabContact`.last_name as \"Last Name\",\n\t`tabContact`.phone as \"Phone\",\n\t`tabContact`.mobile_no as \"Mobile No\",\n\t`tabContact`.email_id as \"Email Address\",\n\t`tabContact`.is_primary_contact as \"Is Primary Contact:Check\"\nFROM\n\t`tabCustomer`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.customer=`tabCustomer`.name\n\t)\n\tleft join `tabContact` on (\n\t\t`tabContact`.customer=`tabCustomer`.name\n\t)\nWHERE\n\t`tabCustomer`.docstatus<2\nORDER BY\n\t`tabCustomer`.name asc", - "ref_doctype": "Customer", - "report_name": "Customer Addresses And Contacts", - "report_type": "Query Report" -} \ No newline at end of file diff --git a/erpnext/selling/report/lead_details/lead_details.json b/erpnext/selling/report/lead_details/lead_details.json index 387b5f89f6..047b4b785b 100644 --- a/erpnext/selling/report/lead_details/lead_details.json +++ b/erpnext/selling/report/lead_details/lead_details.json @@ -1,16 +1,18 @@ { + "add_total_row": 0, "apply_user_permissions": 1, "creation": "2013-10-22 11:58:16", + "disabled": 0, "docstatus": 0, "doctype": "Report", "idx": 1, "is_standard": "Yes", - "modified": "2015-02-02 11:39:57.231750", + "modified": "2017-01-19 15:44:59.742195", "modified_by": "Administrator", "module": "Selling", "name": "Lead Details", "owner": "Administrator", - "query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Address::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.lead=`tabLead`.name\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc", + "query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabDynamic Link` on (\n\t\t`tabDynamic Link`.link_name=`tabLead`.name\n\t)\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.name=`tabDynamic Link`.parent\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc", "ref_doctype": "Lead", "report_name": "Lead Details", "report_type": "Query Report" diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index eb5c043b50..eef8599290 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -73,11 +73,23 @@ def delete_bins(company_name): def delete_lead_addresses(company_name): """Delete addresses to which leads are linked""" - for lead in frappe.get_all("Lead", filters={"company": company_name}): - frappe.db.sql("""delete from `tabAddress` - where lead=%s and (customer='' or customer is null) and (supplier='' or supplier is null)""", lead.name) + leads = frappe.get_all("Lead", filters={"company": company_name}) + leads = [ "'%s'"%row.get("name") for row in leads ] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads)), debug=True) + addresses = ["'%s'"%addr for addr in addresses] - frappe.db.sql("""update `tabAddress` set lead=null, lead_name=null where lead=%s""", lead.name) + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)), debug=True) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' and parenttype='Address' + and link_name in ({leads})""".format(leads=",".join(leads)), debug=True) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)), debug=True) def delete_communications(doctype, company_name, company_fieldname): frappe.db.sql(""" diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.js b/erpnext/setup/doctype/sales_partner/sales_partner.js index 1bfba98b27..84cf749524 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.js +++ b/erpnext/setup/doctype/sales_partner/sales_partner.js @@ -6,12 +6,12 @@ frappe.ui.form.on('Sales Partner', { frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Sales Person'} if(frm.doc.__islocal){ - hide_field(['address_html', 'contact_html']); - erpnext.utils.clear_address_and_contact(frm); + hide_field(['address_html', 'contact_html', 'address_contacts']); + frappe.geo.clear_address_and_contact(frm); } else{ - unhide_field(['address_html', 'contact_html']); - erpnext.utils.render_address_and_contact(frm); + unhide_field(['address_html', 'contact_html', 'address_contacts']); + frappe.geo.render_address_and_contact(frm); } } }); diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.py b/erpnext/setup/doctype/sales_partner/sales_partner.py index 5a2aa4993b..96211afa4d 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.py +++ b/erpnext/setup/doctype/sales_partner/sales_partner.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import cstr, filter_strip_join from frappe.website.website_generator import WebsiteGenerator -from erpnext.utilities.address_and_contact import load_address_and_contact +from frappe.geo.address_and_contact import load_address_and_contact class SalesPartner(WebsiteGenerator): website = frappe._dict( @@ -28,15 +28,6 @@ class SalesPartner(WebsiteGenerator): if self.partner_website and not self.partner_website.startswith("http"): self.partner_website = "http://" + self.partner_website - def get_contacts(self, nm): - if nm: - return frappe.db.convert_to_lists(frappe.db.sql(""" - select name, CONCAT(IFNULL(first_name,''), - ' ',IFNULL(last_name,'')),contact_no,email_id - from `tabContact` where sales_partner = %s""", nm)) - else: - return '' - def get_context(self, context): address = frappe.db.get_value("Address", {"sales_partner": self.name, "is_primary_address": 1}, diff --git a/erpnext/templates/pages/announcements.html b/erpnext/templates/pages/announcements.html deleted file mode 100644 index d6e0d734ca..0000000000 --- a/erpnext/templates/pages/announcements.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "templates/web.html" %} - -{% block header %} -

{{doc.subject}}

-{% endblock %} - -{% block page_content %} - -

{{doc.description}}

-

- {% for file in attached_files%} - {{file.file_name}} -
- {% endfor %} -
- {{ doc.posted_by }} - {{ frappe.format_date(doc.modified) }} -

- -{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/announcements.py b/erpnext/templates/pages/announcements.py deleted file mode 100644 index 4a61fc8ccc..0000000000 --- a/erpnext/templates/pages/announcements.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def get_context(context): - announcement = frappe.get_doc('Announcement', frappe.form_dict.announcement) - context.no_cache = 1 - context.show_sidebar = True - announcement.has_permission('read') - context.doc = announcement - attachments = frappe.db.sql("""select file_url, file_name from tabFile as file - where file.attached_to_name=%s """,(announcement.name), as_dict = True) - - context.attached_files = attachments - - diff --git a/erpnext/templates/pages/courses.py b/erpnext/templates/pages/courses.py index 5b1410efbd..c80d8e7d22 100644 --- a/erpnext/templates/pages/courses.py +++ b/erpnext/templates/pages/courses.py @@ -15,14 +15,6 @@ def get_context(context): course = frappe.get_doc('Course', frappe.form_dict.course) course.has_permission('read') context.doc = course - portal_items = [{'reference_doctype': u'Topic', 'route': u"/topic?course=" + str(course.name), 'show_always': 0L, 'title': u'Topics'}, - {'reference_doctype': u'Discussion', 'route': u"/discussion?course=" + str(course.name), 'show_always': 0L, 'title': u'Discussions'}, - - ] - - context.sidebar_items = portal_items - context.sidebar_title = sidebar_title - context.intro = course.course_intro diff --git a/erpnext/templates/pages/discussions.html b/erpnext/templates/pages/discussions.html deleted file mode 100644 index 28eb01fd65..0000000000 --- a/erpnext/templates/pages/discussions.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "templates/web.html" %} - -{% block header %} -

{{doc.subject}}

-

{{doc.description}}

-

Started by: {{doc.owner}}

-{% endblock %} - -{% block page_content %} - -
- {% include 'templates/includes/comments/comments.html' %} -
- -{% endblock %} diff --git a/erpnext/templates/pages/discussions.py b/erpnext/templates/pages/discussions.py deleted file mode 100644 index 22a1bef079..0000000000 --- a/erpnext/templates/pages/discussions.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.website.utils import get_comment_list - -def get_context(context): - context.doc = frappe.get_doc('Discussion', frappe.form_dict.discussion) - portal_items = [{'reference_doctype': u'Topic', 'route': u"/topic?course=" + str(context.doc.course), 'show_always': 0L, 'title': u'Topics'}, - {'reference_doctype': u'Discussion', 'route': u"/discussion?course=" + str(context.doc.course), 'show_always': 0L, 'title': u'Discussions'}, - - ] - context.show_sidebar = True - context.sidebar_items = portal_items - context.no_cache = 1 - context.doc.has_permission('read') - context.sidebar_title = context.doc.course - context.reference_doctype = "Discussion" - context.reference_name = context.doc.name - context.comment_list = get_comment_list(context.doc.doctype,context.doc.name) \ No newline at end of file diff --git a/erpnext/templates/pages/topics.html b/erpnext/templates/pages/topics.html deleted file mode 100644 index 94d7a1731a..0000000000 --- a/erpnext/templates/pages/topics.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "templates/web.html" %} - - -{% block header %} -

{{ doc.introduction }}

-{% endblock %} - -{% block page_content %} - -

{{ doc.content }}

- -{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/topics.py b/erpnext/templates/pages/topics.py deleted file mode 100644 index 8a55b640e7..0000000000 --- a/erpnext/templates/pages/topics.py +++ /dev/null @@ -1,15 +0,0 @@ -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - - -def get_context(context): - topic = frappe.get_doc('Topic', frappe.form_dict.topic) - context.no_cache = 1 - context.show_sidebar = True - context.doc = topic - attachments = frappe.db.sql("""select file_url, file_name from tabFile as file - where file.attached_to_name=%s """,(topic.name), as_dict = True) - - context.attached_files = attachments diff --git a/erpnext/utilities/address_and_contact.py b/erpnext/utilities/address_and_contact.py deleted file mode 100644 index 12ceb5b61b..0000000000 --- a/erpnext/utilities/address_and_contact.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def load_address_and_contact(doc, key): - """Loads address list and contact list in `__onload`""" - from frappe.geo.doctype.address.address import get_address_display - - address_list = [frappe.get_value('Address', a.parent, '*') - for a in frappe.get_all('Dynamic Link', fields='parent', - filters=dict(parenttype='Address', link_doctype=doc.doctype, link_name=doc.name))] - - address_list = [a.update({"display": get_address_display(a)}) - for a in address_list] - - address_list = sorted(address_list, - lambda a, b: - (int(a.is_primary_address - b.is_primary_address)) or - (1 if a.modified - b.modified else 0)) - - doc.set_onload('addr_list', address_list) - - if doc.doctype != "Lead": - contact_list = [frappe.get_value('Contact', a.parent, '*') - for a in frappe.get_all('Dynamic Link', fields='parent', - filters=dict(parenttype='Contact', link_doctype=doc.doctype, link_name=doc.name))] - - contact_list = sorted(contact_list, - lambda a, b: - (int(a.is_primary_contact - b.is_primary_contact)) or - (1 if a.modified - b.modified else 0)) - - doc.set_onload('contact_list', contact_list) - -def set_default_role(doc, method): - '''Set customer, supplier, student based on email''' - if frappe.flags.setting_role: - return - contact_name = frappe.get_value('Contact', dict(email_id=doc.email)) - if contact_name: - contact = frappe.get_doc('Contact', contact_name) - for link in contact.links: - frappe.flags.setting_role = True - if link.link_doctype=='Customer': - doc.add_roles('Customer') - elif link.link_doctype=='Supplier': - doc.add_roles('Supplier') - elif frappe.get_value('Student', dict(student_email_id=doc.email)): - doc.add_roles('Student') - -def has_permission(doc, ptype, user): - links = get_permitted_and_not_permitted_links(doc.doctype) - if not links.get("not_permitted_links"): - # optimization: don't determine permissions based on link fields - return True - - # True if any one is True or all are empty - names = [] - for df in (links.get("permitted_links") + links.get("not_permitted_links")): - doctype = df.options - name = doc.get(df.fieldname) - names.append(name) - - if name and frappe.has_permission(doctype, ptype, doc=name): - return True - - if not any(names): - return True - return False - -def get_permission_query_conditions_for_contact(user): - return get_permission_query_conditions("Contact") - -def get_permission_query_conditions_for_address(user): - return get_permission_query_conditions("Address") - -def get_permission_query_conditions(doctype): - links = get_permitted_and_not_permitted_links(doctype) - - if not links.get("not_permitted_links"): - # when everything is permitted, don't add additional condition - return "" - - elif not links.get("permitted_links"): - conditions = [] - - # when everything is not permitted - for df in links.get("not_permitted_links"): - # like ifnull(customer, '')='' and ifnull(supplier, '')='' - conditions.append("ifnull(`tab{doctype}`.`{fieldname}`, '')=''".format(doctype=doctype, fieldname=df.fieldname)) - - return "( " + " and ".join(conditions) + " )" - - else: - conditions = [] - - for df in links.get("permitted_links"): - # like ifnull(customer, '')!='' or ifnull(supplier, '')!='' - conditions.append("ifnull(`tab{doctype}`.`{fieldname}`, '')!=''".format(doctype=doctype, fieldname=df.fieldname)) - - return "( " + " or ".join(conditions) + " )" - -def get_permitted_and_not_permitted_links(doctype): - permitted_links = [] - not_permitted_links = [] - - meta = frappe.get_meta(doctype) - - for df in meta.get_link_fields(): - if df.options not in ("Customer", "Supplier", "Company", "Sales Partner"): - continue - - if frappe.has_permission(df.options): - permitted_links.append(df) - else: - not_permitted_links.append(df) - - return { - "permitted_links": permitted_links, - "not_permitted_links": not_permitted_links - } - -def delete_contact_and_address(doctype, name): - for parenttype in ('Contact', 'Address'): - items = frappe.db.sql("""select parent from `tabDynamic Link` - where parenttype=%s and link_doctype=%s and link_name=%s""", - (parenttype, doctype, name)) - - for name in items: - doc = frappe.get_doc(parenttype, name) - if len(doc.links)==1: - doc.delete()