Merge branch 'develop' into email-digest
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index a249783..2c52314 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -43,7 +43,7 @@
{
"hidden": 0,
"label": "Bank Statement",
- "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
+ "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
@@ -98,7 +98,7 @@
"idx": 0,
"is_standard": 1,
"label": "Accounting",
- "modified": "2020-06-19 12:42:44.054598",
+ "modified": "2020-09-03 10:37:07.865801",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
@@ -158,4 +158,4 @@
"type": "Dashboard"
}
]
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index a09face..409c15f 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -638,20 +638,12 @@
return { filters: filters };
},
- reverse_journal_entry: function(frm) {
- var me = frm.doc;
- for(var i=0; i<me.accounts.length; i++) {
- me.accounts[i].credit += me.accounts[i].debit;
- me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit;
- me.accounts[i].credit -= me.accounts[i].debit;
- me.accounts[i].credit_in_account_currency = me.accounts[i].credit;
- me.accounts[i].debit_in_account_currency = me.accounts[i].debit;
- me.accounts[i].reference_type = "Journal Entry";
- me.accounts[i].reference_name = me.name
- }
- frm.copy_doc();
- cur_frm.reload_doc();
- }
+ reverse_journal_entry: function() {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
+ frm: cur_frm
+ })
+ },
});
$.extend(erpnext.journal_entry, {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index dda1708..0a385d0 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1021,3 +1021,34 @@
journal_entry.posting_date = nowdate()
journal_entry.inter_company_journal_entry_reference = name
return journal_entry.as_dict()
+
+@frappe.whitelist()
+def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
+ from frappe.model.mapper import get_mapped_doc
+
+ def update_accounts(source, target, source_parent):
+ target.reference_type = "Journal Entry"
+ target.reference_name = source_parent.name
+
+ doclist = get_mapped_doc("Journal Entry", source_name, {
+ "Journal Entry": {
+ "doctype": "Journal Entry",
+ "validation": {
+ "docstatus": ["=", 1]
+ }
+ },
+ "Journal Entry Account": {
+ "doctype": "Journal Entry Account",
+ "field_map": {
+ "account_currency": "account_currency",
+ "exchange_rate": "exchange_rate",
+ "debit_in_account_currency": "credit_in_account_currency",
+ "debit": "credit",
+ "credit_in_account_currency": "debit_in_account_currency",
+ "credit": "debit",
+ },
+ "postprocess": update_accounts,
+ },
+ }, target_doc, ignore_permissions=ignore_permissions)
+
+ return doclist
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 479d4b6..53c0758 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -167,6 +167,49 @@
self.assertFalse(gle)
+ def test_reverse_journal_entry(self):
+ from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
+ jv = make_journal_entry("_Test Bank USD - _TC",
+ "Sales - _TC", 100, exchange_rate=50, save=False)
+
+ jv.get("accounts")[1].credit_in_account_currency = 5000
+ jv.get("accounts")[1].exchange_rate = 1
+ jv.submit()
+
+ rjv = make_reverse_journal_entry(jv.name)
+ rjv.posting_date = nowdate()
+ rjv.submit()
+
+
+ gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
+ debit_in_account_currency, credit_in_account_currency
+ from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
+ order by account asc""", rjv.name, as_dict=1)
+
+ self.assertTrue(gl_entries)
+
+
+ expected_values = {
+ "_Test Bank USD - _TC": {
+ "account_currency": "USD",
+ "debit": 0,
+ "debit_in_account_currency": 0,
+ "credit": 5000,
+ "credit_in_account_currency": 100,
+ },
+ "Sales - _TC": {
+ "account_currency": "INR",
+ "debit": 5000,
+ "debit_in_account_currency": 5000,
+ "credit": 0,
+ "credit_in_account_currency": 0,
+ }
+ }
+
+ for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_values[gle.account][field], gle[field])
+
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD
jv = make_journal_entry("_Test Bank USD - _TC",
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js
index 2800c19..1ec6805 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js
+++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.js
@@ -10,13 +10,15 @@
}
};
});
+ },
- if (frm.doc.company) {
+ type: function(frm) {
+ if (frm.doc.company && frm.doc.type) {
frm.set_query("account", function() {
return {
filters: {
'company': frm.doc.company,
- 'root_type': 'Liability',
+ 'root_type': frm.doc.type === 'Income' ? 'Liability' : 'Asset',
'is_group': 0
}
};
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json
index 4daafef..457e98c 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json
+++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json
@@ -60,6 +60,7 @@
"reqd": 1
},
{
+ "depends_on": "eval: doc.type",
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
@@ -73,9 +74,10 @@
"reqd": 1
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-02-06 18:18:09.852844",
+ "modified": "2020-09-03 18:07:02.463754",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Deferred Accounting",
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index cf3deb8..01d3903 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -45,8 +45,8 @@
}, as_dict=1)
if accounting_periods:
- frappe.throw(_("You can't create accounting entries in the closed accounting period {0}")
- .format(accounting_periods[0].name), ClosedAccountingPeriod)
+ frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
+ .format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
def process_gl_map(gl_map, merge_entries=True):
if merge_entries:
@@ -301,8 +301,9 @@
})
if gl_entries:
- set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
+ validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
+ set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
for entry in gl_entries:
entry['name'] = None
@@ -342,7 +343,7 @@
"""
Set is_cancelled=1 in all original gl entries for the voucher
"""
- frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
+ frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1,
modified=%s, modified_by=%s
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no))
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
index 3ec4d30..f547ca6 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js
@@ -71,7 +71,22 @@
fieldtype: "Check",
default: 0,
},
- ]
+ ],
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname.includes('variance')) {
+
+ if (data[column.fieldname] < 0) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ else if (data[column.fieldname] > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ }
+
+ return value;
+ }
}
erpnext.dimension_filters.forEach((dimension) => {
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 0599707..f735d87 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -43,8 +43,11 @@
def validate_filters(filters, account_details):
- if not filters.get('company'):
- frappe.throw(_('{0} is mandatory').format(_('Company')))
+ if not filters.get("company"):
+ frappe.throw(_("{0} is mandatory").format(_("Company")))
+
+ if not filters.get("from_date") and not filters.get("to_date"):
+ frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
if filters.get("account") and not account_details.get(filters.account):
frappe.throw(_("Account {0} does not exists").format(filters.account))
@@ -141,7 +144,7 @@
if filters and filters.get('cost_center'):
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
-
+
distributed_cost_center_query = """
UNION ALL
SELECT name as gl_entry,
@@ -155,7 +158,7 @@
against_voucher_type,
against_voucher,
account_currency,
- remarks, against,
+ remarks, against,
is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
FROM `tabGL Entry`,
(
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index 9cfb062..cd6bac2 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -1,24 +1,23 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-02-25 17:03:34",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2017-02-24 20:12:22.464240",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Gross Profit",
- "owner": "Administrator",
- "ref_doctype": "Sales Invoice",
- "report_name": "Gross Profit",
- "report_type": "Script Report",
+ "add_total_row": 1,
+ "creation": "2013-02-25 17:03:34",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "modified": "2020-08-13 11:26:39.112352",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Gross Profit",
+ "owner": "Administrator",
+ "ref_doctype": "Sales Invoice",
+ "report_name": "Gross Profit",
+ "report_type": "Script Report",
"roles": [
{
"role": "Accounts Manager"
- },
+ },
{
"role": "Accounts User"
}
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index 3de9526..019cefc 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -11,6 +11,8 @@
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
+from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
+from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
class TestRequestforQuotation(unittest.TestCase):
def test_quote_status(self):
@@ -110,6 +112,23 @@
self.assertEqual(supplier_quotation.items[0].qty, 5)
self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
+ def test_make_rfq_from_opportunity(self):
+ opportunity = make_opportunity(with_items=1)
+ supplier_data = get_supplier_data()
+ rfq = make_rfq(opportunity.name)
+
+ self.assertEqual(len(rfq.get("items")), len(opportunity.get("items")))
+ rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
+
+ for item in rfq.items:
+ item.warehouse = "_Test Warehouse - _TC"
+
+ for data in supplier_data:
+ rfq.append('suppliers', data)
+
+ rfq.status = 'Draft'
+ rfq.submit()
+
def make_request_for_quotation(**args):
"""
:param supplier_data: List containing supplier data
diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
index a76ffee..518d665 100644
--- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
+++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
@@ -12,7 +12,22 @@
"reqd": 1
},
{
- reqd: 1,
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ {
default: "",
options: "Item",
label: __("Item"),
@@ -45,13 +60,12 @@
}
},
{
- fieldtype: "Link",
+ fieldtype: "MultiSelectList",
label: __("Supplier Quotation"),
- options: "Supplier Quotation",
fieldname: "supplier_quotation",
default: "",
- get_query: () => {
- return { filters: { "docstatus": ["<", 2] } }
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Supplier Quotation', txt, {'docstatus': ["<", 2]});
}
},
{
@@ -63,9 +77,30 @@
get_query: () => {
return { filters: { "docstatus": ["<", 2] } }
}
+ },
+ {
+ fieldtype: "Check",
+ label: __("Include Expired"),
+ fieldname: "include_expired",
+ default: 0
}
],
+ formatter: (value, row, column, data, default_formatter) => {
+ value = default_formatter(value, row, column, data);
+
+ if(column.fieldname === "valid_till" && data.valid_till){
+ if(frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 1){
+ value = `<div style="color:red">${value}</div>`;
+ }
+ else if (frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 7){
+ value = `<div style="color:darkorange">${value}</div>`;
+ }
+ }
+
+ return value;
+ },
+
onload: (report) => {
// Create a button for setting the default supplier
report.page.add_inner_button(__("Select Default Supplier"), () => {
diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
index a33867a..4426560 100644
--- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
+++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py
@@ -16,44 +16,49 @@
supplier_quotation_data = get_data(filters, conditions)
columns = get_columns()
- data, chart_data = prepare_data(supplier_quotation_data)
+ data, chart_data = prepare_data(supplier_quotation_data, filters)
+ message = get_message()
- return columns, data, None, chart_data
+ return columns, data, message, chart_data
def get_conditions(filters):
conditions = ""
+ if filters.get("item_code"):
+ conditions += " AND sqi.item_code = %(item_code)s"
+
if filters.get("supplier_quotation"):
- conditions += " AND sqi.parent = %(supplier_quotation)s"
+ conditions += " AND sqi.parent in %(supplier_quotation)s"
if filters.get("request_for_quotation"):
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
if filters.get("supplier"):
conditions += " AND sq.supplier in %(supplier)s"
+
+ if not filters.get("include_expired"):
+ conditions += " AND sq.status != 'Expired'"
+
return conditions
def get_data(filters, conditions):
- if not filters.get("item_code"):
- return []
-
supplier_quotation_data = frappe.db.sql("""SELECT
- sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
- sq.supplier
+ sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
+ sqi.lead_time_days, sq.supplier, sq.valid_till
FROM
`tabSupplier Quotation Item` sqi,
`tabSupplier Quotation` sq
WHERE
- sqi.item_code = %(item_code)s
- AND sqi.parent = sq.name
+ sqi.parent = sq.name
AND sqi.docstatus < 2
AND sq.company = %(company)s
- AND sq.status != 'Expired'
- {0}""".format(conditions), filters, as_dict=1)
+ AND sq.transaction_date between %(from_date)s and %(to_date)s
+ {0}
+ order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1)
return supplier_quotation_data
-def prepare_data(supplier_quotation_data):
- out, suppliers, qty_list = [], [], []
+def prepare_data(supplier_quotation_data, filters):
+ out, suppliers, qty_list, chart_data = [], [], [], []
supplier_wise_map = defaultdict(list)
supplier_qty_price_map = {}
@@ -70,20 +75,24 @@
exchange_rate = 1
row = {
+ "item_code": data.get('item_code'),
"quotation": data.get("parent"),
"qty": data.get("qty"),
"price": flt(data.get("rate") * exchange_rate, float_precision),
"uom": data.get("uom"),
"request_for_quotation": data.get("request_for_quotation"),
+ "valid_till": data.get('valid_till'),
+ "lead_time_days": data.get('lead_time_days')
}
# map for report view of form {'supplier1':[{},{},...]}
supplier_wise_map[supplier].append(row)
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
- if not supplier in supplier_qty_price_map:
- supplier_qty_price_map[supplier] = {}
- supplier_qty_price_map[supplier][row["qty"]] = row["price"]
+ if filters.get("item_code"):
+ if not supplier in supplier_qty_price_map:
+ supplier_qty_price_map[supplier] = {}
+ supplier_qty_price_map[supplier][row["qty"]] = row["price"]
suppliers.append(supplier)
qty_list.append(data.get("qty"))
@@ -97,7 +106,8 @@
for entry in supplier_wise_map[supplier]:
out.append(entry)
- chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
+ if filters.get("item_code"):
+ chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
return out, chart_data
@@ -117,9 +127,10 @@
data_points_map[qty].append(None)
dataset = []
+ currency_symbol = frappe.db.get_value("Currency", frappe.db.get_default("currency"), "symbol")
for qty in qty_list:
datapoints = {
- "name": _("Price for Qty ") + str(qty),
+ "name": currency_symbol + " (Qty " + str(qty) + " )",
"values": data_points_map[qty]
}
dataset.append(datapoints)
@@ -140,14 +151,21 @@
"label": _("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
+ "width": 150
+ },
+ {
+ "fieldname": "item_code",
+ "label": _("Item"),
+ "fieldtype": "Link",
+ "options": "Item",
"width": 200
},
{
- "fieldname": "quotation",
- "label": _("Supplier Quotation"),
+ "fieldname": "uom",
+ "label": _("UOM"),
"fieldtype": "Link",
- "options": "Supplier Quotation",
- "width": 200
+ "options": "UOM",
+ "width": 90
},
{
"fieldname": "qty",
@@ -163,19 +181,43 @@
"width": 110
},
{
- "fieldname": "uom",
- "label": _("UOM"),
+ "fieldname": "quotation",
+ "label": _("Supplier Quotation"),
"fieldtype": "Link",
- "options": "UOM",
- "width": 90
+ "options": "Supplier Quotation",
+ "width": 200
+ },
+ {
+ "fieldname": "valid_till",
+ "label": _("Valid Till"),
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "fieldname": "lead_time_days",
+ "label": _("Lead Time (Days)"),
+ "fieldtype": "Int",
+ "width": 100
},
{
"fieldname": "request_for_quotation",
"label": _("Request for Quotation"),
"fieldtype": "Link",
"options": "Request for Quotation",
- "width": 200
+ "width": 150
}
]
- return columns
\ No newline at end of file
+ return columns
+
+def get_message():
+ return """<span class="indicator">
+ Valid till :
+ </span>
+ <span class="indicator orange">
+ Expires in a week or less
+ </span>
+
+ <span class="indicator red">
+ Expires today / Already Expired
+ </span>"""
\ No newline at end of file
diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/communication/doctype/call_log/call_log.json
index cfc08eb..31e79f1 100644
--- a/erpnext/communication/doctype/call_log/call_log.json
+++ b/erpnext/communication/doctype/call_log/call_log.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "field:id",
"creation": "2019-06-05 12:07:02.634534",
"doctype": "DocType",
@@ -14,6 +15,7 @@
"contact",
"contact_name",
"column_break_10",
+ "customer",
"lead",
"lead_name",
"section_break_5",
@@ -28,7 +30,8 @@
},
{
"fieldname": "section_break_5",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Call Details"
},
{
"fieldname": "id",
@@ -125,10 +128,19 @@
"in_list_view": 1,
"label": "Lead Name",
"read_only": 1
+ },
+ {
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer",
+ "read_only": 1
}
],
"in_create": 1,
- "modified": "2019-08-06 05:46:53.144683",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-25 17:08:34.085731",
"modified_by": "Administrator",
"module": "Communication",
"name": "Call Log",
diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py
index 5fe3c4e..b31b757 100644
--- a/erpnext/communication/doctype/call_log/call_log.py
+++ b/erpnext/communication/doctype/call_log/call_log.py
@@ -16,6 +16,9 @@
self.contact = get_contact_with_phone_number(number)
self.lead = get_lead_with_phone_number(number)
+ contact = frappe.get_doc("Contact", self.contact)
+ self.customer = contact.get_link_for("Customer")
+
def after_insert(self):
self.trigger_call_popup()
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 6096053..47b05f3 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -267,6 +267,9 @@
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
+ def update_item(obj, target, source_parent):
+ target.conversion_factor = 1.0
+
doclist = get_mapped_doc("Opportunity", source_name, {
"Opportunity": {
"doctype": "Request for Quotation"
@@ -277,7 +280,8 @@
["name", "opportunity_item"],
["parent", "opportunity"],
["uom", "uom"]
- ]
+ ],
+ "postprocess": update_item
}
}, target_doc)
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 33d9007..04cd8a2 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -82,7 +82,8 @@
if args.with_items:
opp_doc.append('items', {
"item_code": args.item_code or "_Test Item",
- "qty": args.qty or 1
+ "qty": args.qty or 1,
+ "uom": "_Test UOM"
})
opp_doc.insert()
diff --git a/erpnext/erpnext_integrations/desk_page/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/desk_page/erpnext_integrations/erpnext_integrations.json
new file mode 100644
index 0000000..8dcc77d
--- /dev/null
+++ b/erpnext/erpnext_integrations/desk_page/erpnext_integrations/erpnext_integrations.json
@@ -0,0 +1,40 @@
+{
+ "cards": [
+ {
+ "hidden": 0,
+ "label": "Marketplace",
+ "links": "[\n {\n \"description\": \"Woocommerce marketplace settings\",\n \"label\": \"Woocommerce Settings\",\n \"name\": \"Woocommerce Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Amazon MWS settings\",\n \"label\": \"Amazon MWS Settings\",\n \"name\": \"Amazon MWS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Shopify settings\",\n \"label\": \"Shopify Settings\",\n \"name\": \"Shopify Settings\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
+ "label": "Payments",
+ "links": "[\n {\n \"description\": \"GoCardless payment gateway settings\",\n \"label\": \"GoCardless Settings\",\n \"name\": \"GoCardless Settings\",\n \"type\": \"doctype\"\n }\n]"
+ },
+ {
+ "hidden": 0,
+ "label": "Settings",
+ "links": "[\n {\n \"description\": \"Plaid settings\",\n \"label\": \"Plaid Settings\",\n \"name\": \"Plaid Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Exotel settings\",\n \"label\": \"Exotel Settings\",\n \"name\": \"Exotel Settings\",\n \"type\": \"doctype\"\n }\n]"
+ }
+ ],
+ "category": "Modules",
+ "charts": [],
+ "creation": "2020-08-20 19:30:48.138801",
+ "developer_mode_only": 0,
+ "disable_user_customization": 0,
+ "docstatus": 0,
+ "doctype": "Desk Page",
+ "extends": "Integrations",
+ "extends_another_page": 1,
+ "hide_custom": 1,
+ "idx": 0,
+ "is_standard": 1,
+ "label": "ERPNext Integrations",
+ "modified": "2020-08-23 16:30:51.494655",
+ "modified_by": "Administrator",
+ "module": "ERPNext Integrations",
+ "name": "ERPNext Integrations",
+ "owner": "Administrator",
+ "pin_to_bottom": 0,
+ "pin_to_top": 0,
+ "shortcuts": []
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js
index ffef60b..9b4c217 100644
--- a/erpnext/loan_management/doctype/loan/loan.js
+++ b/erpnext/loan_management/doctype/loan/loan.js
@@ -73,8 +73,8 @@
loan_type: function(frm) {
frm.toggle_reqd("repayment_method", frm.doc.is_term_loan);
- frm.toggle_display("repayment_method", 1 - frm.doc.is_term_loan);
- frm.toggle_display("repayment_periods", s1 - frm.doc.is_term_loan);
+ frm.toggle_display("repayment_method", frm.doc.is_term_loan);
+ frm.toggle_display("repayment_periods", frm.doc.is_term_loan);
},
@@ -119,12 +119,10 @@
create_loan_security_unpledge: function(frm) {
frappe.call({
- method: "erpnext.loan_management.doctype.loan.loan.create_loan_security_unpledge",
+ method: "erpnext.loan_management.doctype.loan.loan.unpledge_security",
args : {
"loan": frm.doc.name,
- "applicant_type": frm.doc.applicant_type,
- "applicant": frm.doc.applicant,
- "company": frm.doc.company
+ "as_dict": 1
},
callback: function(r) {
if (r.message)
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index e20b484..e2e27dd 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -7,7 +7,7 @@
import erpnext
from frappe import _
from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
-
+from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
from erpnext.controllers.accounts_controller import AccountsController
class Loan(AccountsController):
@@ -223,30 +223,52 @@
return repayment_entry
@frappe.whitelist()
-def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1):
- loan_security_pledge_details = frappe.db.sql("""
- SELECT p.loan_security, sum(p.qty) as qty
- FROM `tabLoan Security Pledge` lsp , `tabPledge` p
- WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
- GROUP BY p.loan_security
- """,(loan), as_dict=1)
+def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
+ # if loan is passed it will be considered as full unpledge
+ if loan:
+ pledge_qty_map = get_pledged_security_qty(loan)
+ loan_doc = frappe.get_doc('Loan', loan)
+ unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
+ loan_doc.applicant_type, loan_doc.applicant)
+ # will unpledge qty based on loan security pledge
+ elif loan_security_pledge:
+ security_map = {}
+ pledge_doc = frappe.get_doc('Loan Security Pledge', loan_security_pledge)
+ for security in pledge_doc.securities:
+ security_map.setdefault(security.loan_security, security.qty)
+ unpledge_request = create_loan_security_unpledge(security_map, pledge_doc.loan,
+ pledge_doc.company, pledge_doc.applicant_type, pledge_doc.applicant)
+
+ if approve:
+ unpledge_request.status = 'Approved'
+
+ if save:
+ unpledge_request.save()
+
+ if submit:
+ unpledge_request.submit()
+
+ if as_dict:
+ return unpledge_request
+ else:
+ return unpledge_request
+
+def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, applicant):
unpledge_request = frappe.new_doc("Loan Security Unpledge")
unpledge_request.applicant_type = applicant_type
unpledge_request.applicant = applicant
unpledge_request.loan = loan
unpledge_request.company = company
- for loan_security in loan_security_pledge_details:
- unpledge_request.append('securities', {
- "loan_security": loan_security.loan_security,
- "qty": loan_security.qty
- })
+ for security, qty in unpledge_map.items():
+ if qty:
+ unpledge_request.append('securities', {
+ "loan_security": security,
+ "qty": qty
+ })
- if as_dict:
- return unpledge_request.as_dict()
- else:
- return unpledge_request
+ return unpledge_request
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index b75f7bd..5faf80e 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -14,10 +14,11 @@
process_loan_interest_accrual_for_term_loans)
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall
-from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
+from erpnext.loan_management.doctype.loan.loan import unpledge_security
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
+from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
class TestLoan(unittest.TestCase):
def setUp(self):
@@ -194,18 +195,14 @@
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
- repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
+ repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
repayment_entry.submit()
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
'paid_principal_amount'])
- unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \
- / (days_in_year(get_datetime(first_date).year) * 100)
-
- self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
- flt(accrued_interest_amount, 3))
+ self.assertEquals(flt(amounts[0], 2),flt(accrued_interest_amount, 2))
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
loan.load_from_db()
@@ -307,13 +304,10 @@
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
repayment_entry.submit()
- amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
- 'paid_principal_amount'])
-
loan.load_from_db()
self.assertEquals(loan.status, "Loan Closure Requested")
- unpledge_request = create_loan_security_unpledge(loan.name, loan.applicant_type, loan.applicant, loan.company, as_dict=0)
+ unpledge_request = unpledge_security(loan=loan.name, save=1)
unpledge_request.submit()
unpledge_request.status = 'Approved'
unpledge_request.save()
@@ -374,6 +368,47 @@
self.assertEqual(get_disbursal_amount(loan.name), 300000)
+ def test_pending_loan_amount_after_closure_request(self):
+ pledge = [{
+ "loan_security": "Test Security 1",
+ "qty": 4000.00
+ }]
+
+ loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+ create_pledge(loan_application)
+
+ loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
+ loan.submit()
+
+ self.assertEquals(loan.loan_amount, 1000000)
+
+ first_date = '2019-10-01'
+ last_date = '2019-10-30'
+
+ no_of_days = date_diff(last_date, first_date) + 1
+
+ no_of_days += 6
+
+ accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
+ / (days_in_year(get_datetime(first_date).year) * 100)
+
+ make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
+ process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+
+ amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
+
+ repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
+ "Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
+ repayment_entry.submit()
+
+ amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
+ 'paid_principal_amount'])
+
+ loan.load_from_db()
+ self.assertEquals(loan.status, "Loan Closure Requested")
+
+ amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
+ self.assertEquals(amounts['pending_principal_amount'], 0.0)
def create_loan_accounts():
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 451ae85..7d83e32 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -116,6 +116,7 @@
def allocate_amounts(self, paid_entries):
self.set('repayment_details', [])
self.principal_amount_paid = 0
+ total_interest_paid = 0
interest_paid = self.amount_paid - self.penalty_amount
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
@@ -137,12 +138,19 @@
interest_paid = 0
paid_principal=0
+ total_interest_paid += interest_amount
self.append('repayment_details', {
'loan_interest_accrual': lia,
'paid_interest_amount': interest_amount,
'paid_principal_amount': paid_principal
})
+ if self.payment_type == 'Loan Closure' and total_interest_paid < self.interest_payable:
+ unaccrued_interest = self.interest_payable - total_interest_paid
+ interest_paid -= unaccrued_interest
+ if self.repayment_details:
+ self.repayment_details[-1].paid_interest_amount += unaccrued_interest
+
if interest_paid:
self.principal_amount_paid += interest_paid
@@ -297,7 +305,7 @@
if not final_due_date:
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
- if against_loan_doc.status == 'Disbursed':
+ if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested'):
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
else:
pending_principal_amount = against_loan_doc.disbursed_amount
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
index 4572e99..7dd5725 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
@@ -21,6 +21,10 @@
"total_security_value",
"column_break_11",
"maximum_loan_value",
+ "more_information_section",
+ "reference_no",
+ "column_break_18",
+ "description",
"amended_from"
],
"fields": [
@@ -129,11 +133,34 @@
"label": "Applicant Type",
"options": "Employee\nMember\nCustomer",
"reqd": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "more_information_section",
+ "fieldtype": "Section Break",
+ "label": "More Information"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "reference_no",
+ "fieldtype": "Data",
+ "label": "Reference No"
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description"
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-07-02 23:38:24.002382",
+ "modified": "2020-09-04 22:38:19.894488",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Pledge",
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
index aece46f..2e2b251 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
@@ -16,6 +16,10 @@
"status",
"loan_security_details_section",
"securities",
+ "more_information_section",
+ "reference_no",
+ "column_break_13",
+ "description",
"amended_from"
],
"fields": [
@@ -95,11 +99,34 @@
"label": "Applicant Type",
"options": "Employee\nMember\nCustomer",
"reqd": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "more_information_section",
+ "fieldtype": "Section Break",
+ "label": "More Information"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "reference_no",
+ "fieldtype": "Data",
+ "label": "Reference No"
+ },
+ {
+ "fieldname": "column_break_13",
+ "fieldtype": "Column Break"
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description"
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-05 07:23:18.440058",
+ "modified": "2020-09-04 22:39:57.756146",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Unpledge",
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index 5e9d82a..a87d832 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -17,10 +17,12 @@
self.validate_unpledge_qty()
def on_cancel(self):
- self.update_loan_security_pledge(cancel=1)
self.update_loan_status(cancel=1)
self.db_set('status', 'Requested')
+ def on_submit(self):
+ self.approve()
+
def validate_duplicate_securities(self):
security_list = []
for d in self.securities:
@@ -43,13 +45,14 @@
"valid_upto": (">=", get_datetime())
}, as_list=1))
- loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid'])
- pending_principal_amount = loan_amount - principal_paid
+ total_payment, principal_paid, interest_payable = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
+ 'total_interest_payable'])
+
+ pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid)
security_value = 0
for security in self.securities:
- pledged_qty = pledge_qty_map.get(security.loan_security)
-
+ pledged_qty = pledge_qty_map.get(security.loan_security, 0)
if security.qty > pledged_qty:
frappe.throw(_("""Row {0}: {1} {2} of {3} is pledged against Loan {4}.
You are trying to unpledge more""").format(security.idx, pledged_qty, security.uom,
@@ -58,16 +61,23 @@
qty_after_unpledge = pledged_qty - security.qty
ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
- security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security)
+ current_price = loan_security_price_map.get(security.loan_security)
+ if not current_price:
+ frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security)))
- if not security_value and pending_principal_amount > 0:
+ security_value += qty_after_unpledge * current_price
+
+ if not security_value and flt(pending_principal_amount, 2) > 0:
frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
- if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio:
+ if security_value and flt(pending_principal_amount/security_value) * 100 > ltv_ratio:
frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
def on_update_after_submit(self):
- if self.status == "Approved":
+ self.approve()
+
+ def approve(self):
+ if self.status == "Approved" and not self.unpledge_time:
self.update_loan_status()
self.db_set('unpledge_time', get_datetime())
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index bab0dfb..b051b32 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -2,6 +2,17 @@
// For license information, please see license.txt
frappe.ui.form.on('Job Card', {
+ setup: function(frm) {
+ frm.set_query('operation', function() {
+ return {
+ query: 'erpnext.manufacturing.doctype.job_card.job_card.get_operations',
+ filters: {
+ 'work_order': frm.doc.work_order
+ }
+ };
+ });
+ },
+
refresh: function(frm) {
frappe.flags.pause_job = 0;
frappe.flags.resume_job = 0;
@@ -20,12 +31,60 @@
}
}
+ frm.trigger("toggle_operation_number");
+
if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
- && (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
+ && (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons");
}
},
+ operation: function(frm) {
+ frm.trigger("toggle_operation_number");
+
+ if (frm.doc.operation && frm.doc.work_order) {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.job_card.job_card.get_operation_details",
+ args: {
+ "work_order":frm.doc.work_order,
+ "operation":frm.doc.operation
+ },
+ callback: function (r) {
+ if (r.message) {
+ if (r.message.length == 1) {
+ frm.set_value("operation_id", r.message[0].name);
+ } else {
+ let args = [];
+
+ r.message.forEach((row) => {
+ args.push({ "label": row.idx, "value": row.name });
+ });
+
+ let description = __("Operation {0} added multiple times in the work order {1}",
+ [frm.doc.operation, frm.doc.work_order]);
+
+ frm.set_df_property("operation_row_number", "options", args);
+ frm.set_df_property("operation_row_number", "description", description);
+ }
+
+ frm.trigger("toggle_operation_number");
+ }
+ }
+ })
+ }
+ },
+
+ operation_row_number(frm) {
+ if (frm.doc.operation_row_number) {
+ frm.set_value("operation_id", frm.doc.operation_row_number);
+ }
+ },
+
+ toggle_operation_number(frm) {
+ frm.toggle_display("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
+ frm.toggle_reqd("operation_row_number", !frm.doc.operation_id && frm.doc.operation);
+ },
+
prepare_timer_buttons: function(frm) {
frm.trigger("make_dashboard");
if (!frm.doc.job_started) {
@@ -35,9 +94,9 @@
fieldname: 'employee'}, d => {
if (d.employee) {
frm.set_value("employee", d.employee);
+ } else {
+ frm.events.start_job(frm);
}
-
- frm.events.start_job(frm);
}, __("Enter Value"), __("Start"));
} else {
frm.events.start_job(frm);
@@ -82,9 +141,7 @@
frm.set_value('current_time' , 0);
}
- frm.save("Save", () => {}, "", () => {
- frm.doc.time_logs.pop(-1);
- });
+ frm.save();
},
complete_job: function(frm, completed_time, completed_qty) {
@@ -116,6 +173,8 @@
employee: function(frm) {
if (frm.doc.job_started && !frm.doc.current_time) {
frm.trigger("reset_timer");
+ } else {
+ frm.events.start_job(frm);
}
},
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index fba670c..087ab6b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -11,6 +11,7 @@
"bom_no",
"workstation",
"operation",
+ "operation_row_number",
"column_break_4",
"posting_date",
"company",
@@ -291,11 +292,15 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "operation_row_number",
+ "fieldtype": "Select",
+ "label": "Operation Row Number"
}
],
"is_submittable": 1,
- "links": [],
- "modified": "2020-04-20 15:14:00.273441",
+ "modified": "2020-08-24 15:21:21.398267",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@@ -347,7 +352,6 @@
"write": 1
}
],
- "quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "operation",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index c29d4ba..8855e0a 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -15,10 +15,13 @@
class OverlapError(frappe.ValidationError): pass
+class OperationMismatchError(frappe.ValidationError): pass
+
class JobCard(Document):
def validate(self):
self.validate_time_logs()
self.set_status()
+ self.validate_operation_id()
def validate_time_logs(self):
self.total_completed_qty = 0.0
@@ -209,11 +212,10 @@
for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], []
- field = "operation_id" if self.operation_id else "operation"
+ field = "operation_id"
data = frappe.get_all('Job Card',
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
- filters = {"docstatus": 1, "work_order": self.work_order,
- "workstation": self.workstation, field: self.get(field)})
+ filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
if data and len(data) > 0:
for_quantity = data[0].completed_qty
@@ -226,14 +228,13 @@
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
- and jc.workstation = %s and jc.{0} = %s and jc.docstatus = 1
- """.format(field), (self.work_order, self.workstation, self.get(field)), as_dict=1)
+ and jc.{0} = %s and jc.docstatus = 1
+ """.format(field), (self.work_order, self.get(field)), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order)
- work_order_field = "name" if field == "operation_id" else field
for data in wo.operations:
- if data.get(work_order_field) == self.get(field) and data.workstation == self.workstation:
+ if data.get("name") == self.get(field):
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
@@ -306,6 +307,37 @@
if update_status:
self.db_set('status', self.status)
+ def validate_operation_id(self):
+ if (self.get("operation_id") and self.get("operation_row_number") and self.operation and self.work_order and
+ frappe.get_cached_value("Work Order Operation", self.operation_row_number, "name") != self.operation_id):
+ work_order = frappe.bold(get_link_to_form("Work Order", self.work_order))
+ frappe.throw(_("Operation {0} does not belong to the work order {1}")
+ .format(frappe.bold(self.operation), work_order), OperationMismatchError)
+
+@frappe.whitelist()
+def get_operation_details(work_order, operation):
+ if work_order and operation:
+ return frappe.get_all("Work Order Operation", fields = ["name", "idx"],
+ filters = {
+ "parent": work_order,
+ "operation": operation
+ }
+ )
+
+@frappe.whitelist()
+def get_operations(doctype, txt, searchfield, start, page_len, filters):
+ if filters.get("work_order"):
+ args = {"parent": filters.get("work_order")}
+ if txt:
+ args["operation"] = ("like", "%{0}%".format(txt))
+
+ return frappe.get_all("Work Order Operation",
+ filters = args,
+ fields = ["distinct operation as operation"],
+ limit_start = start,
+ limit_page_length = page_len,
+ order_by="idx asc", as_list=1)
+
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
def update_item(obj, target, source_parent):
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index ca05fea..b6a6c33 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -4,6 +4,72 @@
from __future__ import unicode_literals
import unittest
+import frappe
+from frappe.utils import random_string
+from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
+from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError
class TestJobCard(unittest.TestCase):
- pass
+ def test_job_card(self):
+ data = frappe.get_cached_value('BOM',
+ {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+
+ if data:
+ bom, bom_item = data
+
+ work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
+
+ job_cards = frappe.get_all('Job Card',
+ filters = {'work_order': work_order.name}, fields = ["operation_id", "name"])
+
+ if job_cards:
+ job_card = job_cards[0]
+ frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id)
+
+ doc = frappe.get_doc("Job Card", job_card.name)
+ doc.operation_id = "Test Data"
+ self.assertRaises(OperationMismatchError, doc.save)
+
+ for d in job_cards:
+ frappe.delete_doc("Job Card", d.name)
+
+ def test_job_card_with_different_work_station(self):
+ data = frappe.get_cached_value('BOM',
+ {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+
+ if data:
+ bom, bom_item = data
+
+ work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
+
+ job_cards = frappe.get_all('Job Card',
+ filters = {'work_order': work_order.name},
+ fields = ["operation_id", "workstation", "name", "for_quantity"])
+
+ job_card = job_cards[0]
+
+ if job_card:
+ workstation = frappe.db.get_value("Workstation",
+ {"name": ("not in", [job_card.workstation])}, "name")
+
+ if not workstation or job_card.workstation == workstation:
+ workstation = make_workstation(workstation_name=random_string(5)).name
+
+ doc = frappe.get_doc("Job Card", job_card.name)
+ doc.workstation = workstation
+ doc.append("time_logs", {
+ "from_time": "2009-01-01 12:06:25",
+ "to_time": "2009-01-01 12:37:25",
+ "time_in_mins": "31.00002",
+ "completed_qty": job_card.for_quantity
+ })
+ doc.submit()
+
+ completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty")
+ self.assertEqual(completed_qty, job_card.for_quantity)
+
+ doc.cancel()
+
+ for d in job_cards:
+ frappe.delete_doc("Job Card", d.name)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 2260bef..b7c7c32 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -7,7 +7,7 @@
import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
-from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
+from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.utils import get_bin
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index 2169260..8266cf7 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -20,3 +20,18 @@
"_Test Workstation 1", "Operation 1", "2013-02-02 05:00:00", "2013-02-02 20:00:00")
self.assertRaises(WorkstationHolidayError, check_if_within_operating_hours,
"_Test Workstation 1", "Operation 1", "2013-02-01 10:00:00", "2013-02-02 20:00:00")
+
+def make_workstation(**args):
+ args = frappe._dict(args)
+
+ try:
+ doc = frappe.get_doc({
+ "doctype": "Workstation",
+ "workstation_name": args.workstation_name
+ })
+
+ doc.insert()
+
+ return doc
+ except frappe.DuplicateEntryError:
+ return frappe.get_doc("Workstation", args.workstation_name)
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
index bb73a84..77cdb94 100644
--- a/erpnext/non_profit/doctype/member/member.json
+++ b/erpnext/non_profit/doctype/member/member.json
@@ -133,7 +133,8 @@
{
"fieldname": "email_id",
"fieldtype": "Data",
- "label": "Email Address"
+ "label": "Email Address",
+ "options": "Email"
},
{
"fieldname": "subscription_id",
@@ -176,7 +177,7 @@
],
"image_field": "image",
"links": [],
- "modified": "2020-04-07 14:20:33.215700",
+ "modified": "2020-08-06 10:06:01.153564",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index c52082c..44b975e 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -9,6 +9,7 @@
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.utils import cint
from frappe.integrations.utils import get_payment_gateway_controller
+from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type
class Member(Document):
def onload(self):
@@ -74,19 +75,23 @@
return create_member(user_details)
def create_member(user_details):
+ user_details = frappe._dict(user_details)
member = frappe.new_doc("Member")
member.update({
"member_name": user_details.fullname,
"email_id": user_details.email,
- "pan_number": user_details.pan,
+ "pan_number": user_details.pan or None,
"membership_type": user_details.plan_id,
- "customer": create_customer(user_details)
+ "subscription_id": user_details.subscription_id or None
})
member.insert(ignore_permissions=True)
+ member.customer = create_customer(user_details, member.name)
+ member.save(ignore_permissions=True)
+
return member
-def create_customer(user_details):
+def create_customer(user_details, member=None):
customer = frappe.new_doc("Customer")
customer.customer_name = user_details.fullname
customer.customer_type = "Individual"
@@ -107,7 +112,13 @@
"link_name": customer.name
})
- contact.save()
+ if member:
+ contact.append("links", {
+ "link_doctype": "Member",
+ "link_name": member
+ })
+
+ contact.save(ignore_permissions=True)
except frappe.DuplicateEntryError:
return customer.name
@@ -139,12 +150,31 @@
user_details = frappe._dict(user_details)
member = get_or_create_member(user_details)
- if not member:
- member = create_member(user_details)
subscription = member.setup_subscription()
member.subscription_id = subscription.get('subscription_id')
member.save(ignore_permissions=True)
- return subscription
\ No newline at end of file
+ return subscription
+
+@frappe.whitelist()
+def register_member(fullname, email, rzpay_plan_id, subscription_id, pan=None, mobile=None):
+ plan = get_membership_type(rzpay_plan_id)
+ if not plan:
+ raise frappe.DoesNotExistError
+
+ member = frappe.db.exists("Member", {'email': email, 'subscription_id': subscription_id })
+ if member:
+ return member
+ else:
+ member = create_member(dict(
+ fullname=fullname,
+ email=email,
+ plan_id=plan,
+ subscription_id=subscription_id,
+ pan=pan,
+ mobile=mobile
+ ))
+
+ return member.name
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership/membership.js b/erpnext/non_profit/doctype/membership/membership.js
index 554549a..ee8a8c0 100644
--- a/erpnext/non_profit/doctype/membership/membership.js
+++ b/erpnext/non_profit/doctype/membership/membership.js
@@ -8,6 +8,24 @@
})
},
+ refresh: function(frm) {
+ !frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
+ frm.call("generate_invoice", {
+ save: true
+ }).then(() => {
+ frm.reload_doc();
+ });
+ });
+
+ frappe.db.get_single_value("Membership Settings", "send_email").then(val => {
+ if (val) frm.add_custom_button("Send Acknowledgement", () => {
+ frm.call("send_acknowlement").then(() => {
+ frm.reload_doc();
+ });
+ });
+ })
+ },
+
onload: function(frm) {
frm.add_fetch('membership_type', 'amount', 'amount');
}
diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json
index 238f4c3..95bb3a5 100644
--- a/erpnext/non_profit/doctype/membership/membership.json
+++ b/erpnext/non_profit/doctype/membership/membership.json
@@ -19,10 +19,10 @@
"paid",
"currency",
"amount",
+ "invoice",
"razorpay_details_section",
"subscription_id",
- "payment_id",
- "webhook_payload"
+ "payment_id"
],
"fields": [
{
@@ -118,17 +118,15 @@
"read_only": 1
},
{
- "fieldname": "webhook_payload",
- "fieldtype": "Code",
- "hidden": 1,
- "label": "Webhook Payload",
- "options": "JSON",
- "read_only": 1
+ "fieldname": "invoice",
+ "fieldtype": "Link",
+ "label": "Invoice",
+ "options": "Sales Invoice"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-07-27 14:28:11.532696",
+ "modified": "2020-07-31 13:57:02.328995",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership",
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 729e111..f058004 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -10,6 +10,7 @@
from frappe.model.document import Document
from frappe.email import sendmail_to_system_managers
from frappe.utils import add_days, add_years, nowdate, getdate, add_months, get_link_to_form
+from erpnext.non_profit.doctype.member.member import create_member
from frappe import _
import erpnext
@@ -57,11 +58,95 @@
self.load_from_db()
self.db_set('paid', 1)
+ def generate_invoice(self, save=True):
+ if not (self.paid or self.currency or self.amount):
+ frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
+
+ if self.invoice:
+ frappe.throw(_("An invoice is already linked to this document"))
+
+ member = frappe.get_doc("Member", self.member)
+ plan = frappe.get_doc("Membership Type", self.membership_type)
+ settings = frappe.get_doc("Membership Settings")
+
+ if not member.customer:
+ frappe.throw(_("No customer linked to member {}", [member.name]))
+
+ if not settings.debit_account:
+ frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
+
+ if not settings.company:
+ frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
+
+ invoice = make_invoice(self, member, plan, settings)
+ self.invoice = invoice.name
+
+ if save:
+ self.save()
+
+ return invoice
+
+ def send_acknowlement(self):
+ settings = frappe.get_doc("Membership Settings")
+ if not settings.send_email:
+ frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in Membership Settings"))
+
+ member = frappe.get_doc("Member", self.member)
+ plan = frappe.get_doc("Membership Type", self.membership_type)
+ email = member.email_id if member.email_id else member.email
+ attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
+
+ if self.invoice and settings.send_invoice:
+ attachments.append(frappe.attach_print("Sales Invoice", self.invoice, print_format=settings.inv_print_format))
+
+ email_template = frappe.get_doc("Email Template", settings.email_template)
+ context = { "doc": self, "member": member}
+
+ email_args = {
+ "recipients": [email],
+ "message": frappe.render_template(email_template.get("response"), context),
+ "subject": frappe.render_template(email_template.get("subject"), context),
+ "attachments": attachments,
+ "reference_doctype": self.doctype,
+ "reference_name": self.name
+ }
+
+ if not frappe.flags.in_test:
+ frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
+ else:
+ frappe.sendmail(**email_args)
+
+ def generate_and_send_invoice(self):
+ invoice = self.generate_invoice(False)
+ self.send_acknowlement()
+
+def make_invoice(membership, member, plan, settings):
+ invoice = frappe.get_doc({
+ 'doctype': 'Sales Invoice',
+ 'customer': member.customer,
+ 'debit_to': settings.debit_account,
+ 'currency': membership.currency,
+ 'is_pos': 0,
+ 'items': [
+ {
+ 'item_code': plan.linked_item,
+ 'rate': membership.amount,
+ 'qty': 1
+ }
+ ]
+ })
+
+ invoice.insert(ignore_permissions=True)
+ invoice.submit()
+
+ return invoice
+
def get_member_based_on_subscription(subscription_id, email):
members = frappe.get_all("Member", filters={
'subscription_id': subscription_id,
'email_id': email
}, order_by="creation desc")
+
try:
return frappe.get_doc("Member", members[0]['name'])
except:
@@ -77,16 +162,15 @@
controller.verify_signature(data, signature, key)
-
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True)
try:
verify_signature(data)
except Exception as e:
- signature = frappe.request.headers.get('X-Razorpay-Signature')
- log = "{0} \n\n {1} \n\n {2} \n\n {3}".format(e, frappe.get_traceback(), signature, data)
- frappe.log_error(e, "Webhook Verification Error")
+ log = frappe.log_error(e, "Webhook Verification Error")
+ notify_failure(log)
+ return { 'status': 'Failed', 'reason': e}
if isinstance(data, six.string_types):
data = json.loads(data)
@@ -99,35 +183,42 @@
payment = frappe._dict(payment)
try:
- data_json = json.dumps(data, indent=4, sort_keys=True)
+ if not data.event == "subscription.charged":
+ return
+
member = get_member_based_on_subscription(subscription.id, payment.email)
- except Exception as e:
- error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed"))
- notify_failure(error_log)
- return { status: 'Failed' }
+ if not member:
+ member = create_member(frappe._dict({
+ 'fullname': payment.email,
+ 'email': payment.email,
+ 'plan_id': get_plan_from_razorpay_id(subscription.plan_id)
+ }))
- if not member:
- return { status: 'Failed' }
- try:
- if data.event == "subscription.activated":
+ member.subscription_id = subscription.id
member.customer_id = payment.customer_id
- elif data.event == "subscription.charged":
- membership = frappe.new_doc("Membership")
- membership.update({
- "member": member.name,
- "membership_status": "Current",
- "membership_type": member.membership_type,
- "currency": "INR",
- "paid": 1,
- "payment_id": payment.id,
- "webhook_payload": data_json,
- "from_date": datetime.fromtimestamp(subscription.current_start),
- "to_date": datetime.fromtimestamp(subscription.current_end),
- "amount": payment.amount / 100 # Convert to rupees from paise
- })
- membership.insert(ignore_permissions=True)
+ if subscription.notes and type(subscription.notes) == dict:
+ notes = '\n'.join("{}: {}".format(k, v) for k, v in subscription.notes.items())
+ member.add_comment("Comment", notes)
+ elif subscription.notes and type(subscription.notes) == str:
+ member.add_comment("Comment", subscription.notes)
- # Update these values anyway
+
+ # Update Membership
+ membership = frappe.new_doc("Membership")
+ membership.update({
+ "member": member.name,
+ "membership_status": "Current",
+ "membership_type": member.membership_type,
+ "currency": "INR",
+ "paid": 1,
+ "payment_id": payment.id,
+ "from_date": datetime.fromtimestamp(subscription.current_start),
+ "to_date": datetime.fromtimestamp(subscription.current_end),
+ "amount": payment.amount / 100 # Convert to rupees from paise
+ })
+ membership.insert(ignore_permissions=True)
+
+ # Update membership values
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
member.subscription_activated = 1
@@ -135,9 +226,9 @@
except Exception as e:
log = frappe.log_error(e, "Error creating membership entry")
notify_failure(log)
- return { status: 'Failed' }
+ return { 'status': 'Failed', 'reason': e}
- return { status: 'Success' }
+ return { 'status': 'Success' }
def notify_failure(log):
@@ -152,3 +243,11 @@
sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
except:
pass
+
+def get_plan_from_razorpay_id(plan_id):
+ plan = frappe.get_all("Membership Type", filters={'razorpay_plan_id': plan_id}, order_by="creation desc")
+
+ try:
+ return plan[0]['name']
+ except:
+ return None
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
index 8c0e3a4..1d89402 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
@@ -10,7 +10,39 @@
})
});
}
+
+ frm.set_query('inv_print_format', function(doc) {
+ return {
+ filters: {
+ "doc_type": "Sales Invoice"
+ }
+ };
+ });
+
+ frm.set_query('membership_print_format', function(doc) {
+ return {
+ filters: {
+ "doc_type": "Membership"
+ }
+ };
+ });
+
+ frm.set_query('debit_account', function(doc) {
+ return {
+ filters: {
+ 'account_type': 'Receivable',
+ 'is_group': 0,
+ 'company': frm.doc.company
+ }
+ };
+ });
+
+ let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership";
+
+ frm.set_intro(__("You can learn more about memberships in the manual. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
+
frm.trigger("add_generate_button");
+ frm.trigger("add_copy_buttonn");
},
add_generate_button: function(frm) {
@@ -27,4 +59,12 @@
});
});
},
+
+ add_copy_buttonn: function(frm) {
+ if (frm.doc.webhook_secret) {
+ frm.add_custom_button(__("Copy Webhook URL"), () => {
+ frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`);
+ });
+ }
+ }
});
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 52b9d01..5b6bab5 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -9,7 +9,17 @@
"razorpay_settings_section",
"billing_cycle",
"billing_frequency",
- "webhook_secret"
+ "webhook_secret",
+ "column_break_6",
+ "enable_auto_invoicing",
+ "company",
+ "debit_account",
+ "column_break_9",
+ "send_email",
+ "send_invoice",
+ "membership_print_format",
+ "inv_print_format",
+ "email_template"
],
"fields": [
{
@@ -41,11 +51,79 @@
"fieldtype": "Password",
"label": "Webhook Secret",
"read_only": 1
+ },
+ {
+ "fieldname": "column_break_6",
+ "fieldtype": "Section Break",
+ "label": "Invoicing"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_auto_invoicing",
+ "fieldtype": "Check",
+ "label": "Enable Auto Invoicing",
+ "mandatory_depends_on": "eval:doc.send_invoice"
+ },
+ {
+ "depends_on": "eval:doc.enable_auto_invoicing",
+ "fieldname": "debit_account",
+ "fieldtype": "Link",
+ "label": "Debit Account",
+ "mandatory_depends_on": "eval:doc.enable_auto_invoicing",
+ "options": "Account"
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval:doc.enable_auto_invoicing",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "mandatory_depends_on": "eval:doc.enable_auto_invoicing",
+ "options": "Company"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
+ "fieldname": "send_invoice",
+ "fieldtype": "Check",
+ "label": "Send Invoice with Email"
+ },
+ {
+ "default": "0",
+ "fieldname": "send_email",
+ "fieldtype": "Check",
+ "label": "Send Membership Acknowledgement"
+ },
+ {
+ "depends_on": "eval: doc.send_invoice",
+ "fieldname": "inv_print_format",
+ "fieldtype": "Link",
+ "label": "Invoice Print Format",
+ "mandatory_depends_on": "eval: doc.send_invoice",
+ "options": "Print Format"
+ },
+ {
+ "depends_on": "eval:doc.send_email",
+ "fieldname": "membership_print_format",
+ "fieldtype": "Link",
+ "label": "Membership Print Format",
+ "options": "Print Format"
+ },
+ {
+ "depends_on": "eval:doc.send_email",
+ "fieldname": "email_template",
+ "fieldtype": "Link",
+ "label": "Email Template",
+ "mandatory_depends_on": "eval:doc.send_email",
+ "options": "Email Template"
}
],
"issingle": 1,
"links": [],
- "modified": "2020-05-22 12:38:27.103759",
+ "modified": "2020-08-05 17:26:37.287395",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership Settings",
@@ -60,6 +138,23 @@
"role": "System Manager",
"share": 1,
"write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Non Profit Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Non Profit Member",
+ "share": 1
}
],
"quick_entry": 1,
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js
index 226981d..43311a2 100644
--- a/erpnext/non_profit/doctype/membership_type/membership_type.js
+++ b/erpnext/non_profit/doctype/membership_type/membership_type.js
@@ -5,6 +5,10 @@
refresh: function(frm) {
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
- })
+ });
+
+ frappe.db.get_single_value("Membership Settings", "enable_auto_invoicing").then(val => {
+ if (val) frm.set_df_property('linked_item', 'hidden', false);
+ });
}
});
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.json b/erpnext/non_profit/doctype/membership_type/membership_type.json
index 319078f..6ce1ecd 100644
--- a/erpnext/non_profit/doctype/membership_type/membership_type.json
+++ b/erpnext/non_profit/doctype/membership_type/membership_type.json
@@ -8,7 +8,8 @@
"field_order": [
"membership_type",
"amount",
- "razorpay_plan_id"
+ "razorpay_plan_id",
+ "linked_item"
],
"fields": [
{
@@ -33,10 +34,17 @@
"hidden": 1,
"label": "Razorpay Plan ID",
"unique": 1
+ },
+ {
+ "fieldname": "linked_item",
+ "fieldtype": "Link",
+ "label": "Linked Item",
+ "options": "Item",
+ "unique": 1
}
],
"links": [],
- "modified": "2020-03-30 12:54:07.850857",
+ "modified": "2020-08-05 15:21:43.595745",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership Type",
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.py b/erpnext/non_profit/doctype/membership_type/membership_type.py
index ed6b549..b95b043 100644
--- a/erpnext/non_profit/doctype/membership_type/membership_type.py
+++ b/erpnext/non_profit/doctype/membership_type/membership_type.py
@@ -4,6 +4,10 @@
from __future__ import unicode_literals
from frappe.model.document import Document
+import frappe
class MembershipType(Document):
pass
+
+def get_membership_type(razorpay_id):
+ return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d24fc96..4ed30ad 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -722,5 +722,7 @@
erpnext.patches.v13_0.stock_entry_enhancements
erpnext.patches.v12_0.update_state_code_for_daman_and_diu
erpnext.patches.v12_0.rename_lost_reason_detail
+erpnext.patches.v13_0.drop_razorpay_payload_column
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
+erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports
erpnext.patches.v13_0.update_recipient_email_digest
diff --git a/erpnext/patches/v13_0/drop_razorpay_payload_column.py b/erpnext/patches/v13_0/drop_razorpay_payload_column.py
new file mode 100644
index 0000000..8980fd0
--- /dev/null
+++ b/erpnext/patches/v13_0/drop_razorpay_payload_column.py
@@ -0,0 +1,7 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ if frappe.db.exists("DocType", "Membership"):
+ if 'webhook_payload' in frappe.db.get_table_columns("Membership"):
+ frappe.db.sql("alter table `tabMembership` drop column webhook_payload")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py
new file mode 100644
index 0000000..ecc7822
--- /dev/null
+++ b/erpnext/patches/v13_0/setting_custom_roles_for_some_regional_reports.py
@@ -0,0 +1,10 @@
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.india.setup import add_custom_roles_for_reports
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ add_custom_roles_for_reports()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/stock_entry_enhancements.py b/erpnext/patches/v13_0/stock_entry_enhancements.py
index dcc4f95..0bdcc9c 100644
--- a/erpnext/patches/v13_0/stock_entry_enhancements.py
+++ b/erpnext/patches/v13_0/stock_entry_enhancements.py
@@ -24,4 +24,8 @@
if not frappe.db.exists('Warehouse Type', 'Transit'):
doc = frappe.new_doc('Warehouse Type')
doc.name = 'Transit'
- doc.insert()
\ No newline at end of file
+ doc.insert()
+
+ frappe.reload_doc("stock", "doctype", "stock_entry_type")
+ frappe.delete_doc_if_exists("Stock Entry Type", "Send to Warehouse")
+ frappe.delete_doc_if_exists("Stock Entry Type", "Receive at Warehouse")
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index a4cc68b..cb76c87 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -503,11 +503,11 @@
if(!r.exc && r.message) {
remove_empty_first_row(frm);
- for ( var i=0; i< r.message.length; i++ ) {
+ for (var i=0; i< r.message.length; i++) {
var d = frm.add_child("items");
var item = r.message[i];
- for ( var key in item) {
- if ( !is_null(item[key]) ) {
+ for (var key in item) {
+ if (!is_null(item[key]) && key !== "doctype") {
d[key] = item[key];
}
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 436a232..792235f 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -781,10 +781,23 @@
else var date = this.frm.doc.transaction_date;
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
- in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)){
+ in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
erpnext.utils.get_shipping_address(this.frm, function(){
set_party_account(set_pricing);
})
+
+ // Get default company billing address in Purchase Invoice, Order and Receipt
+ frappe.call({
+ 'method': 'frappe.contacts.doctype.address.address.get_default_address',
+ 'args': {
+ 'doctype': 'Company',
+ 'name': this.frm.doc.company
+ },
+ 'callback': function(r) {
+ me.frm.set_value('billing_address', r.message);
+ }
+ });
+
} else {
set_party_account(set_pricing);
}
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 290694a..cbcd6e3 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -73,6 +73,19 @@
]
)).insert()
+ for report_name in ('HSN-wise-summary of outward supplies', 'GSTR-1', 'GSTR-2'):
+
+ if not frappe.db.get_value('Custom Role', dict(report=report_name)):
+ frappe.get_doc(dict(
+ doctype='Custom Role',
+ report=report_name,
+ roles= [
+ dict(role='Accounts User'),
+ dict(role='Accounts Manager'),
+ dict(role='Auditor')
+ ]
+ )).insert()
+
def add_permissions():
for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
add_permission(doctype, 'All', 0)
diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js
index fbccc6b..3b6a28f 100644
--- a/erpnext/regional/india/taxes.js
+++ b/erpnext/regional/india/taxes.js
@@ -6,6 +6,9 @@
shipping_address: function(frm) {
frm.trigger('get_tax_template');
},
+ supplier_address: function(frm) {
+ frm.trigger('get_tax_template');
+ },
tax_category: function(frm) {
frm.trigger('get_tax_template');
},
diff --git a/erpnext/regional/report/gstr_1/gstr_1.json b/erpnext/regional/report/gstr_1/gstr_1.json
index 2012bb8..75aed8c 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.json
+++ b/erpnext/regional/report/gstr_1/gstr_1.json
@@ -7,7 +7,7 @@
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
- "modified": "2019-06-30 19:33:59.769385",
+ "modified": "2019-09-03 19:33:59.769385",
"modified_by": "Administrator",
"module": "Regional",
"name": "GSTR-1",
@@ -16,15 +16,5 @@
"ref_doctype": "GL Entry",
"report_name": "GSTR-1",
"report_type": "Script Report",
- "roles": [
- {
- "role": "Accounts User"
- },
- {
- "role": "Accounts Manager"
- },
- {
- "role": "Auditor"
- }
- ]
+ "roles": []
}
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_2/gstr_2.json b/erpnext/regional/report/gstr_2/gstr_2.json
index 929ed91..b70d0f9 100644
--- a/erpnext/regional/report/gstr_2/gstr_2.json
+++ b/erpnext/regional/report/gstr_2/gstr_2.json
@@ -1,29 +1,19 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2018-01-29 12:59:55.650445",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2018-01-29 12:59:55.650445",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "GSTR-2",
- "owner": "Administrator",
- "ref_doctype": "GL Entry",
- "report_name": "GSTR-2",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Accounts User"
- },
- {
- "role": "Accounts Manager"
- },
- {
- "role": "Auditor"
- }
- ]
+ "add_total_row": 0,
+ "apply_user_permissions": 1,
+ "creation": "2018-01-29 12:59:55.650445",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2018-09-03 12:59:55.650445",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "GSTR-2",
+ "owner": "Administrator",
+ "ref_doctype": "GL Entry",
+ "report_name": "GSTR-2",
+ "report_type": "Script Report",
+ "roles": []
}
\ No newline at end of file
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js
index dfdf9dc..b757d53 100644
--- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js
@@ -46,5 +46,28 @@
],
onload: (report) => {
fetch_gstins(report);
+
+ report.page.add_inner_button(__("Download JSON"), function () {
+ var filters = report.get_values();
+
+ frappe.call({
+ method: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.get_json',
+ args: {
+ data: report.data,
+ report_name: report.report_name,
+ filters: filters
+ },
+ callback: function(r) {
+ if (r.message) {
+ const args = {
+ cmd: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.download_json_file',
+ data: r.message.data,
+ report_name: r.message.report_name
+ };
+ open_url_post(frappe.request.url, args);
+ }
+ }
+ });
+ });
}
};
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.json b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.json
index 124a720..cc6ad57 100644
--- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.json
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.json
@@ -1,28 +1,18 @@
{
- "add_total_row": 0,
- "creation": "2018-04-26 10:49:29.159400",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2019-04-26 12:59:38.603649",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "HSN-wise-summary of outward supplies",
- "owner": "Administrator",
- "ref_doctype": "Sales Invoice",
- "report_name": "HSN-wise-summary of outward supplies",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Accounts User"
- },
- {
- "role": "Accounts Manager"
- },
- {
- "role": "Auditor"
- }
- ]
+ "add_total_row": 0,
+ "creation": "2018-04-26 10:49:29.159400",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2019-09-03 12:59:38.603649",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "HSN-wise-summary of outward supplies",
+ "owner": "Administrator",
+ "ref_doctype": "Sales Invoice",
+ "report_name": "HSN-wise-summary of outward supplies",
+ "report_type": "Script Report",
+ "roles": []
}
\ No newline at end of file
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
index a3ed4ce..59389ce 100644
--- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
@@ -4,11 +4,13 @@
from __future__ import unicode_literals
import frappe, erpnext
from frappe import _
-from frappe.utils import flt
+from frappe.utils import flt, getdate, cstr
from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html
from six import iteritems
import json
+from erpnext.regional.india.utils import get_gst_accounts
+from erpnext.regional.report.gstr_1.gstr_1 import get_company_gstin_number
def execute(filters=None):
return _execute(filters)
@@ -141,7 +143,7 @@
tax_details = frappe.db.sql("""
select
- parent, description, item_wise_tax_detail,
+ parent, account_head, item_wise_tax_detail,
base_tax_amount_after_discount_amount
from `tab%s`
where
@@ -153,11 +155,11 @@
""" % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions),
tuple([doctype] + list(invoice_item_row)))
- for parent, description, item_wise_tax_detail, tax_amount in tax_details:
- description = handle_html(description)
- if description not in tax_columns and tax_amount:
+ for parent, account_head, item_wise_tax_detail, tax_amount in tax_details:
+
+ if account_head not in tax_columns and tax_amount:
# as description is text editor earlier and markup can break the column convention in reports
- tax_columns.append(description)
+ tax_columns.append(account_head)
if item_wise_tax_detail:
try:
@@ -175,17 +177,17 @@
for d in item_row_map.get(parent, {}).get(item_code, []):
item_tax_amount = tax_amount
if item_tax_amount:
- itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({
+ itemised_tax.setdefault((parent, item_code), {})[account_head] = frappe._dict({
"tax_amount": flt(item_tax_amount, tax_amount_precision)
})
except ValueError:
continue
tax_columns.sort()
- for desc in tax_columns:
+ for account_head in tax_columns:
columns.append({
- "label": desc,
- "fieldname": frappe.scrub(desc),
+ "label": account_head,
+ "fieldname": frappe.scrub(account_head),
"fieldtype": "Float",
"width": 110
})
@@ -212,3 +214,76 @@
return result
+@frappe.whitelist()
+def get_json(filters, report_name, data):
+ filters = json.loads(filters)
+ report_data = json.loads(data)
+ gstin = filters.get('company_gstin') or get_company_gstin_number(filters["company"])
+
+ if not filters.get('from_date') or not filters.get('to_date'):
+ frappe.throw(_("Please enter From Date and To Date to generate JSON"))
+
+ fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
+
+ gst_json = {"version": "GST2.3.4",
+ "hash": "hash", "gstin": gstin, "fp": fp}
+
+ gst_json["hsn"] = {
+ "data": get_hsn_wise_json_data(filters, report_data)
+ }
+
+ return {
+ 'report_name': report_name,
+ 'data': gst_json
+ }
+
+@frappe.whitelist()
+def download_json_file():
+ '''download json content in a file'''
+ data = frappe._dict(frappe.local.form_dict)
+ frappe.response['filename'] = frappe.scrub("{0}".format(data['report_name'])) + '.json'
+ frappe.response['filecontent'] = data['data']
+ frappe.response['content_type'] = 'application/json'
+ frappe.response['type'] = 'download'
+
+def get_hsn_wise_json_data(filters, report_data):
+
+ filters = frappe._dict(filters)
+ gst_accounts = get_gst_accounts(filters.company)
+ data = []
+ count = 1
+
+ for hsn in report_data:
+ row = {
+ "num": count,
+ "hsn_sc": hsn.get("gst_hsn_code"),
+ "desc": hsn.get("description"),
+ "uqc": hsn.get("stock_uom").upper(),
+ "qty": hsn.get("stock_qty"),
+ "val": flt(hsn.get("total_amount"), 2),
+ "txval": flt(hsn.get("taxable_amount", 2)),
+ "iamt": 0.0,
+ "camt": 0.0,
+ "samt": 0.0,
+ "csamt": 0.0
+
+ }
+
+ for account in gst_accounts.get('igst_account'):
+ row['iamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
+
+ for account in gst_accounts.get('cgst_account'):
+ row['camt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
+
+ for account in gst_accounts.get('sgst_account'):
+ row['samt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
+
+ for account in gst_accounts.get('cess_account'):
+ row['csamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
+
+ data.append(row)
+ count +=1
+
+ return data
+
+
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 93d4832..1f955fc 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -184,10 +184,10 @@
def validate_credit_limit_on_change(self):
if self.get("__islocal") or not self.credit_limits:
return
-
+
past_credit_limits = [d.credit_limit
for d in frappe.db.get_all("Customer Credit Limit", filters={'parent': self.name}, fields=["credit_limit"], order_by="company")]
-
+
current_credit_limits = [d.credit_limit for d in sorted(self.credit_limits, key=lambda k: k.company)]
if past_credit_limits == current_credit_limits:
@@ -396,13 +396,12 @@
credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager")
# form a list of emails and names to show to the user
- credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
-
- if not credit_controller_users:
+ credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
+ if not credit_controller_users_formatted:
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
message = """Please contact any of the following users to extend the credit limits for {0}:
- <br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users))
+ <br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))
# if the current user does not have permissions to override credit limit,
# prompt them to send out an email to the controller users
@@ -427,7 +426,7 @@
subject = (_("Credit limit reached for customer {0}").format(args.get('customer')))
message = (_("Credit limit has been crossed for customer {0} ({1}/{2})")
.format(args.get('customer'), args.get('customer_outstanding'), args.get('credit_limit')))
- frappe.sendmail(recipients=[args.get('credit_controller_users_list')], subject=subject, message=message)
+ frappe.sendmail(recipients=args.get('credit_controller_users_list'), subject=subject, message=message)
def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None):
# Outstanding based on GL Entries
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py
index dba24ef..b613718 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.py
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.py
@@ -20,29 +20,28 @@
validate_filters(from_date, to_date, company)
active_leads = frappe.db.sql("""select count(*) from `tabLead`
- where (date(`modified`) between %s and %s)
- and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0]
-
- active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact
- left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
- and (date(contact.modified) between %s and %s) and status != "Passive" """, (from_date, to_date))[0][0]
+ where (date(`creation`) between %s and %s)
+ and company=%s""", (from_date, to_date, company))[0][0]
opportunities = frappe.db.sql("""select count(*) from `tabOpportunity`
where (date(`creation`) between %s and %s)
- and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
+ and opportunity_from='Lead' and company=%s""", (from_date, to_date, company))[0][0]
quotations = frappe.db.sql("""select count(*) from `tabQuotation`
where docstatus = 1 and (date(`creation`) between %s and %s)
- and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
+ and (opportunity!="" or quotation_to="Lead") and company=%s""", (from_date, to_date, company))[0][0]
- sales_orders = frappe.db.sql("""select count(*) from `tabSales Order`
- where docstatus = 1 and (date(`creation`) between %s and %s) and company=%s""", (from_date, to_date, company))[0][0]
+ converted = frappe.db.sql("""select count(*) from `tabCustomer`
+ JOIN `tabLead` ON `tabLead`.name = `tabCustomer`.lead_name
+ WHERE (date(`tabCustomer`.creation) between %s and %s)
+ and `tabLead`.company=%s""", (from_date, to_date, company))[0][0]
+
return [
- { "title": _("Active Leads / Customers"), "value": active_leads, "color": "#B03B46" },
+ { "title": _("Active Leads"), "value": active_leads, "color": "#B03B46" },
{ "title": _("Opportunities"), "value": opportunities, "color": "#F09C00" },
{ "title": _("Quotations"), "value": quotations, "color": "#006685" },
- { "title": _("Sales Orders"), "value": sales_orders, "color": "#00AD65" }
+ { "title": _("Converted"), "value": converted, "color": "#00AD65" }
]
@frappe.whitelist()
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
index 857b982..ae216ca 100644
--- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
@@ -63,13 +63,13 @@
"label": _(partner_doctype),
"fieldtype": "Link",
"options": partner_doctype,
- "width": 100
+ "width": 150
}, {
"fieldname": "item_group",
"label": _("Item Group"),
"fieldtype": "Link",
"options": "Item Group",
- "width": 100
+ "width": 150
}]
for period in period_list:
@@ -81,19 +81,19 @@
"label": _("Target ({})").format(period.label),
"fieldtype": fieldtype,
"options": options,
- "width": 100
+ "width": 150
}, {
"fieldname": period.key,
"label": _("Achieved ({})").format(period.label),
"fieldtype": fieldtype,
"options": options,
- "width": 100
+ "width": 150
}, {
"fieldname": variance_key,
"label": _("Variance ({})").format(period.label),
"fieldtype": fieldtype,
"options": options,
- "width": 100
+ "width": 150
}])
columns.extend([{
@@ -101,19 +101,19 @@
"label": _("Total Target"),
"fieldtype": fieldtype,
"options": options,
- "width": 100
+ "width": 150
}, {
"fieldname": "total_achieved",
"label": _("Total Achieved"),
"fieldtype": fieldtype,
"options": options,
- "width": 100
+ "width": 150
}, {
"fieldname": "total_variance",
"label": _("Total Variance"),
"fieldtype": fieldtype,
"options": options,
- "width": 100
+ "width": 150
}])
return columns
@@ -154,10 +154,10 @@
if (r.get(sales_field) == d.parent and r.item_group == d.item_group and
period.from_date <= r.get(date_field) and r.get(date_field) <= period.to_date):
details[p_key] += r.get(qty_or_amount_field, 0)
- details[variance_key] = details.get(target_key) - details.get(p_key)
+ details[variance_key] = details.get(p_key) - details.get(target_key)
details["total_achieved"] += details.get(p_key)
- details["total_variance"] = details.get("total_target") - details.get("total_achieved")
+ details["total_variance"] = details.get("total_achieved") - details.get("total_target")
return rows
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js
index f99f68c..38bb127 100644
--- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js
@@ -44,5 +44,20 @@
options: "Quantity\nAmount",
default: "Quantity"
},
- ]
+ ],
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname.includes('variance')) {
+
+ if (data[column.fieldname] < 0) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ else if (data[column.fieldname] > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ }
+
+ return value;
+ }
}
diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js
index 9f6bfc4..a8e2fad 100644
--- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js
+++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.js
@@ -44,5 +44,20 @@
options: "Quantity\nAmount",
default: "Quantity"
},
- ]
+ ],
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname.includes('variance')) {
+
+ if (data[column.fieldname] < 0) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ else if (data[column.fieldname] > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ }
+
+ return value;
+ }
}
diff --git a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js
index dd9607f..263391a 100644
--- a/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js
+++ b/erpnext/selling/report/territory_target_variance_based_on_item_group/territory_target_variance_based_on_item_group.js
@@ -44,5 +44,20 @@
options: "Quantity\nAmount",
default: "Quantity"
},
- ]
+ ],
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname.includes('variance')) {
+
+ if (data[column.fieldname] < 0) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ else if (data[column.fieldname] > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ }
+
+ return value;
+ }
}
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 4f0f572..2225fe1 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -20,6 +20,7 @@
frappe.get_doc({'doctype': "Role", "role_name": "Analytics"}).insert()
set_single_defaults()
create_compact_item_print_custom_field()
+ create_print_uom_after_qty_custom_field()
create_print_zero_amount_taxes_custom_field()
add_all_roles_to("Administrator")
create_default_cash_flow_mapper_templates()
@@ -66,6 +67,16 @@
})
+def create_print_uom_after_qty_custom_field():
+ create_custom_field('Print Settings', {
+ 'label': _('Print UOM after Quantity'),
+ 'fieldname': 'print_uom_after_quantity',
+ 'fieldtype': 'Check',
+ 'default': 0,
+ 'insert_after': 'compact_item_print'
+ })
+
+
def create_print_zero_amount_taxes_custom_field():
create_custom_field('Print Settings', {
'label': _('Print taxes with zero amount'),
diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py
index 5545f13..ef238f1 100644
--- a/erpnext/startup/leaderboard.py
+++ b/erpnext/startup/leaderboard.py
@@ -123,7 +123,8 @@
if field == "outstanding_amount":
filters = [['docstatus', '=', '1'], ['company', '=', company]]
if date_range:
- filters.append(['posting_date', 'between' [date_range[0], date_range[1]]])
+ date_range = frappe.parse_json(date_range)
+ filters.append(['posting_date', 'between', [date_range[0], date_range[1]]])
return frappe.db.get_all('Purchase Invoice',
fields = ['supplier as name', 'sum(outstanding_amount) as value'],
filters = filters,
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 30bcccd..a92d04f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -513,7 +513,7 @@
d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually:
d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
- d.basic_amount = d.basic_rate * d.qty
+ d.basic_amount = d.basic_rate * flt(d.qty)
def distribute_additional_costs(self):
if self.purpose == "Material Issue":
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 43fbc00..b81f8a0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -258,6 +258,7 @@
sl_entries.append(args)
+ qty_after_transaction = 0
for serial_no in serial_nos:
args = self.get_sle_for_items(row, [serial_no])
@@ -271,11 +272,19 @@
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
# If serial no exists in different warehouse
+ warehouse = previous_sle.get("warehouse", '') or row.warehouse
+
+ if not qty_after_transaction:
+ qty_after_transaction = get_stock_balance(row.item_code,
+ warehouse, self.posting_date, self.posting_time)
+
+ qty_after_transaction -= 1
+
new_args = args.copy()
new_args.update({
'actual_qty': -1,
- 'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1,
- 'warehouse': previous_sle.get("warehouse", '') or row.warehouse,
+ 'qty_after_transaction': qty_after_transaction,
+ 'warehouse': warehouse,
'valuation_rate': previous_sle.get("valuation_rate")
})
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
index 2a70d8d..40bc0c7 100644
--- a/erpnext/templates/generators/item/item_add_to_cart.html
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -27,22 +27,25 @@
{% endif %}
</div>
{% endif %}
- {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
<div class="mt-3">
- <a href="/cart"
- class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
- role="button"
- >
- {{ _("View in Cart") }}
- </a>
- <button
- data-item-code="{{item_code}}"
- class="btn btn-outline-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %}"
- >
- {{ _("Add to Cart") }}
- </button>
+ {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
+ <a href="/cart"
+ class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
+ role="button"
+ >
+ {{ _("View in Cart") }}
+ </a>
+ <button
+ data-item-code="{{item_code}}"
+ class="btn btn-outline-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %}"
+ >
+ {{ _("Add to Cart") }}
+ </button>
+ {% endif %}
+ {% if cart_settings.show_contact_us_button %}
+ {% include "templates/generators/item/item_inquiry.html" %}
+ {% endif %}
</div>
- {% endif %}
</div>
</div>
diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html
index b8b0d98..73f9ec9 100644
--- a/erpnext/templates/generators/item/item_configure.html
+++ b/erpnext/templates/generators/item/item_configure.html
@@ -10,14 +10,11 @@
{{ _('Configure') }}
</button>
{% endif %}
- {% if cart_settings.show_contact_us_button | int %}
- <button class="btn btn-link btn-inquiry" data-item-code="{{ doc.name }}">
- {{ _('Contact Us') }}
- </button>
+ {% if cart_settings.show_contact_us_button %}
+ {% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
</div>
<script>
{% include "templates/generators/item/item_configure.js" %}
-{% include "templates/generators/item/item_inquiry.js" %}
</script>
{% endif %}
diff --git a/erpnext/templates/generators/item/item_inquiry.html b/erpnext/templates/generators/item/item_inquiry.html
new file mode 100644
index 0000000..83653b6
--- /dev/null
+++ b/erpnext/templates/generators/item/item_inquiry.html
@@ -0,0 +1,11 @@
+{% if shopping_cart and shopping_cart.cart_settings.enabled %}
+{% set cart_settings = shopping_cart.cart_settings %}
+ {% if cart_settings.show_contact_us_button | int %}
+ <button class="btn btn-inquiry btn-primary-light" data-item-code="{{ doc.name }}">
+ {{ _('Contact Us') }}
+ </button>
+ {% endif %}
+<script>
+{% include "templates/generators/item/item_inquiry.js" %}
+</script>
+{% endif %}
diff --git a/erpnext/templates/print_formats/includes/item_table_qty.html b/erpnext/templates/print_formats/includes/item_table_qty.html
index 239859e..ecaaef4 100644
--- a/erpnext/templates/print_formats/includes/item_table_qty.html
+++ b/erpnext/templates/print_formats/includes/item_table_qty.html
@@ -1,6 +1,15 @@
-{% if (doc.uom and not doc.is_print_hide("uom")) %}
- <small class="pull-left">{{ _(doc.uom) }}</small>
-{% elif (doc.stock_uom and not doc.is_print_hide("stock_uom")) %}
- <small class="pull-left">{{ _(doc.stock_uom) }}</small>
+{% set qty_first=frappe.db.get_single_value("Print Settings", "print_uom_after_quantity") %}
+{% if qty_first %}
+ {{ doc.get_formatted("qty", doc) }}
+ {% if (doc.uom and not doc.is_print_hide("uom")) %} {{ _(doc.uom) }}
+ {% elif (doc.stock_uom and not doc.is_print_hide("stock_uom")) %} {{ _(doc.stock_uom) }}
+ {%- endif %}
+{% else %}
+ {% if (doc.uom and not doc.is_print_hide("uom")) %}
+ <small class="pull-left">{{ _(doc.uom) }}</small>
+ {% elif (doc.stock_uom and not doc.is_print_hide("stock_uom")) %}
+ <small class="pull-left">{{ _(doc.stock_uom) }}</small>
+ {%- endif %}
+ {{ doc.get_formatted("qty", doc) }}
{%- endif %}
-{{ doc.get_formatted("qty", doc) }}
+
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
index c23c1f7..66d6cd3 100644
--- a/erpnext/utilities/product.py
+++ b/erpnext/utilities/product.py
@@ -82,6 +82,7 @@
pricing_rule = get_pricing_rule_for_item(frappe._dict({
"item_code": item_code,
"qty": qty,
+ "stock_qty": qty,
"transaction_type": "selling",
"price_list": price_list,
"customer_group": customer_group,
diff --git a/yarn.lock b/yarn.lock
index b19f566..97a0635 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -282,9 +282,9 @@
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bl@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
- integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f"
+ integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==
dependencies:
readable-stream "^3.0.1"
@@ -866,12 +866,12 @@
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-inherits@2.0.4, inherits@~2.0.1:
+inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1447,9 +1447,9 @@
util-deprecate "~1.0.1"
readable-stream@^3.0.1, readable-stream@^3.1.1:
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606"
- integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+ integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
@@ -1505,9 +1505,9 @@
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@~5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
- integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"