[enhancement] request for quotation
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 0c8d999..244d39e 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -16,7 +16,7 @@
@frappe.whitelist()
def get_party_details(party=None, account=None, party_type="Customer", company=None,
- posting_date=None, price_list=None, currency=None, doctype=None):
+ posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
if not party:
return {}
@@ -25,7 +25,7 @@
frappe.throw(_("{0}: {1} does not exists").format(party_type, party))
return _get_party_details(party, account, party_type,
- company, posting_date, price_list, currency, doctype)
+ company, posting_date, price_list, currency, doctype, ignore_permissions)
def _get_party_details(party=None, account=None, party_type="Customer", company=None,
posting_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False):
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index f4fb5e2..0d509e2 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -6,26 +6,30 @@
frappe.require("assets/erpnext/js/utils.js");
-erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
- refresh: function() {
- this._super();
-
- if (this.frm.doc.docstatus === 1) {
- cur_frm.add_custom_button(__("Supplier Quotation"), this.make_suppplier_quotation,
- __("Make"));
- cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
+frappe.ui.form.on("Request for Quotation",{
+ setup: function(frm){
+ frm.fields_dict["suppliers"].grid.get_field("contact_person").get_query = function(doc, cdt, cdn){
+ var d =locals[cdt][cdn];
+ return {
+ filters: {'supplier': d.supplier}
+ }
}
},
-
- calculate_taxes_and_totals: function() {
- return;
+
+ onload: function(frm){
+ frm.add_fetch('standard_reply', 'response', 'response');
},
- tc_name: function() {
- this.get_terms();
+ refresh: function(frm, cdt, cdn){
+ if (frm.doc.docstatus === 1) {
+ frm.add_custom_button(__("Supplier Quotation"), function(){ frm.trigger("make_suppplier_quotation") },
+ __("Make"));
+ frm.page.set_inner_btn_group_as_primary(__("Make"));
+ }
},
-
- make_suppplier_quotation: function() {
+
+ make_suppplier_quotation: function(frm){
+ var doc = frm.doc;
var dialog = new frappe.ui.Dialog({
title: __("For Supplier"),
fields: [
@@ -33,13 +37,13 @@
"get_query": function () {
return {
query:"erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier",
- filters: {'parent': cur_frm.doc.name}
+ filters: {'parent': doc.name}
}
}, "reqd": 1 },
{"fieldtype": "Button", "label": __("Make Supplier Quotation"), "fieldname": "make_supplier_quotation", "cssClass": "btn-primary"},
]
});
-
+
dialog.fields_dict.make_supplier_quotation.$input.click(function(){
args = dialog.get_values();
if(!args) return;
@@ -48,7 +52,7 @@
type: "GET",
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation",
args: {
- "source_name": cur_frm.doc.name,
+ "source_name": doc.name,
"for_supplier": args.supplier
},
freeze: true,
@@ -62,8 +66,22 @@
});
dialog.show()
}
+})
+erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
+ refresh: function() {
+ this._super();
+ },
+
+ calculate_taxes_and_totals: function() {
+ return;
+ },
+
+ tc_name: function() {
+ this.get_terms();
+ }
});
+
// for backward compatibility: combine new and previous states
$.extend(cur_frm.cscript, new erpnext.buying.RequestforQuotationController({frm: cur_frm}));
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index d46bbff..88a91a0 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -13,32 +13,6 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "section_break_1",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "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
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -150,14 +124,14 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "section_break_2",
+ "fieldname": "suppliers_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Supplier Detail",
+ "label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -188,7 +162,7 @@
"options": "RFQ Supplier",
"permlevel": 0,
"precision": "",
- "print_hide": 0,
+ "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
@@ -208,7 +182,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Items",
+ "label": "",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
@@ -256,7 +230,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "section_break_3",
+ "fieldname": "supplier_response_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -281,16 +255,17 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "message_for_supplier",
- "fieldtype": "Small Text",
+ "fieldname": "standard_reply",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Message for Supplier",
+ "label": "Standard Reply",
"length": 0,
"no_copy": 0,
+ "options": "Standard Reply",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -306,22 +281,23 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "column_break2",
- "fieldtype": "Column Break",
+ "fieldname": "response",
+ "fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
+ "label": "Message for Supplier",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
- "print_hide": 0,
+ "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
- "reqd": 0,
+ "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -633,7 +609,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-03-02 18:50:02.603258",
+ "modified": "2016-03-25 01:14:01.194848",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 63b0357..92089e4 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -3,28 +3,107 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, json
from frappe import _
+from frappe.utils import get_url, cint
+from frappe.utils.user import get_user_fullname
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.doctype.material_request.material_request import set_missing_values
-from frappe.model.document import Document
+from erpnext.controllers.buying_controller import BuyingController
-class RequestforQuotation(Document):
+STANDARD_USERS = ("Guest", "Administrator")
+
+class RequestforQuotation(BuyingController):
def validate(self):
self.validate_duplicate_supplier()
-
+ self.validate_common()
+
def validate_duplicate_supplier(self):
supplier_list = [d.supplier for d in self.suppliers]
if len(supplier_list) != len(set(supplier_list)):
frappe.throw(_("Same supplier has been entered multiple times"))
-
+
+ def validate_common(self):
+ pc = frappe.get_doc('Purchase Common')
+ pc.validate_for_items(self)
+
+ def on_submit(self):
+ frappe.db.set(self, 'status', 'Submitted')
+ self.send_to_supplier()
+
+ def on_cancel(self):
+ frappe.db.set(self, 'status', 'Cancelled')
+
+ def send_to_supplier(self):
+ link = get_url("/rfq/" + self.name)
+ for supplier_data in self.suppliers:
+ if supplier_data.email_id and cint(supplier_data.sent_email_to_supplier)==1:
+ update_password_link = self.create_supplier_user(supplier_data, link)
+ self.supplier_rfq_mail(supplier_data, update_password_link, link)
+
+ def create_supplier_user(self, supplier_data, link):
+ from frappe.utils import random_string, get_url
+
+ update_password_link = ''
+ if not supplier_data.user_id:
+ user = self.create_user(supplier_data)
+ key = random_string(32)
+ user.reset_password_key = key
+ user.redirect_url = link
+ user.save(ignore_permissions=True)
+
+ update_password_link = get_url("/update-password?key=" + key)
+ frappe.get_doc('Contact', supplier_data.contact_person).save()
+
+ return update_password_link
+
+ def create_user(self, supplier_data):
+ user = frappe.get_doc({
+ 'doctype': 'User',
+ 'send_welcome_email': 0,
+ 'email': supplier_data.email_id,
+ 'first_name': supplier_data.supplier_name,
+ 'user_type': 'Website User'
+ })
+
+ return user
+
+ def supplier_rfq_mail(self, data, update_password_link, rfq_link):
+ full_name = get_user_fullname(frappe.session['user'])
+ if full_name == "Guest":
+ full_name = "Administrator"
+
+ args = {
+ 'update_password_link': update_password_link,
+ 'message': frappe.render_template(self.response, data.as_dict()),
+ 'rfq_link': rfq_link,
+ 'user_fullname': full_name
+ }
+
+ subject = _("Request for Quotation")
+ template = "templates/emails/request_for_quotation.html"
+ sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
+
+ frappe.sendmail(recipients=data.email_id, sender=sender, subject=subject,
+ message=frappe.get_template(template).render(args),
+ attachments = [frappe.attach_print('Request for Quotation', self.name)],as_bulk=True)
+
+ frappe.msgprint(_("Email sent to supplier {0}").format(data.supplier))
+
+def get_list_context(context=None):
+ from erpnext.controllers.website_list_for_contact import get_list_context
+ list_context = get_list_context(context)
+ return list_context
+
@frappe.whitelist()
def get_supplier(doctype, txt, searchfield, start, page_len, filters):
- query = """ Select supplier from `tabRFQ Supplier` where parent = '{parent}' and supplier like %s
- limit {start}, {page_len} """
+ query = """Select supplier from `tabRFQ Supplier` where parent = %(parent)s and supplier like %(txt)s
+ limit %(start)s, %(page_len)s """
- return frappe.db.sql(query.format(parent=filters.get('parent'), start=start, page_len=page_len), '%{0}%'.format(txt))
-
+ return frappe.db.sql(query, {'parent': filters.get('parent'),
+ 'start': start, 'page_len': page_len, 'txt': "%%%s%%" % frappe.db.escape(txt)})
+
+# This method is used to make supplier quotation from material request form.
@frappe.whitelist()
def make_supplier_quotation(source_name, for_supplier, target_doc=None):
def postprocess(source, target_doc):
@@ -49,3 +128,50 @@
}, target_doc, postprocess)
return doclist
+
+# This method is used to make supplier quotation from supplier's portal.
+@frappe.whitelist()
+def create_supplier_quotation(doc):
+ if isinstance(doc, basestring):
+ doc = json.loads(doc)
+
+ supplier = frappe.get_doc('Supplier', doc.get('supplier'))
+
+ try:
+ sq_doc = frappe.get_doc({
+ "doctype": "Supplier Quotation",
+ "supplier": supplier.name,
+ "terms": doc.get("terms"),
+ "company": doc.get("company"),
+ "currency": supplier.default_currency,
+ "buying_price_list": supplier.default_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list')
+ })
+ add_items(sq_doc, supplier, doc.get('items'))
+ sq_doc.flags.ignore_permissions = True
+ sq_doc.run_method("set_missing_values")
+ sq_doc.save()
+ frappe.msgprint(_("Supplier Quotation {0} created").format(sq_doc.name))
+ return sq_doc.name
+ except Exception:
+ return
+
+def add_items(sq_doc, supplier, items):
+ for data in items:
+ if data.get("qty") > 0:
+ if isinstance(data, dict):
+ data = frappe._dict(data)
+
+ create_rfq_items(sq_doc, supplier, data)
+
+def create_rfq_items(sq_doc, supplier, data):
+ sq_doc.append('items', {
+ "item_code": data.item_code,
+ "item_name": data.item_name,
+ "description": data.description,
+ "qty": data.qty,
+ "rate": data.rate,
+ "supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
+ "warehouse": data.warehouse or '',
+ "request_for_quotation_item": data.name,
+ "request_for_quotation": data.parent
+ })
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index db828b8..d643a05 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -12,33 +12,48 @@
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)
- sq.supplier = rfq.get('supplier_detail')[0].supplier
+ sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
sq.submit()
- sq1 = make_supplier_quotation(rfq.name)
- sq1.supplier = rfq.get('supplier_detail')[1].supplier
+ sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier)
sq1.submit()
- self.assertEquals(sq.supplier, rfq.get('supplier_detail')[0].supplier)
+ 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('supplier_detail')[1].supplier)
+ 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")
self.assertEquals(sq1.get('items')[0].qty, 5)
+
+ def test_make_supplier_quotation_from_portal(self):
+ from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
+ rfq = make_request_for_quotation()
+ 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 = get_supplier_data()
rfq = frappe.new_doc('Request for Quotation')
rfq.transaction_date = nowdate()
rfq.status = 'Draft'
rfq.company = '_Test Company'
+ rfq.response = 'Test Data'
for data in supplier_data:
- rfq.append('supplier_detail', data)
+ rfq.append('suppliers', data)
rfq.append("items", {
"item_code": "_Test Item",
diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
index 08bc16d..305253c 100644
--- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
+++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
@@ -28,7 +28,7 @@
"options": "Item",
"permlevel": 0,
"precision": "",
- "print_hide": 0,
+ "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
@@ -41,32 +41,6 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "description": "",
- "fieldname": "supplier_part_no",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Supplier Part Number",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -105,7 +79,7 @@
"oldfieldtype": "Data",
"permlevel": 0,
"precision": "",
- "print_hide": 1,
+ "print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
@@ -474,7 +448,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -624,7 +598,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-03-03 13:42:11.020082",
+ "modified": "2016-03-25 01:14:38.490488",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Item",
diff --git a/erpnext/buying/doctype/rfq_supplier/rfq_supplier.json b/erpnext/buying/doctype/rfq_supplier/rfq_supplier.json
index b4a3f68..02a6f0b 100644
--- a/erpnext/buying/doctype/rfq_supplier/rfq_supplier.json
+++ b/erpnext/buying/doctype/rfq_supplier/rfq_supplier.json
@@ -29,6 +29,83 @@
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "contact_person",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Contact Person",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Contact",
+ "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
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "default": "1",
+ "description": "Send Request for Quotation to Supplier",
+ "fieldname": "sent_email_to_supplier",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Sent Email to Supplier",
+ "length": 0,
+ "no_copy": 1,
+ "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
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "column_break_3",
+ "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,
@@ -45,7 +122,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
- "label": "Name",
+ "label": "Supplier Name",
"length": 0,
"no_copy": 0,
"options": "supplier.supplier_name",
@@ -59,6 +136,58 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "email_id",
+ "fieldtype": "Read Only",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Email Id",
+ "length": 0,
+ "no_copy": 1,
+ "options": "contact_person.email_id",
+ "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
+ },
+ {
+ "allow_on_submit": 1,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "user_id",
+ "fieldtype": "Read Only",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "User Id",
+ "length": 0,
+ "no_copy": 1,
+ "options": "contact_person.user",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"hide_heading": 0,
@@ -70,7 +199,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-03-03 12:52:45.937140",
+ "modified": "2016-03-25 13:18:11.017660",
"modified_by": "Administrator",
"module": "Buying",
"name": "RFQ Supplier",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index e12c9ff..c9bcc46 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -1885,6 +1885,26 @@
"share": 0,
"submit": 0,
"write": 1
+ },
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "email": 0,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 1,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "All",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
+ "write": 0
}
],
"read_only": 0,
diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
index 2999b07..1275fcd 100644
--- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
+++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
@@ -883,6 +883,32 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "request_for_quotation",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Request for Quotation",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Request for Quotation",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "col_break4",
"fieldtype": "Column Break",
"hidden": 0,
@@ -932,32 +958,6 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "request_for_quotation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Request for Quotation",
- "length": 0,
- "no_copy": 1,
- "options": "Request for Quotation",
- "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
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"fieldname": "request_for_quotation_item",
"fieldtype": "Data",
"hidden": 1,
@@ -970,9 +970,9 @@
"no_copy": 1,
"permlevel": 0,
"precision": "",
- "print_hide": 0,
+ "print_hide": 1,
"print_hide_if_no_value": 0,
- "read_only": 0,
+ "read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -1097,7 +1097,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-03-18 05:15:03.936587",
+ "modified": "2016-03-25 17:01:59.632826",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index c9b660d..5361126 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -20,8 +20,9 @@
}
def get_feed(self):
- return _("From {0} | {1} {2}").format(self.supplier_name, self.currency,
- self.grand_total)
+ if self.get("supplier_name"):
+ return _("From {0} | {1} {2}").format(self.supplier_name, self.currency,
+ self.grand_total)
def validate(self):
super(BuyingController, self).validate()
@@ -40,7 +41,7 @@
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
- self.update_if_missing(get_party_details(self.supplier, party_type="Supplier"))
+ self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions))
self.set_missing_item_details()
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index ae6e9e4..5882578 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -28,15 +28,14 @@
filters.append((doctype, "docstatus", "=", 1))
if user != "Guest" and is_website_user():
+ parties_doctype = 'RFQ Supplier' if doctype == 'Request for Quotation' else doctype
# find party for this contact
- customers, suppliers = get_customers_suppliers(doctype, user)
+ customers, suppliers = get_customers_suppliers(parties_doctype, user)
+ key, parties = get_party_details(customers, suppliers)
- if customers:
- key, parties = "customer", customers
- elif suppliers:
- key, parties = "supplier", suppliers
- else:
- key, parties = "customer", []
+ if doctype == 'Request for Quotation':
+ if key == 'customer': frappe.throw(_("Not Permitted"), frappe.PermissionError)
+ return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
filters.append((doctype, key, "in", parties))
@@ -52,6 +51,23 @@
return post_process(doctype, get_list(doctype, txt, filters, limit_start, limit_page_length,
fields="name", order_by = "modified desc"))
+def get_party_details(customers, suppliers):
+ if customers:
+ key, parties = "customer", customers
+ elif suppliers:
+ key, parties = "supplier", suppliers
+ else:
+ key, parties = "customer", []
+
+ return key, parties
+
+def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
+ data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
+ where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
+ format(doctype=parties_doctype, supplier=parties[0], start=limit_start, len = limit_page_length), as_dict=1)
+
+ return post_process(doctype, data)
+
def post_process(doctype, data):
result = []
for d in data:
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 6e86d84..01104b8 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -71,6 +71,13 @@
"parents": [{"title": _("Shipments"), "name": "shipments"}]
}
},
+ {"from_route": "/rfq", "to_route": "Request for Quotation"},
+ {"from_route": "/rfq/<path:name>", "to_route": "rfq",
+ "defaults": {
+ "doctype": "Request for Quotation",
+ "parents": [{"title": _("Request for Quotation"), "name": "rfq"}]
+ }
+ },
{"from_route": "/jobs", "to_route": "Job Opening"},
]
diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py
index 3e1afe2..7cd269d 100644
--- a/erpnext/shopping_cart/utils.py
+++ b/erpnext/shopping_cart/utils.py
@@ -16,6 +16,8 @@
return False
def set_cart_count(login_manager):
+ role, parties = check_customer_or_supplier()
+ if role == 'Supplier': return
if show_cart_count():
from erpnext.shopping_cart.cart import set_cart_count
set_cart_count()
@@ -29,6 +31,19 @@
context["shopping_cart_enabled"] = cart_enabled
def update_my_account_context(context):
+ check_user_role, parties = check_customer_or_supplier()
+
+ if check_user_role == 'Supplier':
+ get_supplier_context(context)
+ else:
+ get_customer_context(context)
+
+def get_supplier_context(context):
+ context["my_account_list"].extend([
+ {"label": _("Request for Quotations"), "url": "rfq"},
+ ])
+
+def get_customer_context(context):
context["my_account_list"].extend([
{"label": _("Projects"), "url": "project"},
{"label": _("Orders"), "url": "orders"},
@@ -37,3 +52,15 @@
{"label": _("Issues"), "url": "issues"},
{"label": _("Addresses"), "url": "addresses"}
])
+
+def check_customer_or_supplier():
+ if frappe.session.user:
+ contacts = frappe.get_all("Contact", fields=["customer", "supplier", "email_id"],
+ filters={"email_id": frappe.session.user})
+
+ customer = [d.customer for d in contacts if d.customer] or None
+ supplier = [d.supplier for d in contacts if d.supplier] or None
+
+ if customer: return 'Customer', customer
+ if supplier : return 'Supplier', supplier
+ return 'Customer', None
\ No newline at end of file
diff --git a/erpnext/startup/notifications.py b/erpnext/startup/notifications.py
index 7555312..75e55b5 100644
--- a/erpnext/startup/notifications.py
+++ b/erpnext/startup/notifications.py
@@ -32,6 +32,9 @@
"status": ("not in", ("Stopped",)),
"per_ordered": ("<", 100)
},
+ "Request for Quotation": {
+ "docstatus": 0
+ },
"Purchase Order": {
"status": ("not in", ("Completed", "Closed")),
"docstatus": ("<", 2)
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 72f2060..982534d 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -48,8 +48,10 @@
if(doc.material_request_type === "Purchase")
cur_frm.add_custom_button(__('Purchase Order'),
this.make_purchase_order, __("Make"));
+
+ if(doc.material_request_type === "Purchase")
cur_frm.add_custom_button(__("Request for Quotation"),
- this.make_request_for_quotation, __("Make"))
+ this.make_request_for_quotation, __("Make"));
if(doc.material_request_type === "Purchase")
cur_frm.add_custom_button(__("Supplier Quotation"),
diff --git a/erpnext/templates/emails/request_for_quotation.html b/erpnext/templates/emails/request_for_quotation.html
new file mode 100644
index 0000000..91bdd6b
--- /dev/null
+++ b/erpnext/templates/emails/request_for_quotation.html
@@ -0,0 +1,12 @@
+<h3>{{_("Request for Quotation")}}</h3>
+<br>
+<p>{{ message }}</p>
+{% if update_password_link %}
+<p>{{_("Please click on the following link to set your new password")}}:</p>
+<p><a href="{{ update_password_link }}">{{ update_password_link }}</a></p>
+{% else %}
+<p>{{_("Request for quotation can be access by clicking following link")}}:</p>
+<p><a href="{{ rfq_link }}">{{ rfq_link }}</a></p>
+{% endif %}
+<p>{{_("Thank you")}},<br>
+{{ user_fullname }}</p>
\ No newline at end of file
diff --git a/erpnext/templates/includes/rfq.js b/erpnext/templates/includes/rfq.js
new file mode 100644
index 0000000..3623d77
--- /dev/null
+++ b/erpnext/templates/includes/rfq.js
@@ -0,0 +1,84 @@
+// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+window.doc={{ doc.as_json() }};
+
+$(document).ready(function() {
+ new rfq();
+ doc.supplier = "{{ doc.supplier }}"
+});
+
+rfq = Class.extend({
+ init: function(){
+ this.onfocus_select_all();
+ this.change_qty();
+ this.change_rate();
+ this.terms();
+ this.submit_rfq();
+ },
+
+ onfocus_select_all: function(){
+ $("input").click(function(){
+ $(this).select();
+ })
+ },
+
+ change_qty: function(){
+ var me = this;
+ $('.rfq-items').on("change", ".rfq-qty", function(){
+ me.idx = parseFloat($(this).attr('data-idx'));
+ me.qty = parseFloat($(this).val());
+ me.rate = parseFloat($(repl('.rfq-rate[data-idx=%(idx)s]',{'idx': me.idx})).val());
+ me.update_qty_rate();
+ })
+ },
+
+ change_rate: function(){
+ var me = this;
+ $(".rfq-items").on("change", ".rfq-rate", function(){
+ me.idx = parseFloat($(this).attr('data-idx'));
+ me.rate = parseFloat($(this).val());
+ me.qty = parseFloat($(repl('.rfq-qty[data-idx=%(idx)s]',{'idx': me.idx})).val());
+ me.update_qty_rate();
+ })
+ },
+
+ terms: function(){
+ $(".terms").on("change", ".terms-feedback", function(){
+ doc.terms = $(this).val();
+ })
+ },
+
+ update_qty_rate: function(){
+ var me = this;
+ doc.grand_total = 0.0;
+ $.each(doc.items, function(idx, data){
+ if(data.idx == me.idx){
+ data.qty = me.qty;
+ data.rate = me.rate;
+ data.amount = (me.rate * me.qty) || 0.0;
+ $(repl('.rfq-amount[data-idx=%(idx)s]',{'idx': me.idx})).text(data.amount.toFixed(2));
+ }
+
+ doc.grand_total += flt(data.amount);
+ $('.tax-grand-total').text(doc.grand_total.toFixed(2));
+ })
+ },
+
+ submit_rfq: function(){
+ $('.btn-sm').click(function(){
+ frappe.freeze();
+ frappe.call({
+ type: "POST",
+ method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.create_supplier_quotation",
+ args: {
+ doc: doc
+ },
+ btn: this,
+ callback: function(r){
+ frappe.unfreeze();
+ }
+ })
+ })
+ }
+})
diff --git a/erpnext/templates/includes/rfq/rfq_items.html b/erpnext/templates/includes/rfq/rfq_items.html
new file mode 100644
index 0000000..de9a95f
--- /dev/null
+++ b/erpnext/templates/includes/rfq/rfq_items.html
@@ -0,0 +1,30 @@
+{% from "erpnext/templates/includes/rfq/rfq_macros.html" import item_name_and_description %}
+
+{% for d in doc.items %}
+<div class="rfq-item">
+ <div class="row">
+ <div class="col-sm-6 col-xs-6" style="margin-bottom: 10px;margin-top: 5px;">
+ {{ item_name_and_description(d, doc) }}
+ </div>
+ <!-- <div class="col-sm-2 col-xs-2" style="margin-bottom: 10px;">
+ <textarea type="text" style="margin-top: 5px;" class="input-with-feedback form-control rfq-offer_detail" ></textarea>
+ </div> -->
+ <div class="col-sm-2 col-xs-2 text-right">
+ <input type="number" class="form-control text-right rfq-qty" style="margin-top: 5px; max-width: 70px;display: inline-block"
+ value = "{{ d.get_formatted('qty') }}"
+ data-idx="{{ d.idx }}">
+ <p class="text-muted small" style="margin-top: 10px;">
+ {{_("UOM") + ": "+ d.uom}}
+ </p>
+ </div>
+ <div class="col-sm-2 col-xs-2 text-right">
+ <input type="number" class="form-control text-right rfq-rate"
+ style="margin-top: 5px; max-width: 70px;display: inline-block" value="0.00"
+ data-idx="{{ d.idx }}">
+ </div>
+ <div class="col-sm-2 col-xs-2 text-right">
+ <span class="rfq-amount" data-idx="{{ d.idx }}">0.00</span>
+ </div>
+ </div>
+ </div>
+{% endfor %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/rfq/rfq_macros.html b/erpnext/templates/includes/rfq/rfq_macros.html
new file mode 100644
index 0000000..95bbcfe
--- /dev/null
+++ b/erpnext/templates/includes/rfq/rfq_macros.html
@@ -0,0 +1,21 @@
+{% from "erpnext/templates/includes/macros.html" import product_image_square %}
+
+{% macro item_name_and_description(d, doc) %}
+ <div class="row">
+ <div class="col-xs-4 col-sm-2 order-image-col">
+ <div class="order-image">
+ {{ product_image_square(d.image) }}
+ </div>
+ </div>
+ <div class="col-xs-8 col-sm-10">
+ {{ d.item_code }}
+ <p class="text-muted small">{{ d.description }}</p>
+ {% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %}
+ <p class="text-muted small supplier-part-no">
+ {% if supplier_part_no %}
+ {{_("Supplier Part No") + ": "+ supplier_part_no}}
+ {% endif %}
+ </p>
+ </div>
+ </div>
+{% endmacro %}
diff --git a/erpnext/templates/pages/rfq.html b/erpnext/templates/pages/rfq.html
new file mode 100644
index 0000000..5556d26
--- /dev/null
+++ b/erpnext/templates/pages/rfq.html
@@ -0,0 +1,83 @@
+{% extends "templates/web.html" %}
+
+{% block header %}
+<h1>{{ doc.name }}</h1>
+{% endblock %}
+
+{% block script %}
+<script>{% include "templates/includes/rfq.js" %}</script>
+{% endblock %}
+
+{% block breadcrumbs %}
+ {% include "templates/includes/breadcrumbs.html" %}
+{% endblock %}
+
+{% block style %}
+<style>
+ {% include "templates/includes/order/order.css" %}
+</style>
+{% endblock %}
+
+{% block header_actions %}
+{% if doc.items %}
+<button class="btn btn-primary btn-sm"
+ type="button">
+ {{ _("Submit") }}</button>
+{% endif %}
+{% endblock %}
+
+{% block page_content %}
+<div class="row">
+ <div class="col-xs-6">
+ <div class="rfq-supplier">{{ doc.supplier }}</div>
+ </div>
+ <div class="col-xs-6 text-muted text-right h6">
+ {{ doc.get_formatted("transaction_date") }}
+ </div>
+</div>
+
+<div class="rfq-content">
+ <div id="order-container">
+ <div id="rfq-items">
+ <div class="row cart-item-header">
+ <div class="col-sm-6 col-xs-6">
+ Items
+ </div>
+ <div class="col-sm-2 col-xs-2 text-right">
+ Qty
+ </div>
+ <div class="col-sm-2 col-xs-2 text-right">
+ Rate
+ </div>
+ <div class="col-sm-2 col-xs-2 text-right">
+ Amount
+ </div>
+ </div>
+ <hr>
+ {% if doc.items %}
+ <div class="rfq-items">
+ {% include "templates/includes/rfq/rfq_items.html" %}
+ </div>
+ {% endif %}
+ </div>
+ {% if doc.items %}
+ <div class="row grand-total-row">
+ <div class="col-xs-10 text-right">{{ _("Grand Total") }}</div>
+ <div class="col-xs-2 text-right">
+ <span class="tax-grand-total">0.0</span>
+ </div>
+ </div>
+ {% endif %}
+ <div class="row terms">
+ <div class="col-xs-5 text-left text-muted">{{ _("Terms and Conditions: ") }}</div>
+ </div>
+ <div class="row terms">
+ <div class="col-xs-5 text-left text-muted">
+ <textarea class="form-control terms-feedback" style="border:1px solid #cccccc; padding:4px"></textarea>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- no-sidebar -->
+{% endblock %}
diff --git a/erpnext/templates/pages/rfq.py b/erpnext/templates/pages/rfq.py
new file mode 100644
index 0000000..fbdd6db
--- /dev/null
+++ b/erpnext/templates/pages/rfq.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def get_context(context):
+ context.no_cache = 1
+ context.doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name)
+ context.parents = frappe.form_dict.parents
+ context.doc.supplier = get_supplier()
+ unauthrized_user(context.doc.supplier)
+ context["title"] = frappe.form_dict.name
+
+def unauthrized_user(supplier):
+ status = check_supplier_has_docname_access(supplier)
+ if status == False:
+ frappe.throw(_("Not Permitted"), frappe.PermissionError)
+
+def get_supplier():
+ from erpnext.shopping_cart.utils import check_customer_or_supplier
+ key, parties = check_customer_or_supplier()
+ return parties[0] if key == 'Supplier' else ''
+
+def check_supplier_has_docname_access(supplier):
+ status = True
+ if frappe.form_dict.name not in frappe.db.sql_list("""select parent from `tabRFQ Supplier`
+ where supplier = '{supplier}'""".format(supplier=supplier)):
+ status = False
+ return status