Merge branch 'develop' of https://github.com/frappe/erpnext into uom_after_qty
This commit is contained in:
commit
be3b8a1c96
@ -638,20 +638,12 @@ $.extend(erpnext.journal_entry, {
|
||||
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, {
|
||||
|
@ -1021,3 +1021,34 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
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
|
@ -167,6 +167,49 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
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",
|
||||
|
@ -447,7 +447,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "po_no",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Data",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Customer's Purchase Order",
|
||||
@ -1946,7 +1946,7 @@
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 23:31:12.675040",
|
||||
"modified": "2020-08-27 01:56:28.532140",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -3,6 +3,22 @@
|
||||
|
||||
frappe.ui.form.on('Shipping Rule', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.trigger('toggle_reqd');
|
||||
},
|
||||
calculate_based_on: function(frm) {
|
||||
|
@ -1,134 +1,66 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 0,
|
||||
"creation": "2018-11-22 23:38:39.668804",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Title",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-01-15 17:14:28.951793",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-30 19:41:25.783852",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -45,8 +45,8 @@ def validate_accounting_period(gl_map):
|
||||
}, 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 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
})
|
||||
|
||||
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 @@ def set_as_cancel(voucher_type, voucher_no):
|
||||
"""
|
||||
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))
|
||||
|
@ -71,7 +71,22 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
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) => {
|
||||
|
@ -173,7 +173,7 @@ class PartyLedgerSummaryReport(object):
|
||||
from `tabGL Entry` gle
|
||||
{join}
|
||||
where
|
||||
gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||
gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||
and gle.posting_date <= %(to_date)s {conditions}
|
||||
order by gle.posting_date
|
||||
""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
|
||||
@ -248,7 +248,7 @@ class PartyLedgerSummaryReport(object):
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
docstatus < 2
|
||||
docstatus < 2 and is_cancelled = 0
|
||||
and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
||||
|
@ -43,8 +43,11 @@ def execute(filters=None):
|
||||
|
||||
|
||||
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))
|
||||
|
@ -1,13 +1,12 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"add_total_row": 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": "2020-08-13 11:26:39.112352",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Gross Profit",
|
||||
|
@ -12,7 +12,22 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
"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 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
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 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
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"), () => {
|
||||
|
@ -16,44 +16,49 @@ def execute(filters=None):
|
||||
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 @@ def prepare_data(supplier_quotation_data):
|
||||
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 @@ def prepare_data(supplier_quotation_data):
|
||||
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 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
||||
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 @@ def get_columns():
|
||||
"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 @@ def get_columns():
|
||||
"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
|
||||
|
||||
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>"""
|
@ -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",
|
||||
|
@ -16,6 +16,9 @@ class CallLog(Document):
|
||||
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()
|
||||
|
||||
|
@ -276,6 +276,9 @@ class BuyingController(StockController):
|
||||
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
|
||||
|
||||
for item in self.get('items'):
|
||||
if not item.purchase_order:
|
||||
continue
|
||||
|
||||
# reset raw_material cost
|
||||
item.rm_supp_cost = 0
|
||||
|
||||
@ -288,6 +291,12 @@ class BuyingController(StockController):
|
||||
|
||||
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
|
||||
|
||||
if not fg_yet_to_be_received:
|
||||
frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}")
|
||||
.format(item.idx, frappe.bold(item.item_code),
|
||||
frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)),
|
||||
title=_("Limit Crossed"))
|
||||
|
||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||
|
||||
|
@ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||
from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
||||
validate_taxes_and_charges, validate_inclusive_tax
|
||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
||||
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
@ -607,7 +608,7 @@ class calculate_taxes_and_totals(object):
|
||||
base_rate_with_margin = 0.0
|
||||
if item.price_list_rate:
|
||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||
for d in json.loads(item.pricing_rules):
|
||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
|
||||
|
@ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', {
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span>
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
if (frm.doc.linkedin){
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span>
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
|
@ -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": []
|
||||
}
|
@ -226,7 +226,9 @@ let check_and_set_availability = function(frm) {
|
||||
primary_action_label: __('Book'),
|
||||
primary_action: function() {
|
||||
frm.set_value('appointment_time', selected_slot);
|
||||
frm.set_value('duration', duration);
|
||||
if (!frm.doc.duration) {
|
||||
frm.set_value('duration', duration);
|
||||
}
|
||||
frm.set_value('practitioner', d.get_value('practitioner'));
|
||||
frm.set_value('department', d.get_value('department'));
|
||||
frm.set_value('appointment_date', d.get_value('appointment_date'));
|
||||
|
@ -132,6 +132,9 @@ def get_conditions(filters):
|
||||
if filters.get('employee'):
|
||||
conditions['name'] = filters.get('employee')
|
||||
|
||||
if filters.get('company'):
|
||||
conditions['company'] = filters.get('company')
|
||||
|
||||
return conditions
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
|
@ -17,6 +17,8 @@ from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loa
|
||||
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
|
||||
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):
|
||||
@ -193,18 +195,14 @@ class TestLoan(unittest.TestCase):
|
||||
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()
|
||||
@ -306,9 +304,6 @@ class TestLoan(unittest.TestCase):
|
||||
"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")
|
||||
|
||||
@ -323,6 +318,97 @@ class TestLoan(unittest.TestCase):
|
||||
self.assertEqual(loan.status, 'Closed')
|
||||
self.assertEquals(sum(pledged_qty.values()), 0)
|
||||
|
||||
def test_disbursal_check_with_shortfall(self):
|
||||
pledges = [{
|
||||
"loan_security": "Test Security 2",
|
||||
"qty": 8000.00,
|
||||
"haircut": 50,
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||
loan.submit()
|
||||
|
||||
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||
make_loan_disbursement_entry(loan.name, 700000)
|
||||
|
||||
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
create_process_loan_security_shortfall()
|
||||
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
|
||||
self.assertTrue(loan_security_shortfall)
|
||||
|
||||
self.assertEqual(get_disbursal_amount(loan.name), 0)
|
||||
|
||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
def test_disbursal_check_without_shortfall(self):
|
||||
pledges = [{
|
||||
"loan_security": "Test Security 2",
|
||||
"qty": 8000.00,
|
||||
"haircut": 50,
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||
loan.submit()
|
||||
|
||||
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||
make_loan_disbursement_entry(loan.name, 700000)
|
||||
|
||||
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"):
|
||||
|
@ -33,18 +33,18 @@ frappe.ui.form.on('Loan Application', {
|
||||
|
||||
if (frm.doc.is_secured_loan) {
|
||||
frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||
if (!r) {
|
||||
if (Object.keys(r).length === 0) {
|
||||
frm.add_custom_button(__('Loan Security Pledge'), function() {
|
||||
frm.trigger('create_loan_security_pledge')
|
||||
frm.trigger('create_loan_security_pledge');
|
||||
},__('Create'))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frappe.db.get_value("Loan", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||
if (!r) {
|
||||
if (Object.keys(r).length === 0) {
|
||||
frm.add_custom_button(__('Loan'), function() {
|
||||
frm.trigger('create_loan')
|
||||
frm.trigger('create_loan');
|
||||
},__('Create'))
|
||||
} else {
|
||||
frm.set_df_property('status', 'read_only', 1);
|
||||
@ -54,7 +54,7 @@ frappe.ui.form.on('Loan Application', {
|
||||
},
|
||||
create_loan: function(frm) {
|
||||
if (frm.doc.status != "Approved") {
|
||||
frappe.throw(__("Cannot create loan until application is approved"))
|
||||
frappe.throw(__("Cannot create loan until application is approved"));
|
||||
}
|
||||
|
||||
frappe.model.open_mapped_doc({
|
||||
|
@ -67,28 +67,10 @@ class LoanDisbursement(AccountsController):
|
||||
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
|
||||
total_payment = loan_details.total_payment
|
||||
|
||||
if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
|
||||
possible_disbursal_amount = get_disbursal_amount(self.against_loan)
|
||||
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan_details.disbursed_amount
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(self.against_loan)
|
||||
|
||||
if not security_value:
|
||||
security_value = loan_details.loan_amount
|
||||
|
||||
if pending_principal_amount + self.disbursed_amount > flt(security_value):
|
||||
allowed_amount = security_value - pending_principal_amount
|
||||
if allowed_amount < 0:
|
||||
allowed_amount = 0
|
||||
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount))
|
||||
if self.disbursed_amount > possible_disbursal_amount:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
|
||||
|
||||
if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
|
||||
@ -176,3 +158,32 @@ def get_total_pledged_security_value(loan):
|
||||
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
|
||||
|
||||
return security_value
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_disbursal_amount(loan):
|
||||
loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
|
||||
"total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
|
||||
filters= { "name": loan })[0]
|
||||
|
||||
if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
|
||||
'status': 'Pending'}):
|
||||
return 0
|
||||
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = flt(loan_details.disbursed_amount)
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(loan)
|
||||
|
||||
if not security_value and not loan_details.is_secured_loan:
|
||||
security_value = flt(loan_details.loan_amount)
|
||||
|
||||
disbursal_amount = flt(security_value) - flt(pending_principal_amount)
|
||||
|
||||
return disbursal_amount
|
||||
|
||||
|
||||
|
@ -85,8 +85,11 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
if no_of_days <= 0:
|
||||
return
|
||||
|
||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
if loan.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan.disbursed_amount
|
||||
|
||||
interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
|
||||
payable_interest = interest_per_day * no_of_days
|
||||
@ -107,7 +110,7 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
|
||||
def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None):
|
||||
query_filters = {
|
||||
"status": "Disbursed",
|
||||
"status": ('in', ['Disbursed', 'Partially Disbursed']),
|
||||
"docstatus": 1
|
||||
}
|
||||
|
||||
@ -118,8 +121,9 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte
|
||||
|
||||
if not open_loans:
|
||||
open_loans = frappe.get_all("Loan",
|
||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "is_term_loan",
|
||||
"disbursement_date", "applicant_type", "applicant", "rate_of_interest", "total_interest_payable", "repayment_start_date"],
|
||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account",
|
||||
"is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant",
|
||||
"rate_of_interest", "total_interest_payable", "total_principal_paid", "repayment_start_date"],
|
||||
filters=query_filters)
|
||||
|
||||
for loan in open_loans:
|
||||
|
@ -116,6 +116,7 @@ class LoanRepayment(AccountsController):
|
||||
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 @@ class LoanRepayment(AccountsController):
|
||||
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
|
||||
|
||||
@ -281,7 +289,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
|
||||
due_date = add_days(entry.posting_date, 1)
|
||||
no_of_late_days = date_diff(posting_date,
|
||||
add_days(due_date, loan_type_details.grace_period_in_days))
|
||||
add_days(due_date, loan_type_details.grace_period_in_days))
|
||||
|
||||
if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary):
|
||||
penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365
|
||||
@ -297,7 +305,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
if not final_due_date:
|
||||
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
|
||||
|
||||
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
|
||||
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
|
||||
|
||||
if payment_type == "Loan Closure":
|
||||
if due_date:
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import get_datetime
|
||||
from frappe.utils import get_datetime, flt
|
||||
from frappe.model.document import Document
|
||||
from six import iteritems
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
@ -51,13 +51,19 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
"valid_upto": (">=", update_time)
|
||||
}, as_list=1))
|
||||
|
||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid'],
|
||||
filters={'status': 'Disbursed', 'is_secured_loan': 1})
|
||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid', 'total_payment',
|
||||
'total_interest_payable', 'disbursed_amount', 'status'],
|
||||
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
|
||||
|
||||
loan_security_map = {}
|
||||
|
||||
for loan in loans:
|
||||
outstanding_amount = loan.loan_amount - loan.total_principal_paid
|
||||
if loan.status == 'Disbursed':
|
||||
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
outstanding_amount = loan.disbursed_amount
|
||||
|
||||
pledged_securities = get_pledged_security_qty(loan.name)
|
||||
ltv_ratio = ''
|
||||
security_value = 0.0
|
||||
|
@ -43,8 +43,10 @@ class LoanSecurityUnpledge(Document):
|
||||
"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:
|
||||
@ -60,7 +62,7 @@ class LoanSecurityUnpledge(Document):
|
||||
|
||||
security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security)
|
||||
|
||||
if not security_value and pending_principal_amount > 0:
|
||||
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:
|
||||
|
@ -67,16 +67,16 @@ class MaintenanceSchedule(TransactionBase):
|
||||
|
||||
for key in scheduled_date:
|
||||
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
|
||||
frappe.get_doc({
|
||||
event = frappe.get_doc({
|
||||
"doctype": "Event",
|
||||
"owner": email_map.get(d.sales_person, self.owner),
|
||||
"subject": description,
|
||||
"description": description,
|
||||
"starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
|
||||
"event_type": "Private",
|
||||
"ref_type": self.doctype,
|
||||
"ref_name": self.name
|
||||
}).insert(ignore_permissions=1)
|
||||
})
|
||||
event.add_participant(self.doctype, self.name)
|
||||
event.insert(ignore_permissions=1)
|
||||
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
from frappe.utils.data import get_datetime, add_days
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
@ -9,4 +10,39 @@ import unittest
|
||||
# test_records = frappe.get_test_records('Maintenance Schedule')
|
||||
|
||||
class TestMaintenanceSchedule(unittest.TestCase):
|
||||
pass
|
||||
def test_events_should_be_created_and_deleted(self):
|
||||
ms = make_maintenance_schedule()
|
||||
ms.generate_schedule()
|
||||
ms.submit()
|
||||
|
||||
all_events = get_events(ms)
|
||||
self.assertTrue(len(all_events) > 0)
|
||||
|
||||
ms.cancel()
|
||||
events_after_cancel = get_events(ms)
|
||||
self.assertTrue(len(events_after_cancel) == 0)
|
||||
|
||||
def get_events(ms):
|
||||
return frappe.get_all("Event Participants", filters={
|
||||
"reference_doctype": ms.doctype,
|
||||
"reference_docname": ms.name,
|
||||
"parenttype": "Event"
|
||||
})
|
||||
|
||||
def make_maintenance_schedule():
|
||||
ms = frappe.new_doc("Maintenance Schedule")
|
||||
ms.company = "_Test Company"
|
||||
ms.customer = "_Test Customer"
|
||||
ms.transaction_date = get_datetime()
|
||||
|
||||
ms.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"start_date": get_datetime(),
|
||||
"end_date": add_days(get_datetime(), 32),
|
||||
"periodicity": "Weekly",
|
||||
"no_of_visits": 4,
|
||||
"sales_person": "Sales Team",
|
||||
})
|
||||
ms.insert(ignore_permissions=True)
|
||||
|
||||
return ms
|
||||
|
@ -494,7 +494,7 @@ class BOM(WebsiteGenerator):
|
||||
'image' : d.image,
|
||||
'stock_uom' : d.stock_uom,
|
||||
'stock_qty' : flt(d.stock_qty),
|
||||
'rate' : flt(d.base_rate) / flt(d.conversion_factor),
|
||||
'rate' : flt(d.base_rate) / (flt(d.conversion_factor) or 1.0),
|
||||
'include_item_in_manufacturing': d.include_item_in_manufacturing
|
||||
}))
|
||||
|
||||
|
@ -90,6 +90,7 @@ def update_latest_price_in_all_boms():
|
||||
update_cost()
|
||||
|
||||
def replace_bom(args):
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
args = frappe._dict(args)
|
||||
|
||||
doc = frappe.get_doc("BOM Update Tool")
|
||||
@ -97,6 +98,8 @@ def replace_bom(args):
|
||||
doc.new_bom = args.new_bom
|
||||
doc.replace_bom()
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = 0
|
||||
|
||||
def update_cost():
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
bom_list = get_boms_in_bottom_up_order()
|
||||
|
@ -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 @@ frappe.ui.form.on('Job Card', {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ frappe.ui.form.on('Job Card', {
|
||||
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 @@ frappe.ui.form.on('Job Card', {
|
||||
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 @@ frappe.ui.form.on('Job Card', {
|
||||
employee: function(frm) {
|
||||
if (frm.doc.job_started && !frm.doc.current_time) {
|
||||
frm.trigger("reset_timer");
|
||||
} else {
|
||||
frm.events.start_job(frm);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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",
|
||||
|
@ -15,10 +15,13 @@ from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings
|
||||
|
||||
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 @@ class JobCard(Document):
|
||||
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 @@ class JobCard(Document):
|
||||
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 @@ class JobCard(Document):
|
||||
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):
|
||||
|
@ -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)
|
@ -20,3 +20,18 @@ class TestWorkstation(unittest.TestCase):
|
||||
"_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)
|
@ -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",
|
||||
|
@ -9,6 +9,7 @@ from frappe.model.document import Document
|
||||
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 @@ def get_or_create_member(user_details):
|
||||
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 @@ def create_customer(user_details):
|
||||
"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,8 +150,6 @@ def create_member_subscription_order(user_details):
|
||||
|
||||
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()
|
||||
|
||||
@ -148,3 +157,24 @@ def create_member_subscription_order(user_details):
|
||||
member.save(ignore_permissions=True)
|
||||
|
||||
return subscription
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
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
|
@ -8,6 +8,24 @@ frappe.ui.form.on('Membership', {
|
||||
})
|
||||
},
|
||||
|
||||
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');
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -10,6 +10,7 @@ from datetime import datetime
|
||||
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 @@ class Membership(Document):
|
||||
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 @@ def verify_signature(data):
|
||||
|
||||
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 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
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 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
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 @@ Administrator""".format(get_link_to_form("Error Log", log.name))
|
||||
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
|
@ -10,7 +10,39 @@ frappe.ui.form.on("Membership Settings", {
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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 @@ frappe.ui.form.on("Membership Settings", {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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`);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -5,6 +5,10 @@ frappe.ui.form.on('Membership Type', {
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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})
|
@ -718,7 +718,9 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order
|
||||
erpnext.patches.v12_0.update_item_tax_template_company
|
||||
erpnext.patches.v13_0.move_branch_code_to_bank_account
|
||||
erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
|
||||
erpnext.patches.v13_0.add_standard_navbar_items #4
|
||||
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
|
||||
|
@ -7,6 +7,7 @@ def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True)
|
||||
frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True)
|
||||
frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True)
|
||||
frappe.reload_doc('setup', 'doctype', 'quotation_lost_reason_detail', force=True)
|
||||
|
||||
company = frappe.get_all('Company', filters = {'country': 'United States'})
|
||||
if not company:
|
||||
|
@ -3,6 +3,7 @@ import frappe
|
||||
|
||||
def execute():
|
||||
if frappe.db.exists("DocType", "Lost Reason Detail"):
|
||||
frappe.reload_doc("crm", "doctype", "opportunity_lost_reason")
|
||||
frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail")
|
||||
frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail")
|
||||
|
||||
|
7
erpnext/patches/v13_0/add_standard_navbar_items.py
Normal file
7
erpnext/patches/v13_0/add_standard_navbar_items.py
Normal file
@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from erpnext.setup.install import add_standard_navbar_items
|
||||
|
||||
def execute():
|
||||
# Add standard navbar items for ERPNext in Navbar Settings
|
||||
add_standard_navbar_items()
|
7
erpnext/patches/v13_0/drop_razorpay_payload_column.py
Normal file
7
erpnext/patches/v13_0/drop_razorpay_payload_column.py
Normal file
@ -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")
|
@ -25,3 +25,7 @@ def execute():
|
||||
doc = frappe.new_doc('Warehouse Type')
|
||||
doc.name = 'Transit'
|
||||
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")
|
@ -7,4 +7,7 @@ import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('hr', 'doctype', 'shift_assignment')
|
||||
frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date where date IS NOT NULL and start_date IS NULL and end_date IS NULL;")
|
||||
if frappe.db.has_column('Shift Assignment', 'date'):
|
||||
frappe.db.sql("""update `tabShift Assignment`
|
||||
set end_date=date, start_date=date
|
||||
where date IS NOT NULL and start_date IS NULL and end_date IS NULL;""")
|
||||
|
@ -21,34 +21,6 @@ frappe.ui.form.on('Homepage', {
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Homepage Featured Product', {
|
||||
item_code: function(frm, cdt, cdn) {
|
||||
var featured_product = frappe.model.get_doc(cdt, cdn);
|
||||
if (featured_product.item_code) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get_value',
|
||||
args: {
|
||||
'doctype': 'Item',
|
||||
'filters': {'name': featured_product.item_code},
|
||||
'fieldname': [
|
||||
'item_name',
|
||||
'web_long_description',
|
||||
'description',
|
||||
'image',
|
||||
'thumbnail'
|
||||
]
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
$.extend(featured_product, r.message);
|
||||
if (r.message.web_long_description) {
|
||||
featured_product.description = r.message.web_long_description;
|
||||
}
|
||||
frm.refresh_field('products');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
view: function(frm, cdt, cdn){
|
||||
var child= locals[cdt][cdn]
|
||||
|
@ -1,301 +1,116 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2016-04-22 05:57:06.261401",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"col_break1",
|
||||
"item_name",
|
||||
"view",
|
||||
"section_break_5",
|
||||
"description",
|
||||
"column_break_7",
|
||||
"image",
|
||||
"thumbnail",
|
||||
"route"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "item_code",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "item_name",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "150"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "view",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "View",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "View"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fetch_from": "item_code.web_long_description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "300px",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fetch_from": "item_code.website_image",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Image",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "thumbnail",
|
||||
"fieldtype": "Attach Image",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Thumbnail",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"label": "Thumbnail"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "route",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-08-09 06:09:34.731971",
|
||||
"links": [],
|
||||
"modified": "2020-08-25 15:27:49.573537",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Portal",
|
||||
"name": "Homepage Featured Product",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -3,32 +3,6 @@
|
||||
|
||||
frappe.provide('erpnext');
|
||||
|
||||
// add toolbar icon
|
||||
$(document).bind('toolbar_setup', function() {
|
||||
frappe.app.name = "ERPNext";
|
||||
|
||||
frappe.help_feedback_link = '<p><a class="text-muted" \
|
||||
href="https://discuss.erpnext.com">Feedback</a></p>'
|
||||
|
||||
|
||||
$('[data-link="docs"]').attr("href", "https://erpnext.com/docs")
|
||||
$('[data-link="issues"]').attr("href", "https://github.com/frappe/erpnext/issues")
|
||||
|
||||
|
||||
// default documentation goes to erpnext
|
||||
// $('[data-link-type="documentation"]').attr('data-path', '/erpnext/manual/index');
|
||||
|
||||
// additional help links for erpnext
|
||||
var $help_menu = $('.dropdown-help ul .documentation-links');
|
||||
$('<li><a data-link-type="forum" href="https://erpnext.com/docs/user/manual" \
|
||||
target="_blank">'+__('Documentation')+'</a></li>').insertBefore($help_menu);
|
||||
$('<li><a data-link-type="forum" href="https://discuss.erpnext.com" \
|
||||
target="_blank">'+__('User Forum')+'</a></li>').insertBefore($help_menu);
|
||||
$('<li><a href="https://github.com/frappe/erpnext/issues" \
|
||||
target="_blank">'+__('Report an Issue')+'</a></li>').insertBefore($help_menu);
|
||||
|
||||
});
|
||||
|
||||
// preferred modules for breadcrumbs
|
||||
$.extend(frappe.breadcrumbs.preferred, {
|
||||
"Item Group": "Stock",
|
||||
|
@ -503,11 +503,11 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
@ -781,10 +781,23 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
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);
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
||||
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');
|
||||
},
|
||||
|
@ -46,5 +46,28 @@ frappe.query_reports["HSN-wise-summary of outward supplies"] = {
|
||||
],
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
|
||||
|
||||
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 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
|
||||
""" % (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 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
|
||||
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 @@ def get_merged_data(columns, data):
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -396,13 +396,12 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
|
||||
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 @@ def send_emails(args):
|
||||
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
|
||||
|
@ -33,7 +33,7 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('Support'),
|
||||
'items': ['Issue', 'Maintenance Visit']
|
||||
'items': ['Issue', 'Maintenance Visit', 'Installation Note', 'Warranty Claim']
|
||||
},
|
||||
{
|
||||
'label': _('Projects'),
|
||||
|
@ -285,9 +285,17 @@ def _make_customer(source_name, ignore_permissions=False):
|
||||
return customer
|
||||
else:
|
||||
raise
|
||||
except frappe.MandatoryError:
|
||||
except frappe.MandatoryError as e:
|
||||
mandatory_fields = e.args[0].split(':')[1].split(',')
|
||||
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
|
||||
|
||||
frappe.local.message_log = []
|
||||
frappe.throw(_("Please create Customer from Lead {0}").format(lead_name))
|
||||
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)
|
||||
message = _("Could not auto create Customer due to the following missing mandatory field(s):") + "<br>"
|
||||
message += "<br><ul><li>" + "</li><li>".join(mandatory_fields) + "</li></ul>"
|
||||
message += _("Please create Customer from Lead {0}.").format(lead_link)
|
||||
|
||||
frappe.throw(message, title=_("Mandatory Missing"))
|
||||
else:
|
||||
return customer_name
|
||||
else:
|
||||
|
@ -356,7 +356,7 @@ erpnext.PointOfSale.ItemCart = class {
|
||||
onchange: function() {
|
||||
if (this.value || this.value == 0) {
|
||||
const frm = me.events.get_frm();
|
||||
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', this.value);
|
||||
frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value));
|
||||
me.hide_discount_control(this.value);
|
||||
}
|
||||
},
|
||||
|
@ -20,29 +20,28 @@ def get_funnel_data(from_date, to_date, company):
|
||||
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]
|
||||
|
||||
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]
|
||||
|
||||
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]
|
||||
|
||||
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()
|
||||
|
@ -63,13 +63,13 @@ def get_columns(filters, period_list, partner_doctype):
|
||||
"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 @@ def get_columns(filters, period_list, partner_doctype):
|
||||
"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 @@ def get_columns(filters, period_list, partner_doctype):
|
||||
"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 @@ def prepare_data(filters, sales_users_data, actual_data, date_field, period_list
|
||||
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
|
||||
|
||||
|
@ -44,5 +44,20 @@ frappe.query_reports["Sales Partner Target Variance based on Item Group"] = {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -44,5 +44,20 @@ frappe.query_reports["Sales Person Target Variance Based On Item Group"] = {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -44,5 +44,20 @@ frappe.query_reports["Territory Target Variance Based On Item Group"] = {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ def delete_company_transactions(company_name):
|
||||
tabDocField where fieldtype='Link' and options='Company'"""):
|
||||
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
|
||||
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
||||
"Purchase Taxes and Charges Template", "POS Profile", 'BOM'):
|
||||
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
|
||||
"Company", "Bank Account"):
|
||||
delete_for_doctype(doctype, company_name)
|
||||
|
||||
# reset company values
|
||||
|
@ -26,6 +26,7 @@ def after_install():
|
||||
create_default_success_action()
|
||||
create_default_energy_point_rules()
|
||||
add_company_to_session_defaults()
|
||||
add_standard_navbar_items()
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
@ -104,3 +105,45 @@ def add_company_to_session_defaults():
|
||||
"ref_doctype": "Company"
|
||||
})
|
||||
settings.save()
|
||||
|
||||
def add_standard_navbar_items():
|
||||
navbar_settings = frappe.get_single("Navbar Settings")
|
||||
|
||||
erpnext_navbar_items = [
|
||||
{
|
||||
'item_label': 'Documentation',
|
||||
'item_type': 'Route',
|
||||
'route': 'https://erpnext.com/docs/user/manual',
|
||||
'is_standard': 1
|
||||
},
|
||||
{
|
||||
'item_label': 'User Forum',
|
||||
'item_type': 'Route',
|
||||
'route': 'https://discuss.erpnext.com',
|
||||
'is_standard': 1
|
||||
},
|
||||
{
|
||||
'item_label': 'Report an Issue',
|
||||
'item_type': 'Route',
|
||||
'route': 'https://github.com/frappe/erpnext/issues',
|
||||
'is_standard': 1
|
||||
}
|
||||
]
|
||||
|
||||
current_nabvar_items = navbar_settings.help_dropdown
|
||||
navbar_settings.set('help_dropdown', [])
|
||||
|
||||
for item in erpnext_navbar_items:
|
||||
navbar_settings.append('help_dropdown', item)
|
||||
|
||||
for item in current_nabvar_items:
|
||||
navbar_settings.append('help_dropdown', {
|
||||
'item_label': item.item_label,
|
||||
'item_type': item.item_type,
|
||||
'route': item.route,
|
||||
'action': item.action,
|
||||
'is_standard': item.is_standard,
|
||||
'hidden': item.hidden
|
||||
})
|
||||
|
||||
navbar_settings.save()
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
import json
|
||||
import frappe, erpnext
|
||||
import frappe.defaults
|
||||
from frappe.utils import cint, flt, cstr, today, random_string
|
||||
@ -159,6 +160,71 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_subcontracting_over_receipt(self):
|
||||
"""
|
||||
Behaviour: Raise multiple PRs against one PO that in total
|
||||
receive more than the required qty in the PO.
|
||||
Expected Result: Error Raised for Over Receipt against PO.
|
||||
"""
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import (update_backflush_based_on,
|
||||
make_subcontracted_item, create_purchase_order)
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt,
|
||||
make_rm_stock_entry as make_subcontract_transfer_entry)
|
||||
|
||||
update_backflush_based_on("Material Transferred for Subcontract")
|
||||
item_code = "_Test Subcontracted FG Item 1"
|
||||
make_subcontracted_item(item_code)
|
||||
|
||||
po = create_purchase_order(item_code=item_code, qty=1,
|
||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||
|
||||
#stock raw materials in a warehouse before transfer
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code="_Test Item Home Desktop 100", qty=1, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Test Extra Item 1", qty=1, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "_Test Item", qty=1, basic_rate=100)
|
||||
|
||||
rm_items = [
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": po.supplied_items[0].rm_item_code,
|
||||
"item_name": "_Test Item",
|
||||
"qty": po.supplied_items[0].required_qty,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos"
|
||||
},
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": po.supplied_items[1].rm_item_code,
|
||||
"item_name": "Test Extra Item 1",
|
||||
"qty": po.supplied_items[1].required_qty,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos"
|
||||
},
|
||||
{
|
||||
"item_code": item_code,
|
||||
"rm_item_code": po.supplied_items[2].rm_item_code,
|
||||
"item_name": "_Test Item Home Desktop 100",
|
||||
"qty": po.supplied_items[2].required_qty,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos"
|
||||
}
|
||||
]
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.to_warehouse = "_Test Warehouse 1 - _TC"
|
||||
se.save()
|
||||
se.submit()
|
||||
|
||||
pr1 = make_purchase_receipt(po.name)
|
||||
pr2 = make_purchase_receipt(po.name)
|
||||
|
||||
pr1.submit()
|
||||
self.assertRaises(frappe.ValidationError, pr2.submit)
|
||||
|
||||
def test_serial_no_supplier(self):
|
||||
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
||||
self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"),
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 %}
|
||||
|
11
erpnext/templates/generators/item/item_inquiry.html
Normal file
11
erpnext/templates/generators/item/item_inquiry.html
Normal file
@ -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 %}
|
@ -20,6 +20,13 @@ frappe.ready(() => {
|
||||
options: 'Email',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
label: __('Phone Number'),
|
||||
fieldname: 'phone',
|
||||
options: 'Phone',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
label: __('Subject'),
|
||||
|
Loading…
x
Reference in New Issue
Block a user