From 38ac7f735058597a01fbc1ed43dcaee7b7d83110 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 9 Oct 2019 11:41:33 +0530 Subject: [PATCH] Enhancements to Supplier Portal (#19221) * fix: Add Purchase Order to portal * fix: Create Customer or Supplier on first login Based on default role set in Portal Settings, a Customer or Supplier will be created when the user logs in for the first time. * fix: Styling for transaction_row * fix: Styling for RFQ page * fix: Add Purchase Invoice route - Make Purchase Invoice from PO * fix: minor - Admissions for Student role - Remove print statement --- .../purchase_invoice/purchase_invoice.py | 11 +++ .../doctype/purchase_order/purchase_order.py | 28 +++++- .../controllers/website_list_for_contact.py | 4 +- erpnext/hooks.py | 27 +++++- erpnext/portal/utils.py | 88 +++++++++++++++++++ erpnext/public/scss/website.scss | 27 ++++++ erpnext/templates/includes/rfq/rfq_items.html | 12 +-- .../templates/includes/rfq/rfq_macros.html | 10 +-- .../templates/includes/transaction_row.html | 35 ++++---- erpnext/templates/pages/order.html | 21 ++++- erpnext/templates/pages/order.js | 6 +- erpnext/templates/pages/rfq.html | 36 ++++---- 12 files changed, 246 insertions(+), 59 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index bc9c1783ef2..4ea9b1c6c97 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -880,6 +880,17 @@ class PurchaseInvoice(BuyingController): # calculate totals again after applying TDS self.calculate_taxes_and_totals() +def get_list_context(context=None): + from erpnext.controllers.website_list_for_contact import get_list_context + list_context = get_list_context(context) + list_context.update({ + 'show_sidebar': True, + 'show_search': True, + 'no_breadcrumbs': True, + 'title': _('Purchase Invoices'), + }) + return list_context + @frappe.whitelist() def make_debit_note(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index e3e2f1edde1..845ff747d61 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -386,7 +386,21 @@ def make_purchase_receipt(source_name, target_doc=None): @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): + return get_mapped_purchase_invoice(source_name, target_doc) + +@frappe.whitelist() +def make_purchase_invoice_from_portal(purchase_order_name): + doc = get_mapped_purchase_invoice(purchase_order_name, ignore_permissions=True) + if doc.contact_email != frappe.session.user: + frappe.throw(_('Not Permitted'), frappe.PermissionError) + doc.save() + frappe.db.commit() + frappe.response['type'] = 'redirect' + frappe.response.location = '/purchase-invoices/' + doc.name + +def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False): def postprocess(source, target): + target.flags.ignore_permissions = ignore_permissions set_missing_values(source, target) #Get the advance paid Journal Entries in Purchase Invoice Advance @@ -437,7 +451,8 @@ def make_purchase_invoice(source_name, target_doc=None): "add_if_empty": True } - doc = get_mapped_doc("Purchase Order", source_name, fields, target_doc, postprocess) + doc = get_mapped_doc("Purchase Order", source_name, fields, + target_doc, postprocess, ignore_permissions=ignore_permissions) return doc @@ -501,6 +516,17 @@ def get_item_details(items): return item_details +def get_list_context(context=None): + from erpnext.controllers.website_list_for_contact import get_list_context + list_context = get_list_context(context) + list_context.update({ + 'show_sidebar': True, + 'show_search': True, + 'no_breadcrumbs': True, + 'title': _('Purchase Orders'), + }) + return list_context + @frappe.whitelist() def update_status(status, name): po = frappe.get_doc("Purchase Order", name) diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index 0738fd506f2..ed379389d7e 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -25,7 +25,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p if not filters: filters = [] - if doctype == 'Supplier Quotation': + if doctype in ['Supplier Quotation', 'Purchase Invoice']: filters.append((doctype, 'docstatus', '<', 2)) else: filters.append((doctype, 'docstatus', '=', 1)) @@ -175,4 +175,4 @@ def get_customer_field_name(doctype): if doctype == 'Quotation': return 'party_name' else: - return 'customer' \ No newline at end of file + return 'customer' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9742a0318b6..5c61874f50e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -45,7 +45,10 @@ update_and_get_user_progress = "erpnext.utilities.user_progress_utils.update_def leaderboards = "erpnext.startup.leaderboard.get_leaderboards" -on_session_creation = "erpnext.shopping_cart.utils.set_cart_count" +on_session_creation = [ + "erpnext.portal.utils.create_customer_or_supplier", + "erpnext.shopping_cart.utils.set_cart_count" +] on_logout = "erpnext.shopping_cart.utils.clear_cart_count" treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Group', 'Sales Person', 'Territory', 'Assessment Group', 'Department'] @@ -102,6 +105,20 @@ website_route_rules = [ "parents": [{"label": _("Supplier Quotation"), "route": "supplier-quotations"}] } }, + {"from_route": "/purchase-orders", "to_route": "Purchase Order"}, + {"from_route": "/purchase-orders/", "to_route": "order", + "defaults": { + "doctype": "Purchase Order", + "parents": [{"label": _("Purchase Order"), "route": "purchase-orders"}] + } + }, + {"from_route": "/purchase-invoices", "to_route": "Purchase Invoice"}, + {"from_route": "/purchase-invoices/", "to_route": "order", + "defaults": { + "doctype": "Purchase Invoice", + "parents": [{"label": _("Purchase Invoice"), "route": "purchase-invoices"}] + } + }, {"from_route": "/quotations", "to_route": "Quotation"}, {"from_route": "/quotations/", "to_route": "order", "defaults": { @@ -148,6 +165,8 @@ standard_portal_menu_items = [ {"title": _("Projects"), "route": "/project", "reference_doctype": "Project"}, {"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"}, {"title": _("Supplier Quotation"), "route": "/supplier-quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"}, + {"title": _("Purchase Orders"), "route": "/purchase-orders", "reference_doctype": "Purchase Order", "role": "Supplier"}, + {"title": _("Purchase Invoices"), "route": "/purchase-invoices", "reference_doctype": "Purchase Invoice", "role": "Supplier"}, {"title": _("Quotations"), "route": "/quotations", "reference_doctype": "Quotation", "role":"Customer"}, {"title": _("Orders"), "route": "/orders", "reference_doctype": "Sales Order", "role":"Customer"}, {"title": _("Invoices"), "route": "/invoices", "reference_doctype": "Sales Invoice", "role":"Customer"}, @@ -160,8 +179,8 @@ standard_portal_menu_items = [ {"title": _("Patient Appointment"), "route": "/patient-appointments", "reference_doctype": "Patient Appointment", "role":"Patient"}, {"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"}, {"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"}, - {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission"}, - {"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application"}, + {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"}, + {"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"}, {"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"}, ] @@ -181,6 +200,8 @@ has_website_permission = { "Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", "Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission", "Supplier Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", + "Purchase Order": "erpnext.controllers.website_list_for_contact.has_website_permission", + "Purchase Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission", "Material Request": "erpnext.controllers.website_list_for_contact.has_website_permission", "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 2e710c75f3d..56e4fcde731 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -1,5 +1,8 @@ from __future__ import unicode_literals import frappe +from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings +from erpnext.shopping_cart.cart import get_debtors_account +from frappe.utils.nestedset import get_root_of def set_default_role(doc, method): '''Set customer, supplier, student, guardian based on email''' @@ -21,3 +24,88 @@ def set_default_role(doc, method): doc.add_roles('Student') elif frappe.get_value('Guardian', dict(email_address=doc.email)) and 'Guardian' not in roles: doc.add_roles('Guardian') + +def create_customer_or_supplier(): + '''Based on the default Role (Customer, Supplier), create a Customer / Supplier. + Called on_session_creation hook. + ''' + user = frappe.session.user + + if frappe.db.get_value('User', user, 'user_type') != 'Website User': + return + + user_roles = frappe.get_roles() + portal_settings = frappe.get_single('Portal Settings') + default_role = portal_settings.default_role + + if default_role not in ['Customer', 'Supplier']: + return + + # create customer / supplier if the user has that role + if portal_settings.default_role and portal_settings.default_role in user_roles: + doctype = portal_settings.default_role + else: + doctype = None + + if not doctype: + return + + if party_exists(doctype, user): + return + + party = frappe.new_doc(doctype) + fullname = frappe.utils.get_fullname(user) + + if doctype == 'Customer': + cart_settings = get_shopping_cart_settings() + + if cart_settings.enable_checkout: + debtors_account = get_debtors_account(cart_settings) + else: + debtors_account = '' + + party.update({ + "customer_name": fullname, + "customer_type": "Individual", + "customer_group": cart_settings.default_customer_group, + "territory": get_root_of("Territory") + }) + + if debtors_account: + party.update({ + "accounts": [{ + "company": cart_settings.company, + "account": debtors_account + }] + }) + else: + party.update({ + "supplier_name": fullname, + "supplier_group": "All Supplier Groups", + "supplier_type": "Individual" + }) + + party.flags.ignore_mandatory = True + party.insert(ignore_permissions=True) + + contact = frappe.new_doc("Contact") + contact.update({ + "first_name": fullname, + "email_id": user + }) + contact.append('links', dict(link_doctype=doctype, link_name=party.name)) + contact.flags.ignore_mandatory = True + contact.insert(ignore_permissions=True) + + return party + + +def party_exists(doctype, user): + contact_name = frappe.db.get_value("Contact", {"email_id": user}) + + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + doctypes = [d.link_doctype for d in contact.links] + return doctype in doctypes + + return False diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss index 002498f2735..7b9a70d232e 100644 --- a/erpnext/public/scss/website.scss +++ b/erpnext/public/scss/website.scss @@ -51,3 +51,30 @@ width: 24px; height: 24px; } + +.website-list .result { + margin-top: 2rem; +} + +.result { + border-bottom: 1px solid $border-color; +} + +.transaction-list-item { + padding: 1rem 0; + border-top: 1px solid $border-color; + position: relative; + + a.transaction-item-link { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-decoration: none; + opacity: 0; + overflow: hidden; + text-indent: -9999px; + z-index: 0; + } +} diff --git a/erpnext/templates/includes/rfq/rfq_items.html b/erpnext/templates/includes/rfq/rfq_items.html index cb77f7eea5c..caa15f386b0 100644 --- a/erpnext/templates/includes/rfq/rfq_items.html +++ b/erpnext/templates/includes/rfq/rfq_items.html @@ -3,13 +3,13 @@ {% for d in doc.items %}
-
+
{{ item_name_and_description(d, doc) }}
- -
+
@@ -17,14 +17,14 @@ {{_("UOM") + ":"+ d.uom}}

-
+
-
+
{{doc.currency_symbol}} 0.00
-{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/erpnext/templates/includes/rfq/rfq_macros.html b/erpnext/templates/includes/rfq/rfq_macros.html index 95bbcfec3f3..88724c30de6 100644 --- a/erpnext/templates/includes/rfq/rfq_macros.html +++ b/erpnext/templates/includes/rfq/rfq_macros.html @@ -1,13 +1,11 @@ -{% from "erpnext/templates/includes/macros.html" import product_image_square %} +{% from "erpnext/templates/includes/macros.html" import product_image_square, product_image %} {% macro item_name_and_description(d, doc) %}
-
-
- {{ product_image_square(d.image) }} -
+
+ {{ product_image(d.image) }}
-
+
{{ d.item_code }}

{{ d.description }}

{% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %} diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 6c58b519fc7..80a542f74bf 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -1,22 +1,21 @@
- -
-
- - {{ doc.name }} -
- {{ frappe.utils.global_date_format(doc.modified) }} -
-
-
-
- {{ doc.items_preview }} -
-
-
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 67a8fed8ab1..9e3c58b45be 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -12,7 +12,22 @@ {% endblock %} {% block header_actions %} -{{ _("Print") }} + + {% endblock %} {% block page_content %} @@ -34,7 +49,7 @@

- {%- set party_name = doc.supplier_name if doc.doctype == 'Supplier Quotation' else doc.customer_name %} + {%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %} {{ party_name }} {% if doc.contact_display and doc.contact_display != party_name %} @@ -172,4 +187,4 @@ currency: '{{ doc.currency }}' } -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/erpnext/templates/pages/order.js b/erpnext/templates/pages/order.js index 21c3a144376..0574cdedc0d 100644 --- a/erpnext/templates/pages/order.js +++ b/erpnext/templates/pages/order.js @@ -5,7 +5,9 @@ frappe.ready(function(){ var loyalty_points_input = document.getElementById("loyalty-point-to-redeem"); var loyalty_points_status = document.getElementById("loyalty-points-status"); - loyalty_points_input.onblur = apply_loyalty_points; + if (loyalty_points_input) { + loyalty_points_input.onblur = apply_loyalty_points; + } function apply_loyalty_points() { var loyalty_points = parseInt(loyalty_points_input.value); @@ -37,4 +39,4 @@ frappe.ready(function(){ }); } } -}) \ No newline at end of file +}) diff --git a/erpnext/templates/pages/rfq.html b/erpnext/templates/pages/rfq.html index 591d046d35c..5b27a94553a 100644 --- a/erpnext/templates/pages/rfq.html +++ b/erpnext/templates/pages/rfq.html @@ -22,10 +22,10 @@ {% block page_content %}

-
+
{{ doc.supplier }}
-
+
{{ doc.get_formatted("transaction_date") }}
@@ -33,16 +33,16 @@
-
+
{{ _("Items") }}
-
+
{{ _("Qty") }}
-
+
{{ _("Rate") }}
-
+
{{ _("Amount") }}
@@ -55,30 +55,29 @@
{% if doc.items %}
-
{{ _("Grand Total") }}
-
+
{{ _("Grand Total") }}
+
{{doc.currency_symbol}} 0.0
{% endif %}
-
+


{{ _("Notes: ") }}

-
-
-
-
-

{{ _("Quotations: ") }}

- {% if doc.rfq_links %} +
+
+

{{ _("Quotations: ") }}

+ {% if doc.rfq_links %} +
{% for d in doc.rfq_links %}
- {{d.name}} + {{d.name}}
{{d.status}} @@ -87,10 +86,11 @@ {{d.transaction_date}}
+ Link
{% endfor %} - {% endif %} -
+
+ {% endif %}