diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index fcbd10f606f..dd20632a651 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -9,6 +9,26 @@ frappe.ui.form.on('Accounting Dimension', { frappe.set_route("List", frm.doc.document_type); }); } + + let button = frm.doc.disabled ? "Enable" : "Disable"; + + frm.add_custom_button(__(button), function() { + + frm.set_value('disabled', 1 - frm.doc.disabled); + + frappe.call({ + method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension", + args: { + doc: frm.doc + }, + freeze: true, + callback: function(r) { + let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled"; + frm.save(); + frappe.show_alert({message:__(message), indicator:'green'}); + } + }); + }); }, document_type: function(frm) { @@ -21,13 +41,4 @@ frappe.ui.form.on('Accounting Dimension', { } }); }, - - disabled: function(frm) { - frappe.call({ - method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension", - args: { - doc: frm.doc - } - }); - } }); diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index 1e2bb925a1a..57543a0ef48 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -38,7 +38,8 @@ "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disable" + "label": "Disable", + "read_only": 1 }, { "default": "0", @@ -53,7 +54,7 @@ "label": "Mandatory For Profit and Loss Account" } ], - "modified": "2019-05-27 18:18:17.792726", + "modified": "2019-07-07 18:56:19.517450", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension", diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 91d03be493f..15ace7239ea 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -121,11 +121,11 @@ def delete_accounting_dimension(doc): @frappe.whitelist() def disable_dimension(doc): if frappe.flags.in_test: - frappe.enqueue(start_dimension_disabling, doc=doc) + toggle_disabling(doc=doc) else: - start_dimension_disabling(doc=doc) + frappe.enqueue(toggle_disabling, doc=doc) -def start_dimension_disabling(doc): +def toggle_disabling(doc): doc = json.loads(doc) if doc.get('disabled'): diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index 28807c41180..90cdf834c59 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py @@ -103,7 +103,7 @@ class BankReconciliation(Document): for d in self.get('payment_entries'): if d.clearance_date: if not d.payment_document: - frappe.throw(_("Row #{0}: Payment document is required to complete the trasaction")) + frappe.throw(_("Row #{0}: Payment document is required to complete the transaction")) if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date): frappe.throw(_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}") @@ -113,10 +113,8 @@ class BankReconciliation(Document): if not d.clearance_date: d.clearance_date = None - frappe.db.set_value(d.payment_document, d.payment_entry, "clearance_date", d.clearance_date) - frappe.db.sql("""update `tab{0}` set clearance_date = %s, modified = %s - where name=%s""".format(d.payment_document), - (d.clearance_date, nowdate(), d.payment_entry)) + payment_entry = frappe.get_doc(d.payment_document, d.payment_entry) + payment_entry.db_set('clearance_date', d.clearance_date) clearance_date_updated = True diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index dad75b4ba11..0d5456ece6c 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -21,9 +21,29 @@ frappe.ui.form.on('Exchange Rate Revaluation', { refresh: function(frm) { if(frm.doc.docstatus==1) { - frm.add_custom_button(__('Create Journal Entry'), function() { - return frm.events.make_jv(frm); - }); + frappe.db.get_value("Journal Entry Account", { + 'reference_type': 'Exchange Rate Revaluation', + 'reference_name': frm.doc.name, + 'docstatus': 1 + }, "sum(debit) as sum", (r) =>{ + let total_amt = 0; + frm.doc.accounts.forEach(d=> { + total_amt = total_amt + d['new_balance_in_base_currency']; + }); + if(total_amt === r.sum) { + frm.add_custom_button(__("Journal Entry"), function(){ + frappe.route_options = { + 'reference_type': 'Exchange Rate Revaluation', + 'reference_name': frm.doc.name + }; + frappe.set_route("List", "Journal Entry"); + }, __("View")); + } else { + frm.add_custom_button(__('Create Journal Entry'), function() { + return frm.events.make_jv(frm); + }); + } + }, 'Journal Entry'); } }, diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 333d39aae65..a232a953f31 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -848,6 +848,39 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "due_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Due Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -861,7 +894,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-07 07:05:00.366399", + "modified": "2019-05-01 07:05:00.366399", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 3132c937dd7..8fbddb9b0cc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -498,6 +498,7 @@ class JournalEntry(AccountsController): self.get_gl_dict({ "account": d.account, "party_type": d.party_type, + "due_date": self.due_date, "party": d.party, "against": d.against_account, "debit": flt(d.debit, d.precision("debit")), diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2c382c58f6a..f17b2cbeda4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -302,7 +302,7 @@ frappe.ui.form.on('Payment Entry', { }, () => frm.set_value("party_balance", r.message.party_balance), () => frm.set_value("party_name", r.message.party_name), - () => frm.events.get_outstanding_documents(frm), + () => frm.clear_table("references"), () => frm.events.hide_unhide_fields(frm), () => frm.events.set_dynamic_labels(frm), () => { @@ -323,9 +323,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_account_currency_and_balance(frm, frm.doc.paid_from, "paid_from_account_currency", "paid_from_account_balance", function(frm) { - if (frm.doc.payment_type == "Receive") { - frm.events.get_outstanding_documents(frm); - } else if (frm.doc.payment_type == "Pay") { + if (frm.doc.payment_type == "Pay") { frm.events.paid_amount(frm); } } @@ -337,9 +335,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_account_currency_and_balance(frm, frm.doc.paid_to, "paid_to_account_currency", "paid_to_account_balance", function(frm) { - if(frm.doc.payment_type == "Pay") { - frm.events.get_outstanding_documents(frm); - } else if (frm.doc.payment_type == "Receive") { + if (frm.doc.payment_type == "Receive") { if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { if(frm.doc.source_exchange_rate) { frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate); @@ -533,26 +529,87 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, - get_outstanding_documents: function(frm) { + get_outstanding_invoice: function(frm) { + const today = frappe.datetime.get_today(); + const fields = [ + {fieldtype:"Section Break", label: __("Posting Date")}, + {fieldtype:"Date", label: __("From Date"), + fieldname:"from_posting_date", default:frappe.datetime.add_days(today, -30)}, + {fieldtype:"Column Break"}, + {fieldtype:"Date", label: __("To Date"), fieldname:"to_posting_date", default:today}, + {fieldtype:"Section Break", label: __("Due Date")}, + {fieldtype:"Date", label: __("From Date"), fieldname:"from_due_date"}, + {fieldtype:"Column Break"}, + {fieldtype:"Date", label: __("To Date"), fieldname:"to_due_date"}, + {fieldtype:"Section Break", label: __("Outstanding Amount")}, + {fieldtype:"Float", label: __("Greater Than Amount"), + fieldname:"outstanding_amt_greater_than", default: 0}, + {fieldtype:"Column Break"}, + {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, + {fieldtype:"Section Break"}, + {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, + ]; + + frappe.prompt(fields, function(filters){ + frappe.flags.allocate_payment_amount = true; + frm.events.validate_filters_data(frm, filters); + frm.events.get_outstanding_documents(frm, filters); + }, __("Filters"), __("Get Outstanding Invoices")); + }, + + validate_filters_data: function(frm, filters) { + const fields = { + 'Posting Date': ['from_posting_date', 'to_posting_date'], + 'Due Date': ['from_posting_date', 'to_posting_date'], + 'Advance Amount': ['from_posting_date', 'to_posting_date'], + }; + + for (let key in fields) { + let from_field = fields[key][0]; + let to_field = fields[key][1]; + + if (filters[from_field] && !filters[to_field]) { + frappe.throw(__("Error: {0} is mandatory field", + [to_field.replace(/_/g, " ")] + )); + } else if (filters[from_field] && filters[from_field] > filters[to_field]) { + frappe.throw(__("{0}: {1} must be less than {2}", + [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")] + )); + } + } + }, + + get_outstanding_documents: function(frm, filters) { frm.clear_table("references"); - if(!frm.doc.party) return; + if(!frm.doc.party) { + return; + } frm.events.check_mandatory_to_fetch(frm); var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + var args = { + "posting_date": frm.doc.posting_date, + "company": frm.doc.company, + "party_type": frm.doc.party_type, + "payment_type": frm.doc.payment_type, + "party": frm.doc.party, + "party_account": frm.doc.payment_type=="Receive" ? frm.doc.paid_from : frm.doc.paid_to, + "cost_center": frm.doc.cost_center + } + + for (let key in filters) { + args[key] = filters[key]; + } + + frappe.flags.allocate_payment_amount = filters['allocate_payment_amount']; + return frappe.call({ method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_outstanding_reference_documents', args: { - args: { - "posting_date": frm.doc.posting_date, - "company": frm.doc.company, - "party_type": frm.doc.party_type, - "payment_type": frm.doc.payment_type, - "party": frm.doc.party, - "party_account": frm.doc.payment_type=="Receive" ? frm.doc.paid_from : frm.doc.paid_to, - "cost_center": frm.doc.cost_center - } + args:args }, callback: function(r, rt) { if(r.message) { @@ -608,25 +665,11 @@ frappe.ui.form.on('Payment Entry', { frm.events.allocate_party_amount_against_ref_docs(frm, (frm.doc.payment_type=="Receive" ? frm.doc.paid_amount : frm.doc.received_amount)); + } }); }, - allocate_payment_amount: function(frm) { - if(frm.doc.payment_type == 'Internal Transfer'){ - return - } - - if(frm.doc.references.length == 0){ - frm.events.get_outstanding_documents(frm); - } - if(frm.doc.payment_type == 'Internal Transfer') { - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount); - } else { - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); - } - }, - allocate_party_amount_against_ref_docs: function(frm, paid_amount) { var total_positive_outstanding_including_order = 0; var total_negative_outstanding = 0; @@ -677,7 +720,7 @@ frappe.ui.form.on('Payment Entry', { $.each(frm.doc.references || [], function(i, row) { row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount - if(frm.doc.allocate_payment_amount){ + if(frappe.flags.allocate_payment_amount){ if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { if(row.outstanding_amount >= allocated_positive_outstanding) { row.allocated_amount = allocated_positive_outstanding; @@ -958,7 +1001,7 @@ frappe.ui.form.on('Payment Entry', { }, () => { if(frm.doc.payment_type != "Internal") { - frm.events.get_outstanding_documents(frm); + frm.clear_table("references"); } } ]); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index fcaa570331f..a85eccd30af 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -40,7 +40,7 @@ "target_exchange_rate", "base_received_amount", "section_break_14", - "allocate_payment_amount", + "get_outstanding_invoice", "references", "section_break_34", "total_allocated_amount", @@ -325,19 +325,15 @@ "reqd": 1 }, { - "collapsible": 1, - "collapsible_depends_on": "references", "depends_on": "eval:(doc.party && doc.paid_from && doc.paid_to && doc.paid_amount && doc.received_amount)", "fieldname": "section_break_14", "fieldtype": "Section Break", "label": "Reference" }, { - "default": "1", - "depends_on": "eval:in_list(['Pay', 'Receive'], doc.payment_type)", - "fieldname": "allocate_payment_amount", - "fieldtype": "Check", - "label": "Allocate Payment Amount" + "fieldname": "get_outstanding_invoice", + "fieldtype": "Button", + "label": "Get Outstanding Invoice" }, { "fieldname": "references", @@ -570,7 +566,7 @@ } ], "is_submittable": 1, - "modified": "2019-05-25 22:02:40.575822", + "modified": "2019-05-27 15:53:21.108857", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ea76126d8cc..699f04675fe 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -574,8 +574,8 @@ def get_outstanding_reference_documents(args): # Get negative outstanding sales /purchase invoices negative_outstanding_invoices = [] if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), - args.get("party"), args.get("party_account"), party_account_currency, company_currency) + negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), + args.get("party_account"), args.get("company"), party_account_currency, company_currency) # Get positive outstanding sales /purchase invoices/ Fees condition = "" @@ -585,10 +585,23 @@ def get_outstanding_reference_documents(args): # Add cost center condition if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account(): - condition += " and cost_center='%s'" % args.get("cost_center") + condition += " and cost_center='%s'" % args.get("cost_center") + + date_fields_dict = { + 'posting_date': ['from_posting_date', 'to_posting_date'], + 'due_date': ['from_due_date', 'to_due_date'] + } + + for fieldname, date_fields in date_fields_dict.items(): + if args.get(date_fields[0]) and args.get(date_fields[1]): + condition += " and {0} between '{1}' and '{2}'".format(fieldname, + args.get(date_fields[0]), args.get(date_fields[1])) + + if args.get("company"): + condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"), - args.get("party_account"), condition=condition) + args.get("party_account"), filters=args, condition=condition, limit=100) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -606,12 +619,19 @@ def get_outstanding_reference_documents(args): orders_to_be_billed = [] if (args.get("party_type") != "Student"): orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), - args.get("party"), party_account_currency, company_currency) + args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args) - return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed + + if not data: + frappe.msgprint(_("No outstanding invoices found for the {0} {1}.") + .format(args.get("party_type").lower(), args.get("party"))) + + return data -def get_orders_to_be_billed(posting_date, party_type, party, party_account_currency, company_currency, cost_center=None): +def get_orders_to_be_billed(posting_date, party_type, party, + company, party_account_currency, company_currency, cost_center=None, filters=None): if party_type == "Customer": voucher_type = 'Sales Order' elif party_type == "Supplier": @@ -641,6 +661,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre where {party_type} = %s and docstatus = 1 + and company = %s and ifnull(status, "") != "Closed" and {ref_field} > advance_paid and abs(100 - per_billed) > 0.01 @@ -652,10 +673,14 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre "voucher_type": voucher_type, "party_type": scrub(party_type), "condition": condition - }), party, as_dict=True) + }), (party, company), as_dict=True) order_list = [] for d in orders: + if not (d.outstanding_amount >= filters.get("outstanding_amt_greater_than") + and d.outstanding_amount <= filters.get("outstanding_amt_less_than")): + continue + d["voucher_type"] = voucher_type # This assumes that the exchange rate required is the one in the SO d["exchange_rate"] = get_exchange_rate(party_account_currency, company_currency, posting_date) @@ -663,7 +688,8 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre return order_list -def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency, cost_center=None): +def get_negative_outstanding_invoices(party_type, party, party_account, + company, party_account_currency, company_currency, cost_center=None): voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" supplier_condition = "" if voucher_type == "Purchase Invoice": @@ -684,7 +710,8 @@ def get_negative_outstanding_invoices(party_type, party, party_account, party_ac from `tab{voucher_type}` where - {party_type} = %s and {party_account} = %s and docstatus = 1 and outstanding_amount < 0 + {party_type} = %s and {party_account} = %s and docstatus = 1 and + company = %s and outstanding_amount < 0 {supplier_condition} order by posting_date, name @@ -696,7 +723,7 @@ def get_negative_outstanding_invoices(party_type, party, party_account, party_ac "party_type": scrub(party_type), "party_account": "debit_to" if party_type == "Customer" else "credit_to", "cost_center": cost_center - }), (party, party_account), as_dict=True) + }), (party, party_account, company), as_dict=True) @frappe.whitelist() @@ -924,7 +951,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.paid_to_account_currency = party_account_currency if payment_type=="Pay" else bank.account_currency pe.paid_amount = paid_amount pe.received_amount = received_amount - pe.allocate_payment_amount = 1 pe.letter_head = doc.get("letter_head") if pe.party_type in ["Customer", "Supplier"]: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1b03896dab5..219d9899d28 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -337,7 +337,8 @@ class PurchaseInvoice(BuyingController): if not self.is_return: self.update_against_document_in_jv() self.update_billing_status_for_zero_amount_refdoc("Purchase Order") - self.update_billing_status_in_pr() + + self.update_billing_status_in_pr() # Updating stock ledger should always be called after updating prevdoc status, # because updating ordered qty in bin depends upon updated ordered qty in PO @@ -416,6 +417,7 @@ class PurchaseInvoice(BuyingController): "account": self.credit_to, "party_type": "Supplier", "party": self.supplier, + "due_date": self.due_date, "against": self.against_expense_account, "credit": grand_total_in_company_currency, "credit_in_account_currency": grand_total_in_company_currency \ @@ -773,7 +775,8 @@ class PurchaseInvoice(BuyingController): if not self.is_return: self.update_billing_status_for_zero_amount_refdoc("Purchase Order") - self.update_billing_status_in_pr() + + self.update_billing_status_in_pr() # Updating stock ledger should always be called after updating prevdoc status, # because updating ordered qty in bin depends upon updated ordered qty in PO diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 07494a27d6d..c3c3e2643ec 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -187,9 +187,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte method: "erpnext.selling.doctype.quotation.quotation.make_sales_invoice", source_doctype: "Quotation", target: me.frm, - setters: { - customer: me.frm.doc.customer || undefined, - }, + setters: [{ + fieldtype: 'Link', + label: __('Customer'), + options: 'Customer', + fieldname: 'party_name', + default: me.frm.doc.customer, + }], get_query_filters: { docstatus: 1, status: ["!=", "Lost"], diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 71bdc2aafed..f8bb971933a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -734,6 +734,7 @@ class SalesInvoice(SellingController): "account": self.debit_to, "party_type": "Customer", "party": self.customer, + "due_date": self.due_date, "against": self.against_income_account, "debit": grand_total_in_company_currency, "debit_in_account_currency": grand_total_in_company_currency \ diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 70f193e787b..f6a561f04f6 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -108,3 +108,14 @@ frappe.query_reports["Accounts Payable"] = { }); } } + +erpnext.dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Accounts Payable"].filters.splice(9, 0 ,{ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 06499adeeaf..ec4f0c983f3 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -92,3 +92,14 @@ frappe.query_reports["Accounts Payable Summary"] = { }); } } + +erpnext.dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Accounts Payable Summary"].filters.splice(9, 0 ,{ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 3661afe7977..2a45454baca 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -172,3 +172,14 @@ frappe.query_reports["Accounts Receivable"] = { }); } } + +erpnext.dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Accounts Receivable"].filters.splice(9, 0 ,{ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 29737484c70..0cda2c15dde 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, scrub from frappe.utils import getdate, nowdate, flt, cint, formatdate, cstr +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions class ReceivablePayableReport(object): def __init__(self, filters=None): @@ -553,6 +554,14 @@ class ReceivablePayableReport(object): conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) values += accounts + accounting_dimensions = get_accounting_dimensions() + + if accounting_dimensions: + for dimension in accounting_dimensions: + if self.filters.get(dimension): + conditions.append("{0} = %s".format(dimension)) + values.append(self.filters.get(dimension)) + return " and ".join(conditions), values def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher): diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index f9162adabd0..a7c0787fcd7 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -116,3 +116,14 @@ frappe.query_reports["Accounts Receivable Summary"] = { }); } } + +erpnext.dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Accounts Receivable Summary"].filters.splice(9, 0 ,{ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index b2072f06f12..f2a33a83ee3 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -63,9 +63,7 @@ frappe.query_reports["Budget Variance Report"] = { ] } -let dimension_filters = erpnext.get_dimension_filters(); - -dimension_filters.then((dimensions) => { +erpnext.dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]); }); diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js index 04221110930..03940f4b246 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.js +++ b/erpnext/accounts/report/cash_flow/cash_flow.js @@ -8,17 +8,19 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { // The last item in the array is the definition for Presentation Currency // filter. It won't be used in cash flow for now so we pop it. Please take // of this if you are working here. - frappe.query_reports["Cash Flow"]["filters"].pop(); - frappe.query_reports["Cash Flow"]["filters"].push({ - "fieldname": "accumulated_values", - "label": __("Accumulated Values"), - "fieldtype": "Check" - }); + frappe.query_reports["Cash Flow"]["filters"].splice(5, 1); - frappe.query_reports["Cash Flow"]["filters"].push({ - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check" - }); + frappe.query_reports["Cash Flow"]["filters"].push( + { + "fieldname": "accumulated_values", + "label": __("Accumulated Values"), + "fieldtype": "Check" + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check" + } + ); }); \ No newline at end of file diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 32af6440212..ea82575b808 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -159,9 +159,7 @@ frappe.query_reports["General Ledger"] = { ] } -let dimension_filters = erpnext.get_dimension_filters(); - -dimension_filters.then((dimensions) => { +erpnext.dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { frappe.query_reports["General Ledger"].filters.splice(15, 0 ,{ "fieldname": dimension["fieldname"], diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index 48d7361fe0a..ac11868cabb 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -93,4 +93,6 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss): else: chart["type"] = "line" + chart["fieldtype"] = "Currency" + return chart \ No newline at end of file diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 80b50b92c36..d6864b54f74 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.js +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js @@ -16,7 +16,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldname": "based_on", "label": __("Based On"), "fieldtype": "Select", - "options": "Cost Center\nProject", + "options": ["Cost Center", "Project"], "default": "Cost Center", "reqd": 1 }, @@ -104,5 +104,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "parent_field": "parent_account", "initial_depth": 3 } -}); + erpnext.dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]); + }); + }); +}); diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py index a0d8c5f0e4e..6e9b31f2f6d 100644 --- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py +++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py @@ -24,8 +24,17 @@ def get_accounts_data(based_on, company): if based_on == 'cost_center': return frappe.db.sql("""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt from `tabCost Center` where company=%s order by name""", company, as_dict=True) - else: + elif based_on == 'project': return frappe.get_all('Project', fields = ["name"], filters = {'company': company}, order_by = 'name') + else: + filters = {} + doctype = frappe.unscrub(based_on) + has_company = frappe.db.has_column(doctype, 'company') + + if has_company: + filters.update({'company': company}) + + return frappe.get_all(doctype, fields = ["name"], filters = filters, order_by = 'name') def get_data(accounts, filters, based_on): if not accounts: @@ -42,7 +51,7 @@ def get_data(accounts, filters, based_on): accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, total_row, parent_children_map, based_on) - data = filter_out_zero_value_rows(data, parent_children_map, + data = filter_out_zero_value_rows(data, parent_children_map, show_zero_values=filters.get("show_zero_values")) return data @@ -112,14 +121,14 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on): for key in value_fields: row[key] = flt(d.get(key, 0.0), 3) - + if abs(row[key]) >= 0.005: # ignore zero values has_value = True row["has_value"] = has_value data.append(row) - + data.extend([{},total_row]) return data @@ -174,7 +183,7 @@ def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_ if from_date: additional_conditions.append("and posting_date >= %(from_date)s") - gl_entries = frappe.db.sql("""select posting_date, {based_on} as based_on, debit, credit, + gl_entries = frappe.db.sql("""select posting_date, {based_on} as based_on, debit, credit, is_opening, (select root_type from `tabAccount` where name = account) as type from `tabGL Entry` where company=%(company)s {additional_conditions} diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js index 0b48882ca90..442aa1262e3 100644 --- a/erpnext/accounts/report/sales_register/sales_register.js +++ b/erpnext/accounts/report/sales_register/sales_register.js @@ -67,3 +67,14 @@ frappe.query_reports["Sales Register"] = { } ] } + +erpnext.dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Sales Register"].filters.splice(7, 0 ,{ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index de60995ca22..d08056f6f9b 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import flt from frappe import msgprint, _ +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions def execute(filters=None): return _execute(filters) @@ -163,6 +164,16 @@ def get_conditions(filters): where parent=`tabSales Invoice`.name and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)""" + accounting_dimensions = get_accounting_dimensions() + + if accounting_dimensions: + for dimension in accounting_dimensions: + if filters.get(dimension): + conditions += """ and exists(select name from `tabSales Invoice Item` + where parent=`tabSales Invoice`.name + and ifnull(`tabSales Invoice Item`.{0}, '') = %({0})s)""".format(dimension) + + return conditions def get_invoices(filters, additional_query_columns): diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index 8bc72807b33..73d2ab38989 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -96,9 +96,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { } }); -let dimension_filters = erpnext.get_dimension_filters(); - -dimension_filters.then((dimensions) => { +erpnext.dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{ "fieldname": dimension["fieldname"], diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7a1f6c57c2c..542c7e4e524 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -628,7 +628,7 @@ def get_held_invoices(party_type, party): return held_invoices -def get_outstanding_invoices(party_type, party, account, condition=None): +def get_outstanding_invoices(party_type, party, account, condition=None, filters=None): outstanding_invoices = [] precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2 @@ -644,7 +644,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None): invoice_list = frappe.db.sql(""" select - voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount + voucher_no, voucher_type, posting_date, due_date, + ifnull(sum({dr_or_cr}), 0) as invoice_amount from `tabGL Entry` where @@ -677,7 +678,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): """.format(payment_dr_or_cr=payment_dr_or_cr), { "party_type": party_type, "party": party, - "account": account, + "account": account }, as_dict=True) pe_map = frappe._dict() @@ -688,10 +689,12 @@ def get_outstanding_invoices(party_type, party, account, condition=None): payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0) outstanding_amount = flt(d.invoice_amount - payment_amount, precision) if outstanding_amount > 0.5 / (10**precision): - if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: - due_date = frappe.db.get_value( - d.voucher_type, d.voucher_no, "posting_date" if party_type == "Employee" else "due_date") + if (filters.get("outstanding_amt_greater_than") and + not (outstanding_amount >= filters.get("outstanding_amt_greater_than") and + outstanding_amount <= filters.get("outstanding_amt_less_than"))): + continue + if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: outstanding_invoices.append( frappe._dict({ 'voucher_no': d.voucher_no, @@ -700,7 +703,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): 'invoice_amount': flt(d.invoice_amount), 'payment_amount': payment_amount, 'outstanding_amount': outstanding_amount, - 'due_date': due_date + 'due_date': d.due_date }) ) diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py index 84aa8474d38..f5e48b3b144 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -281,9 +281,9 @@ def get_data(): }, { "type": "report", + "is_query_report": True, "name": "Item Shortage Report", - "route": "#Report/Bin/Item Shortage Report", - "doctype": "Purchase Receipt" + "doctype": "Bin" }, { "type": "report", diff --git a/erpnext/config/support.py b/erpnext/config/support.py index 0301bb3e196..36b42141963 100644 --- a/erpnext/config/support.py +++ b/erpnext/config/support.py @@ -97,4 +97,15 @@ def get_data(): }, ] }, + { + "label": _("Settings"), + "icon": "fa fa-list", + "items": [ + { + "type": "doctype", + "name": "Support Settings", + "label": _("Support Settings"), + }, + ] + }, ] \ No newline at end of file diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d74bc0ea18c..47c9f0a4ce3 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -206,10 +206,11 @@ def bom(doctype, txt, searchfield, start, page_len, filters): if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), idx desc, name limit %(start)s, %(page_len)s """.format( - fcond=get_filters_cond(doctype, filters, conditions), + fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype), - key=searchfield), { - 'txt': '%' + txt + '%', + key=frappe.db.escape(searchfield)), + { + 'txt': "%"+frappe.db.escape(txt)+"%", '_txt': txt.replace("%", ""), 'start': start or 0, 'page_len': page_len or 20 diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 8cf11f785be..2fddcdf24c5 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -75,7 +75,7 @@ def validate_returned_items(doc): items_returned = False for d in doc.get("items"): - if flt(d.qty) < 0 or d.get('received_qty') < 0: + if d.item_code and (flt(d.qty) < 0 or d.get('received_qty') < 0): if d.item_code not in valid_items: frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}") .format(d.idx, d.item_code, doc.doctype, doc.return_against)) @@ -107,6 +107,9 @@ def validate_returned_items(doc): items_returned = True + elif d.item_name: + items_returned = True + if not items_returned: frappe.throw(_("Atleast one item should be entered with negative quantity in return document")) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index f70870b3ad0..b193ac2b6d3 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -294,7 +294,7 @@ class StatusUpdater(Document): frappe.db.sql("""update `tab%(target_parent_dt)s` set %(target_parent_field)s = round( ifnull((select - ifnull(sum(if(%(target_ref_field)s > %(target_field)s, abs(%(target_field)s), abs(%(target_ref_field)s))), 0) + ifnull(sum(if(abs(%(target_ref_field)s) > abs(%(target_field)s), abs(%(target_field)s), abs(%(target_ref_field)s))), 0) / sum(abs(%(target_ref_field)s)) * 100 from `tab%(target_dt)s` where parent="%(name)s" having sum(abs(%(target_ref_field)s)) > 0), 0), 6) %(update_modified)s diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py index 29a1a2b0bb8..96a533ee10c 100644 --- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py +++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py @@ -7,7 +7,9 @@ import frappe from frappe import _ from frappe.model.document import Document from requests_oauthlib import OAuth2Session -import json, requests +import json +import requests +import traceback from erpnext import encode_company_abbr # QuickBooks requires a redirect URL, User will be redirect to this URL @@ -32,7 +34,6 @@ def callback(*args, **kwargs): class QuickBooksMigrator(Document): def __init__(self, *args, **kwargs): super(QuickBooksMigrator, self).__init__(*args, **kwargs) - from pprint import pprint self.oauth = OAuth2Session( client_id=self.client_id, redirect_uri=self.redirect_url, @@ -46,7 +47,9 @@ class QuickBooksMigrator(Document): if self.company: # We need a Cost Center corresponding to the selected erpnext Company self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center') - self.default_warehouse = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})[0]["name"] + company_warehouses = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0}) + if company_warehouses: + self.default_warehouse = company_warehouses[0].name if self.authorization_endpoint: self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0] @@ -218,7 +221,7 @@ class QuickBooksMigrator(Document): def _fetch_general_ledger(self): try: - query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint ,self.quickbooks_company_id) + query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint, self.quickbooks_company_id) response = self._get(query_uri, params={ "columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]), @@ -493,17 +496,17 @@ class QuickBooksMigrator(Document): "account_currency": customer["CurrencyRef"]["value"], "company": self.company, })[0]["name"] - except Exception as e: + except Exception: receivable_account = None erpcustomer = frappe.get_doc({ "doctype": "Customer", "quickbooks_id": customer["Id"], - "customer_name" : encode_company_abbr(customer["DisplayName"], self.company), - "customer_type" : "Individual", - "customer_group" : "Commercial", + "customer_name": encode_company_abbr(customer["DisplayName"], self.company), + "customer_type": "Individual", + "customer_group": "Commercial", "default_currency": customer["CurrencyRef"]["value"], "accounts": [{"company": self.company, "account": receivable_account}], - "territory" : "All Territories", + "territory": "All Territories", "company": self.company, }).insert() if "BillAddr" in customer: @@ -521,7 +524,7 @@ class QuickBooksMigrator(Document): item_dict = { "doctype": "Item", "quickbooks_id": item["Id"], - "item_code" : encode_company_abbr(item["Name"], self.company), + "item_code": encode_company_abbr(item["Name"], self.company), "stock_uom": "Unit", "is_stock_item": 0, "item_group": "All Item Groups", @@ -549,14 +552,14 @@ class QuickBooksMigrator(Document): erpsupplier = frappe.get_doc({ "doctype": "Supplier", "quickbooks_id": vendor["Id"], - "supplier_name" : encode_company_abbr(vendor["DisplayName"], self.company), - "supplier_group" : "All Supplier Groups", + "supplier_name": encode_company_abbr(vendor["DisplayName"], self.company), + "supplier_group": "All Supplier Groups", "company": self.company, }).insert() if "BillAddr" in vendor: self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing") if "ShipAddr" in vendor: - self._create_address(erpsupplier, "Supplier",vendor["ShipAddr"], "Shipping") + self._create_address(erpsupplier, "Supplier", vendor["ShipAddr"], "Shipping") except Exception as e: self._log_error(e) @@ -829,7 +832,7 @@ class QuickBooksMigrator(Document): "currency": invoice["CurrencyRef"]["value"], "conversion_rate": invoice.get("ExchangeRate", 1), "posting_date": invoice["TxnDate"], - "due_date": invoice.get("DueDate", invoice["TxnDate"]), + "due_date": invoice.get("DueDate", invoice["TxnDate"]), "credit_to": credit_to_account, "supplier": frappe.get_all("Supplier", filters={ @@ -1200,7 +1203,7 @@ class QuickBooksMigrator(Document): def _create_address(self, entity, doctype, address, address_type): - try : + try: if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}): frappe.get_doc({ "doctype": "Address", @@ -1252,8 +1255,6 @@ class QuickBooksMigrator(Document): def _log_error(self, execption, data=""): - import json, traceback - traceback.print_exc() frappe.log_error(title="QuickBooks Migration Error", message="\n".join([ "Data", diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py deleted file mode 100644 index b6c650207f0..00000000000 --- a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe, math -from frappe import _ -from frappe.utils import flt, rounded -from frappe.model.mapper import get_mapped_doc -from frappe.model.document import Document - -from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method - -class EmployeeLoanApplication(Document): - def validate(self): - check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods) - self.validate_loan_amount() - self.get_repayment_details() - - def validate_loan_amount(self): - maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount') - if maximum_loan_limit and self.loan_amount > maximum_loan_limit: - frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit)) - - def get_repayment_details(self): - if self.repayment_method == "Repay Over Number of Periods": - self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods) - - if self.repayment_method == "Repay Fixed Amount per Period": - monthly_interest_rate = flt(self.rate_of_interest) / (12 *100) - if monthly_interest_rate: - monthly_interest_amount = self.loan_amount * monthly_interest_rate - if monthly_interest_amount >= self.repayment_amount: - frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}"). - format(self.repayment_amount, monthly_interest_amount)) - - self.repayment_periods = math.ceil((math.log(self.repayment_amount) - - math.log(self.repayment_amount - (monthly_interest_amount))) / - (math.log(1 + monthly_interest_rate))) - else: - self.repayment_periods = self.loan_amount / self.repayment_amount - - self.calculate_payable_amount() - - def calculate_payable_amount(self): - balance_amount = self.loan_amount - self.total_payable_amount = 0 - self.total_payable_interest = 0 - - while(balance_amount > 0): - interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) - balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount) - - self.total_payable_interest += interest_amount - - self.total_payable_amount = self.loan_amount + self.total_payable_interest - -@frappe.whitelist() -def make_employee_loan(source_name, target_doc = None): - doclist = get_mapped_doc("Employee Loan Application", source_name, { - "Employee Loan Application": { - "doctype": "Employee Loan", - "validation": { - "docstatus": ["=", 1] - } - } - }, target_doc) - - return doclist \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index 5e7f276ccc8..35c9f728b61 100644 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py @@ -12,7 +12,7 @@ from erpnext.hr.doctype.employee_onboarding.employee_onboarding import Incomplet class TestEmployeeOnboarding(unittest.TestCase): def test_employee_onboarding_incomplete_task(self): if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}): - return frappe.get_doc('Employee Onboarding', {'employee_name': 'Test Researcher'}) + frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'}) _set_up() applicant = get_job_applicant() onboarding = frappe.new_doc('Employee Onboarding') @@ -39,9 +39,10 @@ class TestEmployeeOnboarding(unittest.TestCase): # complete the task project = frappe.get_doc('Project', onboarding.project) - project.load_tasks() - project.tasks[0].status = 'Completed' - project.save() + for task in frappe.get_all('Task', dict(project=project.name)): + task = frappe.get_doc('Task', task.name) + task.status = 'Completed' + task.save() # make employee onboarding.reload() @@ -71,4 +72,3 @@ def _set_up(): project = "Employee Onboarding : Test Researcher - test@researcher.com" frappe.db.sql("delete from tabProject where name=%s", project) frappe.db.sql("delete from tabTask where project=%s", project) - frappe.db.sql("delete from `tabProject Task` where parent=%s", project) diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 92fdc094431..6618a4f7c57 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -10,33 +10,36 @@ from erpnext.accounts.doctype.account.test_account import create_account test_records = frappe.get_test_records('Expense Claim') test_dependencies = ['Employee'] +company_name = '_Test Company 4' + class TestExpenseClaim(unittest.TestCase): def test_total_expense_claim_for_project(self): frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """) - frappe.db.sql("""delete from `tabProject Task` where parent = "_Test Project 1" """) frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """) - frappe.db.sql("delete from `tabExpense Claim` where project='_Test Project 1'") + frappe.db.sql("update `tabExpense Claim` set project = '', task = ''") frappe.get_doc({ "project_name": "_Test Project 1", - "doctype": "Project", + "doctype": "Project" }).save() - task = frappe.get_doc({ - "doctype": "Task", - "subject": "_Test Project Task 1", - "project": "_Test Project 1" - }).save() + task = frappe.get_doc(dict( + doctype = 'Task', + subject = '_Test Project Task 1', + status = 'Open', + project = '_Test Project 1' + )).insert() - task_name = frappe.db.get_value("Task", {"project": "_Test Project 1"}) - payable_account = get_payable_account("Wind Power LLC") - make_expense_claim(payable_account, 300, 200, "Wind Power LLC","Travel Expenses - WP", "_Test Project 1", task_name) + task_name = task.name + payable_account = get_payable_account(company_name) + + make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", "_Test Project 1", task_name) self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200) - expense_claim2 = make_expense_claim(payable_account, 600, 500, "Wind Power LLC", "Travel Expenses - WP","_Test Project 1", task_name) + expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC4","_Test Project 1", task_name) self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700) @@ -48,8 +51,8 @@ class TestExpenseClaim(unittest.TestCase): self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200) def test_expense_claim_status(self): - payable_account = get_payable_account("Wind Power LLC") - expense_claim = make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP") + payable_account = get_payable_account(company_name) + expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4") je_dict = make_bank_entry("Expense Claim", expense_claim.name) je = frappe.get_doc(je_dict) @@ -66,9 +69,9 @@ class TestExpenseClaim(unittest.TestCase): self.assertEqual(expense_claim.status, "Unpaid") def test_expense_claim_gl_entry(self): - payable_account = get_payable_account("Wind Power LLC") + payable_account = get_payable_account(company_name) taxes = generate_taxes() - expense_claim = make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP", do_not_submit=True, taxes=taxes) + expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", do_not_submit=True, taxes=taxes) expense_claim.submit() gl_entries = frappe.db.sql("""select account, debit, credit @@ -78,9 +81,9 @@ class TestExpenseClaim(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - ['CGST - WP',10.0, 0.0], - [payable_account, 0.0, 210.0], - ["Travel Expenses - WP", 200.0, 0.0] + ['CGST - _TC4',18.0, 0.0], + [payable_account, 0.0, 218.0], + ["Travel Expenses - _TC4", 200.0, 0.0] ]) for gle in gl_entries: @@ -89,14 +92,14 @@ class TestExpenseClaim(unittest.TestCase): self.assertEquals(expected_values[gle.account][2], gle.credit) def test_rejected_expense_claim(self): - payable_account = get_payable_account("Wind Power LLC") + payable_account = get_payable_account(company_name) expense_claim = frappe.get_doc({ "doctype": "Expense Claim", "employee": "_T-Employee-00001", "payable_account": payable_account, "approval_status": "Rejected", "expenses": - [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "amount": 300, "sanctioned_amount": 200 }] + [{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }] }) expense_claim.submit() @@ -111,9 +114,9 @@ def get_payable_account(company): def generate_taxes(): parent_account = frappe.db.get_value('Account', - {'company': "Wind Power LLC", 'is_group':1, 'account_type': 'Tax'}, + {'company': company_name, 'is_group':1, 'account_type': 'Tax'}, 'name') - account = create_account(company="Wind Power LLC", account_name="CGST", account_type="Tax", parent_account=parent_account) + account = create_account(company=company_name, account_name="CGST", account_type="Tax", parent_account=parent_account) return {'taxes':[{ "account_head": account, "rate": 0, @@ -124,15 +127,18 @@ def generate_taxes(): def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None): employee = frappe.db.get_value("Employee", {"status": "Active"}) + currency = frappe.db.get_value('Company', company, 'default_currency') expense_claim = { "doctype": "Expense Claim", "employee": employee, "payable_account": payable_account, "approval_status": "Approved", "company": company, + 'currency': currency, "expenses": [{"expense_type": "Travel", "default_account": account, + 'currency': currency, "amount": amount, "sanctioned_amount": sanctioned_amount}]} if taxes: diff --git a/erpnext/hr/doctype/loan/loan.js b/erpnext/hr/doctype/loan/loan.js index e1b41786f47..3f5c30c4758 100644 --- a/erpnext/hr/doctype/loan/loan.js +++ b/erpnext/hr/doctype/loan/loan.js @@ -39,31 +39,19 @@ frappe.ui.form.on('Loan', { }, refresh: function (frm) { - if (frm.doc.docstatus == 1 && frm.doc.status == "Sanctioned") { - frm.add_custom_button(__('Create Disbursement Entry'), function() { - frm.trigger("make_jv"); - }) - } - if (frm.doc.repayment_schedule) { - let total_amount_paid = 0; - $.each(frm.doc.repayment_schedule || [], function(i, row) { - if (row.paid) { - total_amount_paid += row.total_payment; - } - }); - frm.set_value("total_amount_paid", total_amount_paid); -; } - if (frm.doc.docstatus == 1 && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) { - frm.add_custom_button(__('Create Repayment Entry'), function() { - frm.trigger("make_repayment_entry"); - }) + if (frm.doc.docstatus == 1) { + if (frm.doc.status == "Sanctioned") { + frm.add_custom_button(__('Create Disbursement Entry'), function() { + frm.trigger("make_jv"); + }).addClass("btn-primary"); + } else if (frm.doc.status == "Disbursed" && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) { + frm.add_custom_button(__('Create Repayment Entry'), function() { + frm.trigger("make_repayment_entry"); + }).addClass("btn-primary"); + } } frm.trigger("toggle_fields"); }, - status: function (frm) { - frm.toggle_reqd("disbursement_date", frm.doc.status == 'Disbursed') - frm.toggle_reqd("repayment_start_date", frm.doc.status == 'Disbursed') - }, make_jv: function (frm) { frappe.call({ diff --git a/erpnext/hr/doctype/loan/loan.json b/erpnext/hr/doctype/loan/loan.json index 587b3010ca0..505b601edd5 100644 --- a/erpnext/hr/doctype/loan/loan.json +++ b/erpnext/hr/doctype/loan/loan.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, @@ -20,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicant_type", "fieldtype": "Select", "hidden": 0, @@ -53,6 +55,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicant", "fieldtype": "Dynamic Link", "hidden": 0, @@ -86,6 +89,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicant_name", "fieldtype": "Data", "hidden": 0, @@ -118,6 +122,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_application", "fieldtype": "Link", "hidden": 0, @@ -151,6 +156,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_type", "fieldtype": "Link", "hidden": 0, @@ -184,6 +190,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, @@ -215,7 +222,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "", + "default": "Today", + "fetch_if_empty": 0, "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -248,6 +256,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", "hidden": 0, @@ -282,6 +291,7 @@ "collapsible": 0, "columns": 0, "default": "Sanctioned", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, @@ -299,7 +309,7 @@ "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": 0, @@ -316,6 +326,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.applicant_type==\"Employee\"", + "fetch_if_empty": 0, "fieldname": "repay_from_salary", "fieldtype": "Check", "hidden": 0, @@ -348,6 +359,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", "hidden": 0, @@ -380,6 +392,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_amount", "fieldtype": "Currency", "hidden": 0, @@ -415,6 +428,7 @@ "columns": 0, "default": "", "fetch_from": "loan_type.rate_of_interest", + "fetch_if_empty": 0, "fieldname": "rate_of_interest", "fieldtype": "Percent", "hidden": 0, @@ -448,6 +462,8 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:doc.status==\"Disbursed\"", + "fetch_if_empty": 0, "fieldname": "disbursement_date", "fieldtype": "Date", "hidden": 0, @@ -480,6 +496,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "repayment_start_date", "fieldtype": "Date", "hidden": 0, @@ -499,7 +516,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -512,6 +529,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_11", "fieldtype": "Column Break", "hidden": 0, @@ -544,6 +562,7 @@ "collapsible": 0, "columns": 0, "default": "Repay Over Number of Periods", + "fetch_if_empty": 0, "fieldname": "repayment_method", "fieldtype": "Select", "hidden": 0, @@ -579,6 +598,7 @@ "columns": 0, "default": "", "depends_on": "", + "fetch_if_empty": 0, "fieldname": "repayment_periods", "fieldtype": "Int", "hidden": 0, @@ -613,6 +633,7 @@ "columns": 0, "default": "", "depends_on": "", + "fetch_if_empty": 0, "fieldname": "monthly_repayment_amount", "fieldtype": "Currency", "hidden": 0, @@ -646,6 +667,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "account_info", "fieldtype": "Section Break", "hidden": 0, @@ -678,6 +700,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "mode_of_payment", "fieldtype": "Link", "hidden": 0, @@ -711,6 +734,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "payment_account", "fieldtype": "Link", "hidden": 0, @@ -744,6 +768,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_9", "fieldtype": "Column Break", "hidden": 0, @@ -775,6 +800,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_account", "fieldtype": "Link", "hidden": 0, @@ -808,6 +834,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "interest_income_account", "fieldtype": "Link", "hidden": 0, @@ -841,6 +868,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_15", "fieldtype": "Section Break", "hidden": 0, @@ -873,6 +901,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "repayment_schedule", "fieldtype": "Table", "hidden": 0, @@ -906,6 +935,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_17", "fieldtype": "Section Break", "hidden": 0, @@ -939,6 +969,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "total_payment", "fieldtype": "Currency", "hidden": 0, @@ -972,6 +1003,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_19", "fieldtype": "Column Break", "hidden": 0, @@ -1004,6 +1036,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "total_interest_payable", "fieldtype": "Currency", "hidden": 0, @@ -1037,6 +1070,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "total_amount_paid", "fieldtype": "Currency", "hidden": 0, @@ -1070,6 +1104,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", "hidden": 0, @@ -1106,7 +1141,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 16:15:53.267145", + "modified": "2019-07-10 13:04:20.953694", "modified_by": "Administrator", "module": "HR", "name": "Loan", @@ -1149,7 +1184,6 @@ "set_user_permissions": 0, "share": 0, "submit": 0, - "user_permission_doctypes": "[\"Employee\"]", "write": 0 } ], diff --git a/erpnext/hr/doctype/loan/loan.py b/erpnext/hr/doctype/loan/loan.py index 58c9b8f6676..a803863124d 100644 --- a/erpnext/hr/doctype/loan/loan.py +++ b/erpnext/hr/doctype/loan/loan.py @@ -6,29 +6,33 @@ from __future__ import unicode_literals import frappe, math, json import erpnext from frappe import _ -from frappe.utils import flt, rounded, add_months, nowdate +from frappe.utils import flt, rounded, add_months, nowdate, getdate from erpnext.controllers.accounts_controller import AccountsController class Loan(AccountsController): def validate(self): - check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods) + validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods) + self.set_missing_fields() + self.make_repayment_schedule() + self.set_repayment_period() + self.calculate_totals() + + def set_missing_fields(self): 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) + if self.status == "Repaid/Closed": self.total_amount_paid = self.total_payment - if self.status == 'Disbursed' and self.repayment_start_date < self.disbursement_date: - frappe.throw(_("Repayment Start Date cannot be before Disbursement Date.")) - if self.status == "Disbursed": - self.make_repayment_schedule() - self.set_repayment_period() - self.calculate_totals() def make_jv_entry(self): self.check_permission('write') @@ -105,20 +109,31 @@ def update_total_amount_paid(doc): frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid) def update_disbursement_status(doc): - disbursement = frappe.db.sql("""select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount - from `tabGL Entry` where account = %s and against_voucher_type = 'Loan' and against_voucher = %s""", - (doc.payment_account, doc.name), as_dict=1)[0] - if disbursement.disbursed_amount == doc.loan_amount: - frappe.db.set_value("Loan", doc.name , "status", "Disbursed") - if disbursement.disbursed_amount == 0: - frappe.db.set_value("Loan", doc.name , "status", "Sanctioned") - if disbursement.disbursed_amount > doc.loan_amount: - frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount)) - if disbursement.disbursed_amount > 0: - frappe.db.set_value("Loan", doc.name , "disbursement_date", disbursement.posting_date) - frappe.db.set_value("Loan", doc.name , "repayment_start_date", disbursement.posting_date) + disbursement = frappe.db.sql(""" + select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount + from `tabGL Entry` + where account = %s and against_voucher_type = 'Loan' and against_voucher = %s + """, (doc.payment_account, doc.name), as_dict=1)[0] -def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods): + disbursement_date = None + if not disbursement or disbursement.disbursed_amount == 0: + status = "Sanctioned" + elif disbursement.disbursed_amount == doc.loan_amount: + disbursement_date = disbursement.posting_date + status = "Disbursed" + elif disbursement.disbursed_amount > doc.loan_amount: + frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount)) + + if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")): + frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date")) + + frappe.db.sql(""" + update `tabLoan` + set status = %s, disbursement_date = %s + where name = %s + """, (status, disbursement_date, doc.name)) + +def validate_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")) @@ -222,4 +237,4 @@ def make_jv_entry(loan, company, loan_account, applicant_type, applicant, loan_a "reference_name": loan, }) journal_entry.set("accounts", account_amt_list) - return journal_entry.as_dict() \ No newline at end of file + return journal_entry.as_dict() diff --git a/erpnext/hr/doctype/loan_application/loan_application.js b/erpnext/hr/doctype/loan_application/loan_application.js index febcbd88e76..a73b62a894e 100644 --- a/erpnext/hr/doctype/loan_application/loan_application.js +++ b/erpnext/hr/doctype/loan_application/loan_application.js @@ -23,9 +23,8 @@ frappe.ui.form.on('Loan Application', { }, add_toolbar_buttons: function(frm) { if (frm.doc.status == "Approved") { - frm.add_custom_button(__('Loan'), function() { + frm.add_custom_button(__('Create Loan'), function() { frappe.call({ - type: "GET", method: "erpnext.hr.doctype.loan_application.loan_application.make_loan", args: { "source_name": frm.doc.name @@ -37,7 +36,7 @@ frappe.ui.form.on('Loan Application', { } } }); - }) + }).addClass("btn-primary"); } } }); diff --git a/erpnext/hr/doctype/loan_application/loan_application.py b/erpnext/hr/doctype/loan_application/loan_application.py index 706c9646c73..5dbcf15eac1 100644 --- a/erpnext/hr/doctype/loan_application/loan_application.py +++ b/erpnext/hr/doctype/loan_application/loan_application.py @@ -9,11 +9,11 @@ from frappe.utils import flt, rounded from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, check_repayment_method +from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, validate_repayment_method class LoanApplication(Document): def validate(self): - check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods) + validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods) self.validate_loan_amount() self.get_repayment_details() @@ -29,14 +29,14 @@ class LoanApplication(Document): if self.repayment_method == "Repay Fixed Amount per Period": monthly_interest_rate = flt(self.rate_of_interest) / (12 *100) if monthly_interest_rate: - self.repayment_periods = math.ceil((math.log(self.repayment_amount) - + 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))) else: self.repayment_periods = self.loan_amount / self.repayment_amount self.calculate_payable_amount() - + def calculate_payable_amount(self): balance_amount = self.loan_amount self.total_payable_amount = 0 @@ -47,9 +47,9 @@ class LoanApplication(Document): balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount) self.total_payable_interest += interest_amount - + self.total_payable_amount = self.loan_amount + self.total_payable_interest - + @frappe.whitelist() def make_loan(source_name, target_doc = None): doclist = get_mapped_doc("Loan Application", source_name, { diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 75eb7943868..766f6754b65 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -594,6 +594,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty, item.description, item.image, + bom.project, item.stock_uom, item.allow_alternative_item, item_default.default_warehouse, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bd41c7e8840..571c2dc75b4 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -615,4 +615,7 @@ erpnext.patches.v11_1.set_missing_opportunity_from erpnext.patches.v12_0.set_quotation_status erpnext.patches.v12_0.set_priority_for_support erpnext.patches.v12_0.delete_priority_property_setter +execute:frappe.delete_doc("DocType", "Project Task") +erpnext.patches.v11_1.update_default_supplier_in_item_defaults +erpnext.patches.v12_0.update_due_date_in_gle erpnext.patches.v12_0.add_default_buying_selling_terms_in_company diff --git a/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py new file mode 100644 index 00000000000..347dec1f74d --- /dev/null +++ b/erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py @@ -0,0 +1,25 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + ''' + default supplier was not set in the item defaults for multi company instance, + this patch will set the default supplier + + ''' + if not frappe.db.has_column('Item', 'default_supplier'): + return + + frappe.reload_doc('stock', 'doctype', 'item_default') + frappe.reload_doc('stock', 'doctype', 'item') + + companies = frappe.get_all("Company") + if len(companies) > 1: + frappe.db.sql(""" UPDATE `tabItem Default`, `tabItem` + SET `tabItem Default`.default_supplier = `tabItem`.default_supplier + WHERE + `tabItem Default`.parent = `tabItem`.name and `tabItem Default`.default_supplier is null + and `tabItem`.default_supplier is not null and `tabItem`.default_supplier != '' """) \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_priority_for_support.py b/erpnext/patches/v12_0/set_priority_for_support.py index cc290396f84..5096ed4c3c2 100644 --- a/erpnext/patches/v12_0/set_priority_for_support.py +++ b/erpnext/patches/v12_0/set_priority_for_support.py @@ -33,19 +33,23 @@ def set_priorities_service_level(): service_level_priorities = frappe.get_list("Service Level", fields=["name", "priority", "response_time", "response_time_period", "resolution_time", "resolution_time_period"]) frappe.reload_doc("support", "doctype", "service_level") + frappe.reload_doc("support", "doctype", "support_settings") + frappe.db.set_value('Support Settings', None, 'track_service_level_agreement', 1) for service_level in service_level_priorities: if service_level: doc = frappe.get_doc("Service Level", service_level.name) - doc.append("priorities", { - "priority": service_level.priority, - "default_priority": 1, - "response_time": service_level.response_time, - "response_time_period": service_level.response_time_period, - "resolution_time": service_level.resolution_time, - "resolution_time_period": service_level.resolution_time_period - }) - doc.save(ignore_permissions=True) + if not doc.priorities: + doc.append("priorities", { + "priority": service_level.priority, + "default_priority": 1, + "response_time": service_level.response_time, + "response_time_period": service_level.response_time_period, + "resolution_time": service_level.resolution_time, + "resolution_time_period": service_level.resolution_time_period + }) + doc.flags.ignore_validate = True + doc.save(ignore_permissions=True) except frappe.db.TableMissingError: frappe.reload_doc("support", "doctype", "service_level") @@ -73,6 +77,7 @@ def set_priorities_service_level_agreement(): "resolution_time": service_level_agreement.resolution_time, "resolution_time_period": service_level_agreement.resolution_time_period }) + doc.flags.ignore_validate = True doc.save(ignore_permissions=True) except frappe.db.TableMissingError: frappe.reload_doc("support", "doctype", "service_level_agreement") \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py index 32b8177130b..70f65097dc3 100644 --- a/erpnext/patches/v12_0/set_task_status.py +++ b/erpnext/patches/v12_0/set_task_status.py @@ -2,10 +2,9 @@ import frappe def execute(): frappe.reload_doctype('Task') - frappe.reload_doctype('Project Task') # add "Completed" if customized - for doctype in ('Task', 'Project Task'): + for doctype in ('Task'): property_setter_name = frappe.db.exists('Property Setter', dict(doc_type = doctype, field_name = 'status', property = 'options')) if property_setter_name: property_setter = frappe.get_doc('Property Setter', property_setter_name) diff --git a/erpnext/patches/v12_0/update_due_date_in_gle.py b/erpnext/patches/v12_0/update_due_date_in_gle.py new file mode 100644 index 00000000000..34848725cec --- /dev/null +++ b/erpnext/patches/v12_0/update_due_date_in_gle.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("accounts", "doctype", "gl_entry") + + for doctype in ["Sales Invoice", "Purchase Invoice", "Journal Entry"]: + frappe.reload_doc("accounts", "doctype", frappe.scrub(doctype)) + + frappe.db.sql(""" UPDATE `tabGL Entry`, `tab{doctype}` + SET + `tabGL Entry`.due_date = `tab{doctype}`.due_date + WHERE + `tabGL Entry`.voucher_no = `tab{doctype}`.name and `tabGL Entry`.party is not null + and `tabGL Entry`.voucher_type in ('Sales Invoice', 'Purchase Invoice', 'Journal Entry') + and `tabGL Entry`.account in (select name from `tabAccount` where account_type in ('Receivable', 'Payable'))""" #nosec + .format(doctype=doctype)) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 528c7cd0c71..5613f088e16 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -1,23 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt frappe.ui.form.on("Project", { - setup: function (frm) { - frm.set_indicator_formatter('title', - function (doc) { - let indicator = 'orange'; - if (doc.status == 'Overdue') { - indicator = 'red'; - } else if (doc.status == 'Cancelled') { - indicator = 'dark grey'; - } else if (doc.status == 'Completed') { - indicator = 'green'; - } - return indicator; - } - ); - }, - - onload: function (frm) { var so = frappe.meta.get_docfield("Project", "sales_order"); so.get_route_options_for_new_doc = function (field) { @@ -99,58 +82,4 @@ frappe.ui.form.on("Project", { }); }, - tasks_refresh: function (frm) { - var grid = frm.get_field('tasks').grid; - grid.wrapper.find('select[data-fieldname="status"]').each(function () { - if ($(this).val() === 'Open') { - $(this).addClass('input-indicator-open'); - } else { - $(this).removeClass('input-indicator-open'); - } - }); - }, - - status: function(frm) { - if (frm.doc.status === 'Cancelled') { - frappe.confirm(__('Set tasks in this project as cancelled?'), () => { - frm.doc.tasks = frm.doc.tasks.map(task => { - task.status = 'Cancelled'; - return task; - }); - frm.refresh_field('tasks'); - }); - } - } -}); - -frappe.ui.form.on("Project Task", { - edit_task: function(frm, doctype, name) { - var doc = frappe.get_doc(doctype, name); - if(doc.task_id) { - frappe.set_route("Form", "Task", doc.task_id); - } else { - frappe.msgprint(__("Save the document first.")); - } - }, - - edit_timesheet: function(frm, cdt, cdn) { - var child = locals[cdt][cdn]; - frappe.route_options = {"project": frm.doc.project_name, "task": child.task_id}; - frappe.set_route("List", "Timesheet"); - }, - - make_timesheet: function(frm, cdt, cdn) { - var child = locals[cdt][cdn]; - frappe.model.with_doctype('Timesheet', function() { - var doc = frappe.model.get_new_doc('Timesheet'); - var row = frappe.model.add_child(doc, 'time_logs'); - row.project = frm.doc.project_name; - row.task = child.task_id; - frappe.set_route('Form', doc.doctype, doc.name); - }) - }, - - status: function(frm, doctype, name) { - frm.trigger('tasks_refresh'); - }, }); diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 2fc507b252c..b4536c085c5 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -1,1974 +1,487 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:project_name", - "beta": 0, "creation": "2013-03-07 11:55:07", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "project_name", + "status", + "project_type", + "is_active", + "percent_complete_method", + "percent_complete", + "column_break_5", + "project_template", + "expected_start_date", + "expected_end_date", + "priority", + "department", + "customer_details", + "customer", + "column_break_14", + "sales_order", + "users_section", + "users", + "copied_from", + "section_break0", + "notes", + "section_break_18", + "actual_start_date", + "actual_time", + "column_break_20", + "actual_end_date", + "project_details", + "estimated_costing", + "total_costing_amount", + "total_expense_claim", + "total_purchase_cost", + "company", + "column_break_28", + "total_sales_amount", + "total_billable_amount", + "total_billed_amount", + "total_consumed_material_cost", + "cost_center", + "margin", + "gross_margin", + "column_break_37", + "per_gross_margin", + "monitor_progress", + "collect_progress", + "holiday_list", + "frequency", + "from_time", + "to_time", + "first_email", + "second_email", + "daily_time_to_send", + "day_to_send", + "weekly_time_to_send", + "column_break_45", + "message" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "project_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Project Name", - "length": 0, - "no_copy": 0, "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Open", "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Status", - "length": 0, "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", "options": "Open\nCompleted\nCancelled", - "permlevel": 0, - "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, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "project_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Project Type", - "length": 0, - "no_copy": 0, "oldfieldname": "project_type", "oldfieldtype": "Data", - "options": "Project Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Project Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "is_active", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Is Active", - "length": 0, - "no_copy": 0, "oldfieldname": "is_active", "oldfieldtype": "Select", - "options": "Yes\nNo", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Yes\nNo" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Task Completion", "fieldname": "percent_complete_method", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "% Complete Method", - "length": 0, - "no_copy": 0, - "options": "Task Completion\nTask Progress\nTask Weight", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Task Completion\nTask Progress\nTask Weight" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, "fieldname": "percent_complete", "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "% Completed", - "length": 0, "no_copy": 1, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "project_template", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "From Template", - "length": 0, - "no_copy": 0, - "options": "Project Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Project Template" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "expected_start_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Expected Start Date", - "length": 0, - "no_copy": 0, "oldfieldname": "project_start_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, "fieldname": "expected_end_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Expected End Date", - "length": 0, - "no_copy": 0, "oldfieldname": "completion_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "priority", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Priority", - "length": 0, - "no_copy": 0, "oldfieldname": "priority", "oldfieldtype": "Select", - "options": "Medium\nLow\nHigh", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Medium\nLow\nHigh" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "department", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Department" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "customer_details", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Customer Details", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-user" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "customer", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Customer", - "length": 0, - "no_copy": 0, "oldfieldname": "customer", "oldfieldtype": "Link", "options": "Customer", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "sales_order", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Sales Order", - "length": 0, - "no_copy": 0, - "options": "Sales Order", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Sales Order" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "users_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Users" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Project will be accessible on the website to these users", "fieldname": "users", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Users", - "length": 0, - "no_copy": 0, - "options": "Project User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Project User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_milestones", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tasks", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-flag", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tasks", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tasks", - "length": 0, - "no_copy": 0, - "options": "Project Task", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "copied_from", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Copied From", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "section_break0", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Notes", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-list", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-list" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "notes", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Notes", - "length": 0, - "no_copy": 0, "oldfieldname": "notes", - "oldfieldtype": "Text Editor", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Text Editor" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "section_break_18", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Start and End Dates", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Start and End Dates" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "actual_start_date", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Start Date", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "actual_time", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Time (in Hours)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "actual_end_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual End Date", - "length": 0, - "no_copy": 0, "oldfieldname": "act_completion_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "project_details", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Costing and Billing", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-money" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "estimated_costing", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Estimated Cost", - "length": 0, - "no_copy": 0, "oldfieldname": "project_value", "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Company:company:default_currency" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "total_costing_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Costing Amount (via Timesheets)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "total_expense_claim", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Expense Claim (via Expense Claims)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_purchase_cost", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Purchase Cost (via Purchase Invoice)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "remember_last_selected_value": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_28", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_sales_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Sales Amount (via Sales Order)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "total_billable_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Billable Amount (via Timesheets)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_billed_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Billed Amount (via Sales Invoices)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_consumed_material_cost", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Consumed Material Cost (via Stock Entry)", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "cost_center", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Cost Center", - "length": 0, - "no_copy": 0, - "options": "Cost Center", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Cost Center" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "margin", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Margin", - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "gross_margin", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Gross Margin", - "length": 0, - "no_copy": 0, "oldfieldname": "gross_margin_value", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_37", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "per_gross_margin", "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Gross Margin %", - "length": 0, - "no_copy": 0, "oldfieldname": "per_gross_margin", "oldfieldtype": "Currency", - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "monitor_progress", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Monitor Progress", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Monitor Progress" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "collect_progress", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Collect Progress", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Collect Progress" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "collect_progress", "fieldname": "holiday_list", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Holiday List", - "length": 0, - "no_copy": 0, - "options": "Holiday List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Holiday List" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.collect_progress == true", "fieldname": "frequency", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Frequency To Collect Progress", - "length": 0, - "no_copy": 0, - "options": "Hourly\nTwice Daily\nDaily\nWeekly", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Hourly\nTwice Daily\nDaily\nWeekly" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress)", "fieldname": "from_time", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "From Time" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress)", "fieldname": "to_time", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "To Time" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)\n\n", "fieldname": "first_email", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "First Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "First Email" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)", "fieldname": "second_email", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Second Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Second Email" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)", "fieldname": "daily_time_to_send", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time to send", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Time to send" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", "fieldname": "day_to_send", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Day to Send", - "length": 0, - "no_copy": 0, - "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", "fieldname": "weekly_time_to_send", "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time to send", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Time to send" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_45", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "collect_progress", "description": "Message will sent to users to get their status on the project", "fieldname": "message", "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Message", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Message" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-puzzle-piece", "idx": 29, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, "max_attachments": 4, - "modified": "2019-02-18 17:56:04.789560", + "modified": "2019-06-25 16:14:43.887151", "modified_by": "Administrator", "module": "Projects", "name": "Project", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Projects User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "All" }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Projects Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, "search_fields": "customer, status, priority, is_active", "show_name_in_global_search": 1, "sort_order": "DESC", "timeline_field": "customer", - "track_changes": 0, - "track_seen": 1, - "track_views": 0 + "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 55a689259ae..6176cf89b4f 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -19,10 +19,6 @@ class Project(Document): return '{0}: {1}'.format(_(self.status), frappe.safe_decode(self.project_name)) def onload(self): - """Load project tasks for quick view""" - if not self.get('__unsaved') and not self.get("tasks"): - self.load_tasks() - self.set_onload('activity_summary', frappe.db.sql('''select activity_type, sum(hours) as total_hours from `tabTimesheet Detail` where project=%s and docstatus < 2 group by activity_type @@ -33,57 +29,19 @@ class Project(Document): def before_print(self): self.onload() - def load_tasks(self): - """Load `tasks` from the database""" - if frappe.flags.in_import: - return - project_task_custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task"}, "fieldname") - - self.tasks = [] - for task in self.get_tasks(): - task_map = { - "title": task.subject, - "status": task.status, - "start_date": task.exp_start_date, - "end_date": task.exp_end_date, - "description": task.description, - "task_id": task.name, - "task_weight": task.task_weight - } - - self.map_custom_fields(task, task_map, project_task_custom_fields) - - self.append("tasks", task_map) - - def get_tasks(self): - if self.name is None: - return {} - else: - filters = {"project": self.name} - - if self.get("deleted_task_list"): - filters.update({ - 'name': ("not in", self.deleted_task_list) - }) - - return frappe.get_all("Task", "*", filters, order_by="exp_start_date asc, status asc") def validate(self): - self.validate_weights() - self.sync_tasks() - self.tasks = [] - self.load_tasks() if not self.is_new(): self.copy_from_template() - self.validate_dates() self.send_welcome_email() - self.update_percent_complete(from_validate=True) + self.update_costing() + self.update_percent_complete() def copy_from_template(self): ''' Copy tasks from template ''' - if self.project_template and not len(self.tasks or []): + if self.project_template and not frappe.db.get_all('Task', dict(project = self.name), limit=1): # has a template, and no loaded tasks, so lets create if not self.expected_start_date: @@ -108,104 +66,6 @@ class Project(Document): task_weight = task.task_weight )).insert() - # reload tasks after project - self.load_tasks() - - def validate_dates(self): - if self.tasks: - for d in self.tasks: - if self.expected_start_date: - if d.start_date and getdate(d.start_date) < getdate(self.expected_start_date): - frappe.throw(_("Start date of task {0} cannot be less than {1} expected start date {2}") - .format(d.title, self.name, self.expected_start_date)) - if d.end_date and getdate(d.end_date) < getdate(self.expected_start_date): - frappe.throw(_("End date of task {0} cannot be less than {1} expected start date {2}") - .format(d.title, self.name, self.expected_start_date)) - - if self.expected_end_date: - if d.start_date and getdate(d.start_date) > getdate(self.expected_end_date): - frappe.throw(_("Start date of task {0} cannot be greater than {1} expected end date {2}") - .format(d.title, self.name, self.expected_end_date)) - if d.end_date and getdate(d.end_date) > getdate(self.expected_end_date): - frappe.throw(_("End date of task {0} cannot be greater than {1} expected end date {2}") - .format(d.title, self.name, self.expected_end_date)) - - if self.expected_start_date and self.expected_end_date: - if getdate(self.expected_end_date) < getdate(self.expected_start_date): - frappe.throw(_("Expected End Date can not be less than Expected Start Date")) - - def validate_weights(self): - for task in self.tasks: - if task.task_weight is not None: - if task.task_weight < 0: - frappe.throw(_("Task weight cannot be negative")) - - def sync_tasks(self): - """sync tasks and remove table""" - if not hasattr(self, "deleted_task_list"): - self.set("deleted_task_list", []) - - if self.flags.dont_sync_tasks: return - task_names = [] - - existing_task_data = {} - - fields = ["title", "status", "start_date", "end_date", "description", "task_weight", "task_id"] - exclude_fieldtype = ["Button", "Column Break", - "Section Break", "Table", "Read Only", "Attach", "Attach Image", "Color", "Geolocation", "HTML", "Image"] - - custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task", - "fieldtype": ("not in", exclude_fieldtype)}, "fieldname") - - for d in custom_fields: - fields.append(d.fieldname) - - for d in frappe.get_all('Project Task', - fields = fields, - filters = {'parent': self.name}): - existing_task_data.setdefault(d.task_id, d) - - for t in self.tasks: - if t.task_id: - task = frappe.get_doc("Task", t.task_id) - else: - task = frappe.new_doc("Task") - task.project = self.name - - if not t.task_id or self.is_row_updated(t, existing_task_data, fields): - task.update({ - "subject": t.title, - "status": t.status, - "exp_start_date": t.start_date, - "exp_end_date": t.end_date, - "description": t.description, - "task_weight": t.task_weight - }) - - self.map_custom_fields(t, task, custom_fields) - - task.flags.ignore_links = True - task.flags.from_project = True - task.flags.ignore_feed = True - - if t.task_id: - task.update({ - "modified_by": frappe.session.user, - "modified": now() - }) - - task.run_method("validate") - task.db_update() - else: - task.save(ignore_permissions = True) - task_names.append(task.name) - else: - task_names.append(task.name) - - # delete - for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}): - self.deleted_task_list.append(t.name) - def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: return True @@ -215,48 +75,43 @@ class Project(Document): if row.get(field) != d.get(field): return True - def map_custom_fields(self, source, target, custom_fields): - for field in custom_fields: - target.update({ - field.fieldname: source.get(field.fieldname) - }) - def update_project(self): + '''Called externally by Task''' self.update_percent_complete() self.update_costing() + self.db_update() def after_insert(self): self.copy_from_template() if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) - def update_percent_complete(self, from_validate=False): - if not self.tasks: return - total = frappe.db.sql("""select count(name) from tabTask where project=%s""", self.name)[0][0] + def update_percent_complete(self): + total = frappe.db.count('Task', dict(project=self.name)) - if not total and self.percent_complete: + if not total: self.percent_complete = 0 + else: + if (self.percent_complete_method == "Task Completion" and total > 0) or ( + not self.percent_complete_method and total > 0): + completed = frappe.db.sql("""select count(name) from tabTask where + project=%s and status in ('Cancelled', 'Completed')""", self.name)[0][0] + self.percent_complete = flt(flt(completed) / total * 100, 2) - if (self.percent_complete_method == "Task Completion" and total > 0) or ( - not self.percent_complete_method and total > 0): - completed = frappe.db.sql("""select count(name) from tabTask where - project=%s and status in ('Cancelled', 'Completed')""", self.name)[0][0] - self.percent_complete = flt(flt(completed) / total * 100, 2) + if (self.percent_complete_method == "Task Progress" and total > 0): + progress = frappe.db.sql("""select sum(progress) from tabTask where + project=%s""", self.name)[0][0] + self.percent_complete = flt(flt(progress) / total, 2) - if (self.percent_complete_method == "Task Progress" and total > 0): - progress = frappe.db.sql("""select sum(progress) from tabTask where - project=%s""", self.name)[0][0] - self.percent_complete = flt(flt(progress) / total, 2) - - if (self.percent_complete_method == "Task Weight" and total > 0): - weight_sum = frappe.db.sql("""select sum(task_weight) from tabTask where - project=%s""", self.name)[0][0] - weighted_progress = frappe.db.sql("""select progress, task_weight from tabTask where - project=%s""", self.name, as_dict=1) - pct_complete = 0 - for row in weighted_progress: - pct_complete += row["progress"] * frappe.utils.safe_div(row["task_weight"], weight_sum) - self.percent_complete = flt(flt(pct_complete), 2) + if (self.percent_complete_method == "Task Weight" and total > 0): + weight_sum = frappe.db.sql("""select sum(task_weight) from tabTask where + project=%s""", self.name)[0][0] + weighted_progress = frappe.db.sql("""select progress, task_weight from tabTask where + project=%s""", self.name, as_dict=1) + pct_complete = 0 + for row in weighted_progress: + pct_complete += row["progress"] * frappe.utils.safe_div(row["task_weight"], weight_sum) + self.percent_complete = flt(flt(pct_complete), 2) # don't update status if it is cancelled if self.status == 'Cancelled': @@ -268,9 +123,6 @@ class Project(Document): else: self.status = "Open" - if not from_validate: - self.db_update() - def update_costing(self): from_time_sheet = frappe.db.sql("""select sum(costing_amount) as costing_amount, @@ -297,7 +149,6 @@ class Project(Document): self.update_sales_amount() self.update_billed_amount() self.calculate_gross_margin() - self.db_update() def calculate_gross_margin(self): expense_amount = (flt(self.total_costing_amount) + flt(self.total_expense_claim) @@ -348,57 +199,6 @@ class Project(Document): content=content.format(*messages)) user.welcome_email_sent = 1 - def on_update(self): - self.delete_task() - self.load_tasks() - self.update_project() - self.update_dependencies_on_duplicated_project() - - def delete_task(self): - if not self.get('deleted_task_list'): return - - for d in self.get('deleted_task_list'): - # unlink project - frappe.db.set_value('Task', d, 'project', '') - - self.deleted_task_list = [] - - def update_dependencies_on_duplicated_project(self): - if self.flags.dont_sync_tasks: return - if not self.copied_from: - self.copied_from = self.name - - if self.name != self.copied_from and self.get('__unsaved'): - # duplicated project - dependency_map = {} - for task in self.tasks: - _task = frappe.db.get_value( - 'Task', - {"subject": task.title, "project": self.copied_from}, - ['name', 'depends_on_tasks'], - as_dict=True - ) - - if _task is None: - continue - - name = _task.name - - dependency_map[task.title] = [x['subject'] for x in frappe.get_list( - 'Task Depends On', {"parent": name}, ['subject'])] - - for key, value in iteritems(dependency_map): - task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name }) - - task_doc = frappe.get_doc('Task', task_name) - - for dt in value: - dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name}) - task_doc.append('depends_on', {"task": dt_name}) - - task_doc.db_update() - - def get_timeline_data(doctype, name): '''Return timeline for attendance''' return dict(frappe.db.sql('''select unix_timestamp(from_time), count(*) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index beb1f130f5b..06c62b62d2f 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -19,18 +19,18 @@ class TestProject(unittest.TestCase): project = get_project('Test Project with Template') - project.load_tasks() + tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - task1 = project.tasks[0] - self.assertEqual(task1.title, 'Task 1') + task1 = tasks[0] + self.assertEqual(task1.subject, 'Task 1') self.assertEqual(task1.description, 'Task 1 description') - self.assertEqual(getdate(task1.start_date), getdate('2019-01-01')) - self.assertEqual(getdate(task1.end_date), getdate('2019-01-04')) + self.assertEqual(getdate(task1.exp_start_date), getdate('2019-01-01')) + self.assertEqual(getdate(task1.exp_end_date), getdate('2019-01-04')) - self.assertEqual(len(project.tasks), 4) - task4 = project.tasks[3] - self.assertEqual(task4.title, 'Task 4') - self.assertEqual(getdate(task4.end_date), getdate('2019-01-06')) + self.assertEqual(len(tasks), 4) + task4 = tasks[3] + self.assertEqual(task4.subject, 'Task 4') + self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06')) def get_project(name): template = get_project_template() diff --git a/erpnext/projects/doctype/project/test_records.json b/erpnext/projects/doctype/project/test_records.json index 9379c22b5c6..567f359b50d 100644 --- a/erpnext/projects/doctype/project/test_records.json +++ b/erpnext/projects/doctype/project/test_records.json @@ -1,12 +1,6 @@ [ { "project_name": "_Test Project", - "status": "Open", - "tasks":[ - { - "title": "_Test Task", - "status": "Open" - } - ] + "status": "Open" } ] \ No newline at end of file diff --git a/erpnext/projects/doctype/project_task/__init__.py b/erpnext/projects/doctype/project_task/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/projects/doctype/project_task/project_task.json b/erpnext/projects/doctype/project_task/project_task.json deleted file mode 100644 index e26c191e0b8..00000000000 --- a/erpnext/projects/doctype/project_task/project_task.json +++ /dev/null @@ -1,430 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2015-02-22 11:15:28.201059", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "default": "Open", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "task_id", - "fieldname": "edit_task", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "View Task", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "edit_timesheet", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "View Timesheet", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "make_timesheet", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Make Timesheet", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Start Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "default": "", - "fieldname": "end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "task_weight", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Weight", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "task_id", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Task ID", - "length": 0, - "no_copy": 1, - "options": "Task", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-02-19 12:30:52.648868", - "modified_by": "Administrator", - "module": "Projects", - "name": "Project Task", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/projects/doctype/project_task/project_task.py b/erpnext/projects/doctype/project_task/project_task.py deleted file mode 100644 index 5f9d8d76229..00000000000 --- a/erpnext/projects/doctype/project_task/project_task.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class ProjectTask(Document): - pass diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index d8fc199ec24..50557f1551d 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -158,12 +158,6 @@ class Task(NestedSet): if check_if_child_exists(self.name): throw(_("Child Task exists for this Task. You can not delete this Task.")) - if self.project: - tasks = frappe.get_doc('Project', self.project).tasks - for task in tasks: - if task.get('task_id') == self.name: - frappe.delete_doc('Project Task', task.name) - self.update_nsm_model() def update_status(self): diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js index 91dfe809a43..89657a18372 100644 --- a/erpnext/public/js/call_popup/call_popup.js +++ b/erpnext/public/js/call_popup/call_popup.js @@ -109,7 +109,7 @@ class CallPopup { }); wrapper.append(`