Merge branch 'bcornwellmott-no_quote' into develop
This commit is contained in:
commit
0650d8e155
@ -138,7 +138,6 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
dialog.show();
|
||||
|
||||
},
|
||||
|
||||
make_suppplier_quotation: function(frm) {
|
||||
var doc = frm.doc;
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
@ -207,6 +206,29 @@ frappe.ui.form.on("Request for Quotation Supplier",{
|
||||
if(!w) {
|
||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
||||
}
|
||||
},
|
||||
no_quote: function(frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
if (d.no_quote) {
|
||||
if (d.quote_status != __('Received')) {
|
||||
frappe.model.set_value(cdt, cdn, 'quote_status', 'No Quote');
|
||||
} else {
|
||||
frappe.msgprint(__("Cannot set a received RFQ to No Quote"));
|
||||
frappe.model.set_value(cdt, cdn, 'no_quote', 0);
|
||||
}
|
||||
} else {
|
||||
d.quote_status = __('Pending');
|
||||
frm.call({
|
||||
method:"update_rfq_supplier_status",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
sup_name: d.supplier
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.refresh_field("suppliers");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -54,6 +54,7 @@ class RequestforQuotation(BuyingController):
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
for supplier in self.suppliers:
|
||||
supplier.email_sent = 0
|
||||
supplier.quote_status = 'Pending'
|
||||
|
||||
def on_cancel(self):
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
@ -157,6 +158,28 @@ class RequestforQuotation(BuyingController):
|
||||
attachments.append(frappe.attach_print(self.doctype, self.name, doc=self))
|
||||
return attachments
|
||||
|
||||
def update_rfq_supplier_status(self, sup_name=None):
|
||||
for supplier in self.suppliers:
|
||||
if sup_name == None or supplier.supplier == sup_name:
|
||||
if supplier.quote_status != _('No Quote'):
|
||||
quote_status = _('Received')
|
||||
for item in self.items:
|
||||
sqi_count = frappe.db.sql("""
|
||||
SELECT
|
||||
COUNT(sqi.name) as count
|
||||
FROM
|
||||
`tabSupplier Quotation Item` as sqi,
|
||||
`tabSupplier Quotation` as sq
|
||||
WHERE sq.supplier = %(supplier)s
|
||||
AND sqi.docstatus = 1
|
||||
AND sqi.request_for_quotation_item = %(rqi)s
|
||||
AND sqi.parent = sq.name""",
|
||||
{"supplier": supplier.supplier, "rqi": item.name}, as_dict=1)[0]
|
||||
if (sqi_count.count) == 0:
|
||||
quote_status = _('Pending')
|
||||
supplier.quote_status = quote_status
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_supplier_emails(rfq_name):
|
||||
check_portal_enabled('Request for Quotation')
|
||||
|
@ -10,21 +10,41 @@ from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
||||
from frappe.utils import nowdate
|
||||
|
||||
class TestRequestforQuotation(unittest.TestCase):
|
||||
def test_quote_status(self):
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
rfq = make_request_for_quotation()
|
||||
|
||||
self.assertEquals(rfq.get('suppliers')[0].quote_status, 'Pending')
|
||||
self.assertEquals(rfq.get('suppliers')[1].quote_status, 'Pending')
|
||||
|
||||
# Submit the first supplier quotation
|
||||
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
|
||||
sq.submit()
|
||||
|
||||
# No Quote first supplier quotation
|
||||
rfq.get('suppliers')[1].no_quote = 1
|
||||
rfq.get('suppliers')[1].quote_status = 'No Quote'
|
||||
|
||||
rfq.update_rfq_supplier_status() #rfq.get('suppliers')[1].supplier)
|
||||
|
||||
self.assertEquals(rfq.get('suppliers')[0].quote_status, 'Received')
|
||||
self.assertEquals(rfq.get('suppliers')[1].quote_status, 'No Quote')
|
||||
|
||||
def test_make_supplier_quotation(self):
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
rfq = make_request_for_quotation()
|
||||
|
||||
|
||||
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
|
||||
sq.submit()
|
||||
|
||||
|
||||
sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier)
|
||||
sq1.submit()
|
||||
|
||||
|
||||
self.assertEquals(sq.supplier, rfq.get('suppliers')[0].supplier)
|
||||
self.assertEquals(sq.get('items')[0].request_for_quotation, rfq.name)
|
||||
self.assertEquals(sq.get('items')[0].item_code, "_Test Item")
|
||||
self.assertEquals(sq.get('items')[0].qty, 5)
|
||||
|
||||
|
||||
self.assertEquals(sq1.supplier, rfq.get('suppliers')[1].supplier)
|
||||
self.assertEquals(sq1.get('items')[0].request_for_quotation, rfq.name)
|
||||
self.assertEquals(sq1.get('items')[0].item_code, "_Test Item")
|
||||
@ -61,15 +81,15 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
rfq.get('items')[0].rate = 100
|
||||
rfq.supplier = rfq.suppliers[0].supplier
|
||||
supplier_quotation_name = create_supplier_quotation(rfq)
|
||||
|
||||
|
||||
supplier_quotation_doc = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
|
||||
|
||||
|
||||
self.assertEquals(supplier_quotation_doc.supplier, rfq.get('suppliers')[0].supplier)
|
||||
self.assertEquals(supplier_quotation_doc.get('items')[0].request_for_quotation, rfq.name)
|
||||
self.assertEquals(supplier_quotation_doc.get('items')[0].item_code, "_Test Item")
|
||||
self.assertEquals(supplier_quotation_doc.get('items')[0].qty, 5)
|
||||
self.assertEquals(supplier_quotation_doc.get('items')[0].amount, 500)
|
||||
|
||||
|
||||
|
||||
def make_request_for_quotation(supplier_data=None):
|
||||
"""
|
||||
@ -81,10 +101,10 @@ def make_request_for_quotation(supplier_data=None):
|
||||
rfq.status = 'Draft'
|
||||
rfq.company = '_Test Company'
|
||||
rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
|
||||
|
||||
|
||||
for data in supplier_data:
|
||||
rfq.append('suppliers', data)
|
||||
|
||||
|
||||
rfq.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"description": "_Test Item",
|
||||
@ -93,11 +113,11 @@ def make_request_for_quotation(supplier_data=None):
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"schedule_date": nowdate()
|
||||
})
|
||||
|
||||
|
||||
rfq.submit()
|
||||
|
||||
|
||||
return rfq
|
||||
|
||||
|
||||
def get_supplier_data():
|
||||
return [{
|
||||
"supplier": "_Test Supplier",
|
||||
|
@ -0,0 +1,132 @@
|
||||
QUnit.module('buying');
|
||||
|
||||
QUnit.test("Test: Request for Quotation", function (assert) {
|
||||
assert.expect(5);
|
||||
let done = assert.async();
|
||||
let rfq_name = "";
|
||||
|
||||
frappe.run_serially([
|
||||
// Go to RFQ list
|
||||
() => frappe.set_route("List", "Request for Quotation"),
|
||||
// Create a new RFQ
|
||||
() => frappe.new_doc("Request for Quotation"),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value("transaction_date", "04-04-2017"),
|
||||
() => cur_frm.set_value("company", "_Test Company"),
|
||||
// Add Suppliers
|
||||
() => {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.supplier = "_Test Supplier";
|
||||
frappe.click_check('Send Email');
|
||||
cur_frm.cur_grid.frm.script_manager.trigger('supplier');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.cur_grid.toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Add Row',0),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[1].toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.supplier = "_Test Supplier 1";
|
||||
frappe.click_check('Send Email');
|
||||
cur_frm.cur_grid.frm.script_manager.trigger('supplier');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.cur_grid.toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
// Add Item
|
||||
() => {
|
||||
cur_frm.fields_dict.items.grid.grid_rows[0].toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.fields_dict.items.grid.grid_rows[0].doc.item_code = "_Test Item";
|
||||
frappe.set_control('item_code',"_Test Item");
|
||||
frappe.set_control('qty',5);
|
||||
frappe.set_control('schedule_date', "05-05-2017");
|
||||
cur_frm.cur_grid.frm.script_manager.trigger('supplier');
|
||||
},
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
cur_frm.cur_grid.toggle_view();
|
||||
},
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
cur_frm.fields_dict.items.grid.grid_rows[0].doc.warehouse = "_Test Warehouse - _TC";
|
||||
},
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Submit'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Menu'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Reload'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.docstatus, 1);
|
||||
rfq_name = cur_frm.doc.name;
|
||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.quote_status == "Pending");
|
||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Pending");
|
||||
},
|
||||
() => {
|
||||
cur_frm.fields_dict.suppliers.grid.grid_rows[0].toggle_view();
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.click_check('No Quote');
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_frm.cur_grid.toggle_view();
|
||||
},
|
||||
() => frappe.click_button('Update'),
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => frappe.click_button('Supplier Quotation'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Make'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
frappe.set_control('supplier',"_Test Supplier 1");
|
||||
},
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Make Supplier Quotation'),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value("company", "_Test Company"),
|
||||
() => cur_frm.fields_dict.items.grid.grid_rows[0].doc.rate = 4.99,
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Submit'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Yes'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.set_route("List", "Request for Quotation"),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.set_route("List", "Request for Quotation"),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.click_link(rfq_name),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('Menu'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_link('Reload'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[1].doc.quote_status == "Received");
|
||||
assert.ok(cur_frm.fields_dict.suppliers.grid.grid_rows[0].doc.no_quote == 1);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -137,6 +137,69 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.docstatus >= 1 && doc.quote_status != 'Received'",
|
||||
"fieldname": "no_quote",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "No Quote",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.docstatus >= 1 && !doc.no_quote",
|
||||
"fieldname": "quote_status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Quote Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Pending\nReceived\nNo Quote",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -269,7 +332,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-24 06:52:19.542717",
|
||||
"modified": "2017-07-26 22:25:58.096584",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation Supplier",
|
||||
|
@ -31,9 +31,11 @@ class SupplierQuotation(BuyingController):
|
||||
|
||||
def on_submit(self):
|
||||
frappe.db.set(self, "status", "Submitted")
|
||||
self.update_rfq_supplier_status(1)
|
||||
|
||||
def on_cancel(self):
|
||||
frappe.db.set(self, "status", "Cancelled")
|
||||
self.update_rfq_supplier_status(0)
|
||||
|
||||
def on_trash(self):
|
||||
pass
|
||||
@ -50,6 +52,41 @@ class SupplierQuotation(BuyingController):
|
||||
"is_child_table": True
|
||||
}
|
||||
})
|
||||
def update_rfq_supplier_status(self, include_me):
|
||||
rfq_list = set([])
|
||||
for item in self.items:
|
||||
if item.request_for_quotation:
|
||||
rfq_list.add(item.request_for_quotation)
|
||||
for rfq in rfq_list:
|
||||
doc = frappe.get_doc('Request for Quotation', rfq)
|
||||
doc_sup = frappe.get_all('Request for Quotation Supplier', filters=
|
||||
{'parent': doc.name, 'supplier': self.supplier}, fields=['name', 'quote_status'])[0]
|
||||
|
||||
quote_status = _('Received')
|
||||
for item in doc.items:
|
||||
sqi_count = frappe.db.sql("""
|
||||
SELECT
|
||||
COUNT(sqi.name) as count
|
||||
FROM
|
||||
`tabSupplier Quotation Item` as sqi,
|
||||
`tabSupplier Quotation` as sq
|
||||
WHERE sq.supplier = %(supplier)s
|
||||
AND sqi.docstatus = 1
|
||||
AND sq.name != %(me)s
|
||||
AND sqi.request_for_quotation_item = %(rqi)s
|
||||
AND sqi.parent = sq.name""",
|
||||
{"supplier": self.supplier, "rqi": item.name, 'me': self.name}, as_dict=1)[0]
|
||||
self_count = sum(my_item.request_for_quotation_item == item.name
|
||||
for my_item in self.items) if include_me else 0
|
||||
if (sqi_count.count + self_count) == 0:
|
||||
quote_status = _('Pending')
|
||||
if quote_status == _('Received') and doc_sup.quote_status == _('No Quote'):
|
||||
frappe.msgprint(_("{0} indicates that {1} will not provide a quotation, but all items \
|
||||
have been quoted. Updating the RFQ quote status.").format(doc.name, self.supplier))
|
||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'no_quote', 0)
|
||||
elif doc_sup.quote_status != _('No Quote'):
|
||||
frappe.db.set_value('Request for Quotation Supplier', doc_sup.name, 'quote_status', quote_status)
|
||||
|
||||
def get_list_context(context=None):
|
||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||
|
@ -2,43 +2,40 @@
|
||||
|
||||
A Request for Quotation is a document that an organization submits to one or more suppliers eliciting quotation for items.
|
||||
|
||||
In ERPNext, You can create request for quotation directly by going to:
|
||||
In ERPNext, You can create Request for Quotation directly by going to:
|
||||
|
||||
> Buying > Documents > Request for Quotation > New Request for Quotation
|
||||
|
||||

|
||||
After creation of Request for Quotation, there are two ways to generate Supplier Quotation from Request for Quotation.
|
||||
|
||||
After creation of request for quotation, there are two ways to generate supplier quotation from request for quotation.
|
||||
|
||||
#### For User
|
||||
|
||||
__Step 1:__ Open request for quotation and click on make supplier quotation.
|
||||
__Step 1:__ Open Request for Quotation and click on make Supplier Quotation.
|
||||
|
||||

|
||||
|
||||
__Step 2:__ Select supplier and click on make supplier quotation.
|
||||
__Step 2:__ Select supplier and click on make Supplier Quotation.
|
||||
|
||||

|
||||
|
||||
__Step 3:__ System will open the supplier quotation, user has to enter the rate and submit it.
|
||||
__Step 3:__ System will open the Supplier Quotation, user has to enter the rate and submit it.
|
||||
|
||||

|
||||
|
||||
#### For Supplier
|
||||
|
||||
__Step 1:__ User has to create contact or enter Email Address against the supplier on request for quotation.
|
||||
__Step 1:__ User has to create contact or enter Email Address against the supplier on Request for Quotation.
|
||||
|
||||

|
||||
|
||||
__Step 2:__ User has to click on send supplier emails button.
|
||||
|
||||

|
||||
|
||||
* If supplier's user not available: system will create supplier's user and send details to the supplier, supplier will need to click on the link(Password Update) present in the email. After password update supplier can access his portal with the request for quotation form.
|
||||
* If supplier's user not available: system will create supplier's user and send details to the supplier, supplier will need to click on the link(Password Update) present in the email. After password update supplier can access his portal with the Request for Quotation form.
|
||||
|
||||

|
||||
|
||||
* If supplier's user available: system will send request for quotation link to supplier, supplier has to login using his credentials to view request for quotation form on portal.
|
||||
* If supplier's user available: system will send Request for Quotation link to supplier, supplier has to login using his credentials to view Request for Quotation form on portal.
|
||||
|
||||

|
||||
|
||||
@ -46,9 +43,12 @@ __Step 3:__ Supplier has to enter amount and notes(payment terms) on the form an
|
||||
|
||||

|
||||
|
||||
__Step 4:__ On submission, system will create supplier quotation(draft mode) against the supplier. User has to review the supplier quotation
|
||||
and submit it.
|
||||
|
||||
More details:-
|
||||
__Step 4:__ On submission, system will create Supplier Quotation (draft mode) against the supplier. The user has to review the Supplier Quotation
|
||||
and submit it. When all items from the Request for Quotation have been quoted by a supplier, the status is updated in the Supplier
|
||||
table of the Request for Quotation.
|
||||
|
||||

|
||||
#### More details
|
||||
|
||||
If a supplier indicates that they will not provide a quotation for the item, this can be indicated in the RFQ document by checking the 'No Quote' box after the Request for Quotation has been submitted.
|
||||
|
||||

|
||||
|
@ -431,4 +431,7 @@ erpnext.patches.v8_5.set_default_mode_of_payment
|
||||
erpnext.patches.v8_5.update_customer_group_in_POS_profile
|
||||
erpnext.patches.v8_6.update_timesheet_company_from_PO
|
||||
erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager
|
||||
erpnext.patches.v8_5.remove_project_type_property_setter
|
||||
<<<<<<< HEAD
|
||||
erpnext.patches.v8_5.remove_project_type_property_setter
|
||||
=======
|
||||
>>>>>>> Set write permission to sales manger for permlevel 1 in Quotation doctype
|
||||
|
@ -8,4 +8,4 @@ def execute():
|
||||
# Set write permission to permlevel 1 for sales manager role in Quotation doctype
|
||||
frappe.db.sql(""" update `tabCustom DocPerm` set `tabCustom DocPerm`.write = 1
|
||||
where `tabCustom DocPerm`.parent = 'Quotation' and `tabCustom DocPerm`.role = 'Sales Manager'
|
||||
and `tabCustom DocPerm`.permlevel = 1 """)
|
||||
and `tabCustom DocPerm`.permlevel = 1 """)
|
Loading…
x
Reference in New Issue
Block a user