diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index c820d233a5..e7fa354a2e 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -10,3 +10,6 @@
# Replace use of Class.extend with native JS class
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
+
+# This commit just changes spaces to tabs for indentation in some files
+5f473611bd6ed57703716244a054d3fb5ba9cd23
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 5808090db3..76e8a514d5 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.4.1'
+__version__ = '13.5.0'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 0ebf0eb541..7cd1e7736c 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -27,7 +27,7 @@ class AccountingDimension(Document):
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
if exists and self.is_new():
- frappe.throw("Document Type already used as a dimension")
+ frappe.throw(_("Document Type already used as a dimension"))
if not self.is_new():
self.validate_document_type_change()
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 781f94e203..2735b1ccee 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -18,6 +18,7 @@
"delete_linked_ledger_entries",
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
+ "post_change_gl_entries",
"tax_settings_section",
"determine_address_tax_category_from",
"column_break_19",
@@ -253,6 +254,12 @@
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "1",
+ "fieldname": "post_change_gl_entries",
+ "fieldtype": "Check",
+ "label": "Post Ledger Entries for Given Change"
}
],
"icon": "icon-cog",
@@ -260,7 +267,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-04-30 15:25:10.381008",
+ "modified": "2021-05-25 12:34:05.858669",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 4d3388090d..ac4a2d6f16 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
@@ -24,7 +25,7 @@ class AccountsSettings(Document):
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
- "Stale Days should start from 1.", title='Error', indicator='red',
+ _("Stale Days should start from 1."), title='Error', indicator='red',
raise_exception=1)
def enable_payment_schedule_in_print(self):
diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/__init__.py b/erpnext/accounts/doctype/advance_taxes_and_charges/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
new file mode 100644
index 0000000000..4d63499431
--- /dev/null
+++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
@@ -0,0 +1,197 @@
+{
+ "actions": [],
+ "creation": "2020-09-12 22:26:19.594367",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "add_deduct_tax",
+ "charge_type",
+ "row_id",
+ "account_head",
+ "col_break_1",
+ "description",
+ "included_in_paid_amount",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "section_break_8",
+ "rate",
+ "section_break_9",
+ "currency",
+ "tax_amount",
+ "total",
+ "allocated_amount",
+ "column_break_13",
+ "base_tax_amount",
+ "base_total",
+ "base_allocated_amount"
+ ],
+ "fields": [
+ {
+ "columns": 2,
+ "fieldname": "charge_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Type",
+ "oldfieldname": "charge_type",
+ "oldfieldtype": "Select",
+ "options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
+ "reqd": 1
+ },
+ {
+ "depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
+ "fieldname": "row_id",
+ "fieldtype": "Data",
+ "label": "Reference Row #",
+ "oldfieldname": "row_id",
+ "oldfieldtype": "Data"
+ },
+ {
+ "columns": 2,
+ "fieldname": "account_head",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account Head",
+ "oldfieldname": "account_head",
+ "oldfieldtype": "Link",
+ "options": "Account",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "col_break_1",
+ "fieldtype": "Column Break",
+ "width": "50%"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Small Text",
+ "print_width": "300px",
+ "reqd": 1,
+ "width": "300px"
+ },
+ {
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "default": ":Company",
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "oldfieldname": "cost_center_other_charges",
+ "oldfieldtype": "Link",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break"
+ },
+ {
+ "columns": 2,
+ "fieldname": "rate",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Rate",
+ "oldfieldname": "rate",
+ "oldfieldtype": "Currency"
+ },
+ {
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break"
+ },
+ {
+ "columns": 2,
+ "fieldname": "tax_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "options": "currency"
+ },
+ {
+ "columns": 2,
+ "fieldname": "total",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Total",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "base_tax_amount",
+ "fieldtype": "Currency",
+ "label": "Amount (Company Currency)",
+ "oldfieldname": "tax_amount",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_total",
+ "fieldtype": "Currency",
+ "label": "Total (Company Currency)",
+ "oldfieldname": "total",
+ "oldfieldtype": "Currency",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "add_deduct_tax",
+ "fieldtype": "Select",
+ "label": "Add Or Deduct",
+ "options": "Add\nDeduct",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "included_in_paid_amount",
+ "fieldtype": "Check",
+ "label": "Considered In Paid Amount"
+ },
+ {
+ "fieldname": "allocated_amount",
+ "fieldtype": "Currency",
+ "label": "Allocated Amount",
+ "options": "currency"
+ },
+ {
+ "fieldname": "base_allocated_amount",
+ "fieldtype": "Currency",
+ "label": "Allocated Amount (Company Currency)",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fetch_from": "account_head.account_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Account Currency",
+ "options": "Currency",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-09 11:46:58.373170",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Advance Taxes and Charges",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "ASC"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py
new file mode 100644
index 0000000000..597d2ccc62
--- /dev/null
+++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class AdvanceTaxesandCharges(Document):
+ pass
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 059e1d3158..19041a3f73 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -120,4 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
}
-};
+};
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index f96f59169e..3b764aab10 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -22,7 +22,7 @@ def validate_company(company):
'allow_account_creation_against_child_company'])
if parent_company and (not allow_account_creation_against_child_company):
- msg = _("{} is a child company. ").format(frappe.bold(company))
+ msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold('Allow Account Creation Against Child Company'))
frappe.throw(msg, title=_('Wrong Company'))
@@ -56,7 +56,7 @@ def get_file(file_name):
extension = extension.lstrip(".")
if extension not in ('csv', 'xlsx', 'xls'):
- frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
+ frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
return file_doc, extension
@@ -293,7 +293,7 @@ def validate_accounts(file_name):
accounts_dict = {}
for account in accounts:
accounts_dict.setdefault(account["account_name"], account)
- if not hasattr(account, "parent_account"):
+ if "parent_account" not in account:
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
msg += "
"
msg += _("Alternatively, you can download the template and fill your data in.")
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index d1e5afc48f..b73d8bfbb1 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -49,11 +49,11 @@ class InvoiceDiscounting(AccountsController):
self.make_gl_entries()
def on_cancel(self):
- self.set_status()
+ self.set_status(cancel=1)
self.update_sales_invoice()
self.make_gl_entries()
- def set_status(self, status=None):
+ def set_status(self, status=None, cancel=0):
if status:
self.status = status
self.db_set("status", status)
@@ -66,6 +66,9 @@ class InvoiceDiscounting(AccountsController):
elif self.docstatus == 2:
self.status = "Cancelled"
+ if cancel:
+ self.db_set('status', self.status, update_modified = True)
+
def update_sales_invoice(self):
for d in self.invoices:
if self.docstatus == 1:
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index b80e8ada38..d3ac3a6676 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -3,6 +3,8 @@
{% include "erpnext/public/js/controllers/accounts.js" %}
frappe.provide("erpnext.accounts.dimensions");
+cur_frm.cscript.tax_table = "Advance Taxes and Charges";
+
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
if(frm.doc.__islocal) {
@@ -91,6 +93,16 @@ frappe.ui.form.on('Payment Entry', {
}
});
+ frm.set_query("advance_tax_account", function() {
+ return {
+ filters: {
+ "company": frm.doc.company,
+ "root_type": ["in", ["Asset", "Liability"]],
+ "is_group": 0
+ }
+ }
+ });
+
frm.set_query("reference_doctype", "references", function() {
if (frm.doc.party_type == "Customer") {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
@@ -182,6 +194,8 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
+ frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
+ (frm.doc.paid_from_account_currency != company_currency));
frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency
@@ -216,7 +230,7 @@ frappe.ui.form.on('Payment Entry', {
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
- "difference_amount"], company_currency);
+ "difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
@@ -224,11 +238,13 @@ frappe.ui.form.on('Payment Entry', {
var party_account_currency = frm.doc.payment_type=="Receive" ?
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
- frm.set_currency_labels(["total_allocated_amount", "unallocated_amount"], party_account_currency);
+ frm.set_currency_labels(["total_allocated_amount", "unallocated_amount",
+ "total_taxes_and_charges"], party_account_currency);
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
frm.set_df_property("total_allocated_amount", "options", currency_field);
frm.set_df_property("unallocated_amount", "options", currency_field);
+ frm.set_df_property("total_taxes_and_charges", "options", currency_field);
frm.set_df_property("party_balance", "options", currency_field);
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
@@ -364,6 +380,16 @@ frappe.ui.form.on('Payment Entry', {
}
},
+ apply_tax_withholding_amount: function(frm) {
+ if (!frm.doc.apply_tax_withholding_amount) {
+ frm.set_value("tax_withholding_category", '');
+ } else {
+ frappe.db.get_value('Supplier', frm.doc.party, 'tax_withholding_category', (values) => {
+ frm.set_value("tax_withholding_category", values.tax_withholding_category);
+ });
+ }
+ },
+
paid_from: function(frm) {
if(frm.set_party_account_based_on_party) return;
@@ -843,12 +869,12 @@ frappe.ui.form.on('Payment Entry', {
if(frm.doc.payment_type == "Receive"
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
- unallocated_amount = (frm.doc.base_received_amount + total_deductions
- - frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
+ unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
+ + frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
} else if (frm.doc.payment_type == "Pay"
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
- unallocated_amount = (frm.doc.base_paid_amount - (total_deductions
+ unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
}
}
@@ -874,7 +900,8 @@ frappe.ui.form.on('Payment Entry', {
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
function(d) { return flt(d.amount) }));
- frm.set_value("difference_amount", difference_amount - total_deductions);
+ frm.set_value("difference_amount", difference_amount - total_deductions +
+ frm.doc.base_total_taxes_and_charges);
frm.events.hide_unhide_fields(frm);
},
@@ -1002,7 +1029,266 @@ frappe.ui.form.on('Payment Entry', {
}
});
}
- }
+ },
+
+ sales_taxes_and_charges_template: function(frm) {
+ frm.trigger('fetch_taxes_from_template');
+ },
+
+ purchase_taxes_and_charges_template: function(frm) {
+ frm.trigger('fetch_taxes_from_template');
+ },
+
+ fetch_taxes_from_template: function(frm) {
+ let master_doctype = '';
+ let taxes_and_charges = '';
+
+ if (frm.doc.party_type == 'Supplier') {
+ master_doctype = 'Purchase Taxes and Charges Template';
+ taxes_and_charges = frm.doc.purchase_taxes_and_charges_template;
+ } else if (frm.doc.party_type == 'Customer') {
+ master_doctype = 'Sales Taxes and Charges Template';
+ taxes_and_charges = frm.doc.sales_taxes_and_charges_template;
+ }
+
+ if (!taxes_and_charges) {
+ return;
+ }
+
+ frappe.call({
+ method: "erpnext.controllers.accounts_controller.get_taxes_and_charges",
+ args: {
+ "master_doctype": master_doctype,
+ "master_name": taxes_and_charges
+ },
+ callback: function(r) {
+ if(!r.exc && r.message) {
+ // set taxes table
+ if(r.message) {
+ for (let tax of r.message) {
+ if (tax.charge_type === 'On Net Total') {
+ tax.charge_type = 'On Paid Amount';
+ }
+ me.frm.add_child("taxes", tax);
+ }
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ }
+ }
+ }
+ });
+ },
+
+ apply_taxes: function(frm) {
+ frm.events.initialize_taxes(frm);
+ frm.events.determine_exclusive_rate(frm);
+ frm.events.calculate_taxes(frm);
+ },
+
+ initialize_taxes: function(frm) {
+ $.each(frm.doc["taxes"] || [], function(i, tax) {
+ frm.events.validate_taxes_and_charges(tax);
+ frm.events.validate_inclusive_tax(tax);
+ tax.item_wise_tax_detail = {};
+ let tax_fields = ["total", "tax_fraction_for_current_item",
+ "grand_total_fraction_for_current_item"];
+
+ if (cstr(tax.charge_type) != "Actual") {
+ tax_fields.push("tax_amount");
+ }
+
+ $.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
+
+ frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
+ });
+ },
+
+ validate_taxes_and_charges: function(d) {
+ let msg = "";
+
+ if (d.account_head && !d.description) {
+ // set description from account head
+ d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
+ }
+
+ if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
+ msg = __("Please select Charge Type first");
+ d.row_id = "";
+ d.rate = d.tax_amount = 0.0;
+ } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
+ msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
+ d.row_id = "";
+ } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
+ if (d.idx == 1) {
+ msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
+ d.charge_type = '';
+ } else if (!d.row_id) {
+ msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
+ d.row_id = "";
+ } else if (d.row_id && d.row_id >= d.idx) {
+ msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
+ d.row_id = "";
+ }
+ }
+ if (msg) {
+ frappe.validated = false;
+ refresh_field("taxes");
+ frappe.throw(msg);
+ }
+
+ },
+
+ validate_inclusive_tax: function(tax) {
+ let actual_type_error = function() {
+ let msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
+ frappe.throw(msg);
+ };
+
+ let on_previous_row_error = function(row_range) {
+ let msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included",
+ [tax.idx, __(tax.doctype), tax.charge_type, row_range])
+ frappe.throw(msg);
+ };
+
+ if(cint(tax.included_in_paid_amount)) {
+ if(tax.charge_type == "Actual") {
+ // inclusive tax cannot be of type Actual
+ actual_type_error();
+ } else if(tax.charge_type == "On Previous Row Amount" &&
+ !cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_paid_amount)
+ ) {
+ // referred row should also be an inclusive tax
+ on_previous_row_error(tax.row_id);
+ } else if(tax.charge_type == "On Previous Row Total") {
+ let taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
+ function(t) { return cint(t.included_in_paid_amount) ? null : t; });
+ if(taxes_not_included.length > 0) {
+ // all rows above this tax should be inclusive
+ on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id);
+ }
+ }
+ }
+ },
+
+ determine_exclusive_rate: function(frm) {
+ let has_inclusive_tax = false;
+ $.each(frm.doc["taxes"] || [], function(i, row) {
+ if(cint(row.included_in_paid_amount)) has_inclusive_tax = true;
+ });
+ if(has_inclusive_tax==false) return;
+
+ let cumulated_tax_fraction = 0.0;
+ $.each(frm.doc["taxes"] || [], function(i, tax) {
+ tax.tax_fraction_for_current_item = frm.events.get_current_tax_fraction(frm, tax);
+
+ if(i==0) {
+ tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
+ } else {
+ tax.grand_total_fraction_for_current_item =
+ me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
+ tax.tax_fraction_for_current_item;
+ }
+
+ cumulated_tax_fraction += tax.tax_fraction_for_current_item;
+ frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
+ });
+ },
+
+ get_current_tax_fraction: function(frm, tax) {
+ let current_tax_fraction = 0.0;
+
+ if(cint(tax.included_in_paid_amount)) {
+ let tax_rate = tax.rate;
+
+ if(tax.charge_type == "On Paid Amount") {
+ current_tax_fraction = (tax_rate / 100.0);
+ } else if(tax.charge_type == "On Previous Row Amount") {
+ current_tax_fraction = (tax_rate / 100.0) *
+ frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item;
+ } else if(tax.charge_type == "On Previous Row Total") {
+ current_tax_fraction = (tax_rate / 100.0) *
+ frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
+ }
+ }
+
+ if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
+ current_tax_fraction *= -1;
+ }
+ return current_tax_fraction;
+ },
+
+
+ calculate_taxes: function(frm) {
+ frm.doc.total_taxes_and_charges = 0.0;
+ frm.doc.base_total_taxes_and_charges = 0.0;
+
+ let actual_tax_dict = {};
+
+ // maintain actual tax rate based on idx
+ $.each(frm.doc["taxes"] || [], function(i, tax) {
+ if (tax.charge_type == "Actual") {
+ actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
+ }
+ });
+
+ $.each(me.frm.doc["taxes"] || [], function(i, tax) {
+ let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
+
+ // Adjust divisional loss to the last item
+ if (tax.charge_type == "Actual") {
+ actual_tax_dict[tax.idx] -= current_tax_amount;
+ if (i == frm.doc["taxes"].length - 1) {
+ current_tax_amount += actual_tax_dict[tax.idx];
+ }
+ }
+
+ tax.tax_amount = current_tax_amount;
+ tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
+ current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
+
+ if(i==0) {
+ tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax));
+ } else {
+ tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
+ }
+
+ tax.base_total = tax.total * frm.doc.source_exchange_rate;
+ frm.doc.total_taxes_and_charges += current_tax_amount;
+ frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
+
+ frm.refresh_field('taxes');
+ frm.refresh_field('total_taxes_and_charges');
+ frm.refresh_field('base_total_taxes_and_charges');
+ });
+ },
+
+ get_current_tax_amount: function(frm, tax) {
+ let tax_rate = tax.rate;
+ let current_tax_amount = 0.0;
+
+ // To set row_id by default as previous row.
+ if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
+ if (tax.idx === 1) {
+ frappe.throw(
+ __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"));
+ }
+ }
+
+ if(tax.charge_type == "Actual") {
+ current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax))
+ } else if(tax.charge_type == "On Paid Amount") {
+ current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax);
+ } else if(tax.charge_type == "On Previous Row Amount") {
+ current_tax_amount = flt((tax_rate / 100.0) *
+ frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount);
+
+ } else if(tax.charge_type == "On Previous Row Total") {
+ current_tax_amount = flt((tax_rate / 100.0) *
+ frm.doc["taxes"][cint(tax.row_id) - 1].total);
+ }
+
+ return current_tax_amount;
+ },
});
@@ -1049,6 +1335,38 @@ frappe.ui.form.on('Payment Entry Reference', {
}
})
+frappe.ui.form.on('Advance Taxes and Charges', {
+ rate: function(frm) {
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ },
+
+ tax_amount : function(frm) {
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ },
+
+ row_id: function(frm) {
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ },
+
+ taxes_remove: function(frm) {
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ },
+
+ included_in_paid_amount: function(frm) {
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ },
+
+ charge_type: function(frm) {
+ frm.events.apply_taxes(frm);
+ frm.events.set_unallocated_amount(frm);
+ }
+})
+
frappe.ui.form.on('Payment Entry Deduction', {
amount: function(frm) {
frm.events.set_unallocated_amount(frm);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 328584a61a..54623dd6cd 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -35,12 +35,16 @@
"paid_to_account_balance",
"payment_amounts_section",
"paid_amount",
+ "paid_amount_after_tax",
"source_exchange_rate",
"base_paid_amount",
+ "base_paid_amount_after_tax",
"column_break_21",
"received_amount",
+ "received_amount_after_tax",
"target_exchange_rate",
"base_received_amount",
+ "base_received_amount_after_tax",
"section_break_14",
"get_outstanding_invoice",
"references",
@@ -52,6 +56,17 @@
"unallocated_amount",
"difference_amount",
"write_off_difference_amount",
+ "taxes_and_charges_section",
+ "purchase_taxes_and_charges_template",
+ "sales_taxes_and_charges_template",
+ "advance_tax_account",
+ "column_break_55",
+ "apply_tax_withholding_amount",
+ "tax_withholding_category",
+ "section_break_56",
+ "taxes",
+ "base_total_taxes_and_charges",
+ "total_taxes_and_charges",
"deductions_or_loss_section",
"deductions",
"transaction_references",
@@ -320,6 +335,7 @@
"reqd": 1
},
{
+ "depends_on": "doc.received_amount",
"fieldname": "base_received_amount",
"fieldtype": "Currency",
"label": "Received Amount (Company Currency)",
@@ -584,12 +600,114 @@
"fieldname": "custom_remarks",
"fieldtype": "Check",
"label": "Custom Remarks"
+ },
+ {
+ "depends_on": "eval:doc.apply_tax_withholding_amount",
+ "fieldname": "tax_withholding_category",
+ "fieldtype": "Link",
+ "label": "Tax Withholding Category",
+ "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
+ "options": "Tax Withholding Category"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.party_type == 'Supplier'",
+ "fieldname": "apply_tax_withholding_amount",
+ "fieldtype": "Check",
+ "label": "Apply Tax Withholding Amount"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "taxes_and_charges_section",
+ "fieldtype": "Section Break",
+ "label": "Taxes and Charges"
+ },
+ {
+ "depends_on": "eval:doc.party_type == 'Supplier'",
+ "fieldname": "purchase_taxes_and_charges_template",
+ "fieldtype": "Link",
+ "label": "Taxes and Charges Template",
+ "options": "Purchase Taxes and Charges Template"
+ },
+ {
+ "depends_on": "eval: doc.party_type == 'Customer'",
+ "fieldname": "sales_taxes_and_charges_template",
+ "fieldtype": "Link",
+ "label": "Taxes and Charges Template",
+ "options": "Sales Taxes and Charges Template"
+ },
+ {
+ "depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'",
+ "fieldname": "taxes",
+ "fieldtype": "Table",
+ "label": "Advance Taxes and Charges",
+ "options": "Advance Taxes and Charges"
+ },
+ {
+ "fieldname": "base_total_taxes_and_charges",
+ "fieldtype": "Currency",
+ "label": "Total Taxes and Charges (Company Currency)",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_taxes_and_charges",
+ "fieldtype": "Currency",
+ "label": "Total Taxes and Charges",
+ "read_only": 1
+ },
+ {
+ "fieldname": "paid_amount_after_tax",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Paid Amount After Tax",
+ "options": "paid_from_account_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_paid_amount_after_tax",
+ "fieldtype": "Currency",
+ "label": "Paid Amount After Tax (Company Currency)",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_55",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_56",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "depends_on": "eval:doc.apply_tax_withholding_amount",
+ "description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
+ "fieldname": "advance_tax_account",
+ "fieldtype": "Link",
+ "label": "Advance Tax Account",
+ "mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
+ "options": "Account"
+ },
+ {
+ "depends_on": "eval:doc.received_amount",
+ "fieldname": "received_amount_after_tax",
+ "fieldtype": "Currency",
+ "label": "Received Amount After Tax",
+ "options": "paid_to_account_currency"
+ },
+ {
+ "depends_on": "doc.received_amount",
+ "fieldname": "base_received_amount_after_tax",
+ "fieldtype": "Currency",
+ "label": "Received Amount After Tax (Company Currency)",
+ "options": "Company:company:default_currency"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-03-08 13:05:16.958866",
+ "modified": "2021-06-09 11:55:04.215050",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@@ -633,4 +751,4 @@
"sort_order": "DESC",
"title_field": "title",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 544d530551..b6b2bef963 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -4,8 +4,8 @@
from __future__ import unicode_literals
import frappe, erpnext, json
-from frappe import _, scrub, ValidationError
-from frappe.utils import flt, comma_or, nowdate, getdate
+from frappe import _, scrub, ValidationError, throw
+from frappe.utils import flt, comma_or, nowdate, getdate, cint
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
from erpnext.accounts.party import get_party_account
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
@@ -15,9 +15,11 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
-
+from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from six import string_types, iteritems
+from erpnext.controllers.accounts_controller import validate_taxes_and_charges
+
class InvalidPaymentEntry(ValidationError):
pass
@@ -52,6 +54,8 @@ class PaymentEntry(AccountsController):
self.set_exchange_rate()
self.validate_mandatory()
self.validate_reference_documents()
+ self.set_tax_withholding()
+ self.apply_taxes()
self.set_amounts()
self.clear_unallocated_reference_document_rows()
self.validate_payment_against_negative_invoice()
@@ -65,7 +69,6 @@ class PaymentEntry(AccountsController):
self.set_status()
def on_submit(self):
- self.setup_party_account_field()
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
@@ -78,7 +81,6 @@ class PaymentEntry(AccountsController):
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
- self.setup_party_account_field()
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()
@@ -122,6 +124,11 @@ class PaymentEntry(AccountsController):
if flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
+ # Check for negative outstanding invoices as well
+ if flt(d.allocated_amount) < 0:
+ if flt(d.allocated_amount) < flt(d.outstanding_amount):
+ frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
+
def delink_advance_entry_references(self):
for reference in self.references:
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
@@ -177,7 +184,7 @@ class PaymentEntry(AccountsController):
for field, value in iteritems(ref_details):
if field == 'exchange_rate' or not d.get(field) or force:
- d.set(field, value)
+ d.db_set(field, value)
def validate_payment_type(self):
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
@@ -307,7 +314,6 @@ class PaymentEntry(AccountsController):
+ "
" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange")
-
def validate_journal_entry(self):
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype == "Journal Entry":
@@ -386,12 +392,98 @@ class PaymentEntry(AccountsController):
else:
self.status = 'Draft'
+ self.db_set('status', self.status, update_modified = True)
+
+ def set_tax_withholding(self):
+ if not self.party_type == 'Supplier':
+ return
+
+ if not self.apply_tax_withholding_amount:
+ return
+
+ if not self.advance_tax_account:
+ frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
+
+ reference_doclist = []
+ net_total = self.paid_amount
+ included_in_paid_amount = 0
+
+ # Adding args as purchase invoice to get TDS amount
+ args = frappe._dict({
+ 'company': self.company,
+ 'doctype': 'Purchase Invoice',
+ 'supplier': self.party,
+ 'posting_date': self.posting_date,
+ 'net_total': net_total
+ })
+
+ tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
+
+ if not tax_withholding_details:
+ return
+
+ tax_withholding_details.update({
+ 'included_in_paid_amount': included_in_paid_amount,
+ 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
+ })
+
+ accounts = []
+ for d in self.taxes:
+ if d.account_head == tax_withholding_details.get("account_head"):
+
+ # Preserve user updated included in paid amount
+ if d.included_in_paid_amount:
+ tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount})
+
+ d.update(tax_withholding_details)
+ accounts.append(d.account_head)
+
+ if not accounts or tax_withholding_details.get("account_head") not in accounts:
+ self.append("taxes", tax_withholding_details)
+
+ to_remove = [d for d in self.taxes
+ if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
+
+ for d in to_remove:
+ self.remove(d)
+
+ def apply_taxes(self):
+ self.initialize_taxes()
+ self.determine_exclusive_rate()
+ self.calculate_taxes()
+
def set_amounts(self):
+ self.set_received_amount()
self.set_amounts_in_company_currency()
+ self.set_amounts_after_tax()
self.set_total_allocated_amount()
self.set_unallocated_amount()
self.set_difference_amount()
+ def set_received_amount(self):
+ self.base_received_amount = self.base_paid_amount
+
+ def set_amounts_after_tax(self):
+ applicable_tax = 0
+ base_applicable_tax = 0
+ for tax in self.get('taxes'):
+ if not tax.included_in_paid_amount:
+ amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount
+ base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount
+
+ applicable_tax += amount
+ base_applicable_tax += base_amount
+
+ self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax),
+ self.precision("paid_amount_after_tax"))
+ self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
+ self.precision("base_paid_amount_after_tax"))
+
+ self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax),
+ self.precision("paid_amount_after_tax"))
+ self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
+ self.precision("base_paid_amount_after_tax"))
+
def set_amounts_in_company_currency(self):
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
if self.paid_amount:
@@ -421,15 +513,15 @@ class PaymentEntry(AccountsController):
if self.party:
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
if self.payment_type == "Receive" \
- and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
- and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
- self.unallocated_amount = (self.base_received_amount + total_deductions -
- self.base_total_allocated_amount) / self.source_exchange_rate
+ and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \
+ and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
+ self.unallocated_amount = (self.received_amount_after_tax + total_deductions -
+ self.base_total_allocated_amount) / self.source_exchange_rate
elif self.payment_type == "Pay" \
- and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
- and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
- self.unallocated_amount = (self.base_paid_amount - (total_deductions +
- self.base_total_allocated_amount)) / self.target_exchange_rate
+ and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \
+ and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate):
+ self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions +
+ self.base_total_allocated_amount)) / self.target_exchange_rate
def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@@ -438,11 +530,11 @@ class PaymentEntry(AccountsController):
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
if self.payment_type == "Receive":
- self.difference_amount = base_party_amount - self.base_received_amount
+ self.difference_amount = base_party_amount - self.base_received_amount_after_tax
elif self.payment_type == "Pay":
- self.difference_amount = self.base_paid_amount - base_party_amount
+ self.difference_amount = self.base_paid_amount_after_tax - base_party_amount
else:
- self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
+ self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
@@ -532,6 +624,7 @@ class PaymentEntry(AccountsController):
self.add_party_gl_entries(gl_entries)
self.add_bank_gl_entries(gl_entries)
self.add_deductions_gl_entries(gl_entries)
+ self.add_tax_gl_entries(gl_entries)
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
@@ -571,7 +664,7 @@ class PaymentEntry(AccountsController):
gl_entries.append(gle)
if self.unallocated_amount:
- base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \
+ base_unallocated_amount = self.unallocated_amount * \
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
gle = party_gl_dict.copy()
@@ -590,8 +683,8 @@ class PaymentEntry(AccountsController):
"account": self.paid_from,
"account_currency": self.paid_from_account_currency,
"against": self.party if self.payment_type=="Pay" else self.paid_to,
- "credit_in_account_currency": self.paid_amount,
- "credit": self.base_paid_amount,
+ "credit_in_account_currency": self.paid_amount_after_tax,
+ "credit": self.base_paid_amount_after_tax,
"cost_center": self.cost_center
}, item=self)
)
@@ -601,12 +694,50 @@ class PaymentEntry(AccountsController):
"account": self.paid_to,
"account_currency": self.paid_to_account_currency,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
- "debit_in_account_currency": self.received_amount,
- "debit": self.base_received_amount,
+ "debit_in_account_currency": self.received_amount_after_tax,
+ "debit": self.base_received_amount_after_tax,
"cost_center": self.cost_center
}, item=self)
)
+ def add_tax_gl_entries(self, gl_entries):
+ for d in self.get('taxes'):
+ account_currency = get_account_currency(d.account_head)
+ if account_currency != self.company_currency:
+ frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
+
+ if self.payment_type == 'Pay':
+ dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
+ elif self.payment_type == 'Receive':
+ dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
+
+ payment_or_advance_account = self.get_party_account_for_taxes()
+
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": d.account_head,
+ "against": self.party if self.payment_type=="Receive" else self.paid_from,
+ dr_or_cr: d.base_tax_amount,
+ dr_or_cr + "_in_account_currency": d.base_tax_amount
+ if account_currency==self.company_currency
+ else d.tax_amount,
+ "cost_center": d.cost_center
+ }, account_currency, item=d))
+
+ #Intentionally use -1 to get net values in party account
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": payment_or_advance_account,
+ "against": self.party if self.payment_type=="Receive" else self.paid_from,
+ dr_or_cr: -1 * d.base_tax_amount,
+ dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
+ if account_currency==self.company_currency
+ else d.tax_amount,
+ "cost_center": self.cost_center,
+ "party_type": self.party_type,
+ "party": self.party
+ }, account_currency, item=d))
+
def add_deductions_gl_entries(self, gl_entries):
for d in self.get("deductions"):
if d.amount:
@@ -625,6 +756,14 @@ class PaymentEntry(AccountsController):
}, item=d)
)
+ def get_party_account_for_taxes(self):
+ if self.advance_tax_account:
+ return self.advance_tax_account
+ elif self.payment_type == 'Receive':
+ return self.paid_from
+ elif self.payment_type == 'Pay':
+ return self.paid_to
+
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
@@ -671,6 +810,139 @@ class PaymentEntry(AccountsController):
self.append('deductions', row)
self.set_unallocated_amount()
+ def initialize_taxes(self):
+ for tax in self.get("taxes"):
+ validate_taxes_and_charges(tax)
+ validate_inclusive_tax(tax, self)
+
+ tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
+
+ if tax.charge_type != "Actual":
+ tax_fields.append("tax_amount")
+
+ for fieldname in tax_fields:
+ tax.set(fieldname, 0.0)
+
+ self.paid_amount_after_tax = self.paid_amount
+
+ def determine_exclusive_rate(self):
+ if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
+ return
+
+ cumulated_tax_fraction = 0
+ for i, tax in enumerate(self.get("taxes")):
+ tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
+ if i==0:
+ tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
+ else:
+ tax.grand_total_fraction_for_current_item = \
+ self.get("taxes")[i-1].grand_total_fraction_for_current_item \
+ + tax.tax_fraction_for_current_item
+
+ cumulated_tax_fraction += tax.tax_fraction_for_current_item
+
+ self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
+
+ def calculate_taxes(self):
+ self.total_taxes_and_charges = 0.0
+ self.base_total_taxes_and_charges = 0.0
+
+ actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
+ for tax in self.get("taxes") if tax.charge_type == "Actual"])
+
+ for i, tax in enumerate(self.get('taxes')):
+ current_tax_amount = self.get_current_tax_amount(tax)
+
+ if tax.charge_type == "Actual":
+ actual_tax_dict[tax.idx] -= current_tax_amount
+ if i == len(self.get("taxes")) - 1:
+ current_tax_amount += actual_tax_dict[tax.idx]
+
+ tax.tax_amount = current_tax_amount
+ tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
+
+ if tax.add_deduct_tax == "Deduct":
+ current_tax_amount *= -1.0
+ else:
+ current_tax_amount *= 1.0
+
+ if i == 0:
+ tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
+ else:
+ tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
+
+ tax.base_total = tax.total * self.source_exchange_rate
+
+ self.total_taxes_and_charges += current_tax_amount
+ self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
+
+ if self.get('taxes'):
+ self.paid_amount_after_tax = self.get('taxes')[-1].base_total
+
+ def get_current_tax_amount(self, tax):
+ tax_rate = tax.rate
+
+ # To set row_id by default as previous row.
+ if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
+ if tax.idx == 1:
+ frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
+
+ if not tax.row_id:
+ tax.row_id = tax.idx - 1
+
+ if tax.charge_type == "Actual":
+ current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
+ elif tax.charge_type == "On Paid Amount":
+ current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
+ elif tax.charge_type == "On Previous Row Amount":
+ current_tax_amount = (tax_rate / 100.0) * \
+ self.get('taxes')[cint(tax.row_id) - 1].tax_amount
+
+ elif tax.charge_type == "On Previous Row Total":
+ current_tax_amount = (tax_rate / 100.0) * \
+ self.get('taxes')[cint(tax.row_id) - 1].total
+
+ return current_tax_amount
+
+ def get_current_tax_fraction(self, tax):
+ current_tax_fraction = 0
+
+ if cint(tax.included_in_paid_amount):
+ tax_rate = tax.rate
+
+ if tax.charge_type == "On Paid Amount":
+ current_tax_fraction = tax_rate / 100.0
+ elif tax.charge_type == "On Previous Row Amount":
+ current_tax_fraction = (tax_rate / 100.0) * \
+ self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
+ elif tax.charge_type == "On Previous Row Total":
+ current_tax_fraction = (tax_rate / 100.0) * \
+ self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
+
+ if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
+ current_tax_fraction *= -1.0
+
+ return current_tax_fraction
+
+def validate_inclusive_tax(tax, doc):
+ def _on_previous_row_error(row_range):
+ throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
+
+ if cint(getattr(tax, "included_in_paid_amount", None)):
+ if tax.charge_type == "Actual":
+ # inclusive tax cannot be of type Actual
+ throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
+ elif tax.charge_type == "On Previous Row Amount" and \
+ not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount):
+ # referred row should also be inclusive
+ _on_previous_row_error(tax.row_id)
+ elif tax.charge_type == "On Previous Row Total" and \
+ not all([cint(t.included_in_paid_amount for t in doc.get("taxes")[:cint(tax.row_id) - 1])]):
+ # all rows about the referred tax should be inclusive
+ _on_previous_row_error("1 - %d" % (cint(tax.row_id),))
+ elif tax.get("category") == "Valuation":
+ frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
+
@frappe.whitelist()
def get_outstanding_reference_documents(args):
@@ -791,7 +1063,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
outstanding_invoices.pop(idx - 1)
outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
-
+
return outstanding_invoices
def get_orders_to_be_billed(posting_date, party_type, party,
@@ -989,6 +1261,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Donation":
total_amount = ref_doc.get("amount")
+ outstanding_amount = total_amount
exchange_rate = 1
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
@@ -1235,6 +1508,13 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
})
pe.set_difference_amount()
+ if doc.doctype == 'Purchase Order' and doc.apply_tds:
+ pe.apply_tax_withholding_amount = 1
+ pe.tax_withholding_category = doc.tax_withholding_category
+
+ if not pe.advance_tax_account:
+ pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
+
return pe
def get_bank_cash_account(doc, bank_account):
@@ -1353,6 +1633,13 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
paid_amount = received_amount * doc.get('conversion_rate', 1)
if dt == "Employee Advance":
paid_amount = received_amount * doc.get('exchange_rate', 1)
+
+ if dt == "Purchase Order" and doc.apply_tds:
+ if party_account_currency == bank.account_currency:
+ paid_amount = received_amount = doc.base_net_total
+ else:
+ paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1)
+
return paid_amount, received_amount
def apply_early_payment_discount(paid_amount, received_amount, doc):
diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
index 7060d11691..61a1462dd7 100644
--- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
+++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
@@ -1,140 +1,70 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-06-15 15:56:30.815503",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-06-15 15:56:30.815503",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "field_order": [
+ "account",
+ "cost_center",
+ "amount",
+ "column_break_2",
+ "description"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 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": 1,
- "in_standard_filter": 0,
- "label": "Cost Center",
- "length": 0,
- "no_copy": 0,
- "options": "Cost Center",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Cost Center",
+ "options": "Cost Center",
+ "print_hide": 1,
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Amount",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description",
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-01-07 16:52:07.040146",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Payment Entry Deduction",
- "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
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-09-12 20:38:08.110674",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Entry Deduction",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 520e87a6ce..438951db62 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -101,7 +101,7 @@ class PaymentRequest(Document):
controller.validate_transaction_currency(self.currency)
controller.request_for_payment(**payment_record)
-
+
def get_request_amount(self):
data_of_completed_requests = frappe.get_all("Integration Request", filters={
'reference_doctype': self.doctype,
@@ -492,7 +492,6 @@ def update_payment_req_status(doc, method):
status = 'Requested'
pay_req_doc.db_set('status', status)
- frappe.db.commit()
def get_dummy_message(doc):
return frappe.render_template("""{% if doc.contact_person -%}
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
index 47546c07a4..84c941ecc1 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
@@ -1,350 +1,138 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "ACC-PCV-.YYYY.-.#####",
- "beta": 0,
- "creation": "2013-01-10 16:34:07",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "ACC-PCV-.YYYY.-.#####",
+ "creation": "2013-01-10 16:34:07",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "transaction_date",
+ "posting_date",
+ "fiscal_year",
+ "amended_from",
+ "company",
+ "cost_center_wise_pnl",
+ "column_break1",
+ "closing_account_head",
+ "remarks"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "transaction_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": "Transaction Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "transaction_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
- },
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "label": "Transaction Date",
+ "oldfieldname": "transaction_date",
+ "oldfieldtype": "Date"
+ },
{
- "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,
- "oldfieldname": "posting_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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date",
+ "oldfieldname": "posting_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "fiscal_year",
- "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": "Closing Fiscal Year",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "fiscal_year",
- "oldfieldtype": "Select",
- "options": "Fiscal Year",
- "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
- },
+ "fieldname": "fiscal_year",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Closing Fiscal Year",
+ "oldfieldname": "fiscal_year",
+ "oldfieldtype": "Select",
+ "options": "Fiscal Year",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "amended_from",
- "oldfieldtype": "Data",
- "options": "Period Closing Voucher",
- "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": "amended_from",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Amended From",
+ "no_copy": 1,
+ "oldfieldname": "amended_from",
+ "oldfieldtype": "Data",
+ "options": "Period Closing Voucher",
+ "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,
- "oldfieldname": "company",
- "oldfieldtype": "Select",
- "options": "Company",
- "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
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Select",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_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,
- "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
- },
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
- "fieldname": "closing_account_head",
- "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": "Closing Account Head",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "closing_account_head",
- "oldfieldtype": "Link",
- "options": "Account",
- "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
- },
+ "description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
+ "fieldname": "closing_account_head",
+ "fieldtype": "Link",
+ "label": "Closing Account Head",
+ "oldfieldname": "closing_account_head",
+ "oldfieldtype": "Link",
+ "options": "Account",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "remarks",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Remarks",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "remarks",
- "oldfieldtype": "Small Text",
- "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
+ "fieldname": "remarks",
+ "fieldtype": "Small Text",
+ "label": "Remarks",
+ "oldfieldname": "remarks",
+ "oldfieldtype": "Small Text",
+ "reqd": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "cost_center_wise_pnl",
+ "fieldtype": "Check",
+ "label": "Book Cost Center Wise Profit/Loss"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-file-text",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Period Closing Voucher",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-file-text",
+ "idx": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-05-20 15:27:37.210458",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Period Closing Voucher",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "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": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
- },
+ },
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Accounts Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Accounts Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "posting_date, fiscal_year",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "closing_account_head",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "posting_date, fiscal_year",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "closing_account_head"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index fdb4f6fc71..9cfb47876c 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -51,63 +51,96 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self):
gl_entries = []
- net_pl_balance = 0
- dimension_fields = ['t1.cost_center']
+ net_pl_balance = 0
- accounting_dimensions = get_accounting_dimensions()
- for dimension in accounting_dimensions:
- dimension_fields.append('t1.{0}'.format(dimension))
-
- dimension_filters, default_dimensions = get_dimensions()
-
- pl_accounts = self.get_pl_balances(dimension_fields)
+ pl_accounts = self.get_pl_balances()
for acc in pl_accounts:
- if flt(acc.balance_in_company_currency):
+ if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gl_dict({
"account": acc.account,
"cost_center": acc.cost_center,
"account_currency": acc.account_currency,
- "debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
- if flt(acc.balance_in_account_currency) < 0 else 0,
- "debit": abs(flt(acc.balance_in_company_currency)) \
- if flt(acc.balance_in_company_currency) < 0 else 0,
- "credit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
- if flt(acc.balance_in_account_currency) > 0 else 0,
- "credit": abs(flt(acc.balance_in_company_currency)) \
- if flt(acc.balance_in_company_currency) > 0 else 0
+ "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
+ "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
+ "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
+ "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
}, item=acc))
- net_pl_balance += flt(acc.balance_in_company_currency)
+ net_pl_balance += flt(acc.bal_in_company_currency)
if net_pl_balance:
- cost_center = frappe.db.get_value("Company", self.company, "cost_center")
- gl_entry = self.get_gl_dict({
- "account": self.closing_account_head,
- "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
- "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
- "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
- "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
- "cost_center": cost_center
- })
-
- for dimension in accounting_dimensions:
- gl_entry.update({
- dimension: default_dimensions.get(self.company, {}).get(dimension)
- })
-
- gl_entries.append(gl_entry)
+ if self.cost_center_wise_pnl:
+ costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
+ gl_entries += costcenter_wise_gl_entries
+ else:
+ gl_entry = self.get_pnl_gl_entry(net_pl_balance)
+ gl_entries.append(gl_entry)
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
+
+ def get_pnl_gl_entry(self, net_pl_balance):
+ cost_center = frappe.db.get_value("Company", self.company, "cost_center")
+ gl_entry = self.get_gl_dict({
+ "account": self.closing_account_head,
+ "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
+ "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
+ "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
+ "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
+ "cost_center": cost_center
+ })
+
+ self.update_default_dimensions(gl_entry)
+
+ return gl_entry
+
+ def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
+ company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
+ gl_entries = []
+
+ for acc in pl_accounts:
+ if flt(acc.bal_in_company_currency):
+ gl_entry = self.get_gl_dict({
+ "account": self.closing_account_head,
+ "cost_center": acc.cost_center or company_cost_center,
+ "account_currency": acc.account_currency,
+ "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
+ "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
+ "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
+ "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0
+ }, item=acc)
+
+ self.update_default_dimensions(gl_entry)
+
+ gl_entries.append(gl_entry)
+
+ return gl_entries
+
+ def update_default_dimensions(self, gl_entry):
+ if not self.accounting_dimensions:
+ self.accounting_dimensions = get_accounting_dimensions()
+
+ _, default_dimensions = get_dimensions()
+ for dimension in self.accounting_dimensions:
+ gl_entry.update({
+ dimension: default_dimensions.get(self.company, {}).get(dimension)
+ })
+
+ def get_pl_balances(self):
+ """Get balance for dimension-wise pl accounts"""
+
+ dimension_fields = ['t1.cost_center']
+
+ self.accounting_dimensions = get_accounting_dimensions()
+ for dimension in self.accounting_dimensions:
+ dimension_fields.append('t1.{0}'.format(dimension))
- def get_pl_balances(self, dimension_fields):
- """Get balance for Profit and Loss accounts, only including valid transactions (not cancelled)"""
return frappe.db.sql("""
select
t1.account, t2.account_currency, {dimension_fields},
- sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
- sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
+ sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
+ sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index eb02d97b78..2f29372b01 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -8,6 +8,7 @@ import frappe
from frappe.utils import flt, today
from erpnext.accounts.utils import get_fiscal_year, now
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
class TestPeriodClosingVoucher(unittest.TestCase):
def test_closing_entry(self):
@@ -65,6 +66,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
-1*random_expense_account[0].balance_in_account_currency)
+ def test_cost_center_wise_posting(self):
+ frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+
+ company = create_company()
+ surplus_account = create_account()
+
+ cost_center1 = create_cost_center("Test Cost Center 1")
+ cost_center2 = create_cost_center("Test Cost Center 2")
+
+ create_sales_invoice(
+ company=company,
+ cost_center=cost_center1,
+ income_account="Sales - TPC",
+ expense_account="Cost of Goods Sold - TPC",
+ rate=400,
+ debit_to="Debtors - TPC"
+ )
+ create_sales_invoice(
+ company=company,
+ cost_center=cost_center2,
+ income_account="Sales - TPC",
+ expense_account="Cost of Goods Sold - TPC",
+ rate=200,
+ debit_to="Debtors - TPC"
+ )
+
+ pcv = frappe.get_doc({
+ "transaction_date": today(),
+ "posting_date": today(),
+ "fiscal_year": get_fiscal_year(today())[0],
+ "company": "Test PCV Company",
+ "cost_center_wise_pnl": 1,
+ "closing_account_head": surplus_account,
+ "remarks": "Test",
+ "doctype": "Period Closing Voucher"
+ })
+ pcv.insert()
+ pcv.submit()
+
+ expected_gle = (
+ ('Sales - TPC', 200.0, 0.0, cost_center2),
+ (surplus_account, 0.0, 200.0, cost_center2),
+ ('Sales - TPC', 400.0, 0.0, cost_center1),
+ (surplus_account, 0.0, 400.0, cost_center1)
+ )
+
+ pcv_gle = frappe.db.sql("""
+ select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s
+ """, (pcv.name))
+
+ self.assertTrue(pcv_gle, expected_gle)
+
def make_period_closing_voucher(self):
pcv = frappe.get_doc({
"doctype": "Period Closing Voucher",
@@ -80,6 +133,38 @@ class TestPeriodClosingVoucher(unittest.TestCase):
return pcv
+def create_company():
+ company = frappe.get_doc({
+ 'doctype': 'Company',
+ 'company_name': "Test PCV Company",
+ 'country': 'United States',
+ 'default_currency': 'USD'
+ })
+ company.insert(ignore_if_duplicate = True)
+ return company.name
+
+def create_account():
+ account = frappe.get_doc({
+ "account_name": "Reserve and Surplus",
+ "is_group": 0,
+ "company": "Test PCV Company",
+ "root_type": "Liability",
+ "report_type": "Balance Sheet",
+ "account_currency": "USD",
+ "parent_account": "Current Liabilities - TPC",
+ "doctype": "Account"
+ }).insert(ignore_if_duplicate = True)
+ return account.name
+
+def create_cost_center(cc_name):
+ costcenter = frappe.get_doc({
+ "company": "Test PCV Company",
+ "cost_center_name": cc_name,
+ "doctype": "Cost Center",
+ "parent_cost_center": "Test PCV Company - TPC"
+ })
+ costcenter.insert(ignore_if_duplicate = True)
+ return costcenter.name
test_dependencies = ["Customer", "Cost Center"]
test_records = frappe.get_test_records("Period Closing Voucher")
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 8c5a34a0d8..6418d73090 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -107,7 +107,7 @@ frappe.ui.form.on('POS Closing Entry', {
frm.set_value("taxes", []);
for (let row of frm.doc.payment_reconciliation) {
- row.expected_amount = 0;
+ row.expected_amount = row.opening_amount;
}
for (let row of frm.doc.pos_transactions) {
@@ -154,6 +154,9 @@ function add_to_pos_transaction(d, frm) {
function refresh_payments(d, frm) {
d.payments.forEach(p => {
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
+ if (p.account == d.account_for_change_amount) {
+ p.amount -= flt(d.change_amount);
+ }
if (payment) {
payment.expected_amount += flt(p.amount);
payment.difference = payment.closing_amount - payment.expected_amount;
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index f55fdab21c..8ec4ef224c 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice):
return
available_stock = get_stock_availability(d.item_code, d.warehouse)
+
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
if flt(available_stock) <= 0:
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
@@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice):
for d in self.get("items"):
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
if not is_stock_item:
- frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
- .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
+ if not frappe.db.exists('Product Bundle', d.item_code):
+ frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
+ .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
def validate_mode_of_payment(self):
if len(self.payments) == 0:
@@ -455,15 +457,36 @@ class POSInvoice(SalesInvoice):
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):
+ if frappe.db.get_value('Item', item_code, 'is_stock_item'):
+ bin_qty = get_bin_qty(item_code, warehouse)
+ pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
+ return bin_qty - pos_sales_qty
+ else:
+ if frappe.db.exists('Product Bundle', item_code):
+ return get_bundle_availability(item_code, warehouse)
+
+def get_bundle_availability(bundle_item_code, warehouse):
+ product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
+
+ bundle_bin_qty = 1000000
+ for item in product_bundle.items:
+ item_bin_qty = get_bin_qty(item.item_code, warehouse)
+ item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
+ available_qty = item_bin_qty - item_pos_reserved_qty
+
+ max_available_bundles = available_qty / item.qty
+ if bundle_bin_qty > max_available_bundles:
+ bundle_bin_qty = max_available_bundles
+
+ pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
+ return bundle_bin_qty - pos_sales_qty
+
+def get_bin_qty(item_code, warehouse):
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
where item_code = %s and warehouse = %s
limit 1""", (item_code, warehouse), as_dict=1)
- pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
-
- bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0
-
- return bin_qty - pos_sales_qty
+ return bin_qty[0].actual_qty or 0 if bin_qty else 0
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
@@ -522,4 +545,4 @@ def add_return_modes(doc, pos_profile):
mode_of_payment = pos_payment_method.mode_of_payment
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
- append_payment(payment_mode[0])
\ No newline at end of file
+ append_payment(payment_mode[0])
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
index 6dc46430e0..088c190f45 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
@@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
}
else{
- frappe.msgprint('No Records for these settings.')
+ frappe.msgprint(__('No Records for these settings.'))
}
}
});
@@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
type: 'GET',
success: function(result) {
if(jQuery.isEmptyObject(result)){
- frappe.msgprint('No Records for these settings.');
+ frappe.msgprint(__('No Records for these settings.'));
}
else{
window.location = url;
@@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
frm.refresh_field('customers');
}
else{
- frappe.throw('No Customers found with selected options.');
+ frappe.throw(__('No Customers found with selected options.'));
}
}
}
@@ -129,4 +129,4 @@ frappe.ui.form.on('Process Statement Of Accounts Customer', {
}
})
}
-});
\ No newline at end of file
+});
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index d3d3ffa17f..a714ac7827 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -175,7 +175,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -187,7 +189,9 @@
"options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier",
@@ -199,7 +203,9 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -211,7 +217,9 @@
"label": "Supplier Name",
"oldfieldname": "supplier_name",
"oldfieldtype": "Data",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fetch_from": "supplier.tax_id",
@@ -219,21 +227,27 @@
"fieldtype": "Read Only",
"label": "Tax Id",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date",
"oldfieldname": "due_date",
- "oldfieldtype": "Date"
+ "oldfieldtype": "Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "is_paid",
"fieldtype": "Check",
"label": "Is Paid",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -241,19 +255,25 @@
"fieldtype": "Check",
"label": "Is Return (Debit Note)",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -263,13 +283,17 @@
"label": "Company",
"options": "Company",
"print_hide": 1,
- "remember_last_selected_value": 1
+ "remember_last_selected_value": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center"
+ "options": "Cost Center",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Today",
@@ -281,7 +305,9 @@
"oldfieldtype": "Date",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "posting_time",
@@ -290,6 +316,8 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -298,7 +326,9 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -310,44 +340,58 @@
"oldfieldtype": "Link",
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.on_hold",
"fieldname": "sb_14",
"fieldtype": "Section Break",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "on_hold",
"fieldtype": "Check",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"description": "Once set, this invoice will be on hold till the set date",
"fieldname": "release_date",
"fieldtype": "Date",
- "label": "Release Date"
+ "label": "Release Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_17",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"fieldname": "hold_comment",
"fieldtype": "Small Text",
- "label": "Reason For Putting On Hold"
+ "label": "Reason For Putting On Hold",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "bill_no",
"fieldname": "supplier_invoice_details",
"fieldtype": "Section Break",
- "label": "Supplier Invoice Details"
+ "label": "Supplier Invoice Details",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_no",
@@ -355,11 +399,15 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_date",
@@ -368,13 +416,17 @@
"no_copy": 1,
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
- "label": "Returns"
+ "label": "Returns",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
@@ -384,26 +436,34 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
@@ -411,51 +471,67 @@
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -464,7 +540,9 @@
"oldfieldname": "currency",
"oldfieldtype": "Select",
"options": "Currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -473,18 +551,24 @@
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -492,14 +576,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -508,11 +596,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -521,7 +613,9 @@
"fieldtype": "Link",
"label": "Set Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -531,11 +625,15 @@
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -543,25 +641,33 @@
"fieldtype": "Select",
"label": "Raw Materials Supplied",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -571,42 +677,56 @@
"oldfieldname": "entries",
"oldfieldtype": "Table",
"options": "Purchase Invoice Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_materials_supplied",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
"fieldtype": "Table",
"label": "Supplied Items",
"options": "Purchase Receipt Item Supplied",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_26",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -614,7 +734,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -624,18 +746,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_28",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -645,42 +773,56 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_49",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_51",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -689,7 +831,9 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -697,13 +841,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -712,13 +860,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -728,7 +880,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -738,7 +892,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -748,11 +904,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_40",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_added",
@@ -762,7 +922,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -772,7 +934,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -780,14 +944,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_44",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -795,7 +963,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -803,28 +973,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_46",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_49",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -834,16 +1014,21 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -853,7 +1038,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_in_words",
@@ -863,13 +1050,17 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break8",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -880,16 +1071,21 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment",
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -899,7 +1095,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -909,7 +1107,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_advance",
@@ -920,7 +1120,9 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "outstanding_amount",
@@ -931,14 +1133,18 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"depends_on": "grand_total",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -946,20 +1152,26 @@
"depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
- "label": "Payments"
+ "label": "Payments",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"label": "Mode of Payment",
"options": "Mode of Payment",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "clearance_date",
@@ -967,11 +1179,15 @@
"label": "Clearance Date",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_br_payments",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_paid",
@@ -980,7 +1196,9 @@
"label": "Paid Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_paid_amount",
@@ -989,7 +1207,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -997,7 +1217,9 @@
"depends_on": "grand_total",
"fieldname": "write_off",
"fieldtype": "Section Break",
- "label": "Write Off"
+ "label": "Write Off",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "write_off_amount",
@@ -1005,7 +1227,9 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_write_off_amount",
@@ -1014,11 +1238,15 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_61",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1026,7 +1254,9 @@
"fieldtype": "Link",
"label": "Write Off Account",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1034,7 +1264,9 @@
"fieldtype": "Link",
"label": "Write Off Cost Center",
"options": "Cost Center",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1044,13 +1276,17 @@
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "allocate_advances_automatically",
"fieldtype": "Check",
- "label": "Set Advances and Allocate (FIFO)"
+ "label": "Set Advances and Allocate (FIFO)",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1058,7 +1294,9 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advances",
@@ -1068,20 +1306,26 @@
"oldfieldname": "advance_allocation_details",
"oldfieldtype": "Table",
"options": "Purchase Invoice Advance",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:(!doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -1089,7 +1333,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1097,25 +1343,33 @@
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"label": "Terms and Conditions",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
"fieldtype": "Link",
"label": "Terms",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
- "label": "Terms and Conditions1"
+ "label": "Terms and Conditions1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1123,7 +1377,9 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1131,11 +1387,15 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_112",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1147,14 +1407,18 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1163,7 +1427,9 @@
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "credit_to",
@@ -1174,7 +1440,9 @@
"options": "Account",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -1184,7 +1452,9 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -1194,7 +1464,9 @@
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "against_expense_account",
@@ -1204,11 +1476,15 @@
"no_copy": 1,
"oldfieldname": "against_expense_account",
"oldfieldtype": "Small Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_63",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -1217,7 +1493,9 @@
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_invoice_reference",
@@ -1226,7 +1504,9 @@
"no_copy": 1,
"options": "Sales Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
@@ -1235,14 +1515,18 @@
"no_copy": 1,
"oldfieldname": "remarks",
"oldfieldtype": "Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1251,7 +1535,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1260,11 +1546,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_114",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1273,24 +1563,32 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions "
+ "label": "Accounting Dimensions ",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1298,7 +1596,9 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_withholding_category",
@@ -1306,19 +1606,25 @@
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "billing_address",
"fieldtype": "Link",
"label": "Select Billing Address",
- "options": "Address"
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "billing_address_display",
"fieldtype": "Small Text",
"label": "Billing Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "project",
@@ -1332,7 +1638,9 @@
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_internal_supplier",
@@ -1341,7 +1649,9 @@
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
- "options": "Company"
+ "options": "Company",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
@@ -1353,6 +1663,8 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
@@ -1380,7 +1692,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-30 22:45:58.334107",
+ "modified": "2021-06-09 12:30:25.632109",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 83e9f7583e..0ee0bc7e11 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -68,9 +68,6 @@ class PurchaseInvoice(BuyingController):
super(PurchaseInvoice, self).validate()
- # apply tax withholding only if checked and applicable
- self.set_tax_withholding()
-
if not self.is_return:
self.po_required()
self.pr_required()
@@ -251,11 +248,9 @@ class PurchaseInvoice(BuyingController):
if self.update_stock and (not item.from_warehouse):
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
- msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
- msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
- msg += _("or it is not the default inventory account")
+ msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
+ item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
frappe.msgprint(msg, title=_("Expense Head Changed"))
-
item.expense_account = warehouse_account[item.warehouse]["account"]
else:
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
@@ -266,8 +261,8 @@ class PurchaseInvoice(BuyingController):
if negative_expense_booked_in_pr:
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
- msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
- msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
+ msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format(
+ item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))
frappe.msgprint(msg, title=_("Expense Head Changed"))
item.expense_account = stock_not_billed_account
@@ -275,8 +270,9 @@ class PurchaseInvoice(BuyingController):
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
# This is done in cases when Purchase Invoice is created before Purchase Receipt
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
- msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
- msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
+ msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format(
+ item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))
+ msg += "
"
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
frappe.msgprint(msg, title=_("Expense Head Changed"))
@@ -308,8 +304,8 @@ class PurchaseInvoice(BuyingController):
if not d.purchase_order:
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
msg += "
"
- msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
- msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
+ msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
+ frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
throw(msg, title=_("Mandatory Purchase Order"))
def pr_required(self):
@@ -323,8 +319,8 @@ class PurchaseInvoice(BuyingController):
if not d.purchase_receipt and d.item_code in stock_items:
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
msg += "
"
- msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
- msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
+ msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format(
+ frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
throw(msg, title=_("Mandatory Purchase Receipt"))
def validate_write_off_account(self):
@@ -456,6 +452,8 @@ class PurchaseInvoice(BuyingController):
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
+ self.allocate_advance_taxes(gl_entries)
+
gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries)
@@ -1090,6 +1088,7 @@ class PurchaseInvoice(BuyingController):
for d in self.taxes:
if d.account_head == tax_withholding_details.get("account_head"):
d.update(tax_withholding_details)
+
accounts.append(d.account_head)
if not accounts or tax_withholding_details.get("account_head") not in accounts:
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 6dc0f56148..503dda7728 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.buying.doctype.supplier.test_supplier import create_supplier
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
test_ignore = ["Serial No"]
@@ -950,6 +951,102 @@ class TestPurchaseInvoice(unittest.TestCase):
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.save()
+ def test_purchase_invoice_advance_taxes(self):
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+ from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
+
+ # create a new supplier to test
+ supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
+ tax_withholding_category = 'TDS - 194 - Dividends - Individual')
+
+ # Update tax withholding category with current fiscal year and rate details
+ update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
+
+ # Create Purchase Order with TDS applied
+ po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000)
+ po.apply_tds = 1
+ po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
+ po.save()
+ po.submit()
+
+ # Update Unrealized Profit / Loss Account which is used as default advance tax account
+ frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
+
+ # Create Payment Entry Against the order
+ payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
+ payment_entry.paid_from = 'Cash - _TC'
+ payment_entry.save()
+ payment_entry.submit()
+
+ # Check GLE for Payment Entry
+ expected_gle = [
+ ['_Test Account Excise Duty - _TC', 3000, 0],
+ ['Cash - _TC', 0, 27000],
+ ['Creditors - _TC', 27000, 0],
+ ['TDS Payable - _TC', 0, 3000],
+ ]
+
+ gl_entries = frappe.db.sql("""select account, debit, credit
+ from `tabGL Entry`
+ where voucher_type='Payment Entry' and voucher_no=%s
+ order by account asc""", (payment_entry.name), as_dict=1)
+
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_gle[i][0], gle.account)
+ self.assertEqual(expected_gle[i][1], gle.debit)
+ self.assertEqual(expected_gle[i][2], gle.credit)
+
+ # Create Purchase Invoice against Purchase Order
+ purchase_invoice = get_mapped_purchase_invoice(po.name)
+ purchase_invoice.allocate_advances_automatically = 1
+ purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
+ purchase_invoice.save()
+ purchase_invoice.submit()
+
+ # Check GLE for Purchase Invoice
+ # Zero net effect on final TDS Payable on invoice
+ expected_gle = [
+ ['_Test Account Cost for Goods Sold - _TC', 30000, 0],
+ ['_Test Account Excise Duty - _TC', 0, 3000],
+ ['Creditors - _TC', 0, 27000],
+ ['TDS Payable - _TC', 3000, 3000]
+ ]
+
+ gl_entries = frappe.db.sql("""select account, debit, credit
+ from `tabGL Entry`
+ where voucher_type='Purchase Invoice' and voucher_no=%s
+ order by account asc""", (purchase_invoice.name), as_dict=1)
+
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_gle[i][0], gle.account)
+ self.assertEqual(expected_gle[i][1], gle.debit)
+ self.assertEqual(expected_gle[i][2], gle.credit)
+
+def update_tax_witholding_category(company, account, date):
+ from erpnext.accounts.utils import get_fiscal_year
+
+ fiscal_year = get_fiscal_year(date=date, company=company)
+
+ if not frappe.db.get_value('Tax Withholding Rate',
+ {'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
+ tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
+ tds_category.append('rates', {
+ 'fiscal_year': fiscal_year[0],
+ 'tax_withholding_rate': 10,
+ 'single_threshold': 2500,
+ 'cumulative_threshold': 0
+ })
+ tds_category.save()
+
+ if not frappe.db.get_value('Tax Withholding Account',
+ {'parent': 'TDS - 194 - Dividends - Individual', 'account': account}):
+ tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
+ tds_category.append('accounts', {
+ 'company': company,
+ 'account': account
+ })
+ tds_category.save()
def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
index f9fdc4b605..1fa68e0a8a 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
@@ -12,6 +12,7 @@
"charge_type",
"row_id",
"included_in_print_rate",
+ "included_in_paid_amount",
"col_break1",
"account_head",
"description",
@@ -21,6 +22,7 @@
"cost_center",
"dimension_col_break",
"section_break_9",
+ "currency",
"tax_amount",
"tax_amount_after_discount_amount",
"total",
@@ -205,12 +207,28 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "account_head.account_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Account Currency",
+ "options": "Currency",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
+ "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
+ "fieldname": "included_in_paid_amount",
+ "fieldtype": "Check",
+ "label": "Considered In Paid Amount"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-06-14 01:43:50.750455",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b735fde1ab..bb55651670 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
var me = this;
super.onload();
- this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
this.frm.set_df_property("debit_to", "print_hide", 0);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 023f4b049c..e14f305fc5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -531,7 +531,7 @@ class SalesInvoice(SellingController):
# set pos values in items
for item in self.get("items"):
if item.get('item_code'):
- profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
+ profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True)
for fname, val in iteritems(profile_details):
if (not for_validate) or (for_validate and not item.get(fname)):
item.set(fname, val)
@@ -842,6 +842,8 @@ class SalesInvoice(SellingController):
self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
+ self.allocate_advance_taxes(gl_entries)
+
self.make_item_gl_entries(gl_entries)
# merge gl entries before adding pos entries
@@ -849,7 +851,6 @@ class SalesInvoice(SellingController):
self.make_loyalty_point_redemption_gle(gl_entries)
self.make_pos_gl_entries(gl_entries)
- self.make_gle_for_change_amount(gl_entries)
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
@@ -983,7 +984,13 @@ class SalesInvoice(SellingController):
def make_pos_gl_entries(self, gl_entries):
if cint(self.is_pos):
+
+ skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries'))
+
for payment_mode in self.payments:
+ if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
+ payment_mode.base_amount -= self.change_amount
+
if payment_mode.amount:
# POS, make payment entries
gl_entries.append(
@@ -1015,8 +1022,11 @@ class SalesInvoice(SellingController):
}, payment_mode_account_currency, item=self)
)
+ if not skip_change_gl_entries:
+ self.make_gle_for_change_amount(gl_entries)
+
def make_gle_for_change_amount(self, gl_entries):
- if cint(self.is_pos) and self.change_amount:
+ if self.change_amount:
if self.account_for_change_amount:
gl_entries.append(
self.get_gl_dict({
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index df6d483904..114b7d2d35 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -713,7 +713,7 @@ class TestSalesInvoice(unittest.TestCase):
si.submit()
self.assertEqual(si.paid_amount, 100.0)
- self.pos_gl_entry(si, pos, 50)
+ self.validate_pos_gl_entry(si, pos, 50)
def test_pos_returns_with_repayment(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
@@ -749,7 +749,7 @@ class TestSalesInvoice(unittest.TestCase):
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
- pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
+ make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
@@ -770,7 +770,45 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(pos.grand_total, 100.0)
self.assertEqual(pos.write_off_amount, -5)
- def pos_gl_entry(self, si, pos, cash_amount):
+ def test_pos_with_no_gl_entry_for_change_amount(self):
+ frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0)
+
+ make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
+ expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
+
+ make_purchase_receipt(company= "_Test Company with perpetual inventory",
+ item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
+
+ pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
+ debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
+ income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
+ cost_center = "Main - TCP1", do_not_save=True)
+
+ pos.is_pos = 1
+ pos.update_stock = 1
+
+ taxes = get_taxes_and_charges()
+ pos.taxes = []
+ for tax in taxes:
+ pos.append("taxes", tax)
+
+ pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
+ pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
+
+ pos.insert()
+ pos.submit()
+
+ self.assertEqual(pos.grand_total, 100.0)
+ self.assertEqual(pos.change_amount, 10)
+
+ self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True)
+
+ frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1)
+
+ def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
+ if validate_without_change_gle:
+ cash_amount -= pos.change_amount
+
# check stock ledger entries
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
@@ -1899,69 +1937,80 @@ class TestSalesInvoice(unittest.TestCase):
frappe.flags.country = country
def test_einvoice_json(self):
- from erpnext.regional.india.e_invoice.utils import make_einvoice
+ from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
- si = make_sales_invoice_for_ewaybill()
- si.naming_series = 'INV-2020-.#####'
- si.items = []
- si.append("items", {
- "item_code": "_Test Item",
- "uom": "Nos",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 2000,
- "rate": 12,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- })
- si.append("items", {
- "item_code": "_Test Item 2",
- "uom": "Nos",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 420,
- "rate": 15,
- "income_account": "Sales - _TC",
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC",
- })
+ si = get_sales_invoice_for_e_invoice()
si.discount_amount = 100
si.save()
einvoice = make_einvoice(si)
-
- total_item_ass_value = 0
- total_item_cgst_value = 0
- total_item_sgst_value = 0
- total_item_igst_value = 0
- total_item_value = 0
-
- for item in einvoice['ItemList']:
- total_item_ass_value += item['AssAmt']
- total_item_cgst_value += item['CgstAmt']
- total_item_sgst_value += item['SgstAmt']
- total_item_igst_value += item['IgstAmt']
- total_item_value += item['TotItemVal']
-
- self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
- self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
-
- value_details = einvoice['ValDtls']
-
- self.assertEqual(einvoice['Version'], '1.1')
- self.assertEqual(value_details['AssVal'], total_item_ass_value)
- self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
- self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
- self.assertEqual(value_details['IgstVal'], total_item_igst_value)
-
- calculated_invoice_value = \
- value_details['AssVal'] + value_details['CgstVal'] \
- + value_details['SgstVal'] + value_details['IgstVal'] \
- + value_details['OthChrg'] - value_details['Discount']
-
- self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
-
- self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls'])
+ validate_totals(einvoice)
+
+ si.apply_discount_on = 'Net Total'
+ si.save()
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+ [d.set('included_in_print_rate', 1) for d in si.taxes]
+ si.save()
+ einvoice = make_einvoice(si)
+ validate_totals(einvoice)
+
+def get_sales_invoice_for_e_invoice():
+ si = make_sales_invoice_for_ewaybill()
+ si.naming_series = 'INV-2020-.#####'
+ si.items = []
+ si.append("items", {
+ "item_code": "_Test Item",
+ "uom": "Nos",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 2000,
+ "rate": 12,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ })
+
+ si.append("items", {
+ "item_code": "_Test Item 2",
+ "uom": "Nos",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 420,
+ "rate": 15,
+ "income_account": "Sales - _TC",
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ })
+
+ return si
+
+ def test_item_tax_net_range(self):
+ item = create_item("T Shirt")
+
+ item.set('taxes', [])
+ item.append("taxes", {
+ "item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
+ "minimum_net_rate": 0,
+ "maximum_net_rate": 500
+ })
+
+ item.append("taxes", {
+ "item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
+ "minimum_net_rate": 501,
+ "maximum_net_rate": 1000
+ })
+
+ item.save()
+
+ sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True)
+ self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
+
+ # Apply discount
+ sales_invoice.apply_discount_on = 'Net Total'
+ sales_invoice.discount_amount = 300
+ sales_invoice.save()
+ self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
def make_test_address_for_ewaybill():
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
@@ -2085,27 +2134,6 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
- def test_item_tax_validity(self):
- item = frappe.get_doc("Item", "_Test Item 2")
-
- if item.taxes:
- item.taxes = []
- item.save()
-
- item.append("taxes", {
- "item_tax_template": "_Test Item Tax Template 1 - _TC",
- "valid_from": add_days(nowdate(), 1)
- })
-
- item.save()
-
- sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
- sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC"
- self.assertRaises(frappe.ValidationError, sales_invoice.save)
-
- item.taxes = []
- item.save()
-
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
index 3c8cb6b851..1b7a0fe562 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
@@ -1,8 +1,10 @@
{
+ "actions": [],
"creation": "2013-04-24 11:39:32",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
+ "engine": "InnoDB",
"field_order": [
"charge_type",
"row_id",
@@ -10,12 +12,14 @@
"col_break_1",
"description",
"included_in_print_rate",
+ "included_in_paid_amount",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_8",
"rate",
"section_break_9",
+ "currency",
"tax_amount",
"total",
"tax_amount_after_discount_amount",
@@ -23,8 +27,7 @@
"base_tax_amount",
"base_total",
"base_tax_amount_after_discount_amount",
- "item_wise_tax_detail",
- "parenttype"
+ "item_wise_tax_detail"
],
"fields": [
{
@@ -173,17 +176,6 @@
"oldfieldtype": "Small Text",
"read_only": 1
},
- {
- "fieldname": "parenttype",
- "fieldtype": "Data",
- "hidden": 1,
- "in_filter": 1,
- "label": "Parenttype",
- "oldfieldname": "parenttype",
- "oldfieldtype": "Data",
- "print_hide": 1,
- "search_index": 1
- },
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
@@ -192,15 +184,34 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "account_head.account_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Account Currency",
+ "options": "Currency",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
+ "description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
+ "fieldname": "included_in_paid_amount",
+ "fieldtype": "Check",
+ "label": "Considered In Paid Amount"
}
],
"idx": 1,
+ "index_web_pages_for_search": 1,
"istable": 1,
- "modified": "2019-05-25 22:59:38.740883",
+ "links": [],
+ "modified": "2021-06-14 01:44:36.899147",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",
"owner": "Administrator",
"permissions": [],
+ "sort_field": "modified",
"sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 5c1cbaa4aa..b9ee4a0963 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -49,7 +49,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
if not parties:
parties.append(party)
- fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
+ fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
if not tax_details:
@@ -154,7 +154,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
tax_amount = 0
- posting_date = inv.posting_date
+ posting_date = inv.get('posting_date') or inv.get('transaction_date')
if party_type == 'Supplier':
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
if tax_deducted:
@@ -257,7 +257,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
if ldc and is_valid_certificate(
ldc.valid_from, ldc.valid_upto,
- inv.posting_date, tax_deducted,
+ inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
inv.net_total, ldc.certificate_limit
):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 968ab49b31..59009ae621 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
for d in gl_map:
if d.account == round_off_account:
round_off_gle = d
- if d.debit_in_account_currency:
- debit_credit_diff -= flt(d.debit_in_account_currency)
+ if d.debit:
+ debit_credit_diff -= flt(d.debit)
else:
- debit_credit_diff += flt(d.credit_in_account_currency)
+ debit_credit_diff += flt(d.credit)
round_off_account_exists = True
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index e01cb6e151..e025fc6905 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -457,7 +457,7 @@ def validate_party_frozen_disabled(party_type, party_name):
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
elif party_type == "Employee":
- if frappe.db.get_value("Employee", party_name, "status") == "Left":
+ if frappe.db.get_value("Employee", party_name, "status") != "Active":
frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
def get_timeline_data(doctype, name):
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index db605f7285..a11b77a6f6 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -584,6 +584,7 @@ class ReceivablePayableReport(object):
`tabGL Entry`
where
docstatus < 2
+ and is_cancelled = 0
and party_type=%s
and (party is not null and party != '')
{1} {2} {3}"""
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
index 344539eef6..72de318a48 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
@@ -54,6 +54,32 @@ frappe.query_reports["TDS Payable Monthly"] = {
frappe.query_report.refresh();
}
},
+ {
+ "fieldname":"purchase_order",
+ "label": __("Purchase Order"),
+ "fieldtype": "Link",
+ "options": "Purchase Order",
+ "get_query": function() {
+ return {
+ "filters": {
+ "name": ["in", frappe.query_report.invoices]
+ }
+ }
+ },
+ on_change: function() {
+ let supplier = frappe.query_report.get_filter_value('supplier');
+ if(!supplier) return; // return if no supplier selected
+
+ // filter invoices based on selected supplier
+ let invoices = [];
+ frappe.query_report.invoice_data.map(d => {
+ if(d.supplier==supplier)
+ invoices.push(d.name)
+ });
+ frappe.query_report.invoices = invoices;
+ frappe.query_report.refresh();
+ }
+ },
{
"fieldname":"from_date",
"label": __("From Date"),
@@ -75,15 +101,17 @@ frappe.query_reports["TDS Payable Monthly"] = {
onload: function(report) {
// fetch all tds applied invoices
frappe.call({
- "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices",
+ "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders",
callback: function(r) {
let invoices = [];
+
r.message.map(d => {
invoices.push(d.name);
});
- report["invoice_data"] = r.message;
+ report["invoice_data"] = r.message.invoices;
report["invoices"] = invoices;
+
}
});
}
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index a9fb237a04..ceefa31cfa 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -11,11 +11,14 @@ def execute(filters=None):
validate_filters(filters)
set_filters(filters)
+ # TDS payment entries
+ payment_entries = get_payment_entires(filters)
+
columns = get_columns(filters)
- if not filters["invoices"]:
+ if not filters.get("invoices"):
return columns, []
- res = get_result(filters)
+ res = get_result(filters, payment_entries)
return columns, res
@@ -27,8 +30,9 @@ def validate_filters(filters):
def set_filters(filters):
invoices = []
- if not filters["invoices"]:
- filters["invoices"] = get_tds_invoices()
+ if not filters.get("invoices"):
+ filters["invoices"] = get_tds_invoices_and_orders()
+
if filters.supplier and filters.purchase_invoice:
for d in filters["invoices"]:
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
@@ -41,13 +45,29 @@ def set_filters(filters):
for d in filters["invoices"]:
if d.name == filters.purchase_invoice:
invoices.append(d)
+ elif filters.supplier and filters.purchase_order:
+ for d in filters.get("invoices"):
+ if d.name == filters.purchase_order and d.supplier == filters.supplier:
+ invoices.append(d)
+ elif filters.supplier and not filters.purchase_order:
+ for d in filters.get("invoices"):
+ if d.supplier == filters.supplier:
+ invoices.append(d)
+ elif filters.purchase_order and not filters.supplier:
+ for d in filters.get("invoices"):
+ if d.name == filters.purchase_order:
+ invoices.append(d)
filters["invoices"] = invoices if invoices else filters["invoices"]
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
-def get_result(filters):
- supplier_map, tds_docs = get_supplier_map(filters)
- gle_map = get_gle_map(filters)
+ #print(filters.get('invoices'))
+
+def get_result(filters, payment_entries):
+ supplier_map, tds_docs = get_supplier_map(filters, payment_entries)
+ documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries]
+
+ gle_map = get_gle_map(filters, documents)
out = []
for d in gle_map:
@@ -62,10 +82,11 @@ def get_result(filters):
for k in gle_map[d]:
if k.party == supplier_map[d] and k.credit > 0:
- total_amount_credited += k.credit
- elif account_list and k.account == account and k.credit > 0:
- tds_deducted = k.credit
- total_amount_credited += k.credit
+ total_amount_credited += (k.credit - k.debit)
+ elif account_list and k.account == account and (k.credit - k.debit) > 0:
+ tds_deducted = (k.credit - k.debit)
+ total_amount_credited += (k.credit - k.debit)
+ voucher_type = k.voucher_type
rate = [i.tax_withholding_rate for i in tds_doc.rates
if i.fiscal_year == gle_map[d][0].fiscal_year]
@@ -73,32 +94,36 @@ def get_result(filters):
if rate and len(rate) > 0 and tds_deducted:
rate = rate[0]
- if getdate(filters.from_date) <= gle_map[d][0].posting_date \
- and getdate(filters.to_date) >= gle_map[d][0].posting_date:
- row = [supplier.pan, supplier.name]
+ row = [supplier.pan, supplier.name]
- if filters.naming_series == 'Naming Series':
- row.append(supplier.supplier_name)
+ if filters.naming_series == 'Naming Series':
+ row.append(supplier.supplier_name)
- row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
- tds_deducted, gle_map[d][0].posting_date, "Purchase Invoice", d])
- out.append(row)
+ row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
+ tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
+ out.append(row)
return out
-def get_supplier_map(filters):
+def get_supplier_map(filters, payment_entries):
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
# pre-fetch all distinct applicable tds docs
supplier_map, tds_docs = {}, {}
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
+ supplier_list = [d.supplier for d in filters["invoices"]]
+
supplier_detail = frappe.db.get_all('Supplier',
- {"name": ["in", [d.supplier for d in filters["invoices"]]]},
+ {"name": ["in", supplier_list]},
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
for d in filters["invoices"]:
supplier_map[d.get("name")] = [k for k in supplier_detail
if k.name == d.get("supplier")][0]
+ for d in payment_entries:
+ supplier_map[d.get("name")] = [k for k in supplier_detail
+ if k.name == d.get("supplier")][0]
+
for d in supplier_detail:
if d.get("tax_withholding_category") not in tds_docs:
tds_docs[d.get("tax_withholding_category")] = \
@@ -106,13 +131,19 @@ def get_supplier_map(filters):
return supplier_map, tds_docs
-def get_gle_map(filters):
+def get_gle_map(filters, documents):
# create gle_map of the form
# {"purchase_invoice": list of dict of all gle created for this invoice}
gle_map = {}
- gle = frappe.db.get_all('GL Entry',\
- {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
- ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
+
+ gle = frappe.db.get_all('GL Entry',
+ {
+ "voucher_no": ["in", documents],
+ 'is_cancelled': 0,
+ 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
+ },
+ ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"],
+ )
for d in gle:
if not d.voucher_no in gle_map:
@@ -201,8 +232,26 @@ def get_columns(filters):
return columns
+def get_payment_entires(filters):
+ filter_dict = {
+ 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
+ 'party_type': 'Supplier',
+ 'apply_tax_withholding_amount': 1
+ }
+
+ if filters.get('purchase_invoice') or filters.get('purchase_order'):
+ parent = frappe.db.get_all('Payment Entry Reference',
+ {'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent'])
+
+ filter_dict.update({'name': ('in', [d.get('parent') for d in parent])})
+
+ payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'],
+ filters=filter_dict)
+
+ return payment_entries
+
@frappe.whitelist()
-def get_tds_invoices():
+def get_tds_invoices_and_orders():
# fetch tds applicable supplier and fetch invoices for these suppliers
suppliers = [d.name for d in frappe.db.get_list("Supplier",
{"tax_withholding_category": ["!=", ""]}, ["name"])]
@@ -210,7 +259,12 @@ def get_tds_invoices():
invoices = frappe.db.get_list("Purchase Invoice",
{"supplier": ["in", suppliers]}, ["name", "supplier"])
+ orders = frappe.db.get_list("Purchase Order",
+ {"supplier": ["in", suppliers]}, ["name", "supplier"])
+
+ invoices = invoices + orders
invoices = [d for d in invoices if d.supplier]
+
frappe.cache().hset("invoices", frappe.session.user, invoices)
return invoices
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index b04b03ca79..821fa4d2af 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -707,6 +707,7 @@
"link_to": "GST Settings",
"link_type": "DocType",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -717,6 +718,7 @@
"link_to": "GST HSN Code",
"link_type": "DocType",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -727,6 +729,7 @@
"link_to": "GSTR-1",
"link_type": "Report",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -737,6 +740,7 @@
"link_to": "GSTR-2",
"link_type": "Report",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -747,6 +751,7 @@
"link_to": "GSTR 3B Report",
"link_type": "DocType",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -757,6 +762,7 @@
"link_to": "GST Sales Register",
"link_type": "Report",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -767,6 +773,7 @@
"link_to": "GST Purchase Register",
"link_type": "Report",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -777,6 +784,7 @@
"link_to": "GST Itemised Sales Register",
"link_type": "Report",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -787,6 +795,7 @@
"link_to": "GST Itemised Purchase Register",
"link_type": "Report",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -797,6 +806,7 @@
"link_to": "C-Form",
"link_type": "DocType",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -807,6 +817,7 @@
"link_to": "Lower Deduction Certificate",
"link_type": "DocType",
"onboard": 0,
+ "only_for": "India",
"type": "Link"
},
{
@@ -1065,7 +1076,7 @@
"type": "Link"
}
],
- "modified": "2021-05-13 13:44:56.249888",
+ "modified": "2021-06-10 03:17:31.427945",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@@ -1120,4 +1131,4 @@
"type": "Dashboard"
}
]
-}
+}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 384bbc5385..b5ebc568bc 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -45,6 +45,14 @@ frappe.ui.form.on("Purchase Order", {
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+ },
+
+ apply_tds: function(frm) {
+ if (!frm.doc.apply_tds) {
+ frm.set_value("tax_withholding_category", '');
+ } else {
+ frm.set_value("tax_withholding_category", frm.supplier_tds);
+ }
}
});
@@ -313,7 +321,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
if(me.values) {
me.values.sub_con_rm_items.map((row,i) => {
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
- frappe.throw(__("Item Code, warehouse, quantity are required on row {0}", [i+1]));
+ let row_id = i+1;
+ frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id]));
}
})
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
@@ -509,7 +518,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
args: {
reference_doctype: me.frm.doctype,
reference_name: me.frm.docname,
- content: __('Reason for hold: ')+data.reason_for_hold,
+ content: __('Reason for hold:') + " " +data.reason_for_hold,
comment_email: frappe.session.user,
comment_by: frappe.session.user_fullname
},
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index ee2beea67f..41668c6291 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -14,6 +14,8 @@
"supplier",
"get_items_from_open_material_requests",
"supplier_name",
+ "apply_tds",
+ "tax_withholding_category",
"column_break1",
"company",
"transaction_date",
@@ -142,7 +144,9 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user"
+ "options": "fa fa-user",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -152,7 +156,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -164,7 +170,9 @@
"options": "PUR-ORD-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -178,14 +186,18 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
"description": "Fetch items based on Default Supplier.",
"fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button",
- "label": "Get Items from Open Material Requests"
+ "label": "Get Items from Open Material Requests",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -194,7 +206,9 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company",
@@ -206,13 +220,17 @@
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -224,27 +242,35 @@
"oldfieldname": "transaction_date",
"oldfieldtype": "Date",
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "schedule_date",
"fieldtype": "Date",
- "label": "Required By"
+ "label": "Required By",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.docstatus===1",
"fieldname": "order_confirmation_no",
"fieldtype": "Data",
- "label": "Order Confirmation No"
+ "label": "Order Confirmation No",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.order_confirmation_no",
"fieldname": "order_confirmation_date",
"fieldtype": "Date",
- "label": "Order Confirmation Date"
+ "label": "Order Confirmation Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -256,19 +282,25 @@
"oldfieldtype": "Data",
"options": "Purchase Order",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "drop_ship",
"fieldtype": "Section Break",
- "label": "Drop Ship"
+ "label": "Drop Ship",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -276,31 +308,41 @@
"fieldtype": "Data",
"label": "Customer Name",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_19",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_person",
"fieldtype": "Link",
"label": "Customer Contact",
- "options": "Contact"
+ "options": "Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_display",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_mobile",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Mobile No",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_email",
@@ -308,27 +350,35 @@
"hidden": 1,
"label": "Customer Contact Email",
"options": "Email",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Supplier Contact",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
@@ -355,32 +405,42 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Company Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address Details",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -390,7 +450,9 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -400,18 +462,24 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_price_list",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -419,14 +487,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -435,7 +507,9 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
@@ -448,11 +522,15 @@
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -461,26 +539,34 @@
"in_standard_filter": 1,
"label": "Supply Raw Materials",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"hide_border": 1,
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -490,26 +576,34 @@
"oldfieldname": "po_details",
"oldfieldtype": "Table",
"options": "Purchase Order Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_break_48",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Purchase Order Pricing Rule",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_material_details",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
@@ -519,17 +613,23 @@
"oldfieldtype": "Table",
"options": "Purchase Order Item Supplied",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sb_last_purchase",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -537,7 +637,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -548,18 +650,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_26",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -569,20 +677,26 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -591,18 +705,24 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_52",
@@ -615,13 +735,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -630,14 +754,18 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"label": "Taxes and Charges",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "base_taxes_and_charges_added",
@@ -648,7 +776,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "base_taxes_and_charges_deducted",
@@ -659,7 +789,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "base_total_taxes_and_charges",
@@ -671,11 +803,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_39",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "taxes_and_charges_added",
@@ -686,7 +822,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "taxes_and_charges_deducted",
@@ -697,7 +835,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "total_taxes_and_charges",
@@ -706,14 +846,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "apply_discount_on",
"fieldname": "discount_section",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -721,7 +865,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -729,24 +875,32 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_45",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals_section",
@@ -762,16 +916,21 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "In Words will be visible once you save the Purchase Order.",
@@ -782,7 +941,9 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounded_total",
@@ -792,12 +953,16 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
- "oldfieldtype": "Column Break"
+ "oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "grand_total",
@@ -807,29 +972,38 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment",
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -839,7 +1013,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advance_paid",
@@ -848,19 +1024,25 @@
"no_copy": 1,
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -868,7 +1050,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -877,7 +1061,9 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
@@ -886,21 +1072,27 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor"
+ "oldfieldtype": "Text Editor",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
- "oldfieldtype": "Section Break"
+ "oldfieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -915,7 +1107,9 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "ref_sq",
@@ -926,7 +1120,9 @@
"oldfieldtype": "Data",
"options": "Supplier Quotation",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -936,18 +1132,24 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_order_reference",
"fieldtype": "Link",
"label": "Inter Company Order Reference",
"options": "Sales Order",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_74",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -957,7 +1159,9 @@
"label": "% Received",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -967,7 +1171,9 @@
"label": "% Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -977,6 +1183,8 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -987,7 +1195,9 @@
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -999,11 +1209,15 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_86",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1011,19 +1225,25 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
- "label": "Subscription Section"
+ "label": "Subscription Section",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1031,7 +1251,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1039,11 +1261,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1052,27 +1278,35 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
- "options": "Tax Category"
+ "options": "Tax Category",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "supplied_items",
"fieldname": "set_reserve_warehouse",
"fieldtype": "Link",
"label": "Set Reserve Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1082,7 +1316,9 @@
},
{
"fieldname": "column_break_75",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "billing_address",
@@ -1118,13 +1354,30 @@
"label": "Represents Company",
"options": "Company",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "apply_tds",
+ "fieldtype": "Check",
+ "label": "Apply Tax Withholding Amount",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "depends_on": "eval: doc.apply_tds",
+ "fieldname": "tax_withholding_category",
+ "fieldtype": "Link",
+ "label": "Tax Withholding Category",
+ "options": "Tax Withholding Category",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2021-01-20 22:07:23.487138",
+ "modified": "2021-04-19 00:55:30.781375",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index c023accbfc..2629ba7d61 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -17,6 +17,7 @@ from erpnext.accounts.party import get_party_account_currency
from six import string_types
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
+from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
unlink_inter_company_doc
@@ -39,11 +40,18 @@ class PurchaseOrder(BuyingController):
'percent_join_field': 'material_request'
}]
+ def onload(self):
+ supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
+ self.set_onload("supplier_tds", supplier_tds)
+
def validate(self):
super(PurchaseOrder, self).validate()
self.set_status()
+ # apply tax withholding only if checked and applicable
+ self.set_tax_withholding()
+
self.validate_supplier()
self.validate_schedule_date()
validate_for_items(self)
@@ -87,6 +95,33 @@ class PurchaseOrder(BuyingController):
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
+ def set_tax_withholding(self):
+ if not self.apply_tds:
+ return
+
+ tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
+
+ if not tax_withholding_details:
+ return
+
+ accounts = []
+ for d in self.taxes:
+ if d.account_head == tax_withholding_details.get("account_head"):
+ d.update(tax_withholding_details)
+ accounts.append(d.account_head)
+
+ if not accounts or tax_withholding_details.get("account_head") not in accounts:
+ self.append("taxes", tax_withholding_details)
+
+ to_remove = [d for d in self.taxes
+ if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
+
+ for d in to_remove:
+ self.remove(d)
+
+ # calculate totals again after applying TDS
+ self.calculate_taxes_and_totals()
+
def validate_supplier(self):
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
if prevent_po:
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 565fc920b8..3b9f8e9775 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -1111,7 +1111,7 @@ def create_purchase_order(**args):
po.schedule_date = add_days(nowdate(), 1)
po.company = args.company or "_Test Company"
- po.supplier = args.customer or "_Test Supplier"
+ po.supplier = args.supplier or "_Test Supplier"
po.is_subcontracted = args.is_subcontracted or "No"
po.currency = args.currency or frappe.get_cached_value('Company', po.company, "default_currency")
po.conversion_factor = args.conversion_factor or 1
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 40fbe2c26e..0a51a8e9a1 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -576,6 +576,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment (Company Currency",
@@ -620,6 +621,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment",
@@ -802,7 +804,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-12-03 15:18:29.073368",
+ "modified": "2021-04-19 00:58:20.995491",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
index de2ae8fc73..68426abbb0 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
@@ -9,10 +9,10 @@ def execute(filters=None):
if filters.from_date >= filters.to_date:
frappe.msgprint(_("To Date must be greater than From Date"))
- data = []
columns = get_columns()
- get_data(data , filters)
- return columns, data
+ data = get_data(filters)
+
+ return columns, data or []
def get_columns():
return [
@@ -21,13 +21,12 @@ def get_columns():
"fieldtype": "Link",
"fieldname": "purchase_order",
"options": "Purchase Order",
- "width": 150
+ "width": 200
},
{
"label": _("Date"),
"fieldtype": "Date",
"fieldname": "date",
- "hidden": 1,
"width": 150
},
{
@@ -41,97 +40,58 @@ def get_columns():
"label": _("Item Code"),
"fieldtype": "Data",
"fieldname": "rm_item_code",
- "width": 100
+ "width": 150
},
{
"label": _("Required Quantity"),
"fieldtype": "Float",
- "fieldname": "r_qty",
- "width": 100
+ "fieldname": "reqd_qty",
+ "width": 150
},
{
"label": _("Transferred Quantity"),
"fieldtype": "Float",
- "fieldname": "t_qty",
- "width": 100
+ "fieldname": "transferred_qty",
+ "width": 200
},
{
"label": _("Pending Quantity"),
"fieldtype": "Float",
"fieldname": "p_qty",
- "width": 100
+ "width": 150
}
]
-def get_data(data, filters):
- po = get_po(filters)
- po_transferred_qty_map = frappe._dict(get_transferred_quantity([v.name for v in po]))
+def get_data(filters):
+ po_rm_item_details = get_po_items_to_supply(filters)
- sub_items = get_purchase_order_item_supplied([v.name for v in po])
+ data = []
+ for row in po_rm_item_details:
+ transferred_qty = row.get("transferred_qty") or 0
+ if transferred_qty < row.get("reqd_qty", 0):
+ pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty)
+ row.p_qty = pending_qty if pending_qty > 0 else 0
+ data.append(row)
- for order in po:
- for item in sub_items:
- if order.name == item.parent and order.name in po_transferred_qty_map and \
- item.required_qty != po_transferred_qty_map.get(order.name).get(item.rm_item_code):
- transferred_qty = po_transferred_qty_map.get(order.name).get(item.rm_item_code) \
- if po_transferred_qty_map.get(order.name).get(item.rm_item_code) else 0
- row ={
- 'purchase_order': item.parent,
- 'date': order.transaction_date,
- 'supplier': order.supplier,
- 'rm_item_code': item.rm_item_code,
- 'r_qty': item.required_qty,
- 't_qty':transferred_qty,
- 'p_qty':item.required_qty - transferred_qty
- }
+ return data
- data.append(row)
-
- return(data)
-
-def get_po(filters):
- record_filters = [
- ["is_subcontracted", "=", "Yes"],
- ["supplier", "=", filters.supplier],
- ["transaction_date", "<=", filters.to_date],
- ["transaction_date", ">=", filters.from_date],
- ["docstatus", "=", 1]
- ]
- return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"])
-
-def get_transferred_quantity(po_name):
- stock_entries = get_stock_entry(po_name)
- stock_entries_detail = get_stock_entry_detail([v.name for v in stock_entries])
- po_transferred_qty_map = {}
-
-
- for entry in stock_entries:
- for details in stock_entries_detail:
- if details.parent == entry.name:
- details["Purchase_order"] = entry.purchase_order
- if entry.purchase_order not in po_transferred_qty_map:
- po_transferred_qty_map[entry.purchase_order] = {}
- po_transferred_qty_map[entry.purchase_order][details.item_code] = details.qty
- else:
- po_transferred_qty_map[entry.purchase_order][details.item_code] = po_transferred_qty_map[entry.purchase_order].get(details.item_code, 0) + details.qty
-
- return po_transferred_qty_map
-
-
-def get_stock_entry(po):
- return frappe.get_all("Stock Entry", filters=[
- ('purchase_order', 'IN', po),
- ('stock_entry_type', '=', 'Send to Subcontractor'),
- ('docstatus', '=', 1)
- ], fields=["name", "purchase_order"])
-
-def get_stock_entry_detail(se):
- return frappe.get_all("Stock Entry Detail", filters=[
- ["parent", "in", se]
+def get_po_items_to_supply(filters):
+ return frappe.db.get_all(
+ "Purchase Order",
+ fields=[
+ "name as purchase_order",
+ "transaction_date as date",
+ "supplier as supplier",
+ "`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code",
+ "`tabPurchase Order Item Supplied`.required_qty as reqd_qty",
+ "`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty"
],
- fields=["parent", "item_code", "qty"])
-
-def get_purchase_order_item_supplied(po):
- return frappe.get_all("Purchase Order Item Supplied", filters=[
- ('parent', 'IN', po)
- ], fields=['parent', 'rm_item_code', 'required_qty'])
+ filters = [
+ ["Purchase Order", "per_received", "<", "100"],
+ ["Purchase Order", "is_subcontracted", "=", "Yes"],
+ ["Purchase Order", "supplier", "=", filters.supplier],
+ ["Purchase Order", "transaction_date", "<=", filters.to_date],
+ ["Purchase Order", "transaction_date", ">=", filters.from_date],
+ ["Purchase Order", "docstatus", "=", 1]
+ ]
+ )
\ No newline at end of file
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index c1fc6fb82f..2448e17c50 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -12,34 +12,80 @@ import json, frappe, unittest
class TestSubcontractedItemToBeTransferred(unittest.TestCase):
def test_pending_and_transferred_qty(self):
- po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
+ po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")
+
+ # Material Receipt of RMs
make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
- transfer_subcontracted_raw_materials(po.name)
- col, data = execute(filters=frappe._dict({'supplier': po.supplier,
- 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
- 'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))}))
- self.assertEqual(data[0]['purchase_order'], po.name)
- self.assertIn(data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
- self.assertIn(data[0]['p_qty'], [9, 18])
- self.assertIn(data[0]['t_qty'], [1, 2])
- self.assertEqual(data[1]['purchase_order'], po.name)
- self.assertIn(data[1]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
- self.assertIn(data[1]['p_qty'], [9, 18])
- self.assertIn(data[1]['t_qty'], [1, 2])
+ se = transfer_subcontracted_raw_materials(po)
+ col, data = execute(filters=frappe._dict(
+ {
+ 'supplier': po.supplier,
+ 'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
+ 'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))
+ }
+ ))
+ po.reload()
+
+ po_data = [row for row in data if row.get('purchase_order') == po.name]
+ # Alphabetically sort to be certain of order
+ po_data = sorted(po_data, key = lambda i: i['rm_item_code'])
+
+ self.assertEqual(len(po_data), 2)
+ self.assertEqual(po_data[0]['purchase_order'], po.name)
+
+ self.assertEqual(po_data[0]['rm_item_code'], '_Test Item')
+ self.assertEqual(po_data[0]['p_qty'], 8)
+ self.assertEqual(po_data[0]['transferred_qty'], 2)
+
+ self.assertEqual(po_data[1]['rm_item_code'], '_Test Item Home Desktop 100')
+ self.assertEqual(po_data[1]['p_qty'], 19)
+ self.assertEqual(po_data[1]['transferred_qty'], 1)
+
+ se.cancel()
+ po.cancel()
def transfer_subcontracted_raw_materials(po):
+ # Order of supplied items fetched in PO is flaky
+ transfer_qty_map = {
+ '_Test Item': 2,
+ '_Test Item Home Desktop 100': 1
+ }
+
+ item_1 = po.supplied_items[0].rm_item_code
+ item_2 = po.supplied_items[1].rm_item_code
+
rm_item = [
- {'item_code': '_Test Item', 'rm_item_code': '_Test Item', 'item_name': '_Test Item', 'qty': 1,
- 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100, 'stock_uom': 'Nos'},
- {'item_code': '_Test Item Home Desktop 100', 'rm_item_code': '_Test Item Home Desktop 100', 'item_name': '_Test Item Home Desktop 100', 'qty': 2,
- 'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
+ {
+ 'name': po.supplied_items[0].name,
+ 'item_code': item_1,
+ 'rm_item_code': item_1,
+ 'item_name': item_1,
+ 'qty': transfer_qty_map[item_1],
+ 'warehouse': '_Test Warehouse - _TC',
+ 'rate': 100,
+ 'amount': 100 * transfer_qty_map[item_1],
+ 'stock_uom': 'Nos'
+ },
+ {
+ 'name': po.supplied_items[1].name,
+ 'item_code': item_2,
+ 'rm_item_code': item_2,
+ 'item_name': item_2,
+ 'qty': transfer_qty_map[item_2],
+ 'warehouse': '_Test Warehouse - _TC',
+ 'rate': 100,
+ 'amount': 100 * transfer_qty_map[item_2],
+ 'stock_uom': 'Nos'
+ }
+ ]
rm_item_string = json.dumps(rm_item)
- se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
- se.from_warehouse = '_Test Warehouse 1 - _TC'
- se.to_warehouse = '_Test Warehouse 1 - _TC'
+ se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
+ se.from_warehouse = '_Test Warehouse - _TC'
+ se.to_warehouse = '_Test Warehouse - _TC'
se.stock_entry_type = 'Send to Subcontractor'
se.save()
se.submit()
+ return se
diff --git a/erpnext/change_log/v13/v13_5_0.md b/erpnext/change_log/v13/v13_5_0.md
new file mode 100644
index 0000000000..64c323a23e
--- /dev/null
+++ b/erpnext/change_log/v13/v13_5_0.md
@@ -0,0 +1,54 @@
+# Version 13.5.0 Release Notes
+
+### Features & Enhancements
+
+- Tax deduction against advance payments ([#25831](https://github.com/frappe/erpnext/pull/25831))
+- Cost-center wise period closing entry ([#25766](https://github.com/frappe/erpnext/pull/25766))
+- Create Quality Inspections from account and stock documents ([#25221](https://github.com/frappe/erpnext/pull/25221))
+- Item Taxes based on net rate ([#25961](https://github.com/frappe/erpnext/pull/25961))
+- Enable/disable gl entry posting for change given in pos ([#25822](https://github.com/frappe/erpnext/pull/25822))
+- Add Inactive status to Employee ([#26029](https://github.com/frappe/erpnext/pull/26029))
+- Added check box to combine items with same BOM ([#25478](https://github.com/frappe/erpnext/pull/25478))
+- Item Tax Templates for Germany ([#25858](https://github.com/frappe/erpnext/pull/25858))
+- Refactored leave balance report ([#25771](https://github.com/frappe/erpnext/pull/25771))
+- Refactored Vehicle Expenses Report ([#25727](https://github.com/frappe/erpnext/pull/25727))
+- Refactored maintenance schedule and visit document ([#25358](https://github.com/frappe/erpnext/pull/25358))
+
+### Fixes
+
+- Cannot add same item with different rates ([#25849](https://github.com/frappe/erpnext/pull/25849))
+- Show only company addresses for ITC reversal entry ([#25866](https://github.com/frappe/erpnext/pull/25866))
+- Hiding Rounding Adjustment field ([#25380](https://github.com/frappe/erpnext/pull/25380))
+- Auto tax calculations in Payment Entry ([#26055](https://github.com/frappe/erpnext/pull/26055))
+- Not able to select the item code in work order ([#25915](https://github.com/frappe/erpnext/pull/25915))
+- Cannot reset plaid link for a bank account ([#25869](https://github.com/frappe/erpnext/pull/25869))
+- Student invalid password reset link ([#25826](https://github.com/frappe/erpnext/pull/25826))
+- Multiple pos issues ([#25928](https://github.com/frappe/erpnext/pull/25928))
+- Add Product Bundles to POS ([#25860](https://github.com/frappe/erpnext/pull/25860))
+- Enable Parallel tests ([#25862](https://github.com/frappe/erpnext/pull/25862))
+- Service item check on e-Invoicing ([#25986](https://github.com/frappe/erpnext/pull/25986))
+- Choose correct Salary Structure Assignment when getting data for formula eval ([#25981](https://github.com/frappe/erpnext/pull/25981))
+- Ignore internal transfer invoices from GST Reports ([#25969](https://github.com/frappe/erpnext/pull/25969))
+- Taxable value for invoices with additional discount ([#26056](https://github.com/frappe/erpnext/pull/26056))
+- Validate negative allocated amount in Payment Entry ([#25799](https://github.com/frappe/erpnext/pull/25799))
+- Allow all System Managers to delete company transactions ([#25834](https://github.com/frappe/erpnext/pull/25834))
+- Wrong round off gl entry posted in case of purchase invoice ([#25775](https://github.com/frappe/erpnext/pull/25775))
+- Use dictionary filter instead of list ([#25874](https://github.com/frappe/erpnext/pull/25874))
+- Ageing error in PSOA ([#25855](https://github.com/frappe/erpnext/pull/25855))
+- On click of duplicate button system has not copied the difference account ([#25988](https://github.com/frappe/erpnext/pull/25988))
+- Assign Product Bundle's conversion_factor to Pack… ([#25840](https://github.com/frappe/erpnext/pull/25840))
+- Rename Loan Management workspace to Loans ([#25856](https://github.com/frappe/erpnext/pull/25856))
+- Fix stock quantity calculation when negative_stock_allowe… ([#25859](https://github.com/frappe/erpnext/pull/25859))
+- Update cost center from pos profile ([#25971](https://github.com/frappe/erpnext/pull/25971))
+- Ensure website theme is applied correctly ([#25863](https://github.com/frappe/erpnext/pull/25863))
+- Only display GST card in Accounting Workspace if it's in India ([#26000](https://github.com/frappe/erpnext/pull/26000))
+- Incorrect gstin fetched incase of branch company address ([#25841](https://github.com/frappe/erpnext/pull/25841))
+- Sort account balances by account name ([#26009](https://github.com/frappe/erpnext/pull/26009))
+- Custom conversion factor field not mapped from job card to stock entry ([#25956](https://github.com/frappe/erpnext/pull/25956))
+- Chart of accounts importer always error ([#25882](https://github.com/frappe/erpnext/pull/25882))
+- Create POS Invoice for Product Bundles ([#25847](https://github.com/frappe/erpnext/pull/25847))
+- Wrap dates in getdate for leave application ([#25899](https://github.com/frappe/erpnext/pull/25899))
+- Closing entry shows incorrect expected amount ([#25868](https://github.com/frappe/erpnext/pull/25868))
+- Add Hold status column in the Issue Summary Report ([#25828](https://github.com/frappe/erpnext/pull/25828))
+- Rendering of broken image on pos ([#25872](https://github.com/frappe/erpnext/pull/25872))
+- Timeout error in the repost item valuation ([#25854](https://github.com/frappe/erpnext/pull/25854))
\ No newline at end of file
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 2fdc19e315..243939b275 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -116,6 +116,8 @@ class AccountsController(TransactionBase):
if self.doctype == 'Purchase Invoice':
self.calculate_paid_amount()
+ # apply tax withholding only if checked and applicable
+ self.set_tax_withholding()
if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
@@ -225,7 +227,7 @@ class AccountsController(TransactionBase):
def validate_date_with_fiscal_year(self):
if self.meta.get_field("fiscal_year"):
- date_field = ""
+ date_field = None
if self.meta.get_field("posting_date"):
date_field = "posting_date"
elif self.meta.get_field("transaction_date"):
@@ -700,6 +702,7 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
+ self.update_allocated_advance_taxes_on_cancel()
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self)
@@ -707,6 +710,87 @@ class AccountsController(TransactionBase):
if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
unlink_ref_doc_from_payment_entries(self)
+ def get_tax_map(self):
+ tax_map = {}
+ for tax in self.get('taxes'):
+ tax_map.setdefault(tax.account_head, 0.0)
+ tax_map[tax.account_head] += tax.tax_amount
+
+ return tax_map
+
+ def update_allocated_advance_taxes_on_cancel(self):
+ if self.get('advances'):
+ tax_accounts = [d.account_head for d in self.get('taxes')]
+ allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'],
+ filters={'voucher_no': self.name, 'account': ('in', tax_accounts)},
+ group_by='account', as_list=1))
+
+ tax_map = self.get_tax_map()
+
+ for pe in self.get('advances'):
+ if pe.reference_type == 'Payment Entry':
+ pe = frappe.get_doc('Payment Entry', pe.reference_name)
+ for tax in pe.get('taxes'):
+ allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head)
+ if allocated_amount > tax.tax_amount:
+ allocated_amount = tax.tax_amount
+
+ if allocated_amount:
+ frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount',
+ tax.allocated_amount - allocated_amount)
+ tax_map[tax.account_head] -= allocated_amount
+ allocated_tax_map[tax.account_head] -= allocated_amount
+
+ def allocate_advance_taxes(self, gl_entries):
+ tax_map = self.get_tax_map()
+ for pe in self.get("advances"):
+ if pe.reference_type == "Payment Entry" and \
+ frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'):
+ pe = frappe.get_doc("Payment Entry", pe.reference_name)
+ for tax in pe.get("taxes"):
+ account_currency = get_account_currency(tax.account_head)
+
+ if self.doctype == "Purchase Invoice":
+ dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+ rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
+ else:
+ dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
+ rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+
+ party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
+ unallocated_amount = tax.tax_amount - tax.allocated_amount
+ if tax_map.get(tax.account_head):
+ amount = tax_map.get(tax.account_head)
+ if amount < unallocated_amount:
+ unallocated_amount = amount
+
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": tax.account_head,
+ "against": party,
+ dr_or_cr: unallocated_amount,
+ dr_or_cr + "_in_account_currency": unallocated_amount
+ if account_currency==self.company_currency
+ else unallocated_amount,
+ "cost_center": tax.cost_center
+ }, account_currency, item=tax))
+
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": pe.advance_tax_account,
+ "against": party,
+ rev_dr_cr: unallocated_amount,
+ rev_dr_cr + "_in_account_currency": unallocated_amount
+ if account_currency==self.company_currency
+ else unallocated_amount,
+ "cost_center": tax.cost_center
+ }, account_currency, item=tax))
+
+ frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount",
+ tax.allocated_amount + unallocated_amount)
+
+ tax_map[tax.account_head] -= unallocated_amount
+
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {}
@@ -1108,7 +1192,7 @@ def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, c
def validate_taxes_and_charges(tax):
- if tax.charge_type in ['Actual', 'On Net Total'] and tax.row_id:
+ if tax.charge_type in ['Actual', 'On Net Total', 'On Paid Amount'] and tax.row_id:
frappe.throw(_("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'"))
elif tax.charge_type in ['On Previous Row Amount', 'On Previous Row Total']:
if cint(tax.idx) == 1:
@@ -1125,20 +1209,19 @@ def validate_taxes_and_charges(tax):
def validate_inclusive_tax(tax, doc):
def _on_previous_row_error(row_range):
- throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx,
- row_range))
+ throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
if cint(getattr(tax, "included_in_print_rate", None)):
if tax.charge_type == "Actual":
# inclusive tax cannot be of type Actual
- throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate").format(tax.idx))
+ throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
elif tax.charge_type == "On Previous Row Amount" and \
not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_print_rate):
# referred row should also be inclusive
_on_previous_row_error(tax.row_id)
elif tax.charge_type == "On Previous Row Total" and \
not all([cint(t.included_in_print_rate) for t in doc.get("taxes")[:cint(tax.row_id) - 1]]):
- # all rows about the reffered tax should be inclusive
+ # all rows about the referred tax should be inclusive
_on_previous_row_error("1 - %d" % (tax.row_id,))
elif tax.get("category") == "Valuation":
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
@@ -1240,7 +1323,6 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype,
return list(payment_entries_against_order) + list(unallocated_payment_entries)
-
def update_invoice_status():
# Daily update the status of the invoices
@@ -1449,6 +1531,7 @@ def validate_and_delete_children(parent, data):
for d in deleted_children:
update_bin_on_delete(d, parent.doctype)
+
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
def check_doc_permissions(doc, perm_type='create'):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 71bde7fad7..9c29b0076b 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -1,17 +1,21 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-import frappe, erpnext
-from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
-from frappe import _
-import frappe.defaults
+import json
from collections import defaultdict
-from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
+
+import frappe
+import frappe.defaults
+from frappe import _
+from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
+
+import erpnext
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
+from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year
from erpnext.controllers.accounts_controller import AccountsController
-from erpnext.stock.stock_ledger import get_valuation_rate
from erpnext.stock import get_warehouse_account_map
+from erpnext.stock.stock_ledger import get_valuation_rate
+
class QualityInspectionRequiredError(frappe.ValidationError): pass
class QualityInspectionRejectedError(frappe.ValidationError): pass
@@ -189,7 +193,6 @@ class StockController(AccountsController):
if hasattr(self, "items"):
item_doclist = self.get("items")
elif self.doctype == "Stock Reconciliation":
- import json
item_doclist = []
data = json.loads(self.reconciliation_json)
for row in data[data.index(self.head_row)+1:]:
@@ -319,7 +322,7 @@ class StockController(AccountsController):
return serialized_items
def validate_warehouse(self):
- from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse
+ from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
warehouses = list(set(d.warehouse for d in
self.get("items") if getattr(d, "warehouse", None)))
@@ -498,6 +501,39 @@ class StockController(AccountsController):
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
+
+@frappe.whitelist()
+def make_quality_inspections(doctype, docname, items):
+ if isinstance(items, str):
+ items = json.loads(items)
+
+ inspections = []
+ for item in items:
+ if flt(item.get("sample_size")) > flt(item.get("qty")):
+ frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format(
+ item_name=item.get("item_name"),
+ sample_size=item.get("sample_size"),
+ accepted_quantity=item.get("qty")
+ ))
+
+ quality_inspection = frappe.get_doc({
+ "doctype": "Quality Inspection",
+ "inspection_type": "Incoming",
+ "inspected_by": frappe.session.user,
+ "reference_type": doctype,
+ "reference_name": docname,
+ "item_code": item.get("item_code"),
+ "description": item.get("description"),
+ "sample_size": flt(item.get("sample_size")),
+ "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
+ "batch_no": item.get("batch_no")
+ }).insert()
+ quality_inspection.save()
+ inspections.append(quality_inspection.name)
+
+ return inspections
+
+
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index dda642a532..2bb83ea7f0 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -54,6 +54,7 @@ class calculate_taxes_and_totals(object):
if item.item_code and item.get('item_tax_template'):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
+ 'net_rate': item.net_rate or item.rate,
'tax_category': self.doc.get('tax_category'),
'posting_date': self.doc.get('posting_date'),
'bill_date': self.doc.get('bill_date'),
@@ -77,10 +78,12 @@ class calculate_taxes_and_totals(object):
taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
- if item.item_tax_template not in taxes:
- frappe.throw(_("Row {0}: Invalid Item Tax Template for item {1}").format(
- item.idx, frappe.bold(item.item_code)
- ))
+ if taxes:
+ if item.item_tax_template not in taxes:
+ item.item_tax_template = taxes[0]
+ frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
+ item.idx, frappe.bold(item.item_code)
+ ))
def validate_conversion_rate(self):
# validate conversion rate
@@ -683,7 +686,6 @@ class calculate_taxes_and_totals(object):
self.calculate_paid_amount()
-
def get_itemised_tax_breakup_html(doc):
if not doc.taxes:
return
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 2009ebf7cb..df73f09c49 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -38,7 +38,7 @@ class Appointment(Document):
number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents')
if not number_of_agents == 0:
if (number_of_appointments_in_same_slot >= number_of_agents):
- frappe.throw('Time slot is not available')
+ frappe.throw(_('Time slot is not available'))
# Link lead
if not self.party:
lead = self.find_lead_by_email()
@@ -75,10 +75,10 @@ class Appointment(Document):
subject=_('Appointment Confirmation'))
if frappe.session.user == "Guest":
frappe.msgprint(
- 'Please check your email to confirm the appointment')
+ _('Please check your email to confirm the appointment'))
else :
frappe.msgprint(
- 'Appointment was created. But no lead was found. Please check the email to confirm')
+ _('Appointment was created. But no lead was found. Please check the email to confirm'))
def on_change(self):
# Sync Calendar
@@ -91,7 +91,7 @@ class Appointment(Document):
def set_verified(self, email):
if not email == self.customer_email:
- frappe.throw('Email verification failed.')
+ frappe.throw(_('Email verification failed.'))
# Create new lead
self.create_lead_and_link()
# Remove unverified status
@@ -184,7 +184,7 @@ class Appointment(Document):
appointment_event.insert(ignore_permissions=True)
self.calendar_event = appointment_event.name
self.save(ignore_permissions=True)
-
+
def _get_verify_url(self):
verify_route = '/book_appointment/verify'
params = {
diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
index 6a0dcf460a..0f2ea96a58 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
@@ -75,7 +75,7 @@ class CourseSchedulingTool(Document):
"""Validates if Course Start Date is greater than Course End Date"""
if self.course_start_date > self.course_end_date:
frappe.throw(
- "Course Start Date cannot be greater than Course End Date.")
+ _("Course Start Date cannot be greater than Course End Date."))
def delete_course_schedule(self, rescheduled, reschedule_errors):
"""Delete all course schedule within the Date range and specified filters"""
diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py
index 819106bde7..9db8a4a90d 100644
--- a/erpnext/education/utils.py
+++ b/erpnext/education/utils.py
@@ -236,7 +236,7 @@ def get_quiz(quiz_name, course):
'questions': questions,
'activity': None,
'is_time_bound': quiz.is_time_bound,
- 'duration':quiz.duration
+ 'duration': quiz.duration
}
student = get_current_student()
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
index 5f990cdd03..42d4b9b2b4 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
@@ -99,5 +99,7 @@ class PlaidConnector():
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response["transactions"])
return transactions
+ except ItemError as e:
+ raise e
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index bbc2ca8846..37bf282450 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -16,6 +16,10 @@ frappe.ui.form.on('Plaid Settings', {
new erpnext.integrations.plaidLink(frm);
});
+ frm.add_custom_button(__('Reset Plaid Link'), () => {
+ new erpnext.integrations.plaidLink(frm);
+ });
+
frm.add_custom_button(__("Sync Now"), () => {
frappe.call({
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index ce15e47c5e..3ef069b5e2 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -12,6 +12,7 @@ from frappe.desk.doctype.tag.tag import add_tag
from frappe.model.document import Document
from frappe.utils import add_months, formatdate, getdate, today
+from plaid.errors import ItemError
class PlaidSettings(Document):
@staticmethod
@@ -51,7 +52,7 @@ def add_institution(token, response):
})
bank.insert()
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback(), title=_('Plaid Link Error'))
else:
bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token
@@ -83,7 +84,12 @@ def add_bank_accounts(response, bank, company):
if not acc_subtype:
add_account_subtype(account["subtype"])
- if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])):
+ existing_bank_account = frappe.db.exists("Bank Account", {
+ 'account_name': account["name"],
+ 'bank': bank["bank_name"]
+ })
+
+ if not existing_bank_account:
try:
new_account = frappe.get_doc({
"doctype": "Bank Account",
@@ -103,10 +109,27 @@ def add_bank_accounts(response, bank, company):
except frappe.UniqueValidationError:
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(account["name"]))
except Exception:
- frappe.throw(frappe.get_traceback())
+ frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error"))
+ frappe.throw(_("There was an error creating Bank Account while linking with Plaid."),
+ title=_("Plaid Link Failed"))
else:
- result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name"))
+ try:
+ existing_account = frappe.get_doc('Bank Account', existing_bank_account)
+ existing_account.update({
+ "bank": bank["bank_name"],
+ "account_name": account["name"],
+ "account_type": account.get("type", ""),
+ "account_subtype": account.get("subtype", ""),
+ "mask": account.get("mask", ""),
+ "integration_id": account["id"]
+ })
+ existing_account.save()
+ result.append(existing_bank_account)
+ except Exception:
+ frappe.log_error(frappe.get_traceback(), title=_("Plaid Link Error"))
+ frappe.throw(_("There was an error updating Bank Account {} while linking with Plaid.").format(
+ existing_bank_account), title=_("Plaid Link Failed"))
return result
@@ -172,9 +195,16 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
account_id = None
plaid = PlaidConnector(access_token)
- transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
- return transactions
+ try:
+ transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
+ except ItemError as e:
+ if e.code == "ITEM_LOGIN_REQUIRED":
+ msg = _("There was an error syncing transactions.") + " "
+ msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
+ frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
+
+ return transactions or []
def new_bank_transaction(transaction):
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 22ce4df97c..3da606b68b 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -228,6 +228,7 @@ standard_queries = {
doc_events = {
"*": {
+ "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
"on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record",
"on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record",
"on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record"
@@ -242,6 +243,9 @@ doc_events = {
"on_update": ["erpnext.hr.doctype.employee.employee.update_user_permissions",
"erpnext.portal.utils.set_default_role"]
},
+ "Communication": {
+ "on_update": "erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time"
+ },
("Sales Taxes and Charges Template", 'Price List'): {
"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
},
@@ -332,8 +336,8 @@ scheduler_events = {
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status",
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
- "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
- "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders"
+ "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
+ "erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
],
"hourly_long": [
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 5123d6a5a7..5442ed56c3 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -207,7 +207,7 @@
"label": "Status",
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Active\nLeft",
+ "options": "Active\nInactive\nLeft",
"reqd": 1,
"search_index": 1
},
@@ -813,7 +813,7 @@
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2021-01-02 16:54:33.477439",
+ "modified": "2021-06-12 11:31:37.730760",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index ed7d588434..bc5694226a 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -37,7 +37,7 @@ class Employee(NestedSet):
def validate(self):
from erpnext.controllers.status_updater import validate_status
- validate_status(self.status, ["Active", "Temporary Leave", "Left"])
+ validate_status(self.status, ["Active", "Inactive", "Left"])
self.employee = self.name
self.set_employee_name()
@@ -478,7 +478,7 @@ def get_employee_emails(employee_list):
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
- filters = [['status', '!=', 'Left']]
+ filters = [['status', '=', 'Active']]
if company and company != 'All Companies':
filters.append(['company', '=', company])
diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js
index 44837030be..6679e318c2 100644
--- a/erpnext/hr/doctype/employee/employee_list.js
+++ b/erpnext/hr/doctype/employee/employee_list.js
@@ -3,7 +3,7 @@ frappe.listview_settings['Employee'] = {
filters: [["status","=", "Active"]],
get_indicator: function(doc) {
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
- indicator[1] = {"Active": "green", "Temporary Leave": "red", "Left": "gray"}[doc.status];
+ indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray"}[doc.status];
return indicator;
}
};
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
index 4994921268..83fb235f92 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
@@ -11,12 +11,12 @@ from erpnext.hr.utils import update_employee
class EmployeePromotion(Document):
def validate(self):
- if frappe.get_value("Employee", self.employee, "status") == "Left":
- frappe.throw(_("Cannot promote Employee with status Left"))
+ if frappe.get_value("Employee", self.employee, "status") != "Active":
+ frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
def before_submit(self):
if getdate(self.promotion_date) > getdate():
- frappe.throw(_("Employee Promotion cannot be submitted before Promotion Date "),
+ frappe.throw(_("Employee Promotion cannot be submitted before Promotion Date"),
frappe.DocstatusTransitionError)
def on_submit(self):
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index 3539970a32..6eec9fa12a 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -11,12 +11,12 @@ from erpnext.hr.utils import update_employee
class EmployeeTransfer(Document):
def validate(self):
- if frappe.get_value("Employee", self.employee, "status") == "Left":
- frappe.throw(_("Cannot transfer Employee with status Left"))
+ if frappe.get_value("Employee", self.employee, "status") != "Active":
+ frappe.throw(_("Cannot transfer Employee with status Left or Inactive"))
def before_submit(self):
if getdate(self.transfer_date) > getdate():
- frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date "),
+ frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
frappe.DocstatusTransitionError)
def on_submit(self):
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 0bf551e178..cee6f374fd 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -4,8 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
- comma_or, get_fullname, add_days, nowdate, get_datetime_str
+from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
@@ -85,7 +84,7 @@ class LeaveApplication(Document):
def validate_dates(self):
if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"):
- if self.from_date and self.from_date < frappe.utils.today():
+ if self.from_date and getdate(self.from_date) < getdate():
allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application")
if allowed_role not in frappe.get_roles():
frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role))
@@ -248,9 +247,9 @@ class LeaveApplication(Document):
self.throw_overlap_error(d)
def throw_overlap_error(self, d):
- msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
- d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
- + """ {0}""".format(d["name"])
+ form_link = get_link_to_form("Leave Application", d.name)
+ msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee,
+ d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link)
frappe.throw(msg, OverlapError)
def get_total_leaves_on_half_day(self):
@@ -356,7 +355,7 @@ class LeaveApplication(Document):
sender = dict()
sender['email'] = frappe.get_doc('User', frappe.session.user).email
- sender['full_name'] = frappe.utils.get_fullname(sender['email'])
+ sender['full_name'] = get_fullname(sender['email'])
try:
frappe.sendmail(
@@ -823,4 +822,4 @@ def get_leave_approver(employee):
leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
- return leave_approver
\ No newline at end of file
+ return leave_approver
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index cf0048c1a7..ed52c4e122 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe
import unittest
-from frappe.utils import nowdate,flt, cstr,random_string
+from frappe.utils import nowdate, flt, cstr, random_string
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
@@ -18,23 +18,13 @@ class TestVehicleLog(unittest.TestCase):
self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
self.license_plate = get_vehicle(self.employee_id)
-
+
def tearDown(self):
frappe.delete_doc("Vehicle", self.license_plate, force=1)
frappe.delete_doc("Employee", self.employee_id, force=1)
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
- vehicle_log = frappe.get_doc({
- "doctype": "Vehicle Log",
- "license_plate": cstr(self.license_plate),
- "employee": self.employee_id,
- "date":frappe.utils.nowdate(),
- "odometer":5010,
- "fuel_qty":frappe.utils.flt(50),
- "price": frappe.utils.flt(500)
- })
- vehicle_log.save()
- vehicle_log.submit()
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
#checking value of vehicle odometer value on submit.
vehicle = frappe.get_doc("Vehicle", self.license_plate)
@@ -51,19 +41,9 @@ class TestVehicleLog(unittest.TestCase):
self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
vehicle_log.delete()
-
+
def test_vehicle_log_fuel_expense(self):
- vehicle_log = frappe.get_doc({
- "doctype": "Vehicle Log",
- "license_plate": cstr(self.license_plate),
- "employee": self.employee_id,
- "date": frappe.utils.nowdate(),
- "odometer":5010,
- "fuel_qty":frappe.utils.flt(50),
- "price": frappe.utils.flt(500)
- })
- vehicle_log.save()
- vehicle_log.submit()
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
expense_claim = make_expense_claim(vehicle_log.name)
fuel_expense = expense_claim.expenses[0].amount
@@ -73,6 +53,18 @@ class TestVehicleLog(unittest.TestCase):
frappe.delete_doc("Expense Claim", expense_claim.name)
frappe.delete_doc("Vehicle Log", vehicle_log.name)
+ def test_vehicle_log_with_service_expenses(self):
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True)
+
+ expense_claim = make_expense_claim(vehicle_log.name)
+ expenses = expense_claim.expenses[0].amount
+ self.assertEqual(expenses, 27000)
+
+ vehicle_log.cancel()
+ frappe.delete_doc("Expense Claim", expense_claim.name)
+ frappe.delete_doc("Vehicle Log", vehicle_log.name)
+
+
def get_vehicle(employee_id):
license_plate=random_string(10).upper()
vehicle = frappe.get_doc({
@@ -81,15 +73,46 @@ def get_vehicle(employee_id):
"make": "Maruti",
"model": "PCM",
"employee": employee_id,
- "last_odometer":5000,
- "acquisition_date":frappe.utils.nowdate(),
+ "last_odometer": 5000,
+ "acquisition_date": nowdate(),
"location": "Mumbai",
"chassis_no": "1234ABCD",
"uom": "Litre",
- "vehicle_value":frappe.utils.flt(500000)
+ "vehicle_value": flt(500000)
})
try:
vehicle.insert()
except frappe.DuplicateEntryError:
pass
- return license_plate
\ No newline at end of file
+ return license_plate
+
+
+def make_vehicle_log(license_plate, employee_id, with_services=False):
+ vehicle_log = frappe.get_doc({
+ "doctype": "Vehicle Log",
+ "license_plate": cstr(license_plate),
+ "employee": employee_id,
+ "date": nowdate(),
+ "odometer": 5010,
+ "fuel_qty": flt(50),
+ "price": flt(500)
+ })
+
+ if with_services:
+ vehicle_log.append("service_detail", {
+ "service_item": "Oil Change",
+ "type": "Inspection",
+ "frequency": "Mileage",
+ "expense_amount": flt(500)
+ })
+ vehicle_log.append("service_detail", {
+ "service_item": "Wheels",
+ "type": "Change",
+ "frequency": "Half Yearly",
+ "expense_amount": flt(1500)
+ })
+
+ vehicle_log.save()
+ vehicle_log.submit()
+
+ return vehicle_log
\ No newline at end of file
diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.json b/erpnext/hr/doctype/vehicle_log/vehicle_log.json
index 619e295ebe..4ea904542d 100644
--- a/erpnext/hr/doctype/vehicle_log/vehicle_log.json
+++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2016-09-03 14:14:51.788550",
"doctype": "DocType",
@@ -10,7 +11,6 @@
"naming_series",
"license_plate",
"employee",
- "column_break_4",
"column_break_7",
"model",
"make",
@@ -65,10 +65,6 @@
"options": "Employee",
"reqd": 1
},
- {
- "fieldname": "column_break_4",
- "fieldtype": "Column Break"
- },
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
@@ -142,7 +138,6 @@
{
"fieldname": "service_detail",
"fieldtype": "Table",
- "label": "Service Detail",
"options": "Vehicle Service"
},
{
@@ -158,7 +153,7 @@
"fetch_from": "license_plate.last_odometer",
"fieldname": "last_odometer",
"fieldtype": "Int",
- "label": "last Odometer Value ",
+ "label": "Last Odometer Value ",
"read_only": 1,
"reqd": 1
},
@@ -168,7 +163,8 @@
}
],
"is_submittable": 1,
- "modified": "2020-03-18 16:45:45.060761",
+ "links": [],
+ "modified": "2021-05-17 00:10:21.188352",
"modified_by": "Administrator",
"module": "HR",
"name": "Vehicle Log",
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
index 05728a297b..8bb3457190 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
@@ -37,5 +37,22 @@ frappe.query_reports["Employee Leave Balance"] = {
"fieldtype": "Link",
"options": "Employee",
}
- ]
+ ],
+
+ onload: () => {
+ frappe.call({
+ type: "GET",
+ method: "erpnext.hr.utils.get_leave_period",
+ args: {
+ "from_date": frappe.defaults.get_default("year_start_date"),
+ "to_date": frappe.defaults.get_default("year_end_date"),
+ "company": frappe.defaults.get_user_default("Company")
+ },
+ freeze: true,
+ callback: (data) => {
+ frappe.query_report.set_filter_value("from_date", data.message[0].from_date);
+ frappe.query_report.set_filter_value("to_date", data.message[0].to_date);
+ }
+ });
+ }
}
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 06f9160363..4dd4570e8c 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -6,15 +6,16 @@ import frappe
from frappe.utils import flt, add_days
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
+from itertools import groupby
def execute(filters=None):
if filters.to_date <= filters.from_date:
- frappe.throw(_('"From date" can not be greater than or equal to "To date"'))
+ frappe.throw(_('"From Date" can not be greater than or equal to "To Date"'))
columns = get_columns()
data = get_data(filters)
-
- return columns, data
+ charts = get_chart_data(data)
+ return columns, data, None, charts
def get_columns():
columns = [{
@@ -31,9 +32,10 @@ def get_columns():
'options': 'Employee'
}, {
'label': _('Employee Name'),
- 'fieldtype': 'Data',
+ 'fieldtype': 'Dynamic Link',
'fieldname': 'employee_name',
'width': 100,
+ 'options': 'employee'
}, {
'label': _('Opening Balance'),
'fieldtype': 'float',
@@ -64,8 +66,7 @@ def get_columns():
return columns
def get_data(filters):
- leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
-
+ leave_types = frappe.db.get_list('Leave Type', pluck='name', order_by='name')
conditions = get_conditions(filters)
user = frappe.session.user
@@ -113,12 +114,8 @@ def get_data(filters):
# not be shown on the basis of days left it create in user mind for carry_forward leave
row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken))
-
-
row.indent = 1
data.append(row)
- new_leaves_allocated = 0
-
return data
@@ -129,27 +126,37 @@ def get_conditions(filters):
if filters.get('employee'):
conditions['name'] = filters.get('employee')
- if filters.get('employee'):
- conditions['name'] = filters.get('employee')
-
if filters.get('company'):
conditions['company'] = filters.get('company')
+ if filters.get('department'):
+ conditions['department'] = filters.get('department')
+
return conditions
def get_department_leave_approver_map(department=None):
- conditions=''
- if department:
- conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child
- department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
-
+ department_list = frappe.get_list('Department',
+ filters={
+ 'disabled': 0
+ },
+ or_filters={
+ 'name': department,
+ 'parent_department': department
+ },
+ fields=['name'],
+ pluck='name'
+ )
# retrieve approvers list from current department and from its subsequent child departments
- approver_list = frappe.get_all('Department Approver', filters={
- 'parentfield': 'leave_approvers',
- 'parent': ('in', department_list)
- }, fields=['parent', 'approver'], as_list=1)
+ approver_list = frappe.get_all('Department Approver',
+ filters={
+ 'parentfield': 'leave_approvers',
+ 'parent': ('in', department_list)
+ },
+ fields=['parent', 'approver'],
+ as_list=1
+ )
approvers = {}
@@ -190,3 +197,40 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
new_allocation += record.leaves
return new_allocation, expired_leaves
+
+def get_chart_data(data):
+ labels = []
+ datasets = []
+ employee_data = data
+
+ if data and data[0].get('employee_name'):
+ get_dataset_for_chart(employee_data, datasets, labels)
+
+ chart = {
+ 'data': {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ 'type': 'bar',
+ 'colors': ['#456789', '#EE8888', '#7E77BF']
+ }
+
+ return chart
+
+def get_dataset_for_chart(employee_data, datasets, labels):
+ leaves = []
+ employee_data = sorted(employee_data, key=lambda k: k['employee_name'])
+
+ for key, group in groupby(employee_data, lambda x: x['employee_name']):
+ for grp in group:
+ if grp.closing_balance:
+ leaves.append(frappe._dict({
+ 'leave_type': grp.leave_type,
+ 'closing_balance': grp.closing_balance
+ }))
+
+ if leaves:
+ labels.append(key)
+
+ for leave in leaves:
+ datasets.append({'name': leave.leave_type, 'values': [leave.closing_balance]})
diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
new file mode 100644
index 0000000000..26e0f26392
--- /dev/null
+++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import getdate
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
+from erpnext.hr.doctype.vehicle_log.test_vehicle_log import get_vehicle, make_vehicle_log
+from erpnext.hr.report.vehicle_expenses.vehicle_expenses import execute
+from erpnext.accounts.utils import get_fiscal_year
+
+class TestVehicleExpenses(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ frappe.db.sql('delete from `tabVehicle Log`')
+
+ employee_id = frappe.db.sql('''select name from `tabEmployee` where name="testdriver@example.com"''')
+ self.employee_id = employee_id[0][0] if employee_id else None
+ if not self.employee_id:
+ self.employee_id = make_employee('testdriver@example.com', company='_Test Company')
+
+ self.license_plate = get_vehicle(self.employee_id)
+
+ def test_vehicle_expenses_based_on_fiscal_year(self):
+ vehicle_log = make_vehicle_log(self.license_plate, self.employee_id, with_services=True)
+ expense_claim = make_expense_claim(vehicle_log.name)
+
+ # Based on Fiscal Year
+ filters = {
+ 'filter_based_on': 'Fiscal Year',
+ 'fiscal_year': get_fiscal_year(getdate())[0]
+ }
+
+ report = execute(filters)
+
+ expected_data = [{
+ 'vehicle': self.license_plate,
+ 'make': 'Maruti',
+ 'model': 'PCM',
+ 'location': 'Mumbai',
+ 'log_name': vehicle_log.name,
+ 'odometer': 5010,
+ 'date': getdate(),
+ 'fuel_qty': 50.0,
+ 'fuel_price': 500.0,
+ 'fuel_expense': 25000.0,
+ 'service_expense': 2000.0,
+ 'employee': self.employee_id
+ }]
+
+ self.assertEqual(report[1], expected_data)
+
+ # Based on Date Range
+ fiscal_year = get_fiscal_year(getdate(), as_dict=True)
+ filters = {
+ 'filter_based_on': 'Date Range',
+ 'from_date': fiscal_year.year_start_date,
+ 'to_date': fiscal_year.year_end_date
+ }
+
+ report = execute(filters)
+ self.assertEqual(report[1], expected_data)
+
+ # clean up
+ vehicle_log.cancel()
+ frappe.delete_doc('Expense Claim', expense_claim.name)
+ frappe.delete_doc('Vehicle Log', vehicle_log.name)
+
+ def tearDown(self):
+ frappe.delete_doc('Vehicle', self.license_plate, force=1)
+ frappe.delete_doc('Employee', self.employee_id, force=1)
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js
index b66bebbec1..879acd18ef 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.js
@@ -1,31 +1,52 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.require("assets/erpnext/js/financial_statements.js", function() {
- frappe.query_reports["Vehicle Expenses"] = {
- "filters": [
- {
- "fieldname": "fiscal_year",
- "label": __("Fiscal Year"),
- "fieldtype": "Link",
- "options": "Fiscal Year",
- "default": frappe.defaults.get_user_default("fiscal_year"),
- "reqd": 1,
- "on_change": function(query_report) {
- var fiscal_year = query_report.get_values().fiscal_year;
- if (!fiscal_year) {
- return;
- }
- frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
- var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
-
- frappe.query_report.set_filter({
- from_date: fy.year_start_date,
- to_date: fy.year_end_date
- });
- });
- }
- }
- ]
- }
-});
+frappe.query_reports["Vehicle Expenses"] = {
+ "filters": [
+ {
+ "fieldname": "filter_based_on",
+ "label": __("Filter Based On"),
+ "fieldtype": "Select",
+ "options": ["Fiscal Year", "Date Range"],
+ "default": ["Fiscal Year"],
+ "reqd": 1
+ },
+ {
+ "fieldname": "fiscal_year",
+ "label": __("Fiscal Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
+ "reqd": 1
+ },
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "depends_on": "eval: doc.filter_based_on == 'Date Range'",
+ "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12)
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "depends_on": "eval: doc.filter_based_on == 'Date Range'",
+ "default": frappe.datetime.nowdate()
+ },
+ {
+ "fieldname": "vehicle",
+ "label": __("Vehicle"),
+ "fieldtype": "Link",
+ "options": "Vehicle"
+ },
+ {
+ "fieldname": "employee",
+ "label": __("Employee"),
+ "fieldtype": "Link",
+ "options": "Employee"
+ }
+ ]
+};
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json
index 2ab0c143b8..1a3e5a93bb 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.json
@@ -1,20 +1,23 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2016-09-09 03:33:40.605734",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 2,
- "is_standard": "Yes",
- "modified": "2017-02-24 19:59:18.641284",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Vehicle Expenses",
- "owner": "Administrator",
- "ref_doctype": "Vehicle",
- "report_name": "Vehicle Expenses",
- "report_type": "Script Report",
+ "add_total_row": 1,
+ "columns": [],
+ "creation": "2016-09-09 03:33:40.605734",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 2,
+ "is_standard": "Yes",
+ "modified": "2021-05-16 22:48:22.767535",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Vehicle Expenses",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Vehicle",
+ "report_name": "Vehicle Expenses",
+ "report_type": "Script Report",
"roles": [
{
"role": "Fleet Manager"
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
index eab58ffbbc..d847cbb5c9 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
@@ -5,86 +5,209 @@ from __future__ import unicode_literals
import frappe
import erpnext
from frappe import _
-from frappe.utils import flt,cstr
+from frappe.utils import flt
from erpnext.accounts.report.financial_statements import get_period_list
def execute(filters=None):
- columns, data, chart = [], [], []
- if filters.get('fiscal_year'):
- company = erpnext.get_default_company()
- period_list = get_period_list(filters.get('fiscal_year'), filters.get('fiscal_year'),
- '', '', 'Fiscal Year', 'Monthly', company=company)
- columns=get_columns()
- data=get_log_data(filters)
- chart=get_chart_data(data,period_list)
+ filters = frappe._dict(filters or {})
+
+ columns = get_columns()
+ data = get_vehicle_log_data(filters)
+ chart = get_chart_data(data, filters)
+
return columns, data, None, chart
def get_columns():
- columns = [_("License") + ":Link/Vehicle:100", _('Create') + ":data:50",
- _("Model") + ":data:50", _("Location") + ":data:100",
- _("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80",
- _("Date") + ":Date:100", _("Fuel Qty") + ":Float:80",
- _("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100",
- _("Service Expense") + ":Float:100"
+ return [
+ {
+ 'fieldname': 'vehicle',
+ 'fieldtype': 'Link',
+ 'label': _('Vehicle'),
+ 'options': 'Vehicle',
+ 'width': 150
+ },
+ {
+ 'fieldname': 'make',
+ 'fieldtype': 'Data',
+ 'label': _('Make'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'model',
+ 'fieldtype': 'Data',
+ 'label': _('Model'),
+ 'width': 80
+ },
+ {
+ 'fieldname': 'location',
+ 'fieldtype': 'Data',
+ 'label': _('Location'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'log_name',
+ 'fieldtype': 'Link',
+ 'label': _('Vehicle Log'),
+ 'options': 'Vehicle Log',
+ 'width': 100
+ },
+ {
+ 'fieldname': 'odometer',
+ 'fieldtype': 'Int',
+ 'label': _('Odometer Value'),
+ 'width': 120
+ },
+ {
+ 'fieldname': 'date',
+ 'fieldtype': 'Date',
+ 'label': _('Date'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'fuel_qty',
+ 'fieldtype': 'Float',
+ 'label': _('Fuel Qty'),
+ 'width': 80
+ },
+ {
+ 'fieldname': 'fuel_price',
+ 'fieldtype': 'Float',
+ 'label': _('Fuel Price'),
+ 'width': 100
+ },
+ {
+ 'fieldname': 'fuel_expense',
+ 'fieldtype': 'Currency',
+ 'label': _('Fuel Expense'),
+ 'width': 150
+ },
+ {
+ 'fieldname': 'service_expense',
+ 'fieldtype': 'Currency',
+ 'label': _('Service Expense'),
+ 'width': 150
+ },
+ {
+ 'fieldname': 'employee',
+ 'fieldtype': 'Link',
+ 'label': _('Employee'),
+ 'options': 'Employee',
+ 'width': 150
+ }
]
+
return columns
-def get_log_data(filters):
- fy = frappe.db.get_value('Fiscal Year', filters.get('fiscal_year'), ['year_start_date', 'year_end_date'], as_dict=True)
- data = frappe.db.sql("""select
- vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model",
- vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer",
- log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price",
- log.fuel_qty * log.price as "Fuel Expense"
- from
+
+def get_vehicle_log_data(filters):
+ start_date, end_date = get_period_dates(filters)
+ conditions, values = get_conditions(filters)
+
+ data = frappe.db.sql("""
+ SELECT
+ vhcl.license_plate as vehicle, vhcl.make, vhcl.model,
+ vhcl.location, log.name as log_name, log.odometer,
+ log.date, log.employee, log.fuel_qty,
+ log.price as fuel_price,
+ log.fuel_qty * log.price as fuel_expense
+ FROM
`tabVehicle` vhcl,`tabVehicle Log` log
- where
- vhcl.license_plate = log.license_plate and log.docstatus = 1 and date between %s and %s
- order by date""" ,(fy.year_start_date, fy.year_end_date), as_dict=1)
- dl=list(data)
- for row in dl:
- row["Service Expense"]= get_service_expense(row["Log"])
- return dl
+ WHERE
+ vhcl.license_plate = log.license_plate
+ and log.docstatus = 1
+ and date between %(start_date)s and %(end_date)s
+ {0}
+ ORDER BY date""".format(conditions), values, as_dict=1)
+
+ for row in data:
+ row['service_expense'] = get_service_expense(row.log_name)
+
+ return data
+
+
+def get_conditions(filters):
+ conditions = ''
+
+ start_date, end_date = get_period_dates(filters)
+ values = {
+ 'start_date': start_date,
+ 'end_date': end_date
+ }
+
+ if filters.employee:
+ conditions += ' and log.employee = %(employee)s'
+ values['employee'] = filters.employee
+
+ if filters.vehicle:
+ conditions += ' and vhcl.license_plate = %(vehicle)s'
+ values['vehicle'] = filters.vehicle
+
+ return conditions, values
+
+
+def get_period_dates(filters):
+ if filters.filter_based_on == 'Fiscal Year' and filters.fiscal_year:
+ fy = frappe.db.get_value('Fiscal Year', filters.fiscal_year,
+ ['year_start_date', 'year_end_date'], as_dict=True)
+ return fy.year_start_date, fy.year_end_date
+ else:
+ return filters.from_date, filters.to_date
+
def get_service_expense(logname):
- expense_amount = frappe.db.sql("""select sum(expense_amount)
- from `tabVehicle Log` log,`tabVehicle Service` ser
- where ser.parent=log.name and log.name=%s""",logname)
- return flt(expense_amount[0][0]) if expense_amount else 0
+ expense_amount = frappe.db.sql("""
+ SELECT sum(expense_amount)
+ FROM
+ `tabVehicle Log` log, `tabVehicle Service` service
+ WHERE
+ service.parent=log.name and log.name=%s
+ """, logname)
+
+ return flt(expense_amount[0][0]) if expense_amount else 0.0
+
+
+def get_chart_data(data, filters):
+ period_list = get_period_list(filters.fiscal_year, filters.fiscal_year,
+ filters.from_date, filters.to_date, filters.filter_based_on, 'Monthly')
+
+ fuel_data, service_data = [], []
-def get_chart_data(data,period_list):
- fuel_exp_data,service_exp_data,fueldata,servicedata = [],[],[],[]
- service_exp_data = []
- fueldata = []
for period in period_list:
- total_fuel_exp=0
- total_ser_exp=0
- for row in data:
- if row["Date"] <= period.to_date and row["Date"] >= period.from_date:
- total_fuel_exp+=flt(row["Fuel Expense"])
- total_ser_exp+=flt(row["Service Expense"])
- fueldata.append([period.key,total_fuel_exp])
- servicedata.append([period.key,total_ser_exp])
+ total_fuel_exp = 0
+ total_service_exp = 0
+
+ for row in data:
+ if row.date <= period.to_date and row.date >= period.from_date:
+ total_fuel_exp += flt(row.fuel_expense)
+ total_service_exp += flt(row.service_expense)
+
+ fuel_data.append([period.key, total_fuel_exp])
+ service_data.append([period.key, total_service_exp])
+
+ labels = [period.label for period in period_list]
+ fuel_exp_data= [row[1] for row in fuel_data]
+ service_exp_data= [row[1] for row in service_data]
- labels = [period.key for period in period_list]
- fuel_exp_data= [row[1] for row in fueldata]
- service_exp_data= [row[1] for row in servicedata]
datasets = []
if fuel_exp_data:
datasets.append({
- 'name': 'Fuel Expenses',
+ 'name': _('Fuel Expenses'),
'values': fuel_exp_data
})
+
if service_exp_data:
datasets.append({
- 'name': 'Service Expenses',
+ 'name': _('Service Expenses'),
'values': service_exp_data
})
+
chart = {
- "data": {
+ 'data': {
'labels': labels,
'datasets': datasets
- }
+ },
+ 'type': 'line',
+ 'fieldtype': 'Currency'
}
- chart["type"] = "line"
+
return chart
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 80189e87b7..ebb1734347 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -269,6 +269,7 @@ def get_total_exemption_amount(declarations):
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
return total_exemption_amount
+@frappe.whitelist()
def get_leave_period(from_date, to_date, company):
leave_period = frappe.db.sql("""
select name, from_date, to_date
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 230475f2d1..69d11a8653 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -264,7 +264,7 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict
pending_amount = amounts['pending_principal_amount']
if amount and (amount > pending_amount):
- frappe.throw('Write Off amount cannot be greater than pending loan amount')
+ frappe.throw(_('Write Off amount cannot be greater than pending loan amount'))
if not amount:
amount = pending_amount
diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json
index 18559dceef..d0b67f7c64 100644
--- a/erpnext/loan_management/workspace/loan_management/loan_management.json
+++ b/erpnext/loan_management/workspace/loan_management/loan_management.json
@@ -12,7 +12,7 @@
"idx": 0,
"is_default": 0,
"is_standard": 1,
- "label": "Loan Management",
+ "label": "Loans",
"links": [
{
"hidden": 0,
@@ -220,10 +220,10 @@
"type": "Link"
}
],
- "modified": "2021-02-18 17:31:53.586508",
+ "modified": "2021-05-25 17:31:53.586508",
"modified_by": "Administrator",
"module": "Loan Management",
- "name": "Loan Management",
+ "name": "Loans",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
index 2371d9652c..546a68f268 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js
@@ -2,40 +2,36 @@
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.maintenance");
-
frappe.ui.form.on('Maintenance Schedule', {
- setup: function(frm) {
+ setup: function (frm) {
frm.set_query('contact_person', erpnext.queries.contact_query);
frm.set_query('customer_address', erpnext.queries.address_query);
frm.set_query('customer', erpnext.queries.customer);
-
- frm.add_fetch('item_code', 'item_name', 'item_name');
- frm.add_fetch('item_code', 'description', 'description');
},
- onload: function(frm) {
+ onload: function (frm) {
if (!frm.doc.status) {
- frm.set_value({status:'Draft'});
+ frm.set_value({ status: 'Draft' });
}
if (frm.doc.__islocal) {
- frm.set_value({transaction_date: frappe.datetime.get_today()});
+ frm.set_value({ transaction_date: frappe.datetime.get_today() });
}
},
- refresh: function(frm) {
+ refresh: function (frm) {
setTimeout(() => {
frm.toggle_display('generate_schedule', !(frm.is_new()));
frm.toggle_display('schedule', !(frm.is_new()));
- },10);
+ }, 10);
},
- customer: function(frm) {
+ customer: function (frm) {
erpnext.utils.get_party_details(frm)
},
- customer_address: function(frm) {
+ customer_address: function (frm) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
},
- contact_person: function(frm) {
+ contact_person: function (frm) {
erpnext.utils.get_contact_details(frm);
},
- generate_schedule: function(frm) {
+ generate_schedule: function (frm) {
if (frm.is_new()) {
frappe.msgprint(__('Please save first'));
} else {
@@ -53,7 +49,7 @@ erpnext.maintenance.MaintenanceSchedule = class MaintenanceSchedule extends frap
if (this.frm.doc.docstatus === 0) {
this.frm.add_custom_button(__('Sales Order'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
source_doctype: "Sales Order",
@@ -68,13 +64,79 @@ erpnext.maintenance.MaintenanceSchedule = class MaintenanceSchedule extends frap
});
}, __("Get Items From"));
} else if (this.frm.doc.docstatus === 1) {
- this.frm.add_custom_button(__('Create Maintenance Visit'), function() {
- frappe.model.open_mapped_doc({
- method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
- source_name: me.frm.doc.name,
- frm: me.frm
+ let schedules = me.frm.doc.schedules;
+ let flag = schedules.some(schedule => schedule.completion_status === "Pending");
+ if (flag) {
+ this.frm.add_custom_button(__('Maintenance Visit'), function () {
+ let options = "";
+
+ me.frm.call('get_pending_data', {data_type: "items"}).then(r => {
+ options = r.message;
+
+ let schedule_id = "";
+ let d = new frappe.ui.Dialog({
+ title: __("Enter Visit Details"),
+ fields: [{
+ fieldtype: "Select",
+ fieldname: "item_name",
+ label: __("Item Name"),
+ options: options,
+ reqd: 1,
+ onchange: function () {
+ let field = d.get_field("scheduled_date");
+ me.frm.call('get_pending_data',
+ {
+ item_name: this.value,
+ data_type: "date"
+ }).then(r => {
+ field.df.options = r.message;
+ field.refresh();
+ });
+ }
+ },
+ {
+ label: __('Scheduled Date'),
+ fieldname: 'scheduled_date',
+ fieldtype: 'Select',
+ options: "",
+ reqd: 1,
+ onchange: function () {
+ let field = d.get_field('item_name');
+ me.frm.call(
+ 'get_pending_data',
+ {
+ item_name: field.value,
+ s_date: this.value,
+ data_type: "id"
+ }).then(r => {
+ schedule_id = r.message;
+ });
+ }
+ },
+ ],
+ primary_action_label: 'Create Visit',
+ primary_action(values) {
+ frappe.call({
+ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
+ args: {
+ item_name: values.item_name,
+ s_id: schedule_id,
+ source_name: me.frm.doc.name,
+ },
+ callback: function (r) {
+ if (!r.exc) {
+ frappe.model.sync(r.message);
+ frappe.set_route("Form", r.message.doctype, r.message.name);
+ }
+ }
+ });
+ d.hide();
+ }
+ });
+ d.show();
});
- }, __('Create'));
+ }, __('Create'));
+ }
}
}
@@ -92,25 +154,10 @@ erpnext.maintenance.MaintenanceSchedule = class MaintenanceSchedule extends frap
set_no_of_visits(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
-
- if (item.start_date && item.end_date && item.periodicity) {
- if(item.start_date > item.end_date) {
- frappe.msgprint(__("Row {0}:Start Date must be before End Date", [item.idx]));
- return;
- }
-
- var date_diff = frappe.datetime.get_diff(item.end_date, item.start_date) + 1;
-
- var days_in_period = {
- "Weekly": 7,
- "Monthly": 30,
- "Quarterly": 91,
- "Half Yearly": 182,
- "Yearly": 365
- }
-
- var no_of_visits = cint(date_diff / days_in_period[item.periodicity]);
- frappe.model.set_value(item.doctype, item.name, "no_of_visits", no_of_visits);
+ let me = this;
+ if (item.start_date && item.periodicity) {
+ me.frm.call('validate_end_date_visits');
+
}
}
};
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
index 606d22f52b..4f89a679c8 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
@@ -1,852 +1,264 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2013-01-10 16:34:30",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2013-01-10 16:34:30",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "customer_details",
+ "naming_series",
+ "customer",
+ "column_break0",
+ "status",
+ "transaction_date",
+ "items_section",
+ "items",
+ "schedule",
+ "generate_schedule",
+ "schedules",
+ "contact_info",
+ "customer_name",
+ "contact_person",
+ "contact_mobile",
+ "contact_email",
+ "contact_display",
+ "column_break_17",
+ "customer_address",
+ "address_display",
+ "territory",
+ "customer_group",
+ "company",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "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": "",
- "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
- },
+ "fieldname": "customer_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-user"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Series",
- "length": 0,
- "no_copy": 1,
- "options": "MAT-MSH-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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": 1,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "no_copy": 1,
+ "options": "MAT-MSH-.YYYY.-",
+ "print_hide": 1,
+ "reqd": 1,
+ "set_only_once": 1
+ },
{
- "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": 0,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "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
- },
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Customer",
+ "oldfieldname": "customer",
+ "oldfieldtype": "Link",
+ "options": "Customer",
+ "print_hide": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "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,
- "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
- },
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Draft",
- "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": "\nDraft\nSubmitted\nCancelled",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Status",
+ "no_copy": 1,
+ "oldfieldname": "status",
+ "oldfieldtype": "Select",
+ "options": "\nDraft\nSubmitted\nCancelled",
+ "read_only": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "transaction_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": "Transaction Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "transaction_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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "label": "Transaction Date",
+ "oldfieldname": "transaction_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items_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": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart",
- "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": "items_section",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-shopping-cart"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "items",
- "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": "Items",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_maintenance_detail",
- "oldfieldtype": "Table",
- "options": "Maintenance Schedule Item",
- "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
- },
+ "fieldname": "items",
+ "fieldtype": "Table",
+ "label": "Items",
+ "oldfieldname": "item_maintenance_detail",
+ "oldfieldtype": "Table",
+ "options": "Maintenance Schedule Item",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "schedule",
- "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": "Schedule",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-time",
- "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": "schedule",
+ "fieldtype": "Section Break",
+ "label": "Schedule",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "generate_schedule",
- "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": "Generate Schedule",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Button",
- "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": "generate_schedule",
+ "fieldtype": "Button",
+ "label": "Generate Schedule",
+ "oldfieldtype": "Button"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "schedules",
- "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": "Schedules",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "schedules",
- "oldfieldtype": "Table",
- "options": "Maintenance Schedule Detail",
- "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": "schedules",
+ "fieldtype": "Table",
+ "label": "Schedules",
+ "oldfieldname": "schedules",
+ "oldfieldtype": "Table",
+ "options": "Maintenance Schedule Detail"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_info",
- "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": "Contact Info",
- "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": "contact_info",
+ "fieldtype": "Section Break",
+ "label": "Contact Info"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Customer Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "customer_name",
- "oldfieldtype": "Data",
- "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
- },
+ "bold": 1,
+ "depends_on": "customer",
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Customer Name",
+ "oldfieldname": "customer_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_person",
- "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": "Contact Person",
- "length": 0,
- "no_copy": 0,
- "options": "Contact",
- "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": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "label": "Contact Person",
+ "options": "Contact",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_mobile",
- "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": "Mobile No",
- "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
- },
+ "depends_on": "customer",
+ "fieldname": "contact_mobile",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Mobile No",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_email",
- "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": "Contact Email",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_display",
- "fieldtype": "Small Text",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact",
- "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": "contact_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "in_global_search": 1,
+ "label": "Contact",
+ "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_17",
- "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,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "customer_address",
- "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": "Customer Address",
- "length": 0,
- "no_copy": 0,
- "options": "Address",
- "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": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "customer",
+ "fieldname": "customer_address",
+ "fieldtype": "Link",
+ "label": "Customer Address",
+ "options": "Address",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_display",
- "fieldtype": "Small Text",
- "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": "Address",
- "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": "address_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Address",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "description": "",
- "fieldname": "territory",
- "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": "Territory",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "territory",
- "oldfieldtype": "Link",
- "options": "Territory",
- "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
- },
+ "depends_on": "customer",
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Territory",
+ "oldfieldname": "territory",
+ "oldfieldtype": "Link",
+ "options": "Territory"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "description": "",
- "fieldname": "customer_group",
- "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": "Customer Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "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
- },
+ "depends_on": "customer",
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "label": "Customer Group",
+ "options": "Customer Group"
+ },
{
- "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,
- "oldfieldname": "company",
- "oldfieldtype": "Link",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Link",
+ "options": "Company",
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Maintenance Schedule",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Maintenance Schedule",
+ "print_hide": 1,
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-calendar",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Schedule",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-calendar",
+ "idx": 1,
+ "is_submittable": 1,
+ "links": [
+ {
+ "group": "Visits",
+ "link_doctype": "Maintenance Visit",
+ "link_fieldname": "maintenance_schedule"
+ }
+ ],
+ "modified": "2021-05-27 16:05:10.746465",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Schedule",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Maintenance Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Maintenance Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "status,customer,customer_name",
- "show_name_in_global_search": 0,
- "sort_order": "DESC",
- "timeline_field": "customer",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "status,customer,customer_name",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "timeline_field": "customer"
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 0aefe19c8d..d6e42f3ee1 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -4,12 +4,13 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import add_days, getdate, cint, cstr
+from frappe.utils import add_days, getdate, cint, cstr, date_diff, formatdate
from frappe import throw, _
from erpnext.utilities.transaction_base import TransactionBase, delete_events
from erpnext.stock.utils import get_valid_serial_nos
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class MaintenanceSchedule(TransactionBase):
@frappe.whitelist()
@@ -32,8 +33,40 @@ class MaintenanceSchedule(TransactionBase):
child.idx = count
count = count + 1
child.sales_person = d.sales_person
+ child.completion_status = "Pending"
+ child.item_reference = d.name
+
+ @frappe.whitelist()
+ def validate_end_date_visits(self):
+ days_in_period = {
+ "Weekly": 7,
+ "Monthly": 30,
+ "Quarterly": 91,
+ "Half Yearly": 182,
+ "Yearly": 365
+ }
+ for item in self.items:
+ if item.periodicity and item.start_date:
+ if not item.end_date:
+ if item.no_of_visits:
+ item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+ else:
+ item.end_date = add_days(item.start_date, days_in_period[item.periodicity])
+
+ diff = date_diff(item.end_date, item.start_date) + 1
+ no_of_visits = cint(diff / days_in_period[item.periodicity])
+
+ if not item.no_of_visits or item.no_of_visits == 0:
+ item.end_date = add_days(item.start_date, days_in_period[item.periodicity])
+ diff = date_diff(item.end_date, item.start_date) + 1
+ item.no_of_visits = cint(diff / days_in_period[item.periodicity])
+
+ elif item.no_of_visits > no_of_visits:
+ item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
+
+ elif item.no_of_visits < no_of_visits:
+ item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
- self.save()
def on_submit(self):
if not self.get('schedules'):
@@ -58,9 +91,10 @@ class MaintenanceSchedule(TransactionBase):
if no_email_sp:
frappe.msgprint(
- frappe._("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
+ _("Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}").format(
self.owner, "
" + "
".join(no_email_sp)
- ))
+ )
+ )
scheduled_date = frappe.db.sql("""select scheduled_date from
`tabMaintenance Schedule Detail` where sales_person=%s and item_code=%s and
@@ -106,7 +140,7 @@ class MaintenanceSchedule(TransactionBase):
if employee:
holiday_list = get_holiday_list_for_employee(employee)
else:
- holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list")
+ holiday_list = frappe.get_cached_value('Company', self.company, "default_holiday_list")
holidays = frappe.db.sql_list('''select holiday_date from `tabHoliday` where parent=%s''', holiday_list)
@@ -135,8 +169,7 @@ class MaintenanceSchedule(TransactionBase):
}
if date_diff < days_in_period[d.periodicity]:
- throw(_("Row {0}: To set {1} periodicity, difference between from and to date \
- must be greater than or equal to {2}")
+ throw(_("Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}")
.format(d.idx, d.periodicity, days_in_period[d.periodicity]))
def validate_maintenance_detail(self):
@@ -166,13 +199,15 @@ class MaintenanceSchedule(TransactionBase):
throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
def validate(self):
+ self.validate_end_date_visits()
self.validate_maintenance_detail()
self.validate_dates_with_periodicity()
self.validate_sales_order()
+ self.generate_schedule()
def on_update(self):
frappe.db.set(self, 'status', 'Draft')
-
+
def update_amc_date(self, serial_nos, amc_expiry_date=None):
for serial_no in serial_nos:
serial_no_doc = frappe.get_doc("Serial No", serial_no)
@@ -202,8 +237,8 @@ class MaintenanceSchedule(TransactionBase):
if not sr_details.warehouse and sr_details.delivery_date and \
getdate(sr_details.delivery_date) >= getdate(amc_start_date):
- throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
- .format(serial_no))
+ throw(_("Maintenance start date can not be before delivery date for Serial No {0}")
+ .format(serial_no))
def validate_schedule(self):
item_lst1 =[]
@@ -245,13 +280,50 @@ class MaintenanceSchedule(TransactionBase):
def on_trash(self):
delete_events(self.doctype, self.name)
+ @frappe.whitelist()
+ def get_pending_data(self, data_type, s_date=None, item_name=None):
+ if data_type == "date":
+ dates = ""
+ for schedule in self.schedules:
+ if schedule.item_name == item_name and schedule.completion_status == "Pending":
+ dates = dates + "\n" + formatdate(schedule.scheduled_date, "dd-MM-yyyy")
+ return dates
+ elif data_type == "items":
+ items = ""
+ for item in self.items:
+ for schedule in self.schedules:
+ if item.item_name == schedule.item_name and schedule.completion_status == "Pending":
+ items = items + "\n" + item.item_name
+ break
+ return items
+ elif data_type == "id":
+ for schedule in self.schedules:
+ if schedule.item_name == item_name and s_date == formatdate(schedule.scheduled_date, "dd-mm-yyyy"):
+ return schedule.name
+
@frappe.whitelist()
-def make_maintenance_visit(source_name, target_doc=None):
+def update_serial_nos(s_id):
+ serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no')
+ if serial_nos:
+ serial_nos = get_serial_nos(serial_nos)
+ return serial_nos
+ else:
+ return False
+
+@frappe.whitelist()
+def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
from frappe.model.mapper import get_mapped_doc
- def update_status(source, target, parent):
+ def update_status_and_detail(source, target, parent):
target.maintenance_type = "Scheduled"
-
+ target.maintenance_schedule = source.name
+ target.maintenance_schedule_detail = s_id
+
+ def update_sales(source, target, parent):
+ sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person')
+ target.service_person = sales_person
+ target.serial_no = ''
+
doclist = get_mapped_doc("Maintenance Schedule", source_name, {
"Maintenance Schedule": {
"doctype": "Maintenance Visit",
@@ -261,15 +333,12 @@ def make_maintenance_visit(source_name, target_doc=None):
"validation": {
"docstatus": ["=", 1]
},
- "postprocess": update_status
+ "postprocess": update_status_and_detail
},
"Maintenance Schedule Item": {
"doctype": "Maintenance Visit Purpose",
- "field_map": {
- "parent": "prevdoc_docname",
- "parenttype": "prevdoc_doctype",
- "sales_person": "service_person"
- }
+ "condition": lambda doc: doc.item_name == item_name,
+ "postprocess": update_sales
}
}, target_doc)
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
index 3c307e920f..09981bad05 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
@@ -2,7 +2,8 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
-from frappe.utils.data import get_datetime, add_days
+from frappe.utils.data import add_days, today, formatdate
+from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import make_maintenance_visit
import frappe
import unittest
@@ -21,7 +22,57 @@ class TestMaintenanceSchedule(unittest.TestCase):
ms.cancel()
events_after_cancel = get_events(ms)
self.assertTrue(len(events_after_cancel) == 0)
+
+ def test_make_schedule(self):
+ ms = make_maintenance_schedule()
+ ms.save()
+ i = ms.items[0]
+ expected_dates = []
+ expected_end_date = add_days(i.start_date, i.no_of_visits * 7)
+ self.assertEqual(i.end_date, expected_end_date)
+ i.no_of_visits = 2
+ ms.save()
+ expected_end_date = add_days(i.start_date, i.no_of_visits * 7)
+ self.assertEqual(i.end_date, expected_end_date)
+
+ items = ms.get_pending_data(data_type = "items")
+ items = items.split('\n')
+ items.pop(0)
+ expected_items = ['_Test Item']
+ self.assertTrue(items, expected_items)
+
+ # "dates" contains all generated schedule dates
+ dates = ms.get_pending_data(data_type = "date", item_name = i.item_name)
+ dates = dates.split('\n')
+ dates.pop(0)
+ expected_dates.append(formatdate(add_days(i.start_date, 7), "dd-MM-yyyy"))
+ expected_dates.append(formatdate(add_days(i.start_date, 14), "dd-MM-yyyy"))
+
+ # test for generated schedule dates
+ self.assertEqual(dates, expected_dates)
+
+ ms.submit()
+ s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
+ test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
+ visit = frappe.new_doc('Maintenance Visit')
+ visit = test
+ visit.maintenance_schedule = ms.name
+ visit.maintenance_schedule_detail = s_id
+ visit.completion_status = "Partially Completed"
+ visit.set('purposes', [{
+ 'item_code': i.item_code,
+ 'description': "test",
+ 'work_done': "test",
+ 'service_person': "Sales Team",
+ }])
+ visit.save()
+ visit.submit()
+ ms = frappe.get_doc('Maintenance Schedule', ms.name)
+
+ #checks if visit status is back updated in schedule
+ self.assertTrue(ms.schedules[1].completion_status, "Partially Completed")
+
def get_events(ms):
return frappe.get_all("Event Participants", filters={
"reference_doctype": ms.doctype,
@@ -33,12 +84,11 @@ def make_maintenance_schedule():
ms = frappe.new_doc("Maintenance Schedule")
ms.company = "_Test Company"
ms.customer = "_Test Customer"
- ms.transaction_date = get_datetime()
+ ms.transaction_date = today()
ms.append("items", {
"item_code": "_Test Item",
- "start_date": get_datetime(),
- "end_date": add_days(get_datetime(), 32),
+ "start_date": today(),
"periodicity": "Weekly",
"no_of_visits": 4,
"sales_person": "Sales Team",
diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
index 7cd3086155..8ccef6a817 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
@@ -1,222 +1,137 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:28:05",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:28:05",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "item_name",
+ "column_break_3",
+ "scheduled_date",
+ "actual_date",
+ "section_break_6",
+ "sales_person",
+ "column_break_8",
+ "completion_status",
+ "section_break_10",
+ "serial_no",
+ "item_reference"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_code",
- "oldfieldtype": "Link",
- "options": "Item",
- "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": 1,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "read_only": 1,
+ "search_index": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_name",
- "oldfieldtype": "Data",
- "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,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "label": "Item Name",
+ "oldfieldname": "item_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "scheduled_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": "Scheduled Date",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "scheduled_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": 1,
- "search_index": 1,
- "set_only_once": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "scheduled_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Scheduled Date",
+ "oldfieldname": "scheduled_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "actual_date",
- "fieldtype": "Date",
- "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": "Actual Date",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "actual_date",
- "oldfieldtype": "Date",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 1,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "actual_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Actual Date",
+ "no_copy": 1,
+ "oldfieldname": "actual_date",
+ "oldfieldtype": "Date",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_person",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Sales Person",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "incharge_name",
- "oldfieldtype": "Link",
- "options": "Sales Person",
- "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,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "columns": 2,
+ "fieldname": "sales_person",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Person",
+ "oldfieldname": "incharge_name",
+ "oldfieldtype": "Link",
+ "options": "Sales Person",
+ "read_only_depends_on": "eval:doc.completion_status != \"Pending\""
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Serial No",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "serial_no",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "160px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "serial_no",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Serial No",
+ "oldfieldname": "serial_no",
+ "oldfieldtype": "Small Text",
+ "print_width": "160px",
+ "read_only": 1,
"width": "160px"
+ },
+ {
+ "columns": 2,
+ "fieldname": "completion_status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Completion Status",
+ "options": "Pending\nPartially Completed\nFully Completed",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_8",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "item_reference",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Item Reference",
+ "options": "Maintenance Schedule Item",
+ "read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-02-17 17:05:44.644663",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Schedule Detail",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-27 16:07:25.905015",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Schedule Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
index b371dfc4f5..3dacdead62 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
+++ b/erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
@@ -1,431 +1,160 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:28:05",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:28:05",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_code",
+ "item_name",
+ "description",
+ "column_break_4",
+ "start_date",
+ "end_date",
+ "periodicity",
+ "schedule_details",
+ "no_of_visits",
+ "column_break_10",
+ "sales_person",
+ "reference",
+ "serial_no",
+ "sales_order"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_code",
- "oldfieldtype": "Link",
- "options": "Item",
- "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
- },
+ "columns": 2,
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "columns": 1,
"fetch_from": "item_code.item_name",
- "fieldname": "item_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_name",
- "oldfieldtype": "Data",
- "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
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Item Name",
+ "oldfieldname": "item_name",
+ "oldfieldtype": "Data",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "item_code.description",
- "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,
- "oldfieldname": "description",
- "oldfieldtype": "Data",
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "300px",
- "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": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Data",
+ "print_width": "300px",
+ "read_only": 1,
"width": "300px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "schedule_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": "",
- "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": "schedule_details",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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,
- "oldfieldname": "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": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "start_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Start Date",
+ "oldfieldname": "start_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "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,
- "oldfieldname": "end_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": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "end_date",
+ "fieldtype": "Date",
+ "label": "End Date",
+ "oldfieldname": "end_date",
+ "oldfieldtype": "Date",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "periodicity",
- "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": "Periodicity",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "periodicity",
- "oldfieldtype": "Select",
- "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom",
- "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
- },
+ "columns": 1,
+ "fieldname": "periodicity",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Periodicity",
+ "oldfieldname": "periodicity",
+ "oldfieldtype": "Select",
+ "options": "\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly\nRandom"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "no_of_visits",
- "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": "No of Visits",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "no_of_visits",
- "oldfieldtype": "Int",
- "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
- },
+ "columns": 1,
+ "fieldname": "no_of_visits",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "No of Visits",
+ "oldfieldname": "no_of_visits",
+ "oldfieldtype": "Int",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sales_person",
- "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 Person",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "incharge_name",
- "oldfieldtype": "Link",
- "options": "Sales Person",
- "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": "sales_person",
+ "fieldtype": "Link",
+ "label": "Sales Person",
+ "oldfieldname": "incharge_name",
+ "oldfieldtype": "Link",
+ "options": "Sales Person"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference",
- "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": "Reference",
- "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": "reference",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Serial No",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "serial_no",
- "oldfieldtype": "Small Text",
- "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": "serial_no",
+ "fieldtype": "Small Text",
+ "label": "Serial No",
+ "oldfieldname": "serial_no",
+ "oldfieldtype": "Small Text"
+ },
{
- "allow_bulk_edit": 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": 1,
- "oldfieldname": "prevdoc_docname",
- "oldfieldtype": "Data",
- "options": "Sales Order",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "label": "Sales Order",
+ "no_copy": 1,
+ "oldfieldname": "prevdoc_docname",
+ "oldfieldtype": "Data",
+ "options": "Sales Order",
+ "print_hide": 1,
+ "print_width": "150px",
+ "read_only": 1,
+ "search_index": 1,
"width": "150px"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-05-16 22:43:14.260729",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Schedule Item",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-15 16:09:47.311994",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Schedule Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index 503253040b..53ecdf5a61 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -2,39 +2,62 @@
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.maintenance");
-
+var serial_nos = [];
frappe.ui.form.on('Maintenance Visit', {
- refresh: function(frm) {
+ refresh: function (frm) {
//filters for serial_no based on item_code
- frm.set_query('serial_no', 'purposes', function(frm, cdt, cdn) {
+ frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) {
let item = locals[cdt][cdn];
- return {
- filters: {
- 'item_code': item.item_code
- }
- };
+ if (serial_nos) {
+ return {
+ filters: {
+ 'item_code': item.item_code,
+ 'name': ["in", serial_nos]
+ }
+ };
+ } else {
+ return {
+ filters: {
+ 'item_code': item.item_code
+ }
+ };
+ }
});
},
- setup: function(frm) {
+ setup: function (frm) {
frm.set_query('contact_person', erpnext.queries.contact_query);
frm.set_query('customer_address', erpnext.queries.address_query);
frm.set_query('customer', erpnext.queries.customer);
},
- onload: function(frm) {
+ onload: function (frm, cdt, cdn) {
+ let item = locals[cdt][cdn];
+ if (frm.maintenance_type == 'Scheduled') {
+ let schedule_id = item.purposes[0].prevdoc_detail_docname;
+ frappe.call({
+ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos",
+ args: {
+ s_id: schedule_id
+ },
+ callback: function (r) {
+ serial_nos = r.message;
+ }
+ });
+ }
+
if (!frm.doc.status) {
- frm.set_value({status:'Draft'});
+ frm.set_value({ status: 'Draft' });
}
if (frm.doc.__islocal) {
- frm.set_value({mntc_date: frappe.datetime.get_today()});
+ frm.set_value({ mntc_date: frappe.datetime.get_today() });
}
},
- customer: function(frm) {
+ customer: function (frm) {
erpnext.utils.get_party_details(frm);
},
- customer_address: function(frm) {
+ customer_address: function (frm) {
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display');
},
- contact_person: function(frm) {
+ contact_person: function (frm) {
erpnext.utils.get_contact_details(frm);
}
@@ -47,9 +70,9 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui.
var me = this;
- if (this.frm.doc.docstatus===0) {
+ if (this.frm.doc.docstatus === 0) {
this.frm.add_custom_button(__('Maintenance Schedule'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
source_doctype: "Maintenance Schedule",
@@ -64,7 +87,7 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui.
})
}, __("Get Items From"));
this.frm.add_custom_button(__('Warranty Claim'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
source_doctype: "Warranty Claim",
@@ -80,7 +103,7 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui.
})
}, __("Get Items From"));
this.frm.add_custom_button(__('Sales Order'),
- function() {
+ function () {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
source_doctype: "Sales Order",
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
index 32bfa0e324..ec32239518 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
@@ -1,1042 +1,324 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2013-01-10 16:34:31",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2013-01-10 16:34:31",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "customer_details",
+ "column_break0",
+ "naming_series",
+ "customer",
+ "customer_name",
+ "address_display",
+ "contact_display",
+ "contact_mobile",
+ "contact_email",
+ "maintenance_schedule",
+ "maintenance_schedule_detail",
+ "column_break1",
+ "mntc_date",
+ "mntc_time",
+ "maintenance_details",
+ "completion_status",
+ "column_break_14",
+ "maintenance_type",
+ "section_break0",
+ "purposes",
+ "more_info",
+ "customer_feedback",
+ "col_break3",
+ "status",
+ "amended_from",
+ "company",
+ "contact_info_section",
+ "customer_address",
+ "contact_person",
+ "col_break4",
+ "territory",
+ "customer_group"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "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": "",
- "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
- },
+ "fieldname": "customer_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-user"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "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,
- "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
- },
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Series",
- "length": 0,
- "no_copy": 1,
- "options": "MAT-MVS-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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": 1,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "no_copy": 1,
+ "options": "MAT-MVS-.YYYY.-",
+ "print_hide": 1,
+ "reqd": 1,
+ "set_only_once": 1
+ },
{
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "in_global_search": 1,
+ "label": "Customer",
+ "oldfieldname": "customer",
+ "oldfieldtype": "Link",
+ "options": "Customer",
+ "print_hide": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_name",
- "fieldtype": "Data",
- "hidden": 1,
- "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 Name",
- "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
- },
+ "bold": 1,
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "in_global_search": 1,
+ "label": "Customer Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "address_display",
- "fieldtype": "Small Text",
- "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": "Address",
- "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": "address_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Address",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_display",
- "fieldtype": "Small Text",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Contact",
- "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": "contact_display",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "in_global_search": 1,
+ "label": "Contact",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_mobile",
- "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": "Mobile No",
- "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": "contact_mobile",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Mobile No",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_email",
- "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": "Contact Email",
- "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": "contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_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,
- "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,
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
"width": "50%"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fieldname": "mntc_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": "Maintenance Date",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "mntc_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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "mntc_date",
+ "fieldtype": "Date",
+ "label": "Maintenance Date",
+ "no_copy": 1,
+ "oldfieldname": "mntc_date",
+ "oldfieldtype": "Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "mntc_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": "Maintenance Time",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "mntc_time",
- "oldfieldtype": "Time",
- "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": "mntc_time",
+ "fieldtype": "Time",
+ "label": "Maintenance Time",
+ "no_copy": 1,
+ "oldfieldname": "mntc_time",
+ "oldfieldtype": "Time"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintenance_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": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-wrench",
- "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": "maintenance_details",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-wrench"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "completion_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": 1,
- "label": "Completion Status",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "completion_status",
- "oldfieldtype": "Select",
- "options": "\nPartially Completed\nFully Completed",
- "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
- },
+ "fieldname": "completion_status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Completion Status",
+ "oldfieldname": "completion_status",
+ "oldfieldtype": "Select",
+ "options": "\nPartially Completed\nFully Completed",
+ "reqd": 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,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Unscheduled",
- "fieldname": "maintenance_type",
- "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": 1,
- "label": "Maintenance Type",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "maintenance_type",
- "oldfieldtype": "Select",
- "options": "\nScheduled\nUnscheduled\nBreakdown",
- "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
- },
+ "default": "Unscheduled",
+ "fieldname": "maintenance_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Maintenance Type",
+ "oldfieldname": "maintenance_type",
+ "oldfieldtype": "Select",
+ "options": "\nScheduled\nUnscheduled\nBreakdown",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "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,
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-wrench",
- "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": "section_break0",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-wrench"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "purposes",
- "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": "Purposes",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "maintenance_visit_details",
- "oldfieldtype": "Table",
- "options": "Maintenance Visit Purpose",
- "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
- },
+ "fieldname": "purposes",
+ "fieldtype": "Table",
+ "label": "Purposes",
+ "oldfieldname": "maintenance_visit_details",
+ "oldfieldtype": "Table",
+ "options": "Maintenance Visit Purpose",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "more_info",
- "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": "More Information",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "options": "fa fa-file-text",
- "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": "more_info",
+ "fieldtype": "Section Break",
+ "label": "More Information",
+ "oldfieldtype": "Section Break",
+ "options": "fa fa-file-text"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_feedback",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Customer Feedback",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "customer_feedback",
- "oldfieldtype": "Small Text",
- "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": "customer_feedback",
+ "fieldtype": "Small Text",
+ "label": "Customer Feedback",
+ "oldfieldname": "customer_feedback",
+ "oldfieldtype": "Small Text"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "col_break3",
- "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,
- "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_break3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Draft",
- "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": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "status",
- "oldfieldtype": "Data",
- "options": "\nDraft\nCancelled\nSubmitted",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "no_copy": 1,
+ "oldfieldname": "status",
+ "oldfieldtype": "Data",
+ "options": "\nDraft\nCancelled\nSubmitted",
+ "read_only": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "oldfieldname": "amended_from",
- "oldfieldtype": "Data",
- "options": "Maintenance Visit",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "label": "Amended From",
+ "no_copy": 1,
+ "oldfieldname": "amended_from",
+ "oldfieldtype": "Data",
+ "options": "Maintenance Visit",
+ "print_hide": 1,
+ "read_only": 1,
"width": "150px"
- },
+ },
{
- "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,
- "oldfieldname": "company",
- "oldfieldtype": "Select",
- "options": "Company",
- "permlevel": 0,
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 1,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "oldfieldname": "company",
+ "oldfieldtype": "Select",
+ "options": "Company",
+ "print_hide": 1,
+ "remember_last_selected_value": 1,
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "customer",
- "fieldname": "contact_info_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": "Contact Info",
- "length": 0,
- "no_copy": 0,
- "options": "fa fa-bullhorn",
- "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
- },
+ "depends_on": "customer",
+ "fieldname": "contact_info_section",
+ "fieldtype": "Section Break",
+ "label": "Contact Info",
+ "options": "fa fa-bullhorn"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "customer_address",
- "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": "Customer Address",
- "length": 0,
- "no_copy": 0,
- "options": "Address",
- "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": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "customer_address",
+ "fieldtype": "Link",
+ "label": "Customer Address",
+ "options": "Address",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "contact_person",
- "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": "Contact Person",
- "length": 0,
- "no_copy": 0,
- "options": "Contact",
- "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": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "label": "Contact Person",
+ "options": "Contact",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "col_break4",
- "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,
- "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_break4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "territory",
- "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": "Territory",
- "length": 0,
- "no_copy": 0,
- "options": "Territory",
- "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": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "label": "Territory",
+ "options": "Territory",
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fieldname": "customer_group",
- "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": "Customer Group",
- "length": 0,
- "no_copy": 0,
- "options": "Customer Group",
- "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": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "customer_group",
+ "fieldtype": "Link",
+ "label": "Customer Group",
+ "options": "Customer Group",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "maintenance_schedule",
+ "fieldtype": "Link",
+ "label": "Maintenance Schedule",
+ "options": "Maintenance Schedule",
+ "read_only": 1
+ },
+ {
+ "fieldname": "maintenance_schedule_detail",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Maintenance Schedule Detail",
+ "options": "Maintenance Schedule Detail"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-file-text",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Maintenance",
- "name": "Maintenance Visit",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-file-text",
+ "idx": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-05-27 16:06:17.352572",
+ "modified_by": "Administrator",
+ "module": "Maintenance",
+ "name": "Maintenance Visit",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Maintenance User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Maintenance User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "timeline_field": "customer",
- "title_field": "customer_name",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "timeline_field": "customer",
+ "title_field": "customer_name"
}
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 2f2ad00e02..7fffc942a0 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import get_datetime
from erpnext.utilities.transaction_base import TransactionBase
@@ -16,44 +17,62 @@ class MaintenanceVisit(TransactionBase):
if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
frappe.throw(_("Serial No {0} does not exist").format(d.serial_no))
+ def validate_maintenance_date(self):
+ if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
+ item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference')
+ if item_ref:
+ start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date'])
+ if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date):
+ frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date))
+
def validate(self):
self.validate_serial_no()
+ self.validate_maintenance_date()
+
+ def update_completion_status(self):
+ if self.maintenance_schedule_detail:
+ frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status)
+
+ def update_actual_date(self):
+ if self.maintenance_schedule_detail:
+ frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date)
def update_customer_issue(self, flag):
- for d in self.get('purposes'):
- if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
- if flag==1:
- mntc_date = self.mntc_date
- service_person = d.service_person
- work_done = d.work_done
- status = "Open"
- if self.completion_status == 'Fully Completed':
- status = 'Closed'
- elif self.completion_status == 'Partially Completed':
- status = 'Work In Progress'
- else:
- nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name))
-
- if nm:
- status = 'Work In Progress'
- mntc_date = nm and nm[0][1] or ''
- service_person = nm and nm[0][2] or ''
- work_done = nm and nm[0][3] or ''
+ if not self.maintenance_schedule:
+ for d in self.get('purposes'):
+ if d.prevdoc_docname and d.prevdoc_doctype == 'Warranty Claim' :
+ if flag==1:
+ mntc_date = self.mntc_date
+ service_person = d.service_person
+ work_done = d.work_done
+ status = "Open"
+ if self.completion_status == 'Fully Completed':
+ status = 'Closed'
+ elif self.completion_status == 'Partially Completed':
+ status = 'Work In Progress'
else:
- status = 'Open'
- mntc_date = None
- service_person = None
- work_done = None
+ nm = frappe.db.sql("select t1.name, t1.mntc_date, t2.service_person, t2.work_done from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2 where t2.parent = t1.name and t1.completion_status = 'Partially Completed' and t2.prevdoc_docname = %s and t1.name!=%s and t1.docstatus = 1 order by t1.name desc limit 1", (d.prevdoc_docname, self.name))
- wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
- wc_doc.update({
- 'resolution_date': mntc_date,
- 'resolved_by': service_person,
- 'resolution_details': work_done,
- 'status': status
- })
+ if nm:
+ status = 'Work In Progress'
+ mntc_date = nm and nm[0][1] or ''
+ service_person = nm and nm[0][2] or ''
+ work_done = nm and nm[0][3] or ''
+ else:
+ status = 'Open'
+ mntc_date = None
+ service_person = None
+ work_done = None
- wc_doc.db_update()
+ wc_doc = frappe.get_doc('Warranty Claim', d.prevdoc_docname)
+ wc_doc.update({
+ 'resolution_date': mntc_date,
+ 'resolved_by': service_person,
+ 'resolution_details': work_done,
+ 'status': status
+ })
+
+ wc_doc.db_update()
def check_if_last_visit(self):
"""check if last maintenance visit against same sales order/ Warranty Claim"""
@@ -77,6 +96,8 @@ class MaintenanceVisit(TransactionBase):
def on_submit(self):
self.update_customer_issue(1)
frappe.db.set(self, 'status', 'Submitted')
+ self.update_completion_status()
+ self.update_actual_date()
def on_cancel(self):
self.check_if_last_visit()
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
index 467441d841..158f143ae8 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-02-22 01:28:06",
"doctype": "DocType",
@@ -8,14 +9,15 @@
"field_order": [
"item_code",
"item_name",
+ "column_break_3",
+ "service_person",
"serial_no",
+ "section_break_6",
"description",
"work_details",
- "service_person",
"work_done",
"prevdoc_doctype",
- "prevdoc_docname",
- "prevdoc_detail_docname"
+ "prevdoc_docname"
],
"fields": [
{
@@ -62,6 +64,8 @@
"fieldtype": "Section Break"
},
{
+ "fetch_from": "prevdoc_detail_docname.sales_person",
+ "fetch_if_empty": 1,
"fieldname": "service_person",
"fieldtype": "Link",
"in_list_view": 1,
@@ -83,49 +87,30 @@
{
"fieldname": "prevdoc_doctype",
"fieldtype": "Link",
+ "hidden": 1,
"label": "Document Type",
- "no_copy": 1,
- "oldfieldname": "prevdoc_doctype",
- "oldfieldtype": "Data",
- "options": "DocType",
- "print_hide": 1,
- "print_width": "150px",
- "read_only": 1,
- "report_hide": 1,
- "width": "150px"
+ "options": "DocType"
},
{
"fieldname": "prevdoc_docname",
"fieldtype": "Dynamic Link",
+ "hidden": 1,
"label": "Against Document No",
- "no_copy": 1,
- "oldfieldname": "prevdoc_docname",
- "oldfieldtype": "Data",
- "options": "prevdoc_doctype",
- "print_hide": 1,
- "print_width": "160px",
- "read_only": 1,
- "report_hide": 1,
- "width": "160px"
+ "options": "prevdoc_doctype"
},
{
- "fieldname": "prevdoc_detail_docname",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Against Document Detail No",
- "no_copy": 1,
- "oldfieldname": "prevdoc_detail_docname",
- "oldfieldtype": "Data",
- "print_hide": 1,
- "print_width": "160px",
- "read_only": 1,
- "report_hide": 1,
- "width": "160px"
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
- "modified": "2020-09-18 17:26:09.703215",
+ "links": [],
+ "modified": "2021-05-27 17:47:21.474282",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance Visit Purpose",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 288c1d0cd6..64d584118f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -211,16 +211,27 @@ frappe.ui.form.on('Production Plan', {
});
},
- get_items: function(frm) {
+ get_items: function (frm) {
+ frm.clear_table('prod_plan_references');
+
frappe.call({
method: "get_items",
freeze: true,
doc: frm.doc,
- callback: function() {
+ callback: function () {
refresh_field('po_items');
}
});
},
+ combine_items: function (frm) {
+ frm.clear_table('prod_plan_references');
+
+ frappe.call({
+ method: "get_items",
+ freeze: true,
+ doc: frm.doc,
+ });
+ },
get_items_for_mr: function(frm) {
if (!frm.doc.for_warehouse) {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index f11470086a..1c0dde227c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -28,7 +28,10 @@
"material_requests",
"select_items_to_manufacture_section",
"get_items",
+ "combine_items",
"po_items",
+ "section_break_25",
+ "prod_plan_references",
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
@@ -316,13 +319,31 @@
"fieldname": "include_safety_stock",
"fieldtype": "Check",
"label": "Include Safety Stock in Required Qty Calculation"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.get_items_from == 'Sales Order'",
+ "fieldname": "combine_items",
+ "fieldtype": "Check",
+ "label": "Consolidate Items"
+ },
+ {
+ "fieldname": "section_break_25",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "prod_plan_references",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Production Plan Item Reference",
+ "options": "Production Plan Item Reference"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-03-08 11:17:25.470147",
+ "modified": "2021-05-24 16:59:03.643211",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index a3e23a6897..46e047654b 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -96,8 +96,10 @@ class ProductionPlan(Document):
@frappe.whitelist()
def get_items(self):
+ self.set('po_items', [])
if self.get_items_from == "Sales Order":
- self.get_so_items()
+ self.get_so_items()
+
elif self.get_items_from == "Material Request":
self.get_mr_items()
@@ -165,9 +167,31 @@ class ProductionPlan(Document):
self.calculate_total_planned_qty()
def add_items(self, items):
- self.set('po_items', [])
+ refs = {}
for data in items:
item_details = get_item_details(data.item_code)
+ if self.combine_items:
+ if item_details.bom_no in refs:
+ refs[item_details.bom_no]['so_details'].append({
+ 'sales_order': data.parent,
+ 'sales_order_item': data.name,
+ 'qty': data.pending_qty
+ })
+ refs[item_details.bom_no]['qty'] += data.pending_qty
+ continue
+
+ else:
+ refs[item_details.bom_no] = {
+ 'qty': data.pending_qty,
+ 'po_item_ref': data.name,
+ 'so_details': []
+ }
+ refs[item_details.bom_no]['so_details'].append({
+ 'sales_order': data.parent,
+ 'sales_order_item': data.name,
+ 'qty': data.pending_qty
+ })
+
pi = self.append('po_items', {
'include_exploded_items': 1,
'warehouse': data.warehouse,
@@ -185,11 +209,28 @@ class ProductionPlan(Document):
pi.sales_order = data.parent
pi.sales_order_item = data.name
pi.description = data.description
-
+
elif self.get_items_from == "Material Request":
pi.material_request = data.parent
pi.material_request_item = data.name
pi.description = data.description
+
+ if refs:
+ for po_item in self.po_items:
+ po_item.planned_qty = refs[po_item.bom_no]['qty']
+ po_item.pending_qty = refs[po_item.bom_no]['qty']
+ po_item.sales_order = ''
+ self.add_pp_ref(refs)
+
+ def add_pp_ref(self, refs):
+ for bom_no in refs:
+ for so_detail in refs[bom_no]['so_details']:
+ self.append('prod_plan_references', {
+ 'item_reference': refs[bom_no]['po_item_ref'],
+ 'sales_order': so_detail['sales_order'],
+ 'sales_order_item': so_detail['sales_order_item'],
+ 'qty': so_detail['qty']
+ })
def calculate_total_planned_qty(self):
self.total_planned_qty = 0
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 27335aa204..768f99eb43 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -100,7 +100,7 @@ class TestProductionPlan(unittest.TestCase):
def test_production_plan_sales_orders(self):
item = 'Test Production Item 1'
- so = make_sales_order(item_code=item, qty=5)
+ so = make_sales_order(item_code=item, qty=1)
sales_order = so.name
sales_order_item = so.items[0].name
@@ -124,8 +124,8 @@ class TestProductionPlan(unittest.TestCase):
wo_doc = frappe.get_doc('Work Order', work_order)
wo_doc.update({
- 'wip_warehouse': '_Test Warehouse 1 - _TC',
- 'fg_warehouse': '_Test Warehouse - _TC'
+ 'wip_warehouse': 'Work In Progress - _TC',
+ 'fg_warehouse': 'Finished Goods - _TC'
})
wo_doc.submit()
@@ -145,6 +145,58 @@ class TestProductionPlan(unittest.TestCase):
self.assertEqual(sales_orders, [])
+ def test_production_plan_combine_items(self):
+ item = 'Test Production Item 1'
+ so = make_sales_order(item_code=item, qty=1)
+
+ pln = frappe.new_doc('Production Plan')
+ pln.company = so.company
+ pln.get_items_from = 'Sales Order'
+ pln.append('sales_orders', {
+ 'sales_order': so.name,
+ 'sales_order_date': so.transaction_date,
+ 'customer': so.customer,
+ 'grand_total': so.grand_total
+ })
+ so = make_sales_order(item_code=item, qty=2)
+ pln.append('sales_orders', {
+ 'sales_order': so.name,
+ 'sales_order_date': so.transaction_date,
+ 'customer': so.customer,
+ 'grand_total': so.grand_total
+ })
+ pln.combine_items = 1
+ pln.get_items()
+ pln.submit()
+
+ self.assertTrue(pln.po_items[0].planned_qty, 3)
+
+ pln.make_work_order()
+ work_order = frappe.db.get_value('Work Order', {
+ 'production_plan_item': pln.po_items[0].name,
+ 'production_plan': pln.name
+ }, 'name')
+
+ wo_doc = frappe.get_doc('Work Order', work_order)
+ wo_doc.update({
+ 'wip_warehouse': 'Work In Progress - _TC',
+ })
+
+ wo_doc.submit()
+ so_items = []
+ for plan_reference in pln.prod_plan_references:
+ so_items.append(plan_reference.sales_order_item)
+ so_wo_qty = frappe.db.get_value('Sales Order Item', plan_reference.sales_order_item, 'work_order_qty')
+ self.assertEqual(so_wo_qty, plan_reference.qty)
+
+ wo_doc.cancel()
+ for so_item in so_items:
+ so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
+ self.assertEqual(so_wo_qty, 0.0)
+
+ latest_plan = frappe.get_doc('Production Plan', pln.name)
+ latest_plan.cancel()
+
def test_pp_to_mr_customer_provided(self):
#Material Request from Production Plan for Customer Provided
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index d0dce53437..89ab7aa0a0 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -1,792 +1,229 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:27:49",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:27:49",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "include_exploded_items",
+ "item_code",
+ "bom_no",
+ "planned_qty",
+ "column_break_6",
+ "make_work_order_for_sub_assembly_items",
+ "warehouse",
+ "planned_start_date",
+ "section_break_9",
+ "pending_qty",
+ "ordered_qty",
+ "produced_qty",
+ "column_break_17",
+ "description",
+ "stock_uom",
+ "reference_section",
+ "sales_order",
+ "sales_order_item",
+ "column_break_19",
+ "material_request",
+ "material_request_item",
+ "product_bundle_item",
+ "item_reference"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fetch_if_empty": 0,
- "fieldname": "include_exploded_items",
- "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": "Include Exploded Items",
- "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
- },
+ "columns": 2,
+ "default": "0",
+ "fieldname": "include_exploded_items",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Include Exploded Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fetch_if_empty": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "item_code",
- "oldfieldtype": "Link",
- "options": "Item",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "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": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "oldfieldtype": "Link",
+ "options": "Item",
+ "print_width": "150px",
+ "reqd": 1,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fetch_if_empty": 0,
- "fieldname": "bom_no",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "BOM No",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "bom_no",
- "oldfieldtype": "Link",
- "options": "BOM",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "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": "bom_no",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "BOM No",
+ "oldfieldname": "bom_no",
+ "oldfieldtype": "Link",
+ "options": "BOM",
+ "print_width": "100px",
+ "reqd": 1,
"width": "100px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "planned_qty",
- "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": "Planned Qty",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "planned_qty",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "planned_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Planned Qty",
+ "oldfieldname": "planned_qty",
+ "oldfieldtype": "Currency",
+ "print_width": "100px",
+ "reqd": 1,
"width": "100px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_6",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
- "fetch_if_empty": 0,
- "fieldname": "make_work_order_for_sub_assembly_items",
- "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": "Make Work Order for Sub Assembly Items",
- "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
- },
+ "default": "0",
+ "description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
+ "fieldname": "make_work_order_for_sub_assembly_items",
+ "fieldtype": "Check",
+ "label": "Make Work Order for Sub Assembly Items"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "For Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "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": "warehouse",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "For Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "Today",
- "fetch_if_empty": 0,
- "fieldname": "planned_start_date",
- "fieldtype": "Datetime",
- "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": "Planned 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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "Today",
+ "fieldname": "planned_start_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Planned Start Date",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "section_break_9",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Quantity and 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
- },
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break",
+ "label": "Quantity and Description"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "pending_qty",
- "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": "Pending Qty",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "prevdoc_reqd_qty",
- "oldfieldtype": "Currency",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "100px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "default": "0",
+ "fieldname": "pending_qty",
+ "fieldtype": "Float",
+ "label": "Pending Qty",
+ "oldfieldname": "prevdoc_reqd_qty",
+ "oldfieldtype": "Currency",
+ "print_width": "100px",
+ "read_only": 1,
"width": "100px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "ordered_qty",
- "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": "Ordered Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "label": "Ordered Qty",
+ "print_hide": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "produced_qty",
- "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": "Produced Qty",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "label": "Produced Qty",
+ "no_copy": 1,
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_17",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_17",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 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,
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "200px",
- "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": "description",
+ "fieldtype": "Text Editor",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
+ "print_width": "200px",
+ "read_only": 1,
"width": "200px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "stock_uom",
- "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": "UOM",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "stock_uom",
- "oldfieldtype": "Data",
- "options": "UOM",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "80px",
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0,
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "oldfieldname": "stock_uom",
+ "oldfieldtype": "Data",
+ "options": "UOM",
+ "print_width": "80px",
+ "read_only": 1,
+ "reqd": 1,
"width": "80px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "reference_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": "Reference",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "reference_section",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 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,
- "oldfieldname": "source_docname",
- "oldfieldtype": "Data",
- "options": "Sales Order",
- "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": "sales_order",
+ "fieldtype": "Link",
+ "label": "Sales Order",
+ "oldfieldname": "source_docname",
+ "oldfieldtype": "Data",
+ "options": "Sales Order",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "sales_order_item",
- "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": "Sales Order Item",
- "length": 0,
- "no_copy": 1,
- "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
- },
+ "fieldname": "sales_order_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Sales Order Item",
+ "no_copy": 1,
+ "print_hide": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "column_break_19",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "material_request",
- "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": "Material Request",
- "length": 0,
- "no_copy": 0,
- "options": "Material Request",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "material_request",
+ "fieldtype": "Link",
+ "label": "Material Request",
+ "options": "Material Request",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "material_request_item",
- "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": "material_request_item",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "material_request_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "material_request_item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "product_bundle_item",
- "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": "Product Bundle Item",
- "length": 0,
- "no_copy": 1,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "product_bundle_item",
+ "fieldtype": "Link",
+ "label": "Product Bundle Item",
+ "no_copy": 1,
+ "options": "Item",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "item_reference",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Item Reference"
}
- ],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-04-08 23:09:57.199423",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Production Plan Item",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-28 19:14:57.772123",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "ASC"
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py b/erpnext/manufacturing/doctype/production_plan_item_reference/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
new file mode 100644
index 0000000000..84dee4ad28
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
@@ -0,0 +1,52 @@
+{
+ "actions": [],
+ "creation": "2021-04-22 10:32:58.896330",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "item_reference",
+ "sales_order",
+ "sales_order_item",
+ "qty"
+ ],
+ "fields": [
+ {
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sales Order Reference",
+ "options": "Sales Order"
+ },
+ {
+ "fieldname": "sales_order_item",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Sales Order Item"
+ },
+ {
+ "fieldname": "qty",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "qty"
+ },
+ {
+ "fieldname": "item_reference",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Item Reference"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-05-07 17:03:49.707487",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan Item Reference",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py
new file mode 100644
index 0000000000..51fbc3633b
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ProductionPlanItemReference(Document):
+ pass
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 8507f5eb34..2600790a59 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -240,8 +240,12 @@ class WorkOrder(Document):
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
if not self.fg_warehouse:
frappe.throw(_("For Warehouse is required before Submit"))
+
+ if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ self.update_work_order_qty_in_combined_so()
+ else:
+ self.update_work_order_qty_in_so()
- self.update_work_order_qty_in_so()
self.update_reserved_qty_for_production()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
@@ -250,9 +254,13 @@ class WorkOrder(Document):
def on_cancel(self):
self.validate_cancel()
-
frappe.db.set(self,'status', 'Cancelled')
- self.update_work_order_qty_in_so()
+
+ if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
+ self.update_work_order_qty_in_combined_so()
+ else:
+ self.update_work_order_qty_in_so()
+
self.delete_job_card()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
@@ -357,7 +365,28 @@ class WorkOrder(Document):
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
frappe.db.set_value('Sales Order Item',
self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
+
+ def update_work_order_qty_in_combined_so(self):
+ total_bundle_qty = 1
+ if self.product_bundle_item:
+ total_bundle_qty = frappe.db.sql(""" select sum(qty) from
+ `tabProduct Bundle Item` where parent = %s""", (frappe.db.escape(self.product_bundle_item)))[0][0]
+ if not total_bundle_qty:
+ # product bundle is 0 (product bundle allows 0 qty for items)
+ total_bundle_qty = 1
+
+ prod_plan = frappe.get_doc('Production Plan', self.production_plan)
+ item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
+
+ for plan_reference in prod_plan.prod_plan_references:
+ work_order_qty = 0.0
+ if plan_reference.item_reference == item_reference:
+ if self.docstatus == 1:
+ work_order_qty = flt(plan_reference.qty) / total_bundle_qty
+ frappe.db.set_value('Sales Order Item',
+ plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
+
def update_completed_qty_in_material_request(self):
if self.material_request:
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index efc072ee97..30be585e9a 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -28,7 +28,7 @@ class Member(Document):
def setup_subscription(self):
non_profit_settings = frappe.get_doc('Non Profit Settings')
if not non_profit_settings.enable_razorpay_for_memberships:
- frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format(
+ frappe.throw(_('Please check Enable Razorpay for Memberships in {0} to setup subscription')).format(
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
controller = get_payment_gateway_controller("Razorpay")
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 770bef353f..95cdc308a7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -284,5 +284,8 @@ erpnext.patches.v12_0.add_ewaybill_validity_field
erpnext.patches.v13_0.germany_make_custom_fields
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
erpnext.patches.v13_0.set_pos_closing_as_failed
+execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True)
erpnext.patches.v13_0.update_timesheet_changes
+erpnext.patches.v13_0.add_doctype_to_sla #14-06-2021
erpnext.patches.v13_0.set_training_event_attendance
+erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py
index 1a99b3163b..459221e769 100644
--- a/erpnext/patches/v12_0/purchase_receipt_status.py
+++ b/erpnext/patches/v12_0/purchase_receipt_status.py
@@ -19,6 +19,9 @@ def execute():
logger.info("purchase_receipt_status: begin patch, PR count: {}"
.format(len(affected_purchase_receipts)))
+ frappe.reload_doc("stock", "doctype", "Purchase Receipt")
+ frappe.reload_doc("stock", "doctype", "Purchase Receipt Item")
+
for pr in affected_purchase_receipts:
pr_name = pr[0]
diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py
new file mode 100644
index 0000000000..e2c7fd268a
--- /dev/null
+++ b/erpnext/patches/v13_0/add_doctype_to_sla.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2020, 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('support', 'doctype', 'sla_fulfilled_on_status')
+ frappe.reload_doc('support', 'doctype', 'service_level_agreement')
+ if frappe.db.has_column('Service Level Agreement', 'enable'):
+ rename_field('Service Level Agreement', 'enable', 'enabled')
+
+ for sla in frappe.get_all('Service Level Agreement'):
+ agreement = frappe.get_doc('Service Level Agreement', sla.name)
+ agreement.document_type = 'Issue'
+ agreement.apply_sla_for_resolution = 1
+ agreement.append('sla_fulfilled_on', {'status': 'Resolved'})
+ agreement.append('sla_fulfilled_on', {'status': 'Closed'})
+ agreement.save()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
new file mode 100644
index 0000000000..48325fc2d4
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_issue_status_hold_to_on_hold.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ frappe.reload_doc("support", "doctype", "issue")
+ rename_status()
+
+def rename_status():
+ frappe.db.sql("""
+ UPDATE
+ `tabIssue`
+ SET
+ status = 'On Hold'
+ WHERE
+ status = 'Hold'
+ """)
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 5e17a5cbb7..d9efe458dc 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -7,25 +7,30 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "employee_details_section",
"naming_series",
"employee",
"employee_name",
- "salary_component",
- "type",
- "amount",
- "ref_doctype",
- "ref_docname",
- "amended_from",
"column_break_5",
"company",
"department",
+ "salary_details_section",
+ "salary_component",
+ "type",
"currency",
+ "amount",
+ "column_break_13",
+ "is_recurring",
+ "payroll_date",
"from_date",
"to_date",
- "payroll_date",
- "is_recurring",
+ "properties_and_references_section",
+ "deduct_full_tax_on_selected_payroll_date",
+ "ref_doctype",
+ "ref_docname",
+ "column_break_22",
"overwrite_salary_structure_amount",
- "deduct_full_tax_on_selected_payroll_date"
+ "amended_from"
],
"fields": [
{
@@ -81,7 +86,7 @@
},
{
"depends_on": "eval:(doc.is_recurring==0)",
- "description": "Date on which this component is applied",
+ "description": "The date on which Salary Component with Amount will contribute for Earnings/Deduction in Salary Slip. ",
"fieldname": "payroll_date",
"fieldtype": "Date",
"in_list_view": 1,
@@ -159,6 +164,7 @@
"fieldname": "ref_docname",
"fieldtype": "Dynamic Link",
"label": "Reference Document",
+ "no_copy": 1,
"options": "ref_doctype",
"read_only": 1
},
@@ -171,11 +177,34 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1
+ },
+ {
+ "fieldname": "employee_details_section",
+ "fieldtype": "Section Break",
+ "label": "Employee Details"
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "salary_details_section",
+ "fieldtype": "Section Break",
+ "label": "Salary Details"
+ },
+ {
+ "fieldname": "properties_and_references_section",
+ "fieldtype": "Section Break",
+ "label": "Properties and References"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2021-03-31 22:33:59.098532",
+ "modified": "2021-05-26 11:10:00.812698",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Additional Salary",
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
index b8e56ae42a..049ea265cc 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
@@ -10,8 +10,8 @@ from frappe.utils import getdate
class RetentionBonus(Document):
def validate(self):
- if frappe.get_value('Employee', self.employee, 'status') == 'Left':
- frappe.throw(_('Cannot create Retention Bonus for left Employees'))
+ if frappe.get_value('Employee', self.employee, 'status') != 'Active':
+ frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
if getdate(self.bonus_payment_date) < getdate():
frappe.throw(_('Bonus Payment Date cannot be a past date'))
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index ceeecb28a2..7b997a1153 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -156,31 +156,31 @@ cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) {
var d = locals[cdt][cdn];
var msg = "";
- if(d.account_head && !d.description) {
+ if (d.account_head && !d.description) {
// set description from account head
d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
}
- if(!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
+ if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
msg = __("Please select Charge Type first");
d.row_id = "";
d.rate = d.tax_amount = 0.0;
- } else if((d.charge_type == 'Actual' || d.charge_type == 'On Net Total') && d.row_id) {
+ } else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
d.row_id = "";
- } else if((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
+ } else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
if (d.idx == 1) {
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
d.charge_type = '';
} else if (!d.row_id) {
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
d.row_id = "";
- } else if(d.row_id && d.row_id >= d.idx) {
+ } else if (d.row_id && d.row_id >= d.idx) {
msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
d.row_id = "";
}
}
- if(msg) {
+ if (msg) {
frappe.validated = false;
refresh_field("taxes");
frappe.throw(msg);
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 32439b6819..cc33b8bc35 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -12,7 +12,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
effective_item_rate = item.blanket_order_rate;
}
- if(item.margin_type == "Percentage"){
+ if (item.margin_type == "Percentage") {
item.rate_with_margin = flt(effective_item_rate)
+ flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100);
} else {
@@ -22,7 +22,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
item_rate = flt(item.rate_with_margin , precision("rate", item));
- if(item.discount_percentage){
+ if (item.discount_percentage) {
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
}
@@ -73,15 +73,18 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
_calculate_taxes_and_totals() {
- this.validate_conversion_rate();
- this.calculate_item_values();
- this.initialize_taxes();
- this.determine_exclusive_rate();
- this.calculate_net_total();
- this.calculate_taxes();
- this.manipulate_grand_total_for_inclusive_tax();
- this.calculate_totals();
- this._cleanup();
+ frappe.run_serially([
+ () => this.validate_conversion_rate(),
+ () => this.calculate_item_values(),
+ () => this.update_item_tax_map(),
+ () => this.initialize_taxes(),
+ () => this.determine_exclusive_rate(),
+ () => this.calculate_net_total(),
+ () => this.calculate_taxes(),
+ () => this.manipulate_grand_total_for_inclusive_tax(),
+ () => this.calculate_totals(),
+ () => this._cleanup()
+ ]);
}
validate_conversion_rate() {
@@ -265,6 +268,65 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
}
+ update_item_tax_map() {
+ let me = this;
+ let item_codes = [];
+ let item_rates = {};
+ $.each(this.frm.doc.items || [], function(i, item) {
+ if (item.item_code) {
+ // Use combination of name and item code in case same item is added multiple times
+ item_codes.push([item.item_code, item.name]);
+ item_rates[item.name] = item.net_rate;
+ }
+ });
+
+ if (item_codes.length) {
+ return this.frm.call({
+ method: "erpnext.stock.get_item_details.get_item_tax_info",
+ args: {
+ company: me.frm.doc.company,
+ tax_category: cstr(me.frm.doc.tax_category),
+ item_codes: item_codes,
+ item_rates: item_rates
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ $.each(me.frm.doc.items || [], function(i, item) {
+ if (item.name && r.message.hasOwnProperty(item.name)) {
+ item.item_tax_template = r.message[item.name].item_tax_template;
+ item.item_tax_rate = r.message[item.name].item_tax_rate;
+ me.add_taxes_from_item_tax_template(item.item_tax_rate);
+ } else {
+ item.item_tax_template = "";
+ item.item_tax_rate = "{}";
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ add_taxes_from_item_tax_template(item_tax_map) {
+ let me = this;
+
+ if (item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) {
+ if (typeof (item_tax_map) == "string") {
+ item_tax_map = JSON.parse(item_tax_map);
+ }
+
+ $.each(item_tax_map, function(tax, rate) {
+ let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax);
+ if (!found) {
+ let child = frappe.model.add_child(me.frm.doc, "taxes");
+ child.charge_type = "On Net Total";
+ child.account_head = tax;
+ child.rate = 0;
+ }
+ });
+ }
+ }
+
calculate_taxes() {
var me = this;
this.frm.doc.rounding_adjustment = 0;
@@ -408,6 +470,11 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
let tax_detail = tax.item_wise_tax_detail;
let key = item.item_code || item.item_name;
+ if(typeof (tax_detail) == "string") {
+ tax.item_wise_tax_detail = JSON.parse(tax.item_wise_tax_detail);
+ tax_detail = tax.item_wise_tax_detail;
+ }
+
let item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate;
if (tax_detail && tax_detail[key])
item_wise_tax_amount += tax_detail[key][1];
@@ -564,6 +631,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
});
}
+
+ this.frm.refresh_fields();
}
set_discount_amount() {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 0ffda07cde..7e1ffa96a2 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -6,6 +6,7 @@ frappe.provide('erpnext.accounts.dimensions');
erpnext.TransactionController = class TransactionController extends erpnext.taxes_and_totals {
setup() {
super.setup();
+ let me = this;
frappe.flags.hide_serial_batch_dialog = true;
frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
@@ -43,8 +44,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn);
});
-
-
frappe.ui.form.on(this.frm.cscript.tax_table, "rate", function(frm, cdt, cdn) {
cur_frm.cscript.calculate_taxes_and_totals();
});
@@ -121,7 +120,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
});
- var me = this;
if(this.frm.fields_dict["items"].grid.get_field('batch_no')) {
this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
return me.set_query_for_batch(doc, cdt, cdn);
@@ -261,11 +259,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
return;
}
- var me = this;
- var inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
+
+ const me = this;
+ if (!this.frm.is_new() && this.frm.doc.docstatus === 0) {
+ this.frm.add_custom_button(__("Quality Inspection(s)"), () => {
+ me.make_quality_inspection();
+ }, __("Create"));
+ this.frm.page.set_inner_btn_group_as_primary(__('Create'));
+ }
+
+ const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
? "Incoming" : "Outgoing";
- var quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
+ let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
quality_inspection_field.get_route_options_for_new_doc = function(row) {
if(me.frm.is_new()) return;
return {
@@ -280,7 +286,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ let d = locals[cdt][cdn];
return {
filters: {
docstatus: 1,
@@ -556,6 +562,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
name: me.frm.doc.name,
project: item.project || me.frm.doc.project,
qty: item.qty || 1,
+ net_rate: item.rate,
stock_qty: item.stock_qty,
conversion_factor: item.conversion_factor,
weight_per_unit: item.weight_per_unit,
@@ -835,9 +842,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
- () => me.update_item_tax_map(),
() => me.apply_default_taxes(),
- () => me.apply_pricing_rule()
+ () => me.apply_pricing_rule(),
+ () => me.calculate_taxes_and_totals()
]);
}
}
@@ -953,15 +960,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
(this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) {
var message1 = "";
var message2 = "";
- var final_message = "Please clear the ";
+ var final_message = __("Please clear the") + " ";
if (this.frm.doc.payment_terms_template) {
- message1 = "selected Payment Terms Template";
+ message1 = __("selected Payment Terms Template");
final_message = final_message + message1;
}
if ((this.frm.doc.payment_schedule || []).length) {
- message2 = "Payment Schedule Table";
+ message2 = __("Payment Schedule Table");
if (message1.length !== 0) message2 = " and " + message2;
final_message = final_message + message2;
}
@@ -1816,7 +1823,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
callback: function(r) {
if(!r.exc) {
item.item_tax_rate = r.message;
- me.add_taxes_from_item_tax_template(item.item_tax_rate);
me.calculate_taxes_and_totals();
}
}
@@ -1827,43 +1833,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
- update_item_tax_map() {
- var me = this;
- var item_codes = [];
- $.each(this.frm.doc.items || [], function(i, item) {
- if(item.item_code) {
- item_codes.push(item.item_code);
- }
- });
-
- if(item_codes.length) {
- return this.frm.call({
- method: "erpnext.stock.get_item_details.get_item_tax_info",
- args: {
- company: me.frm.doc.company,
- tax_category: cstr(me.frm.doc.tax_category),
- item_codes: item_codes
- },
- callback: function(r) {
- if(!r.exc) {
- $.each(me.frm.doc.items || [], function(i, item) {
- if(item.item_code && r.message.hasOwnProperty(item.item_code)) {
- if (!item.item_tax_template) {
- item.item_tax_template = r.message[item.item_code].item_tax_template;
- item.item_tax_rate = r.message[item.item_code].item_tax_rate;
- }
- me.add_taxes_from_item_tax_template(item.item_tax_rate);
- } else {
- item.item_tax_template = "";
- item.item_tax_rate = "{}";
- }
- });
- me.calculate_taxes_and_totals();
- }
- }
- });
- }
- }
is_recurring() {
// set default values for recurring documents
@@ -1949,7 +1918,131 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
});
}
- get_method_for_payment(){
+ make_quality_inspection() {
+ let data = [];
+ const fields = [
+ {
+ label: "Items",
+ fieldtype: "Table",
+ fieldname: "items",
+ cannot_add_rows: true,
+ in_place_edit: true,
+ data: data,
+ get_data: () => {
+ return data;
+ },
+ fields: [
+ {
+ fieldtype: "Data",
+ fieldname: "docname",
+ hidden: true
+ },
+ {
+ fieldtype: "Read Only",
+ fieldname: "item_code",
+ label: __("Item Code"),
+ in_list_view: true
+ },
+ {
+ fieldtype: "Read Only",
+ fieldname: "item_name",
+ label: __("Item Name"),
+ in_list_view: true
+ },
+ {
+ fieldtype: "Float",
+ fieldname: "qty",
+ label: __("Accepted Quantity"),
+ in_list_view: true,
+ read_only: true
+ },
+ {
+ fieldtype: "Float",
+ fieldname: "sample_size",
+ label: __("Sample Size"),
+ reqd: true,
+ in_list_view: true
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "description",
+ label: __("Description"),
+ hidden: true
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "serial_no",
+ label: __("Serial No"),
+ hidden: true
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "batch_no",
+ label: __("Batch No"),
+ hidden: true
+ }
+ ]
+ }
+ ];
+
+ const me = this;
+ const dialog = new frappe.ui.Dialog({
+ title: __("Select Items for Quality Inspection"),
+ fields: fields,
+ primary_action: function () {
+ const data = dialog.get_values();
+ frappe.call({
+ method: "erpnext.controllers.stock_controller.make_quality_inspections",
+ args: {
+ doctype: me.frm.doc.doctype,
+ docname: me.frm.doc.name,
+ items: data.items
+ },
+ freeze: true,
+ callback: function (r) {
+ if (r.message.length > 0) {
+ if (r.message.length === 1) {
+ frappe.set_route("Form", "Quality Inspection", r.message[0]);
+ } else {
+ frappe.route_options = {
+ "reference_type": me.frm.doc.doctype,
+ "reference_name": me.frm.doc.name
+ };
+ frappe.set_route("List", "Quality Inspection");
+ }
+ }
+ dialog.hide();
+ }
+ });
+ },
+ primary_action_label: __("Create")
+ });
+
+ this.frm.doc.items.forEach(item => {
+ if (!item.quality_inspection) {
+ let dialog_items = dialog.fields_dict.items;
+ dialog_items.df.data.push({
+ "docname": item.name,
+ "item_code": item.item_code,
+ "item_name": item.item_name,
+ "qty": item.qty,
+ "description": item.description,
+ "serial_no": item.serial_no,
+ "batch_no": item.batch_no
+ });
+ dialog_items.grid.refresh();
+ }
+ });
+
+ data = dialog.fields_dict.items.df.data;
+ if (!data.length) {
+ frappe.msgprint(__("All items in this document already have a linked Quality Inspection."));
+ } else {
+ dialog.show();
+ }
+ }
+
+ get_method_for_payment() {
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index e78992302f..aa9bba17c7 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -644,14 +644,14 @@ frappe.help.help_links["List/Payment Request"] = [
frappe.help.help_links["List/Asset"] = [
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
];
frappe.help.help_links["List/Asset Category"] = [
{
label: "Asset Category",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/asset/asset-category",
},
];
@@ -663,7 +663,7 @@ frappe.help.help_links["List/Item"] = [
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
label: "Item Price",
- url: docsUrl + "user/manual/en/stock/item/item-price",
+ url: docsUrl + "user/manual/en/stock/item-price",
},
{
label: "Barcode",
@@ -672,25 +672,25 @@ frappe.help.help_links["List/Item"] = [
},
{
label: "Item Wise Taxation",
- url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ url: docsUrl + "user/manual/en/accounts/item-tax-template",
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
{
label: "Item Codification",
- url: docsUrl + "user/manual/en/stock/item/item-codification",
+ url: docsUrl + "user/manual/en/stock/articles/item-codification",
},
{
label: "Item Variants",
- url: docsUrl + "user/manual/en/stock/item/item-variants",
+ url: docsUrl + "user/manual/en/stock/item-variants",
},
{
label: "Item Valuation",
url:
docsUrl +
- "user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+ "user/manual/en/stock/articles/item-valuation-fifo-and-moving-average",
},
];
@@ -698,7 +698,7 @@ frappe.help.help_links["Form/Item"] = [
{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
{
label: "Item Price",
- url: docsUrl + "user/manual/en/stock/item/item-price",
+ url: docsUrl + "user/manual/en/stock/item-price",
},
{
label: "Barcode",
@@ -707,19 +707,19 @@ frappe.help.help_links["Form/Item"] = [
},
{
label: "Item Wise Taxation",
- url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+ url: docsUrl + "user/manual/en/accounts/item-tax-template",
},
{
label: "Managing Fixed Assets",
- url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+ url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets",
},
{
label: "Item Codification",
- url: docsUrl + "user/manual/en/stock/item/item-codification",
+ url: docsUrl + "user/manual/en/stock/articles/item-codification",
},
{
label: "Item Variants",
- url: docsUrl + "user/manual/en/stock/item/item-variants",
+ url: docsUrl + "user/manual/en/stock/item-variants",
},
{
label: "Item Valuation",
diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js
index 7df976c1be..4c23669dbb 100644
--- a/erpnext/public/js/payment/payments.js
+++ b/erpnext/public/js/payment/payments.js
@@ -13,17 +13,16 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
this.$body = this.dialog.body;
this.set_payment_primary_action();
this.make_keyboard();
- this.select_text()
+ this.select_text();
}
- select_text(){
- var me = this;
- $(this.$body).find('.form-control').click(function(){
+ select_text() {
+ $(this.$body).find('.form-control').click(function() {
$(this).select();
- })
+ });
}
- set_payment_primary_action(){
+ set_payment_primary_action() {
var me = this;
this.dialog.set_primary_action(__("Submit"), function() {
@@ -50,8 +49,8 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
make_multimode_payment(){
var me = this;
- if(this.frm.doc.change_amount > 0){
- me.payment_val = me.doc.outstanding_amount
+ if (this.frm.doc.change_amount > 0) {
+ me.payment_val = me.doc.outstanding_amount;
}
this.payments = frappe.model.add_child(this.frm.doc, 'Multi Mode Payment', "payments");
@@ -62,8 +61,8 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
show_payment_details(){
var me = this;
var multimode_payments = $(this.$body).find('.multimode-payments').empty();
- if(this.frm.doc.payments.length){
- $.each(this.frm.doc.payments, function(index, data){
+ if (this.frm.doc.payments.length) {
+ $.each(this.frm.doc.payments, function(index, data) {
$(frappe.render_template('payment_details', {
mode_of_payment: data.mode_of_payment,
amount: data.amount,
@@ -86,14 +85,14 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
set_outstanding_amount(){
this.selected_mode = $(this.$body).find(repl("input[idx='%(idx)s']",{'idx': this.idx}));
- this.highlight_selected_row()
- this.payment_val = 0.0
- if(this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0){
+ this.highlight_selected_row();
+ this.payment_val = 0.0;
+ if (this.frm.doc.outstanding_amount > 0 && flt(this.selected_mode.val()) == 0.0) {
//When user first time click on row
this.payment_val = flt(this.frm.doc.outstanding_amount / this.frm.doc.conversion_rate, precision("outstanding_amount"))
this.selected_mode.val(format_currency(this.payment_val, this.frm.doc.currency));
- this.update_payment_amount()
- }else if(flt(this.selected_mode.val()) > 0){
+ this.update_payment_amount();
+ } else if (flt(this.selected_mode.val()) > 0) {
//If user click on existing row which has value
this.payment_val = flt(this.selected_mode.val());
}
@@ -108,68 +107,67 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
this.bind_numeric_keys_event();
}
- bind_form_control_event(){
+ bind_form_control_event() {
var me = this;
- $(this.$body).find('.pos-payment-row').click(function(){
+ $(this.$body).find('.pos-payment-row').click(function() {
me.idx = $(this).attr("idx");
- me.set_outstanding_amount()
- })
+ me.set_outstanding_amount();
+ });
- $(this.$body).find('.form-control').click(function(){
+ $(this.$body).find('.form-control').click(function() {
me.idx = $(this).attr("idx");
me.set_outstanding_amount();
me.update_paid_amount(true);
- })
+ });
- $(this.$body).find('.write_off_amount').change(function(){
+ $(this.$body).find('.write_off_amount').change(function() {
me.write_off_amount(flt($(this).val()), precision("write_off_amount"));
- })
+ });
- $(this.$body).find('.change_amount').change(function(){
+ $(this.$body).find('.change_amount').change(function() {
me.change_amount(flt($(this).val()), precision("change_amount"));
- })
+ });
}
- highlight_selected_row(){
- var me = this;
- var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']",{'idx': this.idx}));
- $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode')
- selected_row.addClass('selected-payment-mode')
+ highlight_selected_row() {
+ var selected_row = $(this.$body).find(repl(".pos-payment-row[idx='%(idx)s']", {'idx': this.idx}));
+ $(this.$body).find('.pos-payment-row').removeClass('selected-payment-mode');
+ selected_row.addClass('selected-payment-mode');
$(this.$body).find('.amount').attr('disabled', true);
this.selected_mode.attr('disabled', false);
}
- bind_numeric_keys_event(){
+ bind_numeric_keys_event() {
var me = this;
$(this.$body).find('.pos-keyboard-key').click(function(){
me.payment_val += $(this).text();
- me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency))
- me.idx = me.selected_mode.attr("idx")
- me.update_paid_amount()
- })
+ me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency));
+ me.idx = me.selected_mode.attr("idx");
+ me.update_paid_amount();
+ });
- $(this.$body).find('.delete-btn').click(function(){
+ $(this.$body).find('.delete-btn').click(function() {
me.payment_val = cstr(flt(me.selected_mode.val())).slice(0, -1);
me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency));
- me.idx = me.selected_mode.attr("idx")
+ me.idx = me.selected_mode.attr("idx");
me.update_paid_amount();
})
}
- bind_amount_change_event(){
+ bind_amount_change_event() {
var me = this;
- this.selected_mode.change(function(){
+ this.selected_mode.change(function() {
me.payment_val = flt($(this).val()) || 0.0;
- me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency))
- me.idx = me.selected_mode.attr("idx")
- me.update_payment_amount()
- })
+ me.selected_mode.val(format_currency(me.payment_val, me.frm.doc.currency));
+ me.idx = me.selected_mode.attr("idx");
+ me.update_payment_amount();
+ });
}
clear_amount() {
var me = this;
- $(this.$body).find('.clr').click(function(e){
+ $(this.$body).find('.clr').click(function(e) {
e.stopPropagation();
me.idx = $(this).attr("idx");
me.selected_mode = $(me.$body).find(repl("input[idx='%(idx)s']",{'idx': me.idx}));
@@ -177,50 +175,48 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
me.selected_mode.val(0.0);
me.highlight_selected_row();
me.update_payment_amount();
- })
+ });
}
write_off_amount(write_off_amount) {
- var me = this;
-
this.frm.doc.write_off_amount = flt(write_off_amount, precision("write_off_amount"));
this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate,
precision("base_write_off_amount"));
- this.calculate_outstanding_amount(false)
- this.show_amounts()
+ this.calculate_outstanding_amount(false);
+ this.show_amounts();
}
change_amount(change_amount) {
var me = this;
this.frm.doc.change_amount = flt(change_amount, precision("change_amount"));
- this.calculate_write_off_amount()
- this.show_amounts()
+ this.calculate_write_off_amount();
+ this.show_amounts();
}
update_paid_amount(update_write_off) {
var me = this;
- if(in_list(['change_amount', 'write_off_amount'], this.idx)){
+ if (in_list(['change_amount', 'write_off_amount'], this.idx)) {
var value = me.selected_mode.val();
- if(me.idx == 'change_amount'){
- me.change_amount(value)
- } else{
+ if (me.idx == 'change_amount') {
+ me.change_amount(value);
+ } else {
if(flt(value) == 0 && update_write_off && me.frm.doc.outstanding_amount > 0) {
value = flt(me.frm.doc.outstanding_amount / me.frm.doc.conversion_rate, precision(me.idx));
}
- me.write_off_amount(value)
+ me.write_off_amount(value);
}
- }else{
- this.update_payment_amount()
+ } else {
+ this.update_payment_amount();
}
}
update_payment_amount(){
var me = this;
- $.each(this.frm.doc.payments, function(index, data){
- if(cint(me.idx) == cint(data.idx)){
- data.amount = flt(me.selected_mode.val(), 2)
+ $.each(this.frm.doc.payments, function(index, data) {
+ if (cint(me.idx) == cint(data.idx)) {
+ data.amount = flt(me.selected_mode.val(), 2);
}
})
@@ -232,8 +228,8 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
var me = this;
$(this.$body).find(".write_off_amount").val(format_currency(this.frm.doc.write_off_amount, this.frm.doc.currency));
$(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency));
- $(this.$body).find('.change_amount').val(format_currency(this.frm.doc.change_amount, this.frm.doc.currency))
- $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency))
+ $(this.$body).find('.change_amount').val(format_currency(this.frm.doc.change_amount, this.frm.doc.currency));
+ $(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, frappe.get_doc(":Company", this.frm.doc.company).default_currency));
this.update_invoice();
}
}
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index ce40ced11f..db7c034596 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -749,6 +749,151 @@ $(document).on('app_ready', function() {
}
});
+// Show SLA dashboard
+$(document).on('app_ready', function() {
+ frappe.call({
+ method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_sla_doctypes',
+ callback: function(r) {
+ if (!r.message)
+ return;
+
+ $.each(r.message, function(_i, d) {
+ frappe.ui.form.on(d, {
+ onload: function(frm) {
+ if (!frm.doc.service_level_agreement)
+ return;
+
+ frappe.call({
+ method: 'erpnext.support.doctype.service_level_agreement.service_level_agreement.get_service_level_agreement_filters',
+ args: {
+ doctype: frm.doc.doctype,
+ name: frm.doc.service_level_agreement,
+ customer: frm.doc.customer
+ },
+ callback: function (r) {
+ if (r && r.message) {
+ frm.set_query('priority', function() {
+ return {
+ filters: {
+ 'name': ['in', r.message.priority],
+ }
+ };
+ });
+ frm.set_query('service_level_agreement', function() {
+ return {
+ filters: {
+ 'name': ['in', r.message.service_level_agreements],
+ }
+ };
+ });
+ }
+ }
+ });
+ },
+
+ refresh: function(frm) {
+ if (frm.doc.status !== 'Closed' && frm.doc.service_level_agreement
+ && frm.doc.agreement_status === 'Ongoing') {
+ frappe.call({
+ 'method': 'frappe.client.get',
+ args: {
+ doctype: 'Service Level Agreement',
+ name: frm.doc.service_level_agreement
+ },
+ callback: function(data) {
+ let statuses = data.message.pause_sla_on;
+ const hold_statuses = [];
+ $.each(statuses, (_i, entry) => {
+ hold_statuses.push(entry.status);
+ });
+ if (hold_statuses.includes(frm.doc.status)) {
+ frm.dashboard.clear_headline();
+ let message = {'indicator': 'orange', 'msg': __('SLA is on hold since {0}', [moment(frm.doc.on_hold_since).fromNow(true)])};
+ frm.dashboard.set_headline_alert(
+ '