From a2408a63da7057af341db5a46af600c5f4fb0f34 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 Jun 2019 01:52:48 +0530 Subject: [PATCH 01/49] feat: payment reconciliation enhancements --- .../doctype/journal_entry/journal_entry.js | 4 + .../doctype/journal_entry/journal_entry.py | 3 +- .../doctype/payment_entry/payment_entry.py | 14 + .../payment_reconciliation.js | 99 ++++ .../payment_reconciliation.py | 145 +++++- .../payment_reconciliation_payment.json | 486 ++++-------------- erpnext/accounts/utils.py | 14 +- 7 files changed, 366 insertions(+), 399 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index b7f383fb13..b6c48c81b9 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -228,6 +228,10 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ frappe.model.validate_missing(jvd, "account"); var party_account_field = jvd.reference_type==="Sales Invoice" ? "debit_to": "credit_to"; out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]); + + if (in_list(['Debit Note', 'Credit Note'], doc.voucher_type)) { + out.filters.push([jvd.reference_type, "is_return", "=", 1]); + } } if(in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d082b60211..3132c937dd 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -331,7 +331,8 @@ class JournalEntry(AccountsController): for reference_name, total in iteritems(self.reference_totals): reference_type = self.reference_types[reference_name] - if reference_type in ("Sales Invoice", "Purchase Invoice"): + if (reference_type in ("Sales Invoice", "Purchase Invoice") and + self.voucher_type not in ['Debit Note', 'Credit Note']): invoice = frappe.db.get_value(reference_type, reference_name, ["docstatus", "outstanding_amount"], as_dict=1) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 92803a6312..ea76126d8c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -535,6 +535,20 @@ class PaymentEntry(AccountsController): "amount": self.total_allocated_amount * (tax_details['tax']['rate'] / 100) } + def set_gain_or_loss(self, account_details=None): + if not self.difference_amount: + self.set_difference_amount() + + row = { + 'amount': self.difference_amount + } + + if account_details: + row.update(account_details) + + self.append('deductions', row) + self.set_unallocated_amount() + @frappe.whitelist() def get_outstanding_reference_documents(args): diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 4c24a9fc97..df31cde551 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -16,6 +16,20 @@ frappe.ui.form.on("Payment Reconciliation Payment", { })[0].outstanding_amount; frappe.model.set_value(cdt, cdn, "allocated_amount", Math.min(invoice_amount, row.amount)); + + frm.call({ + doc: frm.doc, + method: 'get_difference_amount', + args: { + child_row: row + }, + callback: function(r, rt) { + if(r.message) { + frappe.model.set_value(cdt, cdn, + "difference_amount", r.message); + } + } + }); } } }); @@ -104,6 +118,91 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext reconcile: function() { var me = this; + var show_dialog = me.frm.doc.payments.filter(d => d.difference_amount && !d.difference_account); + + if (show_dialog && show_dialog.length) { + + this.data = []; + const dialog = new frappe.ui.Dialog({ + title: __("Select Difference Account"), + fields: [ + { + fieldname: "payments", fieldtype: "Table", label: __("Payments"), + data: this.data, in_place_edit: true, + get_data: () => { + return this.data; + }, + fields: [{ + fieldtype:'Data', + fieldname:"docname", + in_list_view: 1, + hidden: 1 + }, { + fieldtype:'Data', + fieldname:"reference_name", + label: __("Voucher No"), + in_list_view: 1, + read_only: 1 + }, { + fieldtype:'Link', + options: 'Account', + in_list_view: 1, + label: __("Difference Account"), + fieldname: 'difference_account', + reqd: 1, + get_query: function() { + return { + filters: { + company: me.frm.doc.company, + is_group: 0 + } + } + } + }, { + fieldtype:'Currency', + in_list_view: 1, + label: __("Difference Amount"), + fieldname: 'difference_amount', + read_only: 1 + }] + }, + ], + primary_action: function() { + const args = dialog.get_values()["payments"]; + + args.forEach(d => { + frappe.model.set_value("Payment Reconciliation Payment", d.docname, + "difference_account", d.difference_account); + }) + + me.reconcile_payment_entries(); + dialog.hide(); + }, + primary_action_label: __('Reconcile Entries') + }); + + this.frm.doc.payments.forEach(d => { + if (d.difference_amount && !d.difference_account) { + dialog.fields_dict.payments.df.data.push({ + 'docname': d.name, + 'reference_name': d.reference_name, + 'difference_amount': d.difference_amount, + 'difference_account': d.difference_account, + }); + } + }); + + this.data = dialog.fields_dict.payments.df.data; + dialog.fields_dict.payments.grid.refresh(); + dialog.show(); + } else { + this.reconcile_payment_entries(); + } + }, + + reconcile_payment_entries: function() { + var me = this; + return this.frm.call({ doc: me.frm.doc, method: 'reconcile', diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 9e7c3eb55e..64861615b0 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -3,10 +3,11 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import flt +from frappe.utils import flt, today from frappe import msgprint, _ from frappe.model.document import Document -from erpnext.accounts.utils import get_outstanding_invoices +from erpnext.accounts.utils import (get_outstanding_invoices, + update_reference_in_payment_entry, reconcile_against_document) from erpnext.controllers.accounts_controller import get_advance_payment_entries class PaymentReconciliation(Document): @@ -20,14 +21,17 @@ class PaymentReconciliation(Document): payment_entries = self.get_payment_entries() journal_entries = self.get_jv_entries() - self.add_payment_entries(payment_entries + journal_entries) + if self.party_type in ["Customer", "Supplier"]: + dr_or_cr_notes = self.get_dr_or_cr_notes() + + self.add_payment_entries(payment_entries + journal_entries + dr_or_cr_notes) def get_payment_entries(self): order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order" payment_entries = get_advance_payment_entries(self.party_type, self.party, self.receivable_payable_account, order_doctype, against_all_orders=True, limit=self.limit) - + return payment_entries def get_jv_entries(self): @@ -72,6 +76,34 @@ class PaymentReconciliation(Document): return list(journal_entries) + def get_dr_or_cr_notes(self): + dr_or_cr = ("credit_in_account_currency" + if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency") + + reconciled_dr_or_cr = ("debit_in_account_currency" + if dr_or_cr == "credit_in_account_currency" else "credit_in_account_currency") + + voucher_type = ('Sales Invoice' + if self.party_type == 'Customer' else "Purchase Invoice") + + return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, + (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount + FROM `tab{doc}`, `tabGL Entry` + WHERE + (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) + and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s + and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s + and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s + GROUP BY `tabSales Invoice`.name + Having + amount > 0 + """.format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), { + 'party': self.party, + 'party_type': self.party_type, + 'voucher_type': voucher_type, + 'account': self.receivable_payable_account + }, as_dict=1) + def add_payment_entries(self, entries): self.set('payments', []) for e in entries: @@ -112,36 +144,67 @@ class PaymentReconciliation(Document): if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency") lst = [] + dr_or_cr_notes = [] for e in self.get('payments'): + reconciled_entry = [] if e.invoice_number and e.allocated_amount: - lst.append(frappe._dict({ - 'voucher_type': e.reference_type, - 'voucher_no' : e.reference_name, - 'voucher_detail_no' : e.reference_row, - 'against_voucher_type' : e.invoice_type, - 'against_voucher' : e.invoice_number, - 'account' : self.receivable_payable_account, - 'party_type': self.party_type, - 'party': self.party, - 'is_advance' : e.is_advance, - 'dr_or_cr' : dr_or_cr, - 'unadjusted_amount' : flt(e.amount), - 'allocated_amount' : flt(e.allocated_amount) - })) + if e.reference_type in ['Sales Invoice', 'Purchase Invoice']: + reconciled_entry = dr_or_cr_notes + else: + reconciled_entry = lst + + reconciled_entry.append(self.get_payment_details(e, dr_or_cr)) if lst: - from erpnext.accounts.utils import reconcile_against_document reconcile_against_document(lst) - msgprint(_("Successfully Reconciled")) - self.get_unreconciled_entries() + if dr_or_cr_notes: + reconcile_dr_cr_note(dr_or_cr_notes) + + msgprint(_("Successfully Reconciled")) + self.get_unreconciled_entries() + + def get_payment_details(self, row, dr_or_cr): + return frappe._dict({ + 'voucher_type': row.reference_type, + 'voucher_no' : row.reference_name, + 'voucher_detail_no' : row.reference_row, + 'against_voucher_type' : row.invoice_type, + 'against_voucher' : row.invoice_number, + 'account' : self.receivable_payable_account, + 'party_type': self.party_type, + 'party': self.party, + 'is_advance' : row.is_advance, + 'dr_or_cr' : dr_or_cr, + 'unadjusted_amount' : flt(row.amount), + 'allocated_amount' : flt(row.allocated_amount), + 'difference_amount': row.difference_amount, + 'difference_account': row.difference_account + }) + + def get_difference_amount(self, child_row): + if child_row.get("reference_type") != 'Payment Entry': return + + child_row = frappe._dict(child_row) + + if child_row.invoice_number and " | " in child_row.invoice_number: + child_row.invoice_type, child_row.invoice_number = child_row.invoice_number.split(" | ") + + dr_or_cr = ("credit_in_account_currency" + if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency") + + row = self.get_payment_details(child_row, dr_or_cr) + + doc = frappe.get_doc(row.voucher_type, row.voucher_no) + update_reference_in_payment_entry(row, doc, do_not_save=True) + + return doc.difference_amount def check_mandatory_to_fetch(self): for fieldname in ["company", "party_type", "party", "receivable_payable_account"]: if not self.get(fieldname): frappe.throw(_("Please select {0} first").format(self.meta.get_label(fieldname))) - def validate_invoice(self): if not self.get("invoices"): frappe.throw(_("No records found in the Invoice table")) @@ -186,3 +249,41 @@ class PaymentReconciliation(Document): cond += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_amount)) return cond + +def reconcile_dr_cr_note(dr_cr_notes): + for d in dr_cr_notes: + voucher_type = ('Credit Note' + if d.voucher_type == 'Sales Invoice' else 'Debit Note') + + dr_or_cr = ('credit_in_account_currency' + if d.reference_type == 'Sales Invoice' else 'debit_in_account_currency') + + reconcile_dr_or_cr = ('debit_in_account_currency' + if dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') + + jv = frappe.get_doc({ + "doctype": "Journal Entry", + "voucher_type": voucher_type, + "posting_date": today(), + "accounts": [ + { + 'account': d.account, + 'party': d.party, + 'party_type': d.party_type, + reconcile_dr_or_cr: (abs(d.allocated_amount) + if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)), + 'reference_type': d.against_voucher_type, + 'reference_name': d.against_voucher + }, + { + 'account': d.account, + 'party': d.party, + 'party_type': d.party_type, + dr_or_cr: abs(d.allocated_amount), + 'reference_type': d.voucher_type, + 'reference_name': d.voucher_no + } + ] + }) + + jv.submit() \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json index 814257c047..018bfd028a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json +++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json @@ -1,389 +1,127 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-07-09 16:13:35.452759", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "creation": "2014-07-09 16:13:35.452759", + "doctype": "DocType", + "editable_grid": 1, + "field_order": [ + "reference_type", + "reference_name", + "posting_date", + "is_advance", + "reference_row", + "col_break1", + "invoice_number", + "amount", + "allocated_amount", + "section_break_10", + "difference_account", + "difference_amount", + "sec_break1", + "remark" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_type", - "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": "Reference Type", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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 - }, + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "options": "DocType", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_type", - "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 - }, + "columns": 2, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "options": "reference_type", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "posting_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": "Posting Date", - "length": 0, - "no_copy": 0, - "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 - }, + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_advance", - "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": "Is Advance", - "length": 0, - "no_copy": 0, - "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 - }, + "fieldname": "is_advance", + "fieldtype": "Data", + "hidden": 1, + "label": "Is Advance", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_row", - "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": "Reference Row", - "length": 0, - "no_copy": 0, - "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 - }, + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "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, - "label": "", - "length": 0, - "no_copy": 0, - "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 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "invoice_number", - "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": "Invoice Number", - "length": 0, - "no_copy": 0, - "options": "", - "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": 0 - }, + "columns": 2, + "fieldname": "invoice_number", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Invoice Number", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "amount", - "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": "Amount", - "length": 0, - "no_copy": 0, - "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 - }, + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "allocated_amount", - "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": "Allocated amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated amount", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_break1", - "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": "", - "length": 0, - "no_copy": 0, - "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 - }, + "fieldname": "sec_break1", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "remark", - "fieldtype": "Small Text", - "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": "Remark", - "length": 0, - "no_copy": 0, - "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 + "fieldname": "remark", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Remark", + "read_only": 1 + }, + { + "columns": 2, + "fieldname": "difference_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Difference Account", + "options": "Account" + }, + { + "fieldname": "difference_amount", + "fieldtype": "Currency", + "label": "Difference Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" } - ], - "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, - "menu_index": 0, - "modified": "2019-01-07 16:52:07.567027", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Payment Reconciliation Payment", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "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 + ], + "istable": 1, + "modified": "2019-06-24 00:08:11.150796", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Payment Reconciliation Payment", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 96f64e8db3..101a71f8e5 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -435,7 +435,7 @@ def update_reference_in_journal_entry(d, jv_obj): jv_obj.flags.ignore_validate_update_after_submit = True jv_obj.save(ignore_permissions=True) -def update_reference_in_payment_entry(d, payment_entry): +def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): reference_details = { "reference_doctype": d.against_voucher_type, "reference_name": d.against_voucher, @@ -466,7 +466,17 @@ def update_reference_in_payment_entry(d, payment_entry): payment_entry.setup_party_account_field() payment_entry.set_missing_values() payment_entry.set_amounts() - payment_entry.save(ignore_permissions=True) + + if d.difference_amount and d.difference_account: + payment_entry.set_gain_or_loss(account_details={ + 'account': d.difference_account, + 'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company', + payment_entry.company, "cost_center"), + 'amount': d.difference_amount + }) + + if not do_not_save: + payment_entry.save(ignore_permissions=True) def unlink_ref_doc_from_payment_entries(ref_doc): remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name) From 09b44345dfca2e15ecc84ff185b6ac9a457cee6d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 19 Jun 2019 12:52:07 +0530 Subject: [PATCH 02/49] fix: as limit 100 was set in the query therefore not all item's default value moved to the Item Default table --- erpnext/patches.txt | 2 +- ...efaults_to_child_table_for_multicompany.py | 84 +++++++++++++------ 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 36a31cfadc..be9baf4583 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -533,7 +533,7 @@ erpnext.patches.v11_0.create_department_records_for_each_company erpnext.patches.v11_0.make_location_from_warehouse erpnext.patches.v11_0.make_asset_finance_book_against_old_entries erpnext.patches.v11_0.check_buying_selling_in_currency_exchange -erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 +erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019 erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07 erpnext.patches.v11_0.rename_overproduction_percent_field erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom diff --git a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py index 01f84a0313..c7c7635540 100644 --- a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py +++ b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py @@ -17,10 +17,8 @@ def execute(): frappe.reload_doc('stock', 'doctype', 'item_default') frappe.reload_doc('stock', 'doctype', 'item') - if frappe.db.a_row_exists('Item Default'): return - companies = frappe.get_all("Company") - if len(companies) == 1: + if len(companies) == 1 and not frappe.get_all("Item Default", limit=1): try: frappe.db.sql(''' INSERT INTO `tabItem Default` @@ -35,32 +33,64 @@ def execute(): except: pass else: - item_details = frappe.get_all("Item", fields=["name", "default_warehouse", "buying_cost_center", - "expense_account", "selling_cost_center", "income_account"], limit=100) + item_details = frappe.db.sql(""" SELECT name, default_warehouse, + buying_cost_center, expense_account, selling_cost_center, income_account + FROM tabItem + WHERE + name not in (select distinct parent from `tabItem Default`) and ifnull(disabled, 0) = 0""" + , as_dict=1) - for item in item_details: - item_defaults = [] + items_default_data = {} + for item_data in item_details: + for d in [["default_warehouse", "Warehouse"], ["expense_account", "Account"], + ["income_account", "Account"], ["buying_cost_center", "Cost Center"], + ["selling_cost_center", "Cost Center"]]: + if item_data.get(d[0]): + company = frappe.get_value(d[1], item_data.get(d[0]), "company", cache=True) - def insert_into_item_defaults(doc_field_name, doc_field_value, company): - for d in item_defaults: - if d.get("company") == company: - d[doc_field_name] = doc_field_value - return - item_defaults.append({ - "company": company, - doc_field_name: doc_field_value - }) + if item_data.name not in items_default_data: + items_default_data[item_data.name] = {} - for d in [ - ["default_warehouse", "Warehouse"], ["expense_account", "Account"], ["income_account", "Account"], - ["buying_cost_center", "Cost Center"], ["selling_cost_center", "Cost Center"] - ]: - if item.get(d[0]): - company = frappe.get_value(d[1], item.get(d[0]), "company", cache=True) - insert_into_item_defaults(d[0], item.get(d[0]), company) + company_wise_data = items_default_data[item_data.name] - doc = frappe.get_doc("Item", item.name) - doc.extend("item_defaults", item_defaults) + if company not in company_wise_data: + company_wise_data[company] = {} - for child_doc in doc.item_defaults: - child_doc.db_insert() \ No newline at end of file + default_data = company_wise_data[company] + default_data[d[0]] = item_data.get(d[0]) + + to_insert_data = [] + + # items_default_data data structure will be as follow + # { + # 'item_code 1': {'company 1': {'default_warehouse': 'Test Warehouse 1'}}, + # 'item_code 2': { + # 'company 1': {'default_warehouse': 'Test Warehouse 1'}, + # 'company 2': {'default_warehouse': 'Test Warehouse 1'} + # } + # } + + for item_code, companywise_item_data in items_default_data.items(): + for company, item_default_data in companywise_item_data.items(): + to_insert_data.append(( + frappe.generate_hash("", 10), + item_code, + 'Item', + 'item_defaults', + company, + item_default_data.get('default_warehouse'), + item_default_data.get('expense_account'), + item_default_data.get('income_account'), + item_default_data.get('buying_cost_center'), + item_default_data.get('selling_cost_center'), + )) + + if to_insert_data: + frappe.db.sql(''' + INSERT INTO `tabItem Default` + ( + `name`, `parent`, `parenttype`, `parentfield`, `company`, `default_warehouse`, + `expense_account`, `income_account`, `buying_cost_center`, `selling_cost_center` + ) + VALUES {} + '''.format(', '.join(['%s'] * len(to_insert_data))), tuple(to_insert_data)) \ No newline at end of file From 55fbddbee6cc71b81b4b529759f8a208c4554199 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 3 Jul 2019 16:31:06 +0530 Subject: [PATCH 03/49] Add missing semicolon --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index df31cde551..d3992d5111 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -173,7 +173,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext args.forEach(d => { frappe.model.set_value("Payment Reconciliation Payment", d.docname, "difference_account", d.difference_account); - }) + }); me.reconcile_payment_entries(); dialog.hide(); From 7dedd9a8cd9f0d842dbba6bcd02bef378609c434 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 3 Jul 2019 17:29:29 +0530 Subject: [PATCH 04/49] refactor: better error message when changing variant value (#18144) --- erpnext/controllers/item_variant.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 669aa9be7e..dee71dc944 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -98,8 +98,8 @@ def validate_item_attribute_value(attributes_list, attribute, attribute_value, i if allow_rename_attribute_value: pass elif attribute_value not in attributes_list: - frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values for Item {2}").format( - attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Invalid Attribute')) + frappe.throw(_("The value {0} is already assigned to an exisiting Item {2}.").format( + attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Rename Not Allowed')) def get_attribute_values(item): if not frappe.flags.attribute_values: From 58b4644c1a219bc5f9a2f0238dd48eec71e54325 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 3 Jul 2019 18:08:41 +0530 Subject: [PATCH 05/49] fix: Add filtesr for accounting dimensions in trial balance report --- .../report/trial_balance/trial_balance.js | 13 ++++++++ .../report/trial_balance/trial_balance.py | 31 +++++++++++++------ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index cdc77456d0..8bc72807b3 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -96,3 +96,16 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { } }); +let dimension_filters = erpnext.get_dimension_filters(); + +dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); + diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 5758b0bc6b..b6ddaa8e85 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -7,6 +7,7 @@ from frappe import _ from frappe.utils import flt, getdate, formatdate, cstr from erpnext.accounts.report.financial_statements \ import filter_accounts, set_gl_entries_by_account, filter_out_zero_value_rows +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions value_fields = ("opening_debit", "opening_credit", "debit", "credit", "closing_debit", "closing_credit") @@ -109,6 +110,25 @@ def get_rootwise_opening_balances(filters, report_type): additional_conditions += fb_conditions + accounting_dimensions = get_accounting_dimensions() + + query_filters = { + "company": filters.company, + "from_date": filters.from_date, + "report_type": report_type, + "year_start_date": filters.year_start_date, + "finance_book": filters.finance_book, + "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book') + } + + if accounting_dimensions: + for dimension in accounting_dimensions: + additional_conditions += """ and {0} in (%({0})s) """.format(dimension) + + query_filters.update({ + dimension: filters.get(dimension) + }) + gle = frappe.db.sql(""" select account, sum(debit) as opening_debit, sum(credit) as opening_credit @@ -118,16 +138,7 @@ def get_rootwise_opening_balances(filters, report_type): {additional_conditions} and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') and account in (select name from `tabAccount` where report_type=%(report_type)s) - group by account""".format(additional_conditions=additional_conditions), - { - "company": filters.company, - "from_date": filters.from_date, - "report_type": report_type, - "year_start_date": filters.year_start_date, - "finance_book": filters.finance_book, - "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book') - }, - as_dict=True) + group by account""".format(additional_conditions=additional_conditions), query_filters , as_dict=True) opening = frappe._dict() for d in gle: From bb02c5105e1d0b90bb4f931d9b99c755f4bdfcc9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 3 Jul 2019 19:22:21 +0530 Subject: [PATCH 06/49] fix: format values for charts --- .../report/accounts_receivable/accounts_receivable.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 4cba978b88..29737484c7 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -599,9 +599,12 @@ class ReceivablePayableReport(object): rows = [] for d in data: + values = d[self.ageing_col_idx_start : self.ageing_col_idx_start+5] + precision = cint(frappe.db.get_default("float_precision")) or 2 + formatted_values = [frappe.utils.rounded(val, precision) for val in values] rows.append( { - 'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+5] + 'values': formatted_values } ) From 76afcf4cbac7c069fb60470db1d094283e0ed7de Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 3 Jul 2019 19:22:49 +0530 Subject: [PATCH 07/49] fix: Honor price list in Shopping Cart Settings --- erpnext/shopping_cart/cart.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 95bd9ba636..4019e07e4e 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -251,11 +251,13 @@ def _get_cart_quotation(party=None): if quotation: qdoc = frappe.get_doc("Quotation", quotation[0].name) else: + [company, price_list] = frappe.db.get_value("Shopping Cart Settings", None, ["company", "price_list"]) qdoc = frappe.get_doc({ "doctype": "Quotation", "naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-", "quotation_to": party.doctype, - "company": frappe.db.get_value("Shopping Cart Settings", None, "company"), + "company": company, + "selling_price_list": price_list, "order_type": "Shopping Cart", "status": "Draft", "docstatus": 0, From 359a73e1aa0bfa3b0db24a89cef5e1b8faa30274 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 4 Jul 2019 11:37:20 +0530 Subject: [PATCH 08/49] fix: invoice cancellation (#18152) --- erpnext/regional/italy/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 2d7ffa65bd..407677f976 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -326,6 +326,9 @@ def get_company_country(company): return frappe.get_cached_value('Company', company, 'country') def get_e_invoice_attachments(invoice): + if not invoice.company_tax_id: + return [] + out = [] attachments = get_attachments(invoice.doctype, invoice.name) company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id From bdec7fea82bdce5ebdfe2a5080f79a76741341e4 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 4 Jul 2019 12:28:01 +0530 Subject: [PATCH 09/49] feat: purchase order against sales order (#17975) * feat: purchase order against sales order * fix: remove unwanted checks * fix:Related tests --- .../doctype/sales_order/sales_order.js | 76 +++++++++++++------ .../doctype/sales_order/sales_order.py | 7 +- .../doctype/sales_order/test_sales_order.py | 4 +- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index f4bb0709a5..26ca7c6e22 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -107,7 +107,6 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( refresh: function(doc, dt, dn) { var me = this; this._super(); - var allow_purchase = false; var allow_delivery = false; if(doc.docstatus==1) { @@ -129,28 +128,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( me.frm.cscript.update_status('Re-open', 'Draft') }, __("Status")); } - } + } if(doc.status !== 'Closed') { if(doc.status !== 'On Hold') { - for (var i in this.frm.doc.items) { - var item = this.frm.doc.items[i]; - if(item.delivered_by_supplier === 1 || item.supplier){ - if(item.qty > flt(item.ordered_qty) - && item.qty > flt(item.delivered_qty)) { - allow_purchase = true; - } - } - - if (item.delivered_by_supplier===0) { - if(item.qty > flt(item.delivered_qty)) { - allow_delivery = true; - } - } - - if (allow_delivery && allow_purchase) { - break; - } - } if (this.frm.has_perm("submit")) { if(flt(doc.per_delivered, 6) < 100 || flt(doc.per_billed) < 100) { @@ -180,9 +160,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } // make purchase order - if(flt(doc.per_delivered, 6) < 100 && allow_purchase) { this.frm.add_custom_button(__('Purchase Order'), () => this.make_purchase_order(), __('Create')); - } + // maintenance if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) { @@ -543,6 +522,42 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( filters: {'parent': me.frm.doc.name} } }}, + {fieldname: 'items_for_po', fieldtype: 'Table', label: 'Select Items', + fields: [ + { + fieldtype:'Data', + fieldname:'item_code', + label: __('Item'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Data', + fieldname:'item_name', + label: __('Item name'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Float', + fieldname:'qty', + label: __('Quantity'), + read_only: 1, + in_list_view:1 + }, + { + fieldtype:'Link', + read_only:1, + fieldname:'uom', + label: __('UOM'), + in_list_view:1 + } + ], + data: cur_frm.doc.items, + get_data: function() { + return cur_frm.doc.items + } + }, {"fieldtype": "Button", "label": __('Create Purchase Order'), "fieldname": "make_purchase_order", "cssClass": "btn-primary"}, ] @@ -550,13 +565,22 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( dialog.fields_dict.make_purchase_order.$input.click(function() { var args = dialog.get_values(); + let selected_items = dialog.fields_dict.items_for_po.grid.get_selected_children() + if(selected_items.length == 0) { + frappe.throw({message: 'Please select Item form Table', title: __('Message'), indicator:'blue'}) + } + let selected_items_list = [] + for(let i in selected_items){ + selected_items_list.push(selected_items[i].item_code) + } dialog.hide(); return frappe.call({ type: "GET", - method: "erpnext.selling.doctype.sales_order.sales_order.make_purchase_order_for_drop_shipment", + method: "erpnext.selling.doctype.sales_order.sales_order.make_purchase_order", args: { "source_name": me.frm.doc.name, - "for_supplier": args.supplier + "for_supplier": args.supplier, + "selected_items": selected_items_list }, freeze: true, callback: function(r) { @@ -576,6 +600,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } }) }); + dialog.get_field("items_for_po").grid.only_sortable() + dialog.get_field("items_for_po").refresh() dialog.show(); }, hold_sales_order: function(){ diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 493da99303..8ad3bf0607 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -764,7 +764,10 @@ def get_events(start, end, filters=None): return data @frappe.whitelist() -def make_purchase_order_for_drop_shipment(source_name, for_supplier=None, target_doc=None): +def make_purchase_order(source_name, for_supplier=None, selected_items=[], target_doc=None): + if isinstance(selected_items, string_types): + selected_items = json.loads(selected_items) + def set_missing_values(source, target): target.supplier = supplier target.apply_discount_on = "" @@ -843,7 +846,7 @@ def make_purchase_order_for_drop_shipment(source_name, for_supplier=None, target "price_list_rate" ], "postprocess": update_item, - "condition": lambda doc: doc.ordered_qty < doc.qty and doc.supplier == supplier + "condition": lambda doc: doc.ordered_qty < doc.qty and doc.supplier == supplier and doc.item_code in selected_items } }, target_doc, set_missing_values) if not for_supplier: diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e7697e2b0e..569c53f628 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -449,7 +449,7 @@ class TestSalesOrder(unittest.TestCase): frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) def test_drop_shipping(self): - from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_drop_shipment + from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order from erpnext.buying.doctype.purchase_order.purchase_order import update_status make_stock_entry(target="_Test Warehouse - _TC", qty=10, rate=100) @@ -495,7 +495,7 @@ class TestSalesOrder(unittest.TestCase): so = make_sales_order(item_list=so_items, do_not_submit=True) so.submit() - po = make_purchase_order_for_drop_shipment(so.name, '_Test Supplier') + po = make_purchase_order(so.name, '_Test Supplier', selected_items=[so_items[0]['item_code']]) po.submit() dn = create_dn_against_so(so.name, delivered_qty=1) From 100af0a6356316a322bc8a48cde8aeda152ad479 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 4 Jul 2019 12:29:26 +0530 Subject: [PATCH 10/49] fix(salary-slip): Nonetype error on amount calculation (#18147) * fix: amount calculation * Update salary_slip.py --- erpnext/hr/doctype/salary_slip/salary_slip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index db0a3a5ece..6d25c06393 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -618,7 +618,7 @@ class SalarySlip(TransactionBase): elif not self.payment_days and not self.salary_slip_based_on_timesheet and cint(row.depends_on_payment_days): amount, additional_amount = 0, 0 elif not row.amount: - amount = row.default_amount + row.additional_amount + amount = flt(row.default_amount) + flt(row.additional_amount) # apply rounding if frappe.get_cached_value("Salary Component", row.salary_component, "round_to_the_nearest_integer"): From 29734dae181279c8d478d7c58dab291e81c69abf Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 4 Jul 2019 12:50:16 +0530 Subject: [PATCH 11/49] patch: reloads newly created docs in v12 (#18148) --- erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py b/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py index 9925b70a96..3d4a9952c7 100644 --- a/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py +++ b/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py @@ -3,6 +3,8 @@ import frappe from erpnext.regional.india.setup import make_custom_fields def execute(): + frappe.reload_doc("accounts", "doctype", "tax_category") + frappe.reload_doc("stock", "doctype", "item_manufacturer") company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: return From a9ea49c9767c50bd5c69d6fbc8a04a47a3bc12bc Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 4 Jul 2019 15:56:34 +0530 Subject: [PATCH 12/49] fix: Additonal discount not get set in sales transactions --- erpnext/selling/sales_common.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 9bae58b309..ad3f27c9c1 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -145,6 +145,11 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }, discount_amount: function(doc, cdt, cdn) { + + if(doc.name === cdn) { + return; + } + var item = frappe.get_doc(cdt, cdn); item.discount_percentage = 0.0; this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount'); From 95054a5a02ba19c70841ab2da5d151daa281d452 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 16:13:56 +0530 Subject: [PATCH 13/49] fix: Try Faster Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a8a0d82614..4a297e11c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ install: - nvm install 10 - pip install python-coveralls - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - - sudo python install.py --develop --user travis --without-bench-setup + - sudo python install.py --develop --user travis --without-bench-setup --frappe-branch-url https://github.com/adityahase/frappe --frappe-branch faster-travis - sudo pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow From 697f995b27b4717823c13521e4a534ae04531783 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 16:19:22 +0530 Subject: [PATCH 14/49] fix: Send --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4a297e11c0..200668d79b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ install: - nvm install 10 - pip install python-coveralls - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - - sudo python install.py --develop --user travis --without-bench-setup --frappe-branch-url https://github.com/adityahase/frappe --frappe-branch faster-travis + - sudo python install.py --develop --user travis --without-bench-setup --frappe-repo-url https://github.com/adityahase/frappe --frappe-branch faster-travis - sudo pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow From d7291dbcad7dd9036eaad279ba6a30815a7486e6 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 16:51:53 +0530 Subject: [PATCH 15/49] Revert "fix: Send" This reverts commit 697f995b27b4717823c13521e4a534ae04531783. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 200668d79b..4a297e11c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ install: - nvm install 10 - pip install python-coveralls - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - - sudo python install.py --develop --user travis --without-bench-setup --frappe-repo-url https://github.com/adityahase/frappe --frappe-branch faster-travis + - sudo python install.py --develop --user travis --without-bench-setup --frappe-branch-url https://github.com/adityahase/frappe --frappe-branch faster-travis - sudo pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow From e5dc834ddb1f312a4d9514df6af9ddcffa0ad9a4 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 4 Jul 2019 16:52:01 +0530 Subject: [PATCH 16/49] Revert "fix: Try Faster Travis" This reverts commit 95054a5a02ba19c70841ab2da5d151daa281d452. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4a297e11c0..a8a0d82614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ install: - nvm install 10 - pip install python-coveralls - wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py - - sudo python install.py --develop --user travis --without-bench-setup --frappe-branch-url https://github.com/adityahase/frappe --frappe-branch faster-travis + - sudo python install.py --develop --user travis --without-bench-setup - sudo pip install -e ~/bench - rm $TRAVIS_BUILD_DIR/.git/shallow From a5d975d867a0f09cdf97d93b2a579658bde1717b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 4 Jul 2019 17:47:28 +0530 Subject: [PATCH 17/49] fix: Don't show Send SMS in BOM --- erpnext/public/js/controllers/transaction.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index bbd1f1c17b..741f21e712 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -383,8 +383,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ setup_sms: function() { var me = this; + let blacklist = ['Purchase Invoice', 'BOM']; if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status) - && this.frm.doctype != "Purchase Invoice") { + && !blacklist.includes(this.frm.doctype)) { this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); }); } }, From 1d3008750c6de8a83b5ecbde0bb261ebde3fc8c4 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 4 Jul 2019 17:53:10 +0530 Subject: [PATCH 18/49] fix(UX): Move Bill of Materials section to first --- erpnext/config/manufacturing.py | 74 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index da60550221..c79c5b8b11 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -3,43 +3,6 @@ from frappe import _ def get_data(): return [ - { - "label": _("Production"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Work Order", - "description": _("Orders released for production."), - "onboard": 1, - "dependencies": ["Item", "BOM"] - }, - { - "type": "doctype", - "name": "Production Plan", - "description": _("Generate Material Requests (MRP) and Work Orders."), - "onboard": 1, - "dependencies": ["Item", "BOM"] - }, - { - "type": "doctype", - "name": "Stock Entry", - "onboard": 1, - "dependencies": ["Item"] - }, - { - "type": "doctype", - "name": "Timesheet", - "description": _("Time Sheet for manufacturing."), - "onboard": 1, - "dependencies": ["Activity Type"] - }, - { - "type": "doctype", - "name": "Job Card" - } - ] - }, { "label": _("Bill of Materials"), "items": [ @@ -85,6 +48,43 @@ def get_data(): ] }, + { + "label": _("Production"), + "icon": "fa fa-star", + "items": [ + { + "type": "doctype", + "name": "Work Order", + "description": _("Orders released for production."), + "onboard": 1, + "dependencies": ["Item", "BOM"] + }, + { + "type": "doctype", + "name": "Production Plan", + "description": _("Generate Material Requests (MRP) and Work Orders."), + "onboard": 1, + "dependencies": ["Item", "BOM"] + }, + { + "type": "doctype", + "name": "Stock Entry", + "onboard": 1, + "dependencies": ["Item"] + }, + { + "type": "doctype", + "name": "Timesheet", + "description": _("Time Sheet for manufacturing."), + "onboard": 1, + "dependencies": ["Activity Type"] + }, + { + "type": "doctype", + "name": "Job Card" + } + ] + }, { "label": _("Tools"), "icon": "fa fa-wrench", From 2b969a469a05c2e4962cc080e71a6b6acf716616 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 4 Jul 2019 18:52:32 +0530 Subject: [PATCH 19/49] fix: Build item cache in long worker (#18165) --- erpnext/portal/product_configurator/item_variants_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/portal/product_configurator/item_variants_cache.py b/erpnext/portal/product_configurator/item_variants_cache.py index f33c8d605b..fc294ce58b 100644 --- a/erpnext/portal/product_configurator/item_variants_cache.py +++ b/erpnext/portal/product_configurator/item_variants_cache.py @@ -110,4 +110,4 @@ def build_cache(item_code): def enqueue_build_cache(item_code): if frappe.cache().hget('item_cache_build_in_progress', item_code): return - frappe.enqueue(build_cache, item_code=item_code, queue='short') + frappe.enqueue(build_cache, item_code=item_code, queue='long') From 23c80658bae91468f18b6305430ed328ad582f5f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 4 Jul 2019 18:54:04 +0530 Subject: [PATCH 20/49] fix: Add links in validation message --- erpnext/stock/doctype/stock_entry/stock_entry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 464101e672..0abcbb328a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -324,8 +324,10 @@ class StockEntry(StockController): completed_qty = d.completed_qty + (allowance_percentage/100 * d.completed_qty) if total_completed_qty > flt(completed_qty): job_card = frappe.db.get_value('Job Card', {'operation_id': d.name}, 'name') - frappe.throw(_("Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order # {3}. Please update operation status via Job Card # {4}") - .format(d.idx, d.operation, total_completed_qty, self.work_order, job_card), OperationsNotCompleteError) + work_order_link = frappe.utils.get_link_to_form('Work Order', self.work_order) + job_card_link = frappe.utils.get_link_to_form('Job Card', job_card) + frappe.throw(_("Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}.") + .format(d.idx, frappe.bold(d.operation), frappe.bold(total_completed_qty), work_order_link, job_card_link), OperationsNotCompleteError) def check_duplicate_entry_for_work_order(self): other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", { From 7fc6021ca5ec621232354666477dc95e40c12626 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Thu, 4 Jul 2019 22:46:16 +0530 Subject: [PATCH 21/49] feat(setup): adding selling buying filter in terms and conditions --- .../doctype/pos_profile/pos_profile.js | 4 + erpnext/hr/doctype/job_offer/job_offer.js | 6 + .../doctype/blanket_order/blanket_order.js | 23 + erpnext/patches.txt | 1 + ...default_buying_selling_terms_in_company.py | 19 + erpnext/public/js/controllers/buying.js | 8 + erpnext/public/js/controllers/transaction.js | 13 +- erpnext/selling/sales_common.js | 6 + erpnext/setup/doctype/company/company.js | 8 + erpnext/setup/doctype/company/company.json | 1543 +++++++++-------- .../terms_and_conditions.json | 246 +-- .../terms_and_conditions.py | 4 + erpnext/startup/boot.py | 2 +- 13 files changed, 916 insertions(+), 967 deletions(-) create mode 100644 erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index 10f127a53f..5e94118d60 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -8,6 +8,10 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { return { filters: { selling: 1 } }; }); + frm.set_query("tc_name", function() { + return { filters: { selling: 1 } }; + }); + erpnext.queries.setup_queries(frm, "Warehouse", function() { return erpnext.queries.warehouse(frm.doc); }); diff --git a/erpnext/hr/doctype/job_offer/job_offer.js b/erpnext/hr/doctype/job_offer/job_offer.js index 1ee35afe10..c3d83c48cc 100755 --- a/erpnext/hr/doctype/job_offer/job_offer.js +++ b/erpnext/hr/doctype/job_offer/job_offer.js @@ -4,6 +4,12 @@ frappe.provide("erpnext.job_offer"); frappe.ui.form.on("Job Offer", { + onload: function (frm) { + frm.set_query("select_terms", function() { + return { filters: { hr: 1 } }; + }); + }, + select_terms: function (frm) { erpnext.utils.get_terms(frm.doc.select_terms, frm.doc, function (r) { if (!r.exc) { diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index 89efb9369d..d1bef3fcd0 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -2,6 +2,10 @@ // For license information, please see license.txt frappe.ui.form.on('Blanket Order', { + onload: function(frm) { + frm.trigger('set_tc_name_filter'); + }, + setup: function(frm) { frm.add_fetch("customer", "customer_name", "customer_name"); frm.add_fetch("supplier", "supplier_name", "supplier_name"); @@ -44,4 +48,23 @@ frappe.ui.form.on('Blanket Order', { } }); }, + + set_tc_name_filter: function(frm) { + if (frm.doc.blanket_order_type === 'Selling'){ + frm.set_query("tc_name", function() { + return { filters: { selling: 1 } }; + }); + } + if (frm.doc.blanket_order_type === 'Purchasing'){ + frm.set_query("tc_name", function() { + return { filters: { buying: 1 } }; + }); + } + }, + + blanket_order_type: function (frm) { + frm.trigger('set_tc_name_filter'); + } }); + + diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 72db8ad063..bd41c7e884 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -615,3 +615,4 @@ 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 +erpnext.patches.v12_0.add_default_buying_selling_terms_in_company diff --git a/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py new file mode 100644 index 0000000000..484f81a7ac --- /dev/null +++ b/erpnext/patches/v12_0/add_default_buying_selling_terms_in_company.py @@ -0,0 +1,19 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + frappe.reload_doc("setup", "doctype", "company") + if frappe.db.has_column('Company', 'default_terms'): + rename_field('Company', "default_terms", "default_selling_terms") + + for company in frappe.get_all("Company", ["name", "default_selling_terms", "default_buying_terms"]): + if company.default_selling_terms and not company.default_buying_terms: + frappe.db.set_value("Company", company.name, "default_buying_terms", company.default_selling_terms) + + frappe.reload_doc("setup", "doctype", "terms_and_conditions") + frappe.db.sql("update `tabTerms and Conditions` set selling=1, buying=1, hr=1") diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 97c823d053..824b8d98d2 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -61,6 +61,14 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ }); } + if(this.frm.fields_dict.tc_name) { + this.frm.set_query("tc_name", function() { + return{ + filters: { 'buying': 1 } + } + }); + } + me.frm.set_query('supplier', erpnext.queries.supplier); me.frm.set_query('contact_person', erpnext.queries.contact_query); me.frm.set_query('supplier_address', erpnext.queries.address_query); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a3ecea168d..11fdb8b178 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -585,8 +585,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ me.frm.set_value("letter_head", company_doc.default_letter_head); } } - if (company_doc.default_terms && me.frm.doc.doctype != "Purchase Invoice" && frappe.meta.has_field(me.frm.doc.doctype, "tc_name")) { - me.frm.set_value("tc_name", company_doc.default_terms); + let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"]; + if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && + selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) { + me.frm.set_value("tc_name", company_doc.default_selling_terms); + } + let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order", + "Material Request", "Purchase Receipt"]; + // Purchase Invoice is excluded as per issue #3345 + if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && + buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) { + me.frm.set_value("tc_name", company_doc.default_buying_terms); } frappe.run_serially([ diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 9bae58b309..00efb4f219 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -59,6 +59,12 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }); } + if(this.frm.fields_dict.tc_name) { + this.frm.set_query("tc_name", function() { + return { filters: { selling: 1 } }; + }); + } + if(!this.frm.fields_dict["items"]) { return; } diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 1e6056ec86..313de677fc 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -17,6 +17,14 @@ frappe.ui.form.on("Company", { filters: {"is_group": 1} } }); + + frm.set_query("default_selling_terms", function() { + return { filters: { selling: 1 } }; + }); + + frm.set_query("default_buying_terms", function() { + return { filters: { buying: 1 } }; + }); }, company_name: function(frm) { diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index bb652ca6bf..bc3418997d 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -1,769 +1,776 @@ { - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:company_name", - "creation": "2013-04-10 08:35:39", - "description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "details", - "company_name", - "abbr", - "change_abbr", - "is_group", - "cb0", - "domain", - "parent_company", - "charts_section", - "default_currency", - "default_letter_head", - "default_holiday_list", - "default_finance_book", - "standard_working_hours", - "default_terms", - "default_warehouse_for_sales_return", - "column_break_10", - "country", - "create_chart_of_accounts_based_on", - "chart_of_accounts", - "existing_company", - "tax_id", - "date_of_establishment", - "sales_settings", - "monthly_sales_target", - "sales_monthly_history", - "column_break_goals", - "transactions_annual_history", - "total_monthly_sales", - "default_settings", - "default_bank_account", - "default_cash_account", - "default_receivable_account", - "round_off_account", - "round_off_cost_center", - "write_off_account", - "discount_allowed_account", - "discount_received_account", - "exchange_gain_loss_account", - "unrealized_exchange_gain_loss_account", - "column_break0", - "allow_account_creation_against_child_company", - "default_payable_account", - "default_employee_advance_account", - "default_expense_account", - "default_income_account", - "default_deferred_revenue_account", - "default_deferred_expense_account", - "default_payroll_payable_account", - "default_expense_claim_payable_account", - "section_break_22", - "cost_center", - "column_break_26", - "credit_limit", - "payment_terms", - "auto_accounting_for_stock_settings", - "enable_perpetual_inventory", - "default_inventory_account", - "stock_adjustment_account", - "column_break_32", - "stock_received_but_not_billed", - "expenses_included_in_valuation", - "fixed_asset_depreciation_settings", - "accumulated_depreciation_account", - "depreciation_expense_account", - "series_for_depreciation_entry", - "expenses_included_in_asset_valuation", - "column_break_40", - "disposal_account", - "depreciation_cost_center", - "capital_work_in_progress_account", - "asset_received_but_not_billed", - "budget_detail", - "exception_budget_approver_role", - "company_info", - "company_logo", - "date_of_incorporation", - "address_html", - "date_of_commencement", - "phone_no", - "fax", - "email", - "website", - "column_break1", - "company_description", - "registration_info", - "registration_details", - "delete_company_transactions", - "lft", - "rgt", - "old_parent" - ], - "fields": [ - { - "fieldname": "details", - "fieldtype": "Section Break", - "oldfieldtype": "Section Break" - }, - { - "fieldname": "company_name", - "fieldtype": "Data", - "label": "Company", - "oldfieldname": "company_name", - "oldfieldtype": "Data", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "abbr", - "fieldtype": "Data", - "label": "Abbr", - "oldfieldname": "abbr", - "oldfieldtype": "Data", - "reqd": 1 - }, - { - "depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")", - "fieldname": "change_abbr", - "fieldtype": "Button", - "label": "Change Abbreviation" - }, - { - "bold": 1, - "default": "0", - "fieldname": "is_group", - "fieldtype": "Check", - "label": "Is Group" - }, - { - "fieldname": "default_finance_book", - "fieldtype": "Link", - "label": "Default Finance Book", - "options": "Finance Book" - }, - { - "fieldname": "cb0", - "fieldtype": "Column Break" - }, - { - "fieldname": "domain", - "fieldtype": "Link", - "label": "Domain", - "options": "Domain" - }, - { - "fieldname": "parent_company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Parent Company", - "options": "Company" - }, - { - "fieldname": "company_logo", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Company Logo" - }, - { - "fieldname": "company_description", - "fieldtype": "Text Editor", - "label": "Company Description" - }, - { - "collapsible": 1, - "fieldname": "sales_settings", - "fieldtype": "Section Break", - "label": "Sales Settings" - }, - { - "fieldname": "sales_monthly_history", - "fieldtype": "Small Text", - "hidden": 1, - "label": "Sales Monthly History", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "transactions_annual_history", - "fieldtype": "Code", - "hidden": 1, - "label": "Transactions Annual History", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "monthly_sales_target", - "fieldtype": "Currency", - "label": "Monthly Sales Target", - "options": "default_currency" - }, - { - "fieldname": "column_break_goals", - "fieldtype": "Column Break" - }, - { - "fieldname": "total_monthly_sales", - "fieldtype": "Currency", - "label": "Total Monthly Sales", - "no_copy": 1, - "options": "default_currency", - "read_only": 1 - }, - { - "fieldname": "charts_section", - "fieldtype": "Section Break", - "label": "Default Values" - }, - { - "fieldname": "default_currency", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Currency", - "options": "Currency", - "reqd": 1 - }, - { - "fieldname": "default_letter_head", - "fieldtype": "Link", - "label": "Default Letter Head", - "options": "Letter Head" - }, - { - "fieldname": "default_holiday_list", - "fieldtype": "Link", - "label": "Default Holiday List", - "options": "Holiday List" - }, - { - "fieldname": "standard_working_hours", - "fieldtype": "Float", - "label": "Standard Working Hours" - }, - { - "fieldname": "default_terms", - "fieldtype": "Link", - "label": "Default Terms", - "options": "Terms and Conditions" - }, - { - "fieldname": "default_warehouse_for_sales_return", - "fieldtype": "Link", - "label": "Default warehouse for Sales Return", - "options": "Warehouse" - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "fieldname": "country", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Country", - "options": "Country", - "reqd": 1 - }, - { - "fieldname": "create_chart_of_accounts_based_on", - "fieldtype": "Select", - "label": "Create Chart Of Accounts Based On", - "options": "\nStandard Template\nExisting Company" - }, - { - "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", - "fieldname": "chart_of_accounts", - "fieldtype": "Select", - "label": "Chart Of Accounts Template", - "no_copy": 1 - }, - { - "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", - "fieldname": "existing_company", - "fieldtype": "Link", - "label": "Existing Company ", - "no_copy": 1, - "options": "Company" - }, - { - "fieldname": "tax_id", - "fieldtype": "Data", - "label": "Tax ID" - }, - { - "fieldname": "date_of_establishment", - "fieldtype": "Date", - "label": "Date of Establishment" - }, - { - "fieldname": "default_settings", - "fieldtype": "Section Break", - "label": "Accounts Settings", - "oldfieldtype": "Section Break" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_bank_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Bank Account", - "no_copy": 1, - "oldfieldname": "default_bank_account", - "oldfieldtype": "Link", - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_cash_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Cash Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_receivable_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Receivable Account", - "no_copy": 1, - "oldfieldname": "receivables_group", - "oldfieldtype": "Link", - "options": "Account" - }, - { - "fieldname": "round_off_account", - "fieldtype": "Link", - "label": "Round Off Account", - "options": "Account" - }, - { - "fieldname": "round_off_cost_center", - "fieldtype": "Link", - "label": "Round Off Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "write_off_account", - "fieldtype": "Link", - "label": "Write Off Account", - "options": "Account" - }, - { - "fieldname": "discount_allowed_account", - "fieldtype": "Link", - "label": "Discount Allowed Account", - "options": "Account" - }, - { - "fieldname": "discount_received_account", - "fieldtype": "Link", - "label": "Discount Received Account", - "options": "Account" - }, - { - "fieldname": "exchange_gain_loss_account", - "fieldtype": "Link", - "label": "Exchange Gain / Loss Account", - "options": "Account" - }, - { - "fieldname": "unrealized_exchange_gain_loss_account", - "fieldtype": "Link", - "label": "Unrealized Exchange Gain/Loss Account", - "options": "Account" - }, - { - "fieldname": "column_break0", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "default": "0", - "depends_on": "eval:doc.parent_company", - "fieldname": "allow_account_creation_against_child_company", - "fieldtype": "Check", - "label": "Allow Account Creation Against Child Company" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_payable_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Payable Account", - "no_copy": 1, - "oldfieldname": "payables_group", - "oldfieldtype": "Link", - "options": "Account" - }, - { - "fieldname": "default_employee_advance_account", - "fieldtype": "Link", - "label": "Default Employee Advance Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Cost of Goods Sold Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_income_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Income Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_deferred_revenue_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Deferred Revenue Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_deferred_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Deferred Expense Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_payroll_payable_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Payroll Payable Account", - "no_copy": 1, - "options": "Account" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "default_expense_claim_payable_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Expense Claim Payable Account", - "no_copy": 1, - "options": "Account" - }, - { - "fieldname": "section_break_22", - "fieldtype": "Section Break" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "cost_center", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Cost Center", - "no_copy": 1, - "options": "Cost Center" - }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "credit_limit", - "fieldtype": "Currency", - "label": "Credit Limit", - "oldfieldname": "credit_limit", - "oldfieldtype": "Currency", - "options": "default_currency" - }, - { - "fieldname": "payment_terms", - "fieldtype": "Link", - "label": "Default Payment Terms Template", - "options": "Payment Terms Template" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "auto_accounting_for_stock_settings", - "fieldtype": "Section Break", - "label": "Stock Settings" - }, - { - "default": "1", - "fieldname": "enable_perpetual_inventory", - "fieldtype": "Check", - "label": "Enable Perpetual Inventory" - }, - { - "fieldname": "default_inventory_account", - "fieldtype": "Link", - "label": "Default Inventory Account", - "options": "Account" - }, - { - "fieldname": "stock_adjustment_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Stock Adjustment Account", - "no_copy": 1, - "options": "Account" - }, - { - "fieldname": "column_break_32", - "fieldtype": "Column Break" - }, - { - "fieldname": "stock_received_but_not_billed", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Stock Received But Not Billed", - "no_copy": 1, - "options": "Account" - }, - { - "fieldname": "expenses_included_in_valuation", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Expenses Included In Valuation", - "no_copy": 1, - "options": "Account" - }, - { - "collapsible": 1, - "fieldname": "fixed_asset_depreciation_settings", - "fieldtype": "Section Break", - "label": "Fixed Asset Depreciation Settings" - }, - { - "fieldname": "accumulated_depreciation_account", - "fieldtype": "Link", - "label": "Accumulated Depreciation Account", - "no_copy": 1, - "options": "Account" - }, - { - "fieldname": "depreciation_expense_account", - "fieldtype": "Link", - "label": "Depreciation Expense Account", - "no_copy": 1, - "options": "Account" - }, - { - "fieldname": "series_for_depreciation_entry", - "fieldtype": "Data", - "label": "Series for Asset Depreciation Entry (Journal Entry)" - }, - { - "fieldname": "expenses_included_in_asset_valuation", - "fieldtype": "Link", - "label": "Expenses Included In Asset Valuation", - "options": "Account" - }, - { - "fieldname": "column_break_40", - "fieldtype": "Column Break" - }, - { - "fieldname": "disposal_account", - "fieldtype": "Link", - "label": "Gain/Loss Account on Asset Disposal", - "no_copy": 1, - "options": "Account" - }, - { - "fieldname": "depreciation_cost_center", - "fieldtype": "Link", - "label": "Asset Depreciation Cost Center", - "no_copy": 1, - "options": "Cost Center" - }, - { - "fieldname": "capital_work_in_progress_account", - "fieldtype": "Link", - "label": "Capital Work In Progress Account", - "options": "Account" - }, - { - "fieldname": "asset_received_but_not_billed", - "fieldtype": "Link", - "label": "Asset Received But Not Billed", - "options": "Account" - }, - { - "collapsible": 1, - "fieldname": "budget_detail", - "fieldtype": "Section Break", - "label": "Budget Detail" - }, - { - "fieldname": "exception_budget_approver_role", - "fieldtype": "Link", - "label": "Exception Budget Approver Role", - "options": "Role" - }, - { - "collapsible": 1, - "description": "For reference only.", - "fieldname": "company_info", - "fieldtype": "Section Break", - "label": "Company Info" - }, - { - "fieldname": "date_of_incorporation", - "fieldtype": "Date", - "label": "Date of Incorporation" - }, - { - "fieldname": "address_html", - "fieldtype": "HTML" - }, - { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "depends_on": "eval:doc.date_of_incorporation", - "fieldname": "date_of_commencement", - "fieldtype": "Date", - "label": "Date of Commencement" - }, - { - "fieldname": "phone_no", - "fieldtype": "Data", - "label": "Phone No", - "oldfieldname": "phone_no", - "oldfieldtype": "Data", - "options": "Phone" - }, - { - "fieldname": "fax", - "fieldtype": "Data", - "label": "Fax", - "oldfieldname": "fax", - "oldfieldtype": "Data", - "options": "Phone" - }, - { - "fieldname": "email", - "fieldtype": "Data", - "label": "Email", - "oldfieldname": "email", - "oldfieldtype": "Data", - "options": "Email" - }, - { - "fieldname": "website", - "fieldtype": "Data", - "label": "Website", - "oldfieldname": "website", - "oldfieldtype": "Data" - }, - { - "fieldname": "registration_info", - "fieldtype": "Section Break", - "oldfieldtype": "Section Break", - "width": "50%" - }, - { - "description": "Company registration numbers for your reference. Tax numbers etc.", - "fieldname": "registration_details", - "fieldtype": "Code", - "label": "Registration Details", - "oldfieldname": "registration_details", - "oldfieldtype": "Code" - }, - { - "fieldname": "delete_company_transactions", - "fieldtype": "Button", - "label": "Delete Company Transactions" - }, - { - "fieldname": "lft", - "fieldtype": "Int", - "hidden": 1, - "label": "Lft", - "print_hide": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "rgt", - "fieldtype": "Int", - "hidden": 1, - "label": "Rgt", - "print_hide": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "old_parent", - "fieldtype": "Data", - "hidden": 1, - "label": "old_parent", - "print_hide": 1, - "read_only": 1 - } - ], - "icon": "fa fa-building", - "idx": 1, - "image_field": "company_logo", - "modified": "2019-06-14 14:36:11.363309", - "modified_by": "Administrator", - "module": "Setup", - "name": "Company", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "email": 1, - "print": 1, - "read": 1, - "role": "Accounts User" - }, - { - "read": 1, - "role": "Employee" - }, - { - "read": 1, - "role": "Sales User" - }, - { - "read": 1, - "role": "Purchase User" - }, - { - "read": 1, - "role": "Stock User" - }, - { - "read": 1, - "role": "Projects User" - } - ], - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "ASC", - "track_changes": 1 - } \ No newline at end of file + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:company_name", + "creation": "2013-04-10 08:35:39", + "description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "details", + "company_name", + "abbr", + "change_abbr", + "is_group", + "cb0", + "domain", + "parent_company", + "charts_section", + "default_currency", + "default_letter_head", + "default_holiday_list", + "default_finance_book", + "standard_working_hours", + "default_selling_terms", + "default_buying_terms", + "default_warehouse_for_sales_return", + "column_break_10", + "country", + "create_chart_of_accounts_based_on", + "chart_of_accounts", + "existing_company", + "tax_id", + "date_of_establishment", + "sales_settings", + "monthly_sales_target", + "sales_monthly_history", + "column_break_goals", + "transactions_annual_history", + "total_monthly_sales", + "default_settings", + "default_bank_account", + "default_cash_account", + "default_receivable_account", + "round_off_account", + "round_off_cost_center", + "write_off_account", + "discount_allowed_account", + "discount_received_account", + "exchange_gain_loss_account", + "unrealized_exchange_gain_loss_account", + "column_break0", + "allow_account_creation_against_child_company", + "default_payable_account", + "default_employee_advance_account", + "default_expense_account", + "default_income_account", + "default_deferred_revenue_account", + "default_deferred_expense_account", + "default_payroll_payable_account", + "default_expense_claim_payable_account", + "section_break_22", + "cost_center", + "column_break_26", + "credit_limit", + "payment_terms", + "auto_accounting_for_stock_settings", + "enable_perpetual_inventory", + "default_inventory_account", + "stock_adjustment_account", + "column_break_32", + "stock_received_but_not_billed", + "expenses_included_in_valuation", + "fixed_asset_depreciation_settings", + "accumulated_depreciation_account", + "depreciation_expense_account", + "series_for_depreciation_entry", + "expenses_included_in_asset_valuation", + "column_break_40", + "disposal_account", + "depreciation_cost_center", + "capital_work_in_progress_account", + "asset_received_but_not_billed", + "budget_detail", + "exception_budget_approver_role", + "company_info", + "company_logo", + "date_of_incorporation", + "address_html", + "date_of_commencement", + "phone_no", + "fax", + "email", + "website", + "column_break1", + "company_description", + "registration_info", + "registration_details", + "delete_company_transactions", + "lft", + "rgt", + "old_parent" + ], + "fields": [ + { + "fieldname": "details", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break" + }, + { + "fieldname": "company_name", + "fieldtype": "Data", + "label": "Company", + "oldfieldname": "company_name", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "abbr", + "fieldtype": "Data", + "label": "Abbr", + "oldfieldname": "abbr", + "oldfieldtype": "Data", + "reqd": 1 + }, + { + "depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")", + "fieldname": "change_abbr", + "fieldtype": "Button", + "label": "Change Abbreviation" + }, + { + "bold": 1, + "default": "0", + "fieldname": "is_group", + "fieldtype": "Check", + "label": "Is Group" + }, + { + "fieldname": "default_finance_book", + "fieldtype": "Link", + "label": "Default Finance Book", + "options": "Finance Book" + }, + { + "fieldname": "cb0", + "fieldtype": "Column Break" + }, + { + "fieldname": "domain", + "fieldtype": "Link", + "label": "Domain", + "options": "Domain" + }, + { + "fieldname": "parent_company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parent Company", + "options": "Company" + }, + { + "fieldname": "company_logo", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Company Logo" + }, + { + "fieldname": "company_description", + "fieldtype": "Text Editor", + "label": "Company Description" + }, + { + "collapsible": 1, + "fieldname": "sales_settings", + "fieldtype": "Section Break", + "label": "Sales Settings" + }, + { + "fieldname": "sales_monthly_history", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Sales Monthly History", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "transactions_annual_history", + "fieldtype": "Code", + "hidden": 1, + "label": "Transactions Annual History", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "monthly_sales_target", + "fieldtype": "Currency", + "label": "Monthly Sales Target", + "options": "default_currency" + }, + { + "fieldname": "column_break_goals", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_monthly_sales", + "fieldtype": "Currency", + "label": "Total Monthly Sales", + "no_copy": 1, + "options": "default_currency", + "read_only": 1 + }, + { + "fieldname": "charts_section", + "fieldtype": "Section Break", + "label": "Default Values" + }, + { + "fieldname": "default_currency", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Currency", + "options": "Currency", + "reqd": 1 + }, + { + "fieldname": "default_letter_head", + "fieldtype": "Link", + "label": "Default Letter Head", + "options": "Letter Head" + }, + { + "fieldname": "default_holiday_list", + "fieldtype": "Link", + "label": "Default Holiday List", + "options": "Holiday List" + }, + { + "fieldname": "standard_working_hours", + "fieldtype": "Float", + "label": "Standard Working Hours" + }, + { + "fieldname": "default_warehouse_for_sales_return", + "fieldtype": "Link", + "label": "Default warehouse for Sales Return", + "options": "Warehouse" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "country", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Country", + "options": "Country", + "reqd": 1 + }, + { + "fieldname": "create_chart_of_accounts_based_on", + "fieldtype": "Select", + "label": "Create Chart Of Accounts Based On", + "options": "\nStandard Template\nExisting Company" + }, + { + "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", + "fieldname": "chart_of_accounts", + "fieldtype": "Select", + "label": "Chart Of Accounts Template", + "no_copy": 1 + }, + { + "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", + "fieldname": "existing_company", + "fieldtype": "Link", + "label": "Existing Company ", + "no_copy": 1, + "options": "Company" + }, + { + "fieldname": "tax_id", + "fieldtype": "Data", + "label": "Tax ID" + }, + { + "fieldname": "date_of_establishment", + "fieldtype": "Date", + "label": "Date of Establishment" + }, + { + "fieldname": "default_settings", + "fieldtype": "Section Break", + "label": "Accounts Settings", + "oldfieldtype": "Section Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_bank_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Bank Account", + "no_copy": 1, + "oldfieldname": "default_bank_account", + "oldfieldtype": "Link", + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_cash_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Cash Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_receivable_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Receivable Account", + "no_copy": 1, + "oldfieldname": "receivables_group", + "oldfieldtype": "Link", + "options": "Account" + }, + { + "fieldname": "round_off_account", + "fieldtype": "Link", + "label": "Round Off Account", + "options": "Account" + }, + { + "fieldname": "round_off_cost_center", + "fieldtype": "Link", + "label": "Round Off Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "write_off_account", + "fieldtype": "Link", + "label": "Write Off Account", + "options": "Account" + }, + { + "fieldname": "discount_allowed_account", + "fieldtype": "Link", + "label": "Discount Allowed Account", + "options": "Account" + }, + { + "fieldname": "discount_received_account", + "fieldtype": "Link", + "label": "Discount Received Account", + "options": "Account" + }, + { + "fieldname": "exchange_gain_loss_account", + "fieldtype": "Link", + "label": "Exchange Gain / Loss Account", + "options": "Account" + }, + { + "fieldname": "unrealized_exchange_gain_loss_account", + "fieldtype": "Link", + "label": "Unrealized Exchange Gain/Loss Account", + "options": "Account" + }, + { + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "default": "0", + "depends_on": "eval:doc.parent_company", + "fieldname": "allow_account_creation_against_child_company", + "fieldtype": "Check", + "label": "Allow Account Creation Against Child Company" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_payable_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Payable Account", + "no_copy": 1, + "oldfieldname": "payables_group", + "oldfieldtype": "Link", + "options": "Account" + }, + { + "fieldname": "default_employee_advance_account", + "fieldtype": "Link", + "label": "Default Employee Advance Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_expense_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Cost of Goods Sold Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_income_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Income Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_deferred_revenue_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Deferred Revenue Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_deferred_expense_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Deferred Expense Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_payroll_payable_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Payroll Payable Account", + "no_copy": 1, + "options": "Account" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "default_expense_claim_payable_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Expense Claim Payable Account", + "no_copy": 1, + "options": "Account" + }, + { + "fieldname": "section_break_22", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "cost_center", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Default Cost Center", + "no_copy": 1, + "options": "Cost Center" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "credit_limit", + "fieldtype": "Currency", + "label": "Credit Limit", + "oldfieldname": "credit_limit", + "oldfieldtype": "Currency", + "options": "default_currency" + }, + { + "fieldname": "payment_terms", + "fieldtype": "Link", + "label": "Default Payment Terms Template", + "options": "Payment Terms Template" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "auto_accounting_for_stock_settings", + "fieldtype": "Section Break", + "label": "Stock Settings" + }, + { + "default": "1", + "fieldname": "enable_perpetual_inventory", + "fieldtype": "Check", + "label": "Enable Perpetual Inventory" + }, + { + "fieldname": "default_inventory_account", + "fieldtype": "Link", + "label": "Default Inventory Account", + "options": "Account" + }, + { + "fieldname": "stock_adjustment_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Stock Adjustment Account", + "no_copy": 1, + "options": "Account" + }, + { + "fieldname": "column_break_32", + "fieldtype": "Column Break" + }, + { + "fieldname": "stock_received_but_not_billed", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Stock Received But Not Billed", + "no_copy": 1, + "options": "Account" + }, + { + "fieldname": "expenses_included_in_valuation", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Expenses Included In Valuation", + "no_copy": 1, + "options": "Account" + }, + { + "collapsible": 1, + "fieldname": "fixed_asset_depreciation_settings", + "fieldtype": "Section Break", + "label": "Fixed Asset Depreciation Settings" + }, + { + "fieldname": "accumulated_depreciation_account", + "fieldtype": "Link", + "label": "Accumulated Depreciation Account", + "no_copy": 1, + "options": "Account" + }, + { + "fieldname": "depreciation_expense_account", + "fieldtype": "Link", + "label": "Depreciation Expense Account", + "no_copy": 1, + "options": "Account" + }, + { + "fieldname": "series_for_depreciation_entry", + "fieldtype": "Data", + "label": "Series for Asset Depreciation Entry (Journal Entry)" + }, + { + "fieldname": "expenses_included_in_asset_valuation", + "fieldtype": "Link", + "label": "Expenses Included In Asset Valuation", + "options": "Account" + }, + { + "fieldname": "column_break_40", + "fieldtype": "Column Break" + }, + { + "fieldname": "disposal_account", + "fieldtype": "Link", + "label": "Gain/Loss Account on Asset Disposal", + "no_copy": 1, + "options": "Account" + }, + { + "fieldname": "depreciation_cost_center", + "fieldtype": "Link", + "label": "Asset Depreciation Cost Center", + "no_copy": 1, + "options": "Cost Center" + }, + { + "fieldname": "capital_work_in_progress_account", + "fieldtype": "Link", + "label": "Capital Work In Progress Account", + "options": "Account" + }, + { + "fieldname": "asset_received_but_not_billed", + "fieldtype": "Link", + "label": "Asset Received But Not Billed", + "options": "Account" + }, + { + "collapsible": 1, + "fieldname": "budget_detail", + "fieldtype": "Section Break", + "label": "Budget Detail" + }, + { + "fieldname": "exception_budget_approver_role", + "fieldtype": "Link", + "label": "Exception Budget Approver Role", + "options": "Role" + }, + { + "collapsible": 1, + "description": "For reference only.", + "fieldname": "company_info", + "fieldtype": "Section Break", + "label": "Company Info" + }, + { + "fieldname": "date_of_incorporation", + "fieldtype": "Date", + "label": "Date of Incorporation" + }, + { + "fieldname": "address_html", + "fieldtype": "HTML" + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "depends_on": "eval:doc.date_of_incorporation", + "fieldname": "date_of_commencement", + "fieldtype": "Date", + "label": "Date of Commencement" + }, + { + "fieldname": "phone_no", + "fieldtype": "Data", + "label": "Phone No", + "oldfieldname": "phone_no", + "oldfieldtype": "Data", + "options": "Phone" + }, + { + "fieldname": "fax", + "fieldtype": "Data", + "label": "Fax", + "oldfieldname": "fax", + "oldfieldtype": "Data", + "options": "Phone" + }, + { + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "oldfieldname": "email", + "oldfieldtype": "Data", + "options": "Email" + }, + { + "fieldname": "website", + "fieldtype": "Data", + "label": "Website", + "oldfieldname": "website", + "oldfieldtype": "Data" + }, + { + "fieldname": "registration_info", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "width": "50%" + }, + { + "description": "Company registration numbers for your reference. Tax numbers etc.", + "fieldname": "registration_details", + "fieldtype": "Code", + "label": "Registration Details", + "oldfieldname": "registration_details", + "oldfieldtype": "Code" + }, + { + "fieldname": "delete_company_transactions", + "fieldtype": "Button", + "label": "Delete Company Transactions" + }, + { + "fieldname": "lft", + "fieldtype": "Int", + "hidden": 1, + "label": "Lft", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "rgt", + "fieldtype": "Int", + "hidden": 1, + "label": "Rgt", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "old_parent", + "fieldtype": "Data", + "hidden": 1, + "label": "old_parent", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "default_selling_terms", + "fieldtype": "Link", + "label": "Default Selling Terms", + "options": "Terms and Conditions" + }, + { + "fieldname": "default_buying_terms", + "fieldtype": "Link", + "label": "Default Buying Terms", + "options": "Terms and Conditions" + } + ], + "icon": "fa fa-building", + "idx": 1, + "image_field": "company_logo", + "modified": "2019-07-04 22:20:45.104307", + "modified_by": "Administrator", + "module": "Setup", + "name": "Company", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User" + }, + { + "read": 1, + "role": "Employee" + }, + { + "read": 1, + "role": "Sales User" + }, + { + "read": 1, + "role": "Purchase User" + }, + { + "read": 1, + "role": "Stock User" + }, + { + "read": 1, + "role": "Projects User" + } + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json index c1d459f265..aba6a791a4 100644 --- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json +++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json @@ -1,288 +1,142 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:title", - "beta": 0, "creation": "2013-01-10 16:34:24", - "custom": 0, "description": "Standard Terms and Conditions that can be added to Sales and Purchases.\n\nExamples:\n\n1. Validity of the offer.\n1. Payment Terms (In Advance, On Credit, part advance etc).\n1. What is extra (or payable by the Customer).\n1. Safety / usage warning.\n1. Warranty if any.\n1. Returns Policy.\n1. Terms of shipping, if applicable.\n1. Ways of addressing disputes, indemnity, liability, etc.\n1. Address and Contact of your Company.", - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "title", + "disabled", + "applicable_modules_section", + "selling", + "buying", + "hr", + "section_break_7", + "terms", + "terms_and_conditions_help" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Title", - "length": 0, "no_copy": 1, "oldfieldname": "title", "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": "0", "fieldname": "disabled", "fieldtype": "Check", - "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": "Disabled", - "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": "Disabled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "allow_in_quick_entry": 1, "fieldname": "terms", "fieldtype": "Text Editor", - "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": "Terms and Conditions", - "length": 0, - "no_copy": 0, "oldfieldname": "terms", - "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": 0, - "columns": 0, "fieldname": "terms_and_conditions_help", "fieldtype": "HTML", - "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": "Terms and Conditions Help", - "length": 0, - "no_copy": 0, - "options": "

Standard Terms and Conditions Example

\n\n
Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n
\n\n

How to get fieldnames

\n\n

The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)

\n\n

Templating

\n\n

Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, read this documentation.

", - "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": "

Standard Terms and Conditions Example

\n\n
Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n
\n\n

How to get fieldnames

\n\n

The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)

\n\n

Templating

\n\n

Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, read this documentation.

" + }, + { + "fieldname": "applicable_modules_section", + "fieldtype": "Section Break", + "label": "Applicable Modules" + }, + { + "default": "1", + "fieldname": "selling", + "fieldtype": "Check", + "label": "Selling" + }, + { + "default": "1", + "fieldname": "buying", + "fieldtype": "Check", + "label": "Buying" + }, + { + "default": "1", + "fieldname": "hr", + "fieldtype": "Check", + "label": "HR" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "icon-legal", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 06:36:33.131473", + "modified": "2019-07-04 13:31:30.393425", "modified_by": "Administrator", "module": "Setup", "name": "Terms and Conditions", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales Master Manager", - "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": 0, - "print": 0, "read": 1, - "report": 0, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, - "report": 0, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Purchase User" }, { - "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": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "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": "Accounts 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": 0, - "print": 0, "read": 1, - "report": 0, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py index a2152a6eb5..372cc6d3e3 100644 --- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py +++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.py @@ -3,9 +3,11 @@ from __future__ import unicode_literals import frappe +from frappe import _, throw import json from frappe.model.document import Document from frappe.utils.jinja import validate_template +from frappe.utils import cint from six import string_types @@ -13,6 +15,8 @@ class TermsandConditions(Document): def validate(self): if self.terms: validate_template(self.terms) + if not cint(self.buying) and not cint(self.selling) and not cint(self.hr) and not cint(self.disabled): + throw(_("At least one of the Applicable Modules should be selected")) @frappe.whitelist() def get_terms_and_conditions(template_name, doc): diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index a5bd93fc2c..4ca43a89b8 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -33,7 +33,7 @@ def boot_session(bootinfo): FROM `tabCompany` LIMIT 1""") and 'Yes' or 'No' - bootinfo.docs += frappe.db.sql("""select name, default_currency, cost_center, default_terms, + bootinfo.docs += frappe.db.sql("""select name, default_currency, cost_center, default_selling_terms, default_buying_terms, default_letter_head, default_bank_account, enable_perpetual_inventory, country from `tabCompany`""", as_dict=1, update={"doctype":":Company"}) From 830f2b642bcaa7fd82f60b598d1a26a096523a4b Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Fri, 5 Jul 2019 09:48:38 +0530 Subject: [PATCH 22/49] chore(manufacturing): added dashboard for doctypes --- .../blanket_order/blanket_order_dashboard.py | 12 +++++++++ .../doctype/bom/bom_dashboard.py | 27 +++++++++++++++++++ .../doctype/operation/operation_dashboard.py | 13 +++++++++ .../doctype/routing/routing_dashboard.py | 12 +++++++++ .../workstation/workstation_dashboard.py | 13 +++++++++ erpnext/stock/doctype/item/item_dashboard.py | 2 +- 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py create mode 100644 erpnext/manufacturing/doctype/bom/bom_dashboard.py create mode 100644 erpnext/manufacturing/doctype/operation/operation_dashboard.py create mode 100644 erpnext/manufacturing/doctype/routing/routing_dashboard.py create mode 100644 erpnext/manufacturing/doctype/workstation/workstation_dashboard.py diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py new file mode 100644 index 0000000000..0005439cb1 --- /dev/null +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'blanket_order', + 'transactions': [ + { + 'items': ['Purchase Order', 'Sales Order'] + } + ] + } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py new file mode 100644 index 0000000000..af710b0464 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py @@ -0,0 +1,27 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'bom_no', + 'non_standard_fieldnames': { + 'Item': 'default_bom', + 'Purchase Order': 'bom', + 'Purchase Receipt': 'bom', + 'Purchase Invoice': 'bom' + }, + 'transactions': [ + { + 'label': _('Stock'), + 'items': ['Item', 'Stock Entry', 'Quality Inspection'] + }, + { + 'label': _('Manufacture'), + 'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan'] + }, + { + 'label': _('Purchase'), + 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] + } + ] + } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/operation/operation_dashboard.py b/erpnext/manufacturing/doctype/operation/operation_dashboard.py new file mode 100644 index 0000000000..9e53a20ad5 --- /dev/null +++ b/erpnext/manufacturing/doctype/operation/operation_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'operation', + 'transactions': [ + { + 'label': _('Manufacture'), + 'items': ['BOM', 'Work Order', 'Job Card', 'Timesheet'] + } + ] + } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/routing/routing_dashboard.py b/erpnext/manufacturing/doctype/routing/routing_dashboard.py new file mode 100644 index 0000000000..ab309cc9d5 --- /dev/null +++ b/erpnext/manufacturing/doctype/routing/routing_dashboard.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'routing', + 'transactions': [ + { + 'items': ['BOM'] + } + ] + } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py new file mode 100644 index 0000000000..c5683134f2 --- /dev/null +++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'workstation', + 'transactions': [ + { + 'label': _('Manufacture'), + 'items': ['BOM', 'Routing', 'Work Order', 'Job Card', 'Operation', 'Timesheet'] + } + ] + } \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py index b3733d357b..4bf3c38e22 100644 --- a/erpnext/stock/doctype/item/item_dashboard.py +++ b/erpnext/stock/doctype/item/item_dashboard.py @@ -41,7 +41,7 @@ def get_data(): }, { 'label': _('Manufacture'), - 'items': ['Work Order', 'Item Manufacturer'] + 'items': ['Production Plan', 'Work Order', 'Item Manufacturer'] } ] } \ No newline at end of file From ad33c195100abfd35fd1d5ed16998c8ac1a3563b Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Fri, 5 Jul 2019 11:28:59 +0530 Subject: [PATCH 23/49] fix: converting spaces to tabs on a few lines --- .../doctype/blanket_order/blanket_order_dashboard.py | 2 +- erpnext/manufacturing/doctype/bom/bom_dashboard.py | 10 +++++----- .../doctype/job_card/job_card_dashboard.py | 2 +- .../doctype/operation/operation_dashboard.py | 2 +- .../doctype/workstation/workstation_dashboard.py | 2 +- erpnext/stock/doctype/item/item_dashboard.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py index 0005439cb1..ed319a0cef 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order_dashboard.py @@ -9,4 +9,4 @@ def get_data(): 'items': ['Purchase Order', 'Sales Order'] } ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/bom/bom_dashboard.py b/erpnext/manufacturing/doctype/bom/bom_dashboard.py index af710b0464..803ece7c78 100644 --- a/erpnext/manufacturing/doctype/bom/bom_dashboard.py +++ b/erpnext/manufacturing/doctype/bom/bom_dashboard.py @@ -6,9 +6,9 @@ def get_data(): 'fieldname': 'bom_no', 'non_standard_fieldnames': { 'Item': 'default_bom', - 'Purchase Order': 'bom', - 'Purchase Receipt': 'bom', - 'Purchase Invoice': 'bom' + 'Purchase Order': 'bom', + 'Purchase Receipt': 'bom', + 'Purchase Invoice': 'bom' }, 'transactions': [ { @@ -19,9 +19,9 @@ def get_data(): 'label': _('Manufacture'), 'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan'] }, - { + { 'label': _('Purchase'), 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] } ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py index d48bccf9d4..c2aa2bd968 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py +++ b/erpnext/manufacturing/doctype/job_card/job_card_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['Material Request', 'Stock Entry'] } ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/operation/operation_dashboard.py b/erpnext/manufacturing/doctype/operation/operation_dashboard.py index 9e53a20ad5..8deb9ec6e0 100644 --- a/erpnext/manufacturing/doctype/operation/operation_dashboard.py +++ b/erpnext/manufacturing/doctype/operation/operation_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['BOM', 'Work Order', 'Job Card', 'Timesheet'] } ] - } \ No newline at end of file + } diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py index c5683134f2..9e0d1d1739 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py +++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py @@ -10,4 +10,4 @@ def get_data(): 'items': ['BOM', 'Routing', 'Work Order', 'Job Card', 'Operation', 'Timesheet'] } ] - } \ No newline at end of file + } diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py index 4bf3c38e22..dd4676a037 100644 --- a/erpnext/stock/doctype/item/item_dashboard.py +++ b/erpnext/stock/doctype/item/item_dashboard.py @@ -44,4 +44,4 @@ def get_data(): 'items': ['Production Plan', 'Work Order', 'Item Manufacturer'] } ] - } \ No newline at end of file + } From cdb75b56c5a9c858b2a4b8b87aaa632f1e1a3ec6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 5 Jul 2019 12:20:02 +0530 Subject: [PATCH 24/49] fix: removed item manaufacturer child table from item master --- erpnext/stock/doctype/item/item.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 3936bf7524..164ffa443b 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -76,7 +76,6 @@ "is_customer_provided_item", "customer", "supplier_details", - "manufacturers", "delivered_by_supplier", "column_break2", "supplier_items", @@ -1022,12 +1021,6 @@ "fieldtype": "Check", "label": "Synced With Hub", "read_only": 1 - }, - { - "fieldname": "manufacturers", - "fieldtype": "Table", - "label": "Manufacturers", - "options": "Item Manufacturer" } ], "has_web_view": 1, @@ -1035,7 +1028,7 @@ "idx": 2, "image_field": "image", "max_attachments": 1, - "modified": "2019-06-02 04:45:59.911507", + "modified": "2019-07-05 12:18:13.977931", "modified_by": "Administrator", "module": "Stock", "name": "Item", @@ -1097,4 +1090,4 @@ "sort_order": "DESC", "title_field": "item_name", "track_changes": 1 - } \ No newline at end of file + } From 82e2bac89112b74d577db1c917dece714439b651 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Fri, 5 Jul 2019 12:41:19 +0530 Subject: [PATCH 25/49] fix(coding style): adding a few spaces --- erpnext/manufacturing/doctype/blanket_order/blanket_order.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index d1bef3fcd0..0bbf689d4a 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -50,12 +50,12 @@ frappe.ui.form.on('Blanket Order', { }, set_tc_name_filter: function(frm) { - if (frm.doc.blanket_order_type === 'Selling'){ + if (frm.doc.blanket_order_type === 'Selling') { frm.set_query("tc_name", function() { return { filters: { selling: 1 } }; }); } - if (frm.doc.blanket_order_type === 'Purchasing'){ + if (frm.doc.blanket_order_type === 'Purchasing') { frm.set_query("tc_name", function() { return { filters: { buying: 1 } }; }); From 7a7c66e95e281181520ed0f5ea8c1ca5d10a1aa2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 5 Jul 2019 13:06:53 +0530 Subject: [PATCH 26/49] refactor: added throw if supplier is not default for any item --- .../material_request/material_request.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index ef5f24e59d..2b079e7995 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -370,19 +370,20 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None): def get_material_requests_based_on_supplier(supplier): supplier_items = [d.parent for d in frappe.db.get_all("Item Default", {"default_supplier": supplier}, 'parent')] - if supplier_items: - material_requests = frappe.db.sql_list("""select distinct mr.name - from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item - where mr.name = mr_item.parent - and mr_item.item_code in (%s) - and mr.material_request_type = 'Purchase' - and mr.per_ordered < 99.99 - and mr.docstatus = 1 - and mr.status != 'Stopped' - order by mr_item.item_code ASC""" % ', '.join(['%s']*len(supplier_items)), - tuple(supplier_items)) - else: - material_requests = [] + if not supplier_items: + frappe.throw(_("{0} is not the default supplier for any items.".format(supplier))) + + material_requests = frappe.db.sql_list("""select distinct mr.name + from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item + where mr.name = mr_item.parent + and mr_item.item_code in (%s) + and mr.material_request_type = 'Purchase' + and mr.per_ordered < 99.99 + and mr.docstatus = 1 + and mr.status != 'Stopped' + order by mr_item.item_code ASC""" % ', '.join(['%s']*len(supplier_items)), + tuple(supplier_items)) + return material_requests, supplier_items @frappe.whitelist() From 2f3f7507c4d131d204b473938f9f33a4338983fd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 28 Apr 2019 23:30:00 +0530 Subject: [PATCH 27/49] fix: browser hangs while making payment entry for large number of outstaning invoices --- .../doctype/payment_entry/payment_entry.js | 87 ++++++++++++------- .../doctype/payment_entry/payment_entry.json | 12 ++- .../doctype/payment_entry/payment_entry.py | 30 ++++--- erpnext/accounts/utils.py | 10 ++- 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2c382c58f6..986457fac8 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,49 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, - get_outstanding_documents: function(frm) { + get_outstanding_invoice: function(frm) { + const fields = [ + {fieldtype:"Date", label: __("Posting From Date"), fieldname:"from_date"}, + {fieldtype:"Column Break"}, + {fieldtype:"Date", label: __("Posting To Date"), fieldname:"to_date"}, + {fieldtype:"Section Break"}, + ]; + + frappe.prompt(fields, function(data){ + frappe.flags.allocate_payment_amount = true; + frm.events.get_outstanding_documents(frm, data); + }, __("Select Date"), __("Get Outstanding Invoices")); + }, + + get_outstanding_documents: function(frm, date_args) { 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 + } + + if(date_args) { + args["from_date"] = date_args["from_date"]; + args["to_date"] = date_args["to_date"]; + } + 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,24 +627,26 @@ 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)); + + frappe.flags.allocate_payment_amount = false; } }); }, - allocate_payment_amount: function(frm) { - if(frm.doc.payment_type == 'Internal Transfer'){ - return - } + // 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); - } - }, + // 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; @@ -677,7 +698,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 +979,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 fcaa570331..4950bd12d3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -325,19 +325,17 @@ "reqd": 1 }, { - "collapsible": 1, - "collapsible_depends_on": "references", + "collapsible": 0, + "collapsible_depends_on": "", "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", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index ea76126d8c..bb0d44e9d4 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,16 @@ 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") + + if args.get("from_date") and args.get("to_date"): + condition += " and posting_date between '{0}' and '{1}'".format(args.get("from_date"), args.get("to_date")) + + 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"), condition=condition, limit=100) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -606,12 +612,13 @@ 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) return negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed -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): if party_type == "Customer": voucher_type = 'Sales Order' elif party_type == "Supplier": @@ -641,6 +648,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,7 +660,7 @@ 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: @@ -663,7 +671,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 +693,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 +706,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() diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 7a1f6c57c2..d1861785af 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -632,6 +632,10 @@ def get_outstanding_invoices(party_type, party, account, condition=None): outstanding_invoices = [] precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2 + limit_cond = '' + if limit: + limit_cond = " limit {}".format(limit) + if erpnext.get_party_account_type(party_type) == 'Receivable': dr_or_cr = "debit_in_account_currency - credit_in_account_currency" payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency" @@ -673,11 +677,11 @@ def get_outstanding_invoices(party_type, party, account, condition=None): and account = %(account)s and {payment_dr_or_cr} > 0 and against_voucher is not null and against_voucher != '' - group by against_voucher_type, against_voucher - """.format(payment_dr_or_cr=payment_dr_or_cr), { + group by against_voucher_type, against_voucher {limit_cond} + """.format(payment_dr_or_cr=payment_dr_or_cr, limit_cond= limit_cond), { "party_type": party_type, "party": party, - "account": account, + "account": account }, as_dict=True) pe_map = frappe._dict() From 0f065d5de163a0eeb778a0e0c3d3d1af1808a1bc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 2 May 2019 00:06:04 +0530 Subject: [PATCH 28/49] Added due date in the gl entry --- .../accounts/doctype/gl_entry/gl_entry.json | 35 ++++++++- .../doctype/journal_entry/journal_entry.py | 1 + .../doctype/payment_entry/payment_entry.js | 72 ++++++++++++------- .../doctype/payment_entry/payment_entry.json | 6 +- .../doctype/payment_entry/payment_entry.py | 32 ++++++--- .../purchase_invoice/purchase_invoice.py | 1 + .../doctype/sales_invoice/sales_invoice.py | 1 + erpnext/accounts/utils.py | 23 +++--- erpnext/patches.txt | 1 + .../patches/v12_0/update_due_date_in_gle.py | 17 +++++ 10 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 erpnext/patches/v12_0/update_due_date_in_gle.py diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 333d39aae6..a232a953f3 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 3132c937dd..8fbddb9b0c 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 986457fac8..f17b2cbeda 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -530,20 +530,57 @@ frappe.ui.form.on('Payment Entry', { }, get_outstanding_invoice: function(frm) { + const today = frappe.datetime.get_today(); const fields = [ - {fieldtype:"Date", label: __("Posting From Date"), fieldname:"from_date"}, + {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: __("Posting To Date"), fieldname:"to_date"}, + {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(data){ + frappe.prompt(fields, function(filters){ frappe.flags.allocate_payment_amount = true; - frm.events.get_outstanding_documents(frm, data); - }, __("Select Date"), __("Get Outstanding Invoices")); + frm.events.validate_filters_data(frm, filters); + frm.events.get_outstanding_documents(frm, filters); + }, __("Filters"), __("Get Outstanding Invoices")); }, - get_outstanding_documents: function(frm, date_args) { + 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) { @@ -563,11 +600,12 @@ frappe.ui.form.on('Payment Entry', { "cost_center": frm.doc.cost_center } - if(date_args) { - args["from_date"] = date_args["from_date"]; - args["to_date"] = date_args["to_date"]; + 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: { @@ -628,26 +666,10 @@ 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)); - frappe.flags.allocate_payment_amount = false; } }); }, - // 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; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 4950bd12d3..a85eccd30a 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,8 +325,6 @@ "reqd": 1 }, { - "collapsible": 0, - "collapsible_depends_on": "", "depends_on": "eval:(doc.party && doc.paid_from && doc.paid_to && doc.paid_amount && doc.received_amount)", "fieldname": "section_break_14", "fieldtype": "Section Break", @@ -568,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 bb0d44e9d4..699f04675f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -587,14 +587,21 @@ def get_outstanding_reference_documents(args): if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account(): condition += " and cost_center='%s'" % args.get("cost_center") - if args.get("from_date") and args.get("to_date"): - condition += " and posting_date between '{0}' and '{1}'".format(args.get("from_date"), args.get("to_date")) + 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"))) + 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, limit=100) + args.get("party_account"), filters=args, condition=condition, limit=100) for d in outstanding_invoices: d["exchange_rate"] = 1 @@ -612,13 +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"), args.get("company"), 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, - company, party_account_currency, company_currency, cost_center=None): + company, party_account_currency, company_currency, cost_center=None, filters=None): if party_type == "Customer": voucher_type = 'Sales Order' elif party_type == "Supplier": @@ -664,6 +677,10 @@ def get_orders_to_be_billed(posting_date, party_type, party, 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) @@ -934,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 a6f6acea66..1a49be3399 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -416,6 +416,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 \ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b725c73c8b..6d44811f19 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/utils.py b/erpnext/accounts/utils.py index d1861785af..542c7e4e52 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -628,14 +628,10 @@ 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 - limit_cond = '' - if limit: - limit_cond = " limit {}".format(limit) - if erpnext.get_party_account_type(party_type) == 'Receivable': dr_or_cr = "debit_in_account_currency - credit_in_account_currency" payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency" @@ -648,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,8 +674,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None): and account = %(account)s and {payment_dr_or_cr} > 0 and against_voucher is not null and against_voucher != '' - group by against_voucher_type, against_voucher {limit_cond} - """.format(payment_dr_or_cr=payment_dr_or_cr, limit_cond= limit_cond), { + group by against_voucher_type, against_voucher + """.format(payment_dr_or_cr=payment_dr_or_cr), { "party_type": party_type, "party": party, "account": account @@ -692,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, @@ -704,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/patches.txt b/erpnext/patches.txt index 72db8ad063..b35a6da586 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -615,3 +615,4 @@ 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 +erpnext.patches.v12_0.update_due_date_in_gle 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 0000000000..9e2be6fcee --- /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 account in (select name from `tabAccount` where account_type in ('Receivable', 'Payable') )""" + .format(doctype=doctype)) \ No newline at end of file From 909d7734626d65e8e179b28c8a8545abbbfab401 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 4 Jul 2019 19:32:46 +0530 Subject: [PATCH 29/49] fix: on credit note / debit note deferred reversed instead of income --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 6 +++++- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a6f6acea66..1b03896dab 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -484,9 +484,13 @@ class PurchaseInvoice(BuyingController): "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()): + + expense_account = (item.expense_account + if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) + gl_entries.append( self.get_gl_dict({ - "account": item.expense_account if not item.enable_deferred_expense else item.deferred_expense_account, + "account": expense_account, "against": self.supplier, "debit": flt(item.base_net_amount, item.precision("base_net_amount")), "debit_in_account_currency": (flt(item.base_net_amount, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index b725c73c8b..71bdc2aafe 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -783,10 +783,13 @@ class SalesInvoice(SellingController): asset.db_set("disposal_date", self.posting_date) asset.set_status("Sold" if self.docstatus==1 else None) else: - account_currency = get_account_currency(item.income_account) + income_account = (item.income_account + if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) + + account_currency = get_account_currency(income_account) gl_entries.append( self.get_gl_dict({ - "account": item.income_account if not item.enable_deferred_revenue else item.deferred_revenue_account, + "account": income_account, "against": self.customer, "credit": flt(item.base_net_amount, item.precision("base_net_amount")), "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) From ee01ba587902ee15b7e08ee9cb0a98a2b55cdd3a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 5 Jul 2019 15:21:45 +0530 Subject: [PATCH 30/49] fix: linting --- erpnext/patches/v12_0/update_due_date_in_gle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v12_0/update_due_date_in_gle.py b/erpnext/patches/v12_0/update_due_date_in_gle.py index 9e2be6fcee..4c47a82dcc 100644 --- a/erpnext/patches/v12_0/update_due_date_in_gle.py +++ b/erpnext/patches/v12_0/update_due_date_in_gle.py @@ -13,5 +13,5 @@ def execute(): 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 account in (select name from `tabAccount` where account_type in ('Receivable', 'Payable') )""" - .format(doctype=doctype)) \ No newline at end of file + and account in (select name from `tabAccount` where account_type in ('Receivable', 'Payable') )""" #nosec + .format(doctype=doctype)) From 6277966105a8765178ddc69e4487ccbfd4aa3a72 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 5 Jul 2019 15:58:13 +0530 Subject: [PATCH 31/49] chore: added check for auto-reorder in stock settings (#18174) * chore: added check for auto-reorder in stock settings * style: removed explicit return --- erpnext/stock/doctype/item/item.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 80d4e172a5..6484b93485 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -122,6 +122,7 @@ class Item(WebsiteGenerator): self.validate_item_defaults() self.validate_customer_provided_part() self.update_defaults_from_item_group() + self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() if not self.get("__islocal"): @@ -859,6 +860,12 @@ class Item(WebsiteGenerator): filters={"production_item": self.name, "docstatus": 1}): return True + def validate_auto_reorder_enabled_in_stock_settings(self): + if self.reorder_levels: + enabled = frappe.db.get_single_value('Stock Settings', 'auto_indent') + if not enabled: + frappe.msgprint(msg=_("You have to enable auto re-order in Stock Settings to maintain re-order levels."), title=_("Enable Auto Re-Order"), indicator="orange") + def get_timeline_data(doctype, name): '''returns timeline data based on stock ledger entry''' out = {} From 5d4d70b75f1404e28bfca4c435fcfb6d992d19fe Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 5 Jul 2019 16:59:27 +0530 Subject: [PATCH 32/49] feat: provision to make debit / credit note against the stock returned entry --- .../purchase_invoice/purchase_invoice.py | 6 +++-- erpnext/controllers/status_updater.py | 2 +- .../doctype/delivery_note/delivery_note.js | 26 +++++++++++++++++++ .../doctype/delivery_note/delivery_note.py | 5 +++- .../purchase_receipt/purchase_receipt.js | 23 ++++++++++++++++ .../purchase_receipt/purchase_receipt.py | 10 +++++++ 6 files changed, 68 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4d87edf375..18a38d8c20 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 @@ -769,7 +770,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/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 42e0a43e6e..62be2e4e2a 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -290,7 +290,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/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 78bc06a47b..8c4a5cd11c 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -77,8 +77,34 @@ frappe.ui.form.on("Delivery Note", { }, + print_without_amount: function(frm) { erpnext.stock.delivery_note.set_print_hide(frm.doc); + }, + + refresh: function(frm) { + if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) { + frm.add_custom_button(__('Credit Note'), function() { + frappe.confirm(__("Are you sure you want to make credit note?"), + function() { + frm.trigger("make_credit_note"); + } + ); + }, __('Create')); + + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + }, + + make_credit_note: function(frm) { + frm.call({ + method: "make_return_invoice", + doc: frm.doc, + freeze: true, + callback: function() { + frm.reload_doc(); + } + }); } }); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 1e522b834e..ec7df2da6d 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -333,7 +333,10 @@ class DeliveryNote(SellingController): return_invoice.is_return = True return_invoice.save() return_invoice.submit() - frappe.msgprint(_("Credit Note {0} has been created automatically").format(return_invoice.name)) + + credit_note_link = frappe.utils.get_link_to_form('Sales Invoice', return_invoice.name) + + frappe.msgprint(_("Credit Note {0} has been created automatically").format(credit_note_link)) except: frappe.throw(_("Could not create Credit Note automatically, please uncheck 'Issue Credit Note' and submit again")) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index e82aa2c63e..a2d3e75f23 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -38,6 +38,29 @@ frappe.ui.form.on("Purchase Receipt", { if(frm.doc.company) { frm.trigger("toggle_display_account_head"); } + + if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) { + frm.add_custom_button(__('Debit Note'), function() { + frappe.confirm(__("Are you sure you want to make debit note?"), + function() { + frm.trigger("make_debit_note"); + } + ); + }, __('Create')); + + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + }, + + make_debit_note: function(frm) { + frm.call({ + method: "make_return_invoice", + doc: frm.doc, + freeze: true, + callback: function() { + frm.reload_doc(); + } + }); }, company: function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index cdca44d60b..11e60b0438 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -396,6 +396,16 @@ class PurchaseReceipt(BuyingController): self.load_from_db() + def make_return_invoice(self): + return_invoice = make_purchase_invoice(self.name) + return_invoice.is_return = True + return_invoice.save() + return_invoice.submit() + + debit_note_link = frappe.utils.get_link_to_form('Purchase Invoice', return_invoice.name) + + frappe.msgprint(_("Debit Note {0} has been created automatically").format(debit_note_link)) + def update_billed_amount_based_on_po(po_detail, update_modified=True): # Billed against Sales Order directly billed_against_po = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item` From 7ce199807cafc3c25c634adeb13b8924a081c578 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 7 Jul 2019 14:25:08 +0530 Subject: [PATCH 33/49] fix: GSTR-1 B2CS Json file generation and cess amount fixes --- erpnext/regional/report/gstr_1/gstr_1.py | 55 ++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index eff578001f..e8c170e721 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -74,7 +74,6 @@ class Gstr1Report(object): for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): invoice_details = self.invoices.get(inv) - for rate, items in items_based_on_rate.items(): place_of_supply = invoice_details.get("place_of_supply") ecommerce_gstin = invoice_details.get("ecommerce_gstin") @@ -85,7 +84,7 @@ class Gstr1Report(object): "rate": "", "taxable_value": 0, "cess_amount": 0, - "type": 0 + "type": "" }) row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) @@ -94,6 +93,7 @@ class Gstr1Report(object): row["rate"] = rate row["taxable_value"] += sum([abs(net_amount) for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) + row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) row["type"] = "E" if ecommerce_gstin else "OE" for key, value in iteritems(b2cs_output): @@ -123,6 +123,10 @@ class Gstr1Report(object): row += [tax_rate or 0, taxable_value] + for column in self.other_columns: + if column.get('fieldname') == 'cess_amount': + row.append(flt(self.invoice_cess.get(invoice), 2)) + return row, taxable_value def get_invoice_data(self): @@ -327,7 +331,7 @@ class Gstr1Report(object): "fieldtype": "Data" }, { - "fieldname": "invoice_type", + "fieldname": "gst_category", "label": "Invoice Type", "fieldtype": "Data" }, @@ -564,12 +568,18 @@ def get_json(): out = get_b2b_json(res, gstin) gst_json["b2b"] = out + elif filters["type_of_business"] == "B2C Large": for item in report_data[:-1]: res.setdefault(item["place_of_supply"], []).append(item) out = get_b2cl_json(res, gstin) gst_json["b2cl"] = out + + elif filters["type_of_business"] == "B2C Small": + out = get_b2cs_json(report_data[:-1], gstin) + gst_json["b2cs"] = out + elif filters["type_of_business"] == "EXPORT": for item in report_data[:-1]: res.setdefault(item["export_type"], []).append(item) @@ -605,6 +615,45 @@ def get_b2b_json(res, gstin): return out +def get_b2cs_json(data, gstin): + + company_state_number = gstin[0:2] + + out = [] + for d in data: + + pos = d.get('place_of_supply').split('-')[0] + tax_details = {} + + rate = d.get('rate', 0) + tax = flt((d["taxable_value"]*rate)/100.0, 2) + + if company_state_number == pos: + tax_details.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)}) + else: + tax_details.update({"iamt": tax}) + + inv = { + "sply_ty": "INTRA" if company_state_number == pos else "INTER", + "pos": pos, + "typ": d.get('type'), + "txval": flt(d.get('taxable_value'), 2), + "rt": rate, + "iamt": flt(tax_details.get('iamt'), 2), + "camt": flt(tax_details.get('camt'), 2), + "samt": flt(tax_details.get('samt'), 2), + "csamt": flt(d.get('cess_amount'), 2) + } + + if d.get('type') == "E" and d.get('ecommerce_gstin'): + inv.update({ + "etin": d.get('ecommerce_gstin') + }) + + out.append(inv) + + return out + def get_b2cl_json(res, gstin): out = [] for pos in res: From 6bb5ade667120dd5ec01cb2ea5d2aed9aa86eb6b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 7 Jul 2019 21:24:45 +0530 Subject: [PATCH 34/49] fix: Add accounting dimensions to various reports and fixes --- .../accounting_dimension.js | 29 +++++++++++++------ .../accounting_dimension.json | 5 ++-- .../accounting_dimension.py | 6 ++-- .../accounts_payable/accounts_payable.js | 11 +++++++ .../accounts_payable_summary.js | 11 +++++++ .../accounts_receivable.js | 11 +++++++ .../accounts_receivable.py | 9 ++++++ .../accounts_receivable_summary.js | 11 +++++++ .../budget_variance_report.js | 4 +-- .../report/general_ledger/general_ledger.js | 4 +-- .../profitability_analysis.js | 9 ++++-- .../profitability_analysis.py | 19 ++++++++---- .../report/sales_register/sales_register.js | 11 +++++++ .../report/sales_register/sales_register.py | 11 +++++++ .../report/trial_balance/trial_balance.js | 4 +-- erpnext/public/js/financial_statements.js | 4 +-- .../public/js/utils/dimension_tree_filter.js | 2 +- 17 files changed, 127 insertions(+), 34 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index fcbd10f606..dd20632a65 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 1e2bb925a1..57543a0ef4 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 91d03be493..15ace7239e 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/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 70f193e787..f6a561f04f 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 06499adeea..ec4f0c983f 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 3661afe797..2a45454bac 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 29737484c7..0cda2c15dd 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 f9162adabd..a7c0787fcd 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 b2072f06f1..f2a33a83ee 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/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 32af644021..ea82575b80 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/profitability_analysis/profitability_analysis.js b/erpnext/accounts/report/profitability_analysis/profitability_analysis.js index 80b50b92c3..d6864b54f7 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 a0d8c5f0e4..6e9b31f2f6 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 0b48882ca9..442aa1262e 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 de60995ca2..d08056f6f9 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 8bc72807b3..73d2ab3898 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/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index d1113a4ca4..89cb13d981 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -129,9 +129,7 @@ function get_filters(){ } ] - let dimension_filters = erpnext.get_dimension_filters(); - - dimension_filters.then((dimensions) => { + erpnext.dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { filters.push({ "fieldname": dimension["fieldname"], diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index fef450795b..a9122d8dff 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -7,7 +7,7 @@ erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoi "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile"]; -let dimension_filters = erpnext.get_dimension_filters(); +erpnext.dimension_filters = erpnext.get_dimension_filters(); erpnext.doctypes_with_dimensions.forEach((doctype) => { frappe.ui.form.on(doctype, { From 35f15148fb60df17d709925ee81155d98c7422c6 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Mon, 8 Jul 2019 04:55:28 +0000 Subject: [PATCH 35/49] fix: do not update modified (#18193) --- erpnext/support/doctype/issue/issue.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 93f13f1f9f..ad1c263250 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -258,15 +258,15 @@ def set_service_level_agreement_variance(issue=None): if not doc.first_responded_on: # first_responded_on set when first reply is sent to customer variance = round(time_diff_in_hours(doc.response_by, current_time), 2) - frappe.db.set_value("Issue", doc.name, "response_by_variance", variance) + frappe.db.set_value(dt="Issue", dn=doc.name, field="response_by_variance", val=variance, update_modified=False) if variance < 0: - frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed") + frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False) if not doc.resolution_date: # resolution_date set when issue has been closed variance = round(time_diff_in_hours(doc.resolution_by, current_time), 2) - frappe.db.set_value("Issue", doc.name, "resolution_by_variance", variance) + frappe.db.set_value(dt="Issue", dn=doc.name, field="resolution_by_variance", val=variance, update_modified=False) if variance < 0: - frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed") + frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False) def get_list_context(context=None): return { From 334335a2efdcb86a41230a57cb6e41f1e1342c25 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 8 Jul 2019 10:26:29 +0530 Subject: [PATCH 36/49] fix: not able to make credit note for the sales invoice in which item code is not set (#18184) --- erpnext/controllers/sales_and_purchase_return.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 8cf11f785b..2fddcdf24c 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")) From d86f027ce011bd194c092998d531f2192d9c599f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 8 Jul 2019 10:29:26 +0530 Subject: [PATCH 37/49] fix: default supplier was not set from the patch in item defaults for multi company instance (#18178) --- erpnext/patches.txt | 1 + ...pdate_default_supplier_in_item_defaults.py | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 erpnext/patches/v11_1/update_default_supplier_in_item_defaults.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0dd88e94e0..70bad34209 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -615,5 +615,6 @@ 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 +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 0000000000..347dec1f74 --- /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 From bef897602d5ab745bcd0051da7843f11fcf6b3fd Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 8 Jul 2019 10:30:26 +0530 Subject: [PATCH 38/49] fix: Use db_set since it triggers on_update event (#18175) --- .../doctype/bank_reconciliation/bank_reconciliation.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index 28807c4118..90cdf834c5 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 From 8309fcfbbc6cc5a97568aa988b993ebc8157e4e7 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jul 2019 10:39:30 +0530 Subject: [PATCH 39/49] BREAKING CHANGE: Remove anti-pattern "Project Task" (#18059) * BREAKING CHANGE: Remove anti-pattern "Project Task" * fix(tests): remove `tasks` from project/test_records.json * fix(tests) * fix(test): test_employee_onboarding.py * fix(tests): test_expense_claim.py * fix(refactor): cleanup project.py validate/update * fix(refactor): cleanup project.py validate/update * fix(test): test_expense_claim * fix(test): test_expense_claim * fix(test): test_expense_claim, try Test Company 4 * Update project.py --- .../test_employee_onboarding.py | 10 +- .../expense_claim/test_expense_claim.py | 52 +- erpnext/patches.txt | 1 + erpnext/patches/v12_0/set_task_status.py | 3 +- erpnext/projects/doctype/project/project.js | 71 - erpnext/projects/doctype/project/project.json | 1715 ++--------------- erpnext/projects/doctype/project/project.py | 254 +-- .../projects/doctype/project/test_project.py | 18 +- .../doctype/project/test_records.json | 8 +- .../projects/doctype/project_task/__init__.py | 0 .../doctype/project_task/project_task.json | 430 ----- .../doctype/project_task/project_task.py | 9 - erpnext/projects/doctype/task/task.py | 6 - .../doctype/sales_order/sales_order.py | 6 - 14 files changed, 187 insertions(+), 2396 deletions(-) delete mode 100644 erpnext/projects/doctype/project_task/__init__.py delete mode 100644 erpnext/projects/doctype/project_task/project_task.json delete mode 100644 erpnext/projects/doctype/project_task/project_task.py diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index 5e7f276ccc..35c9f728b6 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 92fdc09443..6618a4f7c5 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/patches.txt b/erpnext/patches.txt index 70bad34209..571c2dc75b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -615,6 +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/v12_0/set_task_status.py b/erpnext/patches/v12_0/set_task_status.py index 32b8177130..70f65097dc 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/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 528c7cd0c7..5613f088e1 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 2fc507b252..b4536c085c 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 55a689259a..6176cf89b4 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 beb1f130f5..06c62b62d2 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 9379c22b5c..567f359b50 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 e69de29bb2..0000000000 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 e26c191e0b..0000000000 --- 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 5f9d8d7622..0000000000 --- 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 d8fc199ec2..50557f1551 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/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 8ad3bf0607..6c0b02dd48 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -547,12 +547,6 @@ def make_project(source_name, target_doc=None): "base_grand_total" : "estimated_costing", } }, - "Sales Order Item": { - "doctype": "Project Task", - "field_map": { - "item_code": "title", - }, - } }, target_doc, postprocess) return doc From 6a7969117f1ec438f25ec5f8bfbbec10a04ef01d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 8 Jul 2019 10:40:40 +0530 Subject: [PATCH 40/49] fix(bom): escape name with wildcard character (#18164) --- erpnext/controllers/queries.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d74bc0ea18..47c9f0a4ce 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 From de13faf19a123998fa9dd1578b4d2d3d966faddc Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 9 Jul 2019 11:53:31 +0530 Subject: [PATCH 41/49] fix: sending sms from quotation --- erpnext/public/js/sms_manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/sms_manager.js b/erpnext/public/js/sms_manager.js index 6ce8bb1150..a7003a272d 100644 --- a/erpnext/public/js/sms_manager.js +++ b/erpnext/public/js/sms_manager.js @@ -20,8 +20,10 @@ erpnext.SMSManager = function SMSManager(doc) { 'Purchase Receipt' : 'Items has been received against purchase receipt: ' + doc.name } - if (in_list(['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype)) + if (in_list(['Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype)) this.show(doc.contact_person, 'Customer', doc.customer, '', default_msg[doc.doctype]); + else if (in_list(['Quotation'], doc.doctype)) + this.show(doc.contact_person, 'Customer', doc.party_name, '', default_msg[doc.doctype]); else if (in_list(['Purchase Order', 'Purchase Receipt'], doc.doctype)) this.show(doc.contact_person, 'Supplier', doc.supplier, '', default_msg[doc.doctype]); else if (doc.doctype == 'Lead') From 890ea195f3cc6487ab0809926c3a3cbea2837d12 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 9 Jul 2019 16:56:56 +0530 Subject: [PATCH 42/49] fix: Tree filter fixes for erpnext dimensions --- erpnext/public/js/utils/dimension_tree_filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index a9122d8dff..549f95e039 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -12,7 +12,7 @@ erpnext.dimension_filters = erpnext.get_dimension_filters(); erpnext.doctypes_with_dimensions.forEach((doctype) => { frappe.ui.form.on(doctype, { onload: function(frm) { - dimension_filters.then((dimensions) => { + erpnext.dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { frappe.model.with_doctype(dimension['document_type'], () => { if (frappe.meta.has_field(dimension['document_type'], 'is_group')) { From e9dd9b842e289a9683743a92b8974cba2c968df8 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 9 Jul 2019 17:54:00 +0530 Subject: [PATCH 43/49] Update erpnext/public/js/sms_manager.js Co-Authored-By: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/public/js/sms_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/sms_manager.js b/erpnext/public/js/sms_manager.js index a7003a272d..a058da23ac 100644 --- a/erpnext/public/js/sms_manager.js +++ b/erpnext/public/js/sms_manager.js @@ -22,7 +22,7 @@ erpnext.SMSManager = function SMSManager(doc) { if (in_list(['Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype)) this.show(doc.contact_person, 'Customer', doc.customer, '', default_msg[doc.doctype]); - else if (in_list(['Quotation'], doc.doctype)) + else if (doc.doctype === 'Quotation') this.show(doc.contact_person, 'Customer', doc.party_name, '', default_msg[doc.doctype]); else if (in_list(['Purchase Order', 'Purchase Receipt'], doc.doctype)) this.show(doc.contact_person, 'Supplier', doc.supplier, '', default_msg[doc.doctype]); From 838697261d1d8aa1968b724f4e3168b5d3364295 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 10 Jul 2019 10:39:43 +0530 Subject: [PATCH 44/49] fix: Call popup avatar Avoid getting session users image if customer doc has no image --- erpnext/public/js/call_popup/call_popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js index 91dfe809a4..89657a1837 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(`
- ${frappe.avatar(null, 'avatar-xl', contact.name, contact.image)} + ${frappe.avatar(null, 'avatar-xl', contact.name, contact.image || '')}
${contact_name}
${contact.mobile_no || ''}
From 3d28c065dbbd0f8e4a25b2f5e579a3a26d24f49d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 10 Jul 2019 17:03:23 +0530 Subject: [PATCH 45/49] fix(exchange-rate-revaluation): change create to view button on creation of journal entry (#18201) --- .../exchange_rate_revaluation.js | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) 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 dad75b4ba1..0d5456ece6 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'); } }, From 058787d2044b83c8a12885e1879b711b388f570f Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 10 Jul 2019 17:03:48 +0530 Subject: [PATCH 46/49] fix: error report for item price (#18212) --- erpnext/stock/doctype/item_price/item_price.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index d182290427..30675b54b3 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -31,13 +31,16 @@ class ItemPrice(Document): frappe.throw(_("Valid From Date must be lesser than Valid Upto Date.")) def update_price_list_details(self): - self.buying, self.selling, self.currency = \ - frappe.db.get_value("Price List", - {"name": self.price_list, "enabled": 1}, - ["buying", "selling", "currency"]) + if self.price_list: + self.buying, self.selling, self.currency = \ + frappe.db.get_value("Price List", + {"name": self.price_list, "enabled": 1}, + ["buying", "selling", "currency"]) def update_item_details(self): - self.item_name, self.item_description = frappe.db.get_value("Item",self.item_code,["item_name", "description"]) + if self.item_code: + self.item_name, self.item_description = frappe.db.get_value("Item", + self.item_code,["item_name", "description"]) def check_duplicates(self): conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s" From 61d8088be406822d224aec1399cfce3d9d3fd092 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 10 Jul 2019 17:05:25 +0530 Subject: [PATCH 47/49] fix: Return fieldtype so that the client-side can format chart values (#18211) --- .../profit_and_loss_statement/profit_and_loss_statement.py | 2 ++ 1 file changed, 2 insertions(+) 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 48d7361fe0..ac11868cab 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 From 59c5c3c347b2fd14adb7d39f4165ae50dc61d3a9 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 10 Jul 2019 17:05:49 +0530 Subject: [PATCH 48/49] fix: Make material request against SO only for pending qty (#18216) --- .../selling/doctype/sales_order/sales_order.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 6c0b02dd48..e9b310eb30 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -492,13 +492,27 @@ def close_or_unclose_sales_orders(names, status): frappe.local.message_log = [] +def get_requested_item_qty(sales_order): + return frappe._dict(frappe.db.sql(""" + select sales_order_item, sum(stock_qty) + from `tabMaterial Request Item` + where docstatus = 1 + and sales_order = %s + group by sales_order_item + """, sales_order)) + @frappe.whitelist() def make_material_request(source_name, target_doc=None): + requested_item_qty = get_requested_item_qty(source_name) + def postprocess(source, doc): doc.material_request_type = "Purchase" def update_item(source, target, source_parent): target.project = source_parent.project + target.qty = source.stock_qty - requested_item_qty.get(source.name, 0) + target.conversion_factor = 1 + target.stock_qty = source.stock_qty - requested_item_qty.get(source.name, 0) doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { @@ -523,7 +537,7 @@ def make_material_request(source_name, target_doc=None): "stock_uom": "uom", "stock_qty": "qty" }, - "condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code), + "condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0), "postprocess": update_item } }, target_doc, postprocess) From e7bb54ed767fc429027020f2e94c4a0a827ceeca Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 10 Jul 2019 11:45:59 +0000 Subject: [PATCH 49/49] feat(Issue): Settings to stop SLA tracking (#18112) * fix: fix msgprint when creating new issues * fix: use getdate * fix: do not self save * fix: test case * fix: error due to comparing date and str * fix: creation time for replicated issue * feat: sla in support settings * fix: remove sla settings * feat: enable check in sla * fix: throw if sla is disabled * fix: enable sla --- erpnext/config/support.py | 11 + .../doctype/customer/customer_dashboard.py | 4 - erpnext/support/doctype/issue/issue.js | 23 +- erpnext/support/doctype/issue/issue.json | 7 +- erpnext/support/doctype/issue/issue.py | 30 +- erpnext/support/doctype/issue/test_issue.py | 1 + .../service_level_agreement.json | 9 +- .../service_level_agreement.py | 24 +- .../test_service_level_agreement.py | 2 + .../support_settings/support_settings.json | 515 ++---------------- 10 files changed, 138 insertions(+), 488 deletions(-) diff --git a/erpnext/config/support.py b/erpnext/config/support.py index 0301bb3e19..36b4214196 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/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 87ca6a76bc..8e790bf9ce 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -25,10 +25,6 @@ def get_data(): 'label': _('Orders'), 'items': ['Sales Order', 'Delivery Note', 'Sales Invoice'] }, - { - 'label': _('Service Level Agreement'), - 'items': ['Service Level Agreement'] - }, { 'label': _('Payments'), 'items': ['Payment Entry'] diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 1a272d1bc4..9d93f37af7 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -129,8 +129,15 @@ frappe.ui.form.on("Issue", { function set_time_to_resolve_and_response(frm) { frm.dashboard.clear_headline(); - var time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_fulfilled); - var time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_fulfilled); + var time_to_respond = get_status(frm.doc.response_by_variance); + if (!frm.doc.first_responded_on && frm.doc.agreement_fulfilled === "Ongoing") { + time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_fulfilled); + } + + var time_to_resolve = get_status(frm.doc.resolution_by_variance); + if (!frm.doc.resolution_date && frm.doc.agreement_fulfilled === "Ongoing") { + time_to_resolve = get_time_left(frm.doc.response_by, frm.doc.agreement_fulfilled); + } frm.dashboard.set_headline_alert( '
' + @@ -146,7 +153,15 @@ function set_time_to_resolve_and_response(frm) { function get_time_left(timestamp, agreement_fulfilled) { const diff = moment(timestamp).diff(moment()); - const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : moment(0, 'seconds').format('HH:mm'); - let indicator = (diff_display == '00:00' && agreement_fulfilled != "Fulfilled") ? "red" : "green"; + const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed"; + let indicator = (diff_display == 'Failed' && agreement_fulfilled != "Fulfilled") ? "red" : "green"; return {"diff_display": diff_display, "indicator": indicator}; } + +function get_status(variance) { + if (variance > 0) { + return {"diff_display": "Fulfilled", "indicator": "green"}; + } else { + return {"diff_display": "Failed", "indicator": "red"}; + } +} \ No newline at end of file diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index b7c4166f6c..72153dcdea 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -113,6 +113,7 @@ "search_index": 1 }, { + "default": "Medium", "fieldname": "priority", "fieldtype": "Link", "in_standard_filter": 1, @@ -143,7 +144,6 @@ }, { "collapsible": 1, - "depends_on": "eval: doc.service_level_agreement", "fieldname": "service_level_section", "fieldtype": "Section Break", "label": "Service Level" @@ -314,6 +314,7 @@ }, { "default": "Ongoing", + "depends_on": "eval: doc.service_level_agreement", "fieldname": "agreement_fulfilled", "fieldtype": "Select", "label": "Service Level Agreement Fulfilled", @@ -321,6 +322,7 @@ "read_only": 1 }, { + "depends_on": "eval: doc.service_level_agreement", "description": "in hours", "fieldname": "response_by_variance", "fieldtype": "Float", @@ -328,6 +330,7 @@ "read_only": 1 }, { + "depends_on": "eval: doc.service_level_agreement", "description": "in hours", "fieldname": "resolution_by_variance", "fieldtype": "Float", @@ -337,7 +340,7 @@ ], "icon": "fa fa-ticket", "idx": 7, - "modified": "2019-06-27 15:19:00.771333", + "modified": "2019-06-30 13:19:38.215525", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index ad1c263250..226676fec3 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -92,7 +92,6 @@ class Issue(Document): self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()), 2) self.agreement_fulfilled = "Fulfilled" if self.response_by_variance > 0 and self.resolution_by_variance > 0 else "Failed" - self.save(ignore_permissions=True) def create_communication(self): communication = frappe.new_doc("Communication") @@ -118,6 +117,17 @@ class Issue(Document): replicated_issue = deepcopy(self) replicated_issue.subject = subject + replicated_issue.creation = now_datetime() + + # Reset SLA + if replicated_issue.service_level_agreement: + replicated_issue.service_level_agreement = None + replicated_issue.agreement_fulfilled = "Ongoing" + replicated_issue.response_by = None + replicated_issue.response_by_variance = None + replicated_issue.resolution_by = None + replicated_issue.resolution_by_variance = None + frappe.get_doc(replicated_issue).insert() # Replicate linked Communications @@ -136,7 +146,8 @@ class Issue(Document): return replicated_issue.name def before_insert(self): - self.set_response_and_resolution_time() + if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): + self.set_response_and_resolution_time() def set_response_and_resolution_time(self, priority=None, service_level_agreement=None): service_level_agreement = get_active_service_level_agreement_for(priority=priority, @@ -171,13 +182,16 @@ class Issue(Document): self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime())) def change_service_level_agreement_and_priority(self): - if not self.priority == frappe.db.get_value("Issue", self.name, "priority"): - self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement) - frappe.msgprint("Priority has been updated.") + if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \ + frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): - if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"): - self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement) - frappe.msgprint("Service Level Agreement has been updated.") + if not self.priority == frappe.db.get_value("Issue", self.name, "priority"): + self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement) + frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority)) + + if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"): + self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement) + frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement)) def get_expected_time_for(parameter, service_level, start_date_time): current_date_time = start_date_time diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py index 75d70b1318..eb1736e6c9 100644 --- a/erpnext/support/doctype/issue/test_issue.py +++ b/erpnext/support/doctype/issue/test_issue.py @@ -11,6 +11,7 @@ from datetime import timedelta class TestIssue(unittest.TestCase): def test_response_time_and_resolution_time_based_on_different_sla(self): + frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) create_service_level_agreements_for_issues() creation = datetime.datetime(2019, 3, 4, 12, 0) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json index f91a80c60d..9a83ca7ac0 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json @@ -5,6 +5,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "enable", "service_level", "default_service_level_agreement", "holiday_list", @@ -149,9 +150,15 @@ "in_standard_filter": 1, "label": "Entity Type", "options": "\nCustomer\nCustomer Group\nTerritory" + }, + { + "default": "1", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" } ], - "modified": "2019-06-20 18:04:14.293378", + "modified": "2019-07-09 17:22:16.402939", "modified_by": "Administrator", "module": "Support", "name": "Service Level Agreement", diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 82c0ffb65c..a399c58b16 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -6,19 +6,23 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ +from frappe.utils import getdate class ServiceLevelAgreement(Document): def validate(self): + if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): + frappe.throw(_("Service Level Agreement tracking is not enabled.")) + if self.default_service_level_agreement: if frappe.db.exists("Service Level Agreement", {"default_service_level_agreement": "1", "name": ["!=", self.name]}): frappe.throw(_("A Default Service Level Agreement already exists.")) else: if self.start_date and self.end_date: - if self.start_date >= self.end_date: + if getdate(self.start_date) >= getdate(self.end_date): frappe.throw(_("Start Date of Agreement can't be greater than or equal to End Date.")) - if self.end_date < frappe.utils.getdate(): + if getdate(self.end_date) < getdate(frappe.utils.getdate()): frappe.throw(_("End Date of Agreement can't be less than today.")) if self.entity_type and self.entity: @@ -44,12 +48,16 @@ def check_agreement_status(): for service_level_agreement in service_level_agreements: doc = frappe.get_doc("Service Level Agreement", service_level_agreement.name) - if doc.end_date and doc.end_date < frappe.utils.getdate(): + if doc.end_date and getdate(doc.end_date) < getdate(frappe.utils.getdate()): frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "active", 0) def get_active_service_level_agreement_for(priority, customer=None, service_level_agreement=None): + if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): + return + filters = [ ["Service Level Agreement", "active", "=", 1], + ["Service Level Agreement", "enable", "=", 1] ] if priority: @@ -80,6 +88,14 @@ def get_customer_territory(customer): @frappe.whitelist() def get_service_level_agreement_filters(name, customer=None): + if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): + return + + filters = [ + ["Service Level Agreement", "active", "=", 1], + ["Service Level Agreement", "enable", "=", 1] + ] + if not customer: or_filters = [ ["Service Level Agreement", "default_service_level_agreement", "=", 1] @@ -93,5 +109,5 @@ def get_service_level_agreement_filters(name, customer=None): return { "priority": [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])], - "service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", or_filters=or_filters)] + "service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters)] } \ No newline at end of file diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py index 6aa5394192..68b82d10ec 100644 --- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py @@ -10,6 +10,8 @@ from erpnext.support.doctype.service_level.test_service_level import create_serv class TestServiceLevelAgreement(unittest.TestCase): def test_service_level_agreement(self): + frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1) + create_service_level_for_sla() # Default Service Level Agreement diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json index 2b79107269..2dced15d4e 100644 --- a/erpnext/support/doctype/support_settings/support_settings.json +++ b/erpnext/support/doctype/support_settings/support_settings.json @@ -1,560 +1,145 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2017-02-17 13:07:35.686409", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "sb_00", + "track_service_level_agreement", + "issues_sb", + "close_issue_after_days", + "portal_sb", + "get_started_sections", + "show_latest_forum_posts", + "forum_sb", + "forum_url", + "get_latest_query", + "response_key_list", + "column_break_10", + "post_title_key", + "post_description_key", + "post_route_key", + "post_route_string", + "search_apis_sb", + "search_apis" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "issues_sb", "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": "Issues", - "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": "Issues" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "7", "description": "Auto close Issue after 7 days", "fieldname": "close_issue_after_days", "fieldtype": "Int", - "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": "Close Issue After Days", - "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": "Close Issue After Days" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "portal_sb", "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": "Support Portal", - "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": "Support Portal" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "get_started_sections", "fieldtype": "Code", - "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": "Get Started Sections", - "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": "Get Started Sections" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "show_latest_forum_posts", "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": "Show Latest Forum Posts", - "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": "Show Latest Forum Posts" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "show_latest_forum_posts", "fieldname": "forum_sb", "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": "Forum Posts", - "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": "Forum Posts" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "forum_url", "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": "Forum URL", - "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": "Forum URL" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "get_latest_query", "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": "Get Latest Query", - "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": "Get Latest Query" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "response_key_list", "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": "Response Key List", - "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": "Response Key List" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_10", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "post_title_key", "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": "Post Title Key", - "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": "Post Title Key" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "post_description_key", "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": "Post Description Key", - "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": "Post Description Key" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "post_route_key", "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": "Post Route Key", - "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": "Post Route Key" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "post_route_string", "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": "Post Route String", - "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": "Post Route String" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "search_apis_sb", "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": "Search APIs", - "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": "Search APIs" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "search_apis", "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": "Search APIs", - "length": 0, - "no_copy": 0, - "options": "Support Search Source", - "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": "Support Search Source" + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Service Level Agreements" + }, + { + "default": "0", + "fieldname": "track_service_level_agreement", + "fieldtype": "Check", + "label": "Track Service Level Agreement" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-05-17 02:11:33.462444", + "modified": "2019-07-09 17:11:38.216732", "modified_by": "Administrator", "module": "Support", "name": "Support Settings", - "name_case": "", "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": 0, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + "track_changes": 1 } \ No newline at end of file