From 1f20f6348d93b12f2400beef7acbab40f4e1f2a5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:13:29 +0530 Subject: [PATCH 01/15] feat: added membership settings doctype --- .../doctype/membership_settings/__init__.py | 0 .../membership_settings.js | 12 +++ .../membership_settings.json | 89 +++++++++++++++++++ .../membership_settings.py | 17 ++++ .../test_membership_settings.py | 10 +++ 5 files changed, 128 insertions(+) create mode 100644 erpnext/non_profit/doctype/membership_settings/__init__.py create mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.js create mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.json create mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.py create mode 100644 erpnext/non_profit/doctype/membership_settings/test_membership_settings.py diff --git a/erpnext/non_profit/doctype/membership_settings/__init__.py b/erpnext/non_profit/doctype/membership_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js new file mode 100644 index 0000000000..092c854b43 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js @@ -0,0 +1,12 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Membership Settings', { + refresh: function(frm) { + // if (frm.doc.enable_razorpay) { + // frm.add_custom_button(__("Fetch Plans from RazorPay"), () => { + // frm.trigger("fetch_razorpay_plans") + // }); + // } + } +}); diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json new file mode 100644 index 0000000000..d64e392287 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json @@ -0,0 +1,89 @@ +{ + "actions": [], + "creation": "2020-03-29 12:57:03.005120", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable_razorpay", + "razorpay_settings_section", + "billing_cycle", + "billing_frequency", + "column_break_2", + "auto_capture_payment", + "create_subscription", + "auto_create_invoice" + ], + "fields": [ + { + "fieldname": "billing_cycle", + "fieldtype": "Select", + "label": "Billing Cycle", + "options": "Monthly\nYearly" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_razorpay", + "fieldtype": "Check", + "label": "Enable RazorPay For Memberships" + }, + { + "default": "0", + "fieldname": "create_subscription", + "fieldtype": "Check", + "label": "Create Subscription" + }, + { + "default": "0", + "depends_on": "eval:doc.enable_razorpay", + "fieldname": "auto_capture_payment", + "fieldtype": "Check", + "label": "Auto Capture Payment" + }, + { + "default": "0", + "fieldname": "auto_create_invoice", + "fieldtype": "Check", + "label": "Auto Create Invoice" + }, + { + "depends_on": "eval:doc.enable_razorpay", + "fieldname": "razorpay_settings_section", + "fieldtype": "Section Break", + "label": "RazorPay Settings" + }, + { + "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", + "fieldname": "billing_frequency", + "fieldtype": "Data", + "label": "Billing Frequency" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-03-30 16:02:00.060583", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Membership Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/membership_settings/membership_settings.py new file mode 100644 index 0000000000..2b8e37f2a6 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.integrations.utils import get_payment_gateway_controller +from frappe.model.document import Document + +class MembershipSettings(Document): + pass + +@frappe.whitelist() +def get_plans_for_membership(*args, **kwargs): + controller = get_payment_gateway_controller("Razorpay") + plans = controller.get_plans() + return [plan.get("item") for plan in plans.get("items")] \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py b/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py new file mode 100644 index 0000000000..2ad7984583 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestMembershipSettings(unittest.TestCase): + pass From e836cdec135ac393e89ab28288695f1547a33d41 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:14:10 +0530 Subject: [PATCH 02/15] feat: update member and membership doctype --- erpnext/non_profit/doctype/member/member.json | 723 ++++-------------- .../membership_type/membership_type.json | 167 ++-- 2 files changed, 198 insertions(+), 692 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index 941497494c..c6c6dcd366 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -1,604 +1,171 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "autoname": "naming_series:", - "beta": 0, - "creation": "2017-09-11 09:24:52.898356", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2017-09-11 09:24:52.898356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "member_name", + "membership_expiry_date", + "column_break_5", + "membership_type", + "email", + "email_id", + "image", + "customer_section", + "customer", + "customer_name", + "supplier_section", + "supplier", + "address_contacts", + "address_html", + "column_break_9", + "contact_html" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "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": "Series", - "length": 0, - "no_copy": 0, - "options": "NPO-MEM-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "NPO-MEM-.YYYY.-", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "member_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Member Name", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "member_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Member Name", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_expiry_date", - "fieldtype": "Date", - "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": "Membership Expiry Date", - "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": 0 - }, + "fieldname": "membership_expiry_date", + "fieldtype": "Date", + "label": "Membership Expiry Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "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, - "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": 0 - }, + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Membership Type", - "length": 0, - "no_copy": 0, - "options": "Membership Type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "membership_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Membership Type", + "options": "Membership Type", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "email", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Email", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "email", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Image", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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": 0 - }, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "customer_section", - "fieldtype": "Section Break", - "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": "Customer", - "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": 0 - }, + "collapsible": 1, + "fieldname": "customer_section", + "fieldtype": "Section Break", + "label": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "fieldtype": "Link", - "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": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "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": 0 - }, + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "customer.customer_name", - "fieldname": "customer_name", - "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": "Customer Name", - "length": 0, - "no_copy": 0, - "options": "", - "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, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "supplier_section", - "fieldtype": "Section Break", - "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": "Supplier", - "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": 0 - }, + "collapsible": 1, + "fieldname": "supplier_section", + "fieldtype": "Section Break", + "label": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier", - "fieldtype": "Link", - "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": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "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": 0 - }, + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_contacts", - "fieldtype": "Section Break", - "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": "Address and Contact", - "length": 0, - "no_copy": 0, - "options": "fa fa-map-marker", - "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": 0 - }, + "fieldname": "address_contacts", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-map-marker" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_html", - "fieldtype": "HTML", - "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": "Address HTML", - "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": 0 - }, + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "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, - "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": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_html", - "fieldtype": "HTML", - "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": "Contact HTML", - "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": 0 + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML" + }, + { + "fieldname": "email_id", + "fieldtype": "Data", + "label": "Email Address" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:23.218109", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Member", - "name_case": "", - "owner": "Administrator", + ], + "image_field": "image", + "links": [], + "modified": "2020-03-30 13:42:06.488851", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Member", + "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": "Non Profit Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "share": 1, "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": "Non Profit Member", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Member", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Non Profit", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "member_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "restrict_to_domain": "Non Profit", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "member_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.json b/erpnext/non_profit/doctype/membership_type/membership_type.json index 35a7902c9f..319078fd6c 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.json +++ b/erpnext/non_profit/doctype/membership_type/membership_type.json @@ -1,124 +1,63 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:membership_type", - "beta": 0, - "creation": "2017-09-18 12:56:56.343999", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "field:membership_type", + "creation": "2017-09-18 12:56:56.343999", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "membership_type", + "amount", + "razorpay_plan_id" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_type", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Membership Type", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "membership_type", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Membership Type", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Amount", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "amount", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Amount", + "reqd": 1 + }, + { + "fieldname": "razorpay_plan_id", + "fieldtype": "Data", + "hidden": 1, + "label": "Razorpay Plan ID", + "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": "2017-12-05 07:03:45.860757", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Membership Type", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-03-30 12:54:07.850857", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Membership Type", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 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": "Non Profit Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Non Profit", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "restrict_to_domain": "Non Profit", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 1d6395c5cf57db4cb9989c24c3d4bd9d9d7db64c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:14:44 +0530 Subject: [PATCH 03/15] feat: add PAN details field to member for India --- erpnext/regional/india/setup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 970a831e0e..adc0b2cb4d 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -507,6 +507,14 @@ def make_custom_fields(update=True): 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } + ], + "Member": [ + { + 'fieldname': 'pan_number', + 'label': 'PAN Details', + 'fieldtype': 'Data', + 'insert_after': 'email' + } ] } create_custom_fields(custom_fields, update=update) @@ -728,4 +736,4 @@ def get_tds_details(accounts, fiscal_year): doctype="Tax Withholding Category", accounts=accounts, rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) - ] + ] \ No newline at end of file From 12816343065f20eacb4be1ad148b66b242fbd6b5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:15:22 +0530 Subject: [PATCH 04/15] feat: client code to handle razorpay fields --- .../non_profit/doctype/membership_type/membership_type.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js index 3ef39aee7d..ce8225e384 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.js +++ b/erpnext/non_profit/doctype/membership_type/membership_type.js @@ -2,7 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Membership Type', { - refresh: function() { - + 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'); + }) } }); From 28fafd0ddafd4a43fb8da4e74f772180bf315a67 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:15:37 +0530 Subject: [PATCH 05/15] feat: setup subscription API --- erpnext/non_profit/doctype/member/member.py | 5 +- .../doctype/membership/membership.py | 103 +++++++++++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 9afaf90e7a..cec1ea0112 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -14,7 +14,10 @@ class Member(Document): def validate(self): - self.validate_email_type(self.email) + if self.email: + self.validate_email_type(self.email) + if self.email_id: + self.validate_email_type(self.email_id) def validate_email_type(self, email): from frappe.utils import validate_email_address diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 98bee56979..409d6f3af4 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.utils import add_days, add_years, nowdate, getdate +from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint +from frappe.integrations.utils import get_payment_gateway_controller from frappe import _ import erpnext @@ -43,11 +44,109 @@ class Membership(Document): else: self.from_date = nowdate() - self.to_date = add_years(self.from_date, 1) + if frappe.db.get_single_value("Membership Settings", "billing_cycle") == "Yearly": + self.to_date = add_years(self.from_date, 1) + else: + self.to_date = add_months(self.from_date, 1) def on_payment_authorized(self, status_changed_to=None): if status_changed_to in ("Completed", "Authorized"): self.load_from_db() self.db_set('paid', 1) + def setup_subscription(self): + membership_settings = frappe.get_doc("Membership Settings") + if not membership_settings.enable_razorpay: + frappe.throw("Please enable Razorpay to setup subscription") + controller = get_payment_gateway_controller("Razorpay") + settings = controller.get_settings({}) + + plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") + + if not plan_id: + frappe.throw(_("Please setup Razorpay Plan ID")) + + subscription_details = { + "plan_id": plan_id, + "billing_frequency": cint(membership_settings.billing_frequency), + "customer_notify": 1 + } + + args = { + 'subscription_details': subscription_details + } + + subscription = controller.setup_subscription(settings, **args) + + return subscription + + +def get_member_if_exists(email, plan): + member_list = frappe.get_all("Member", filters={'email': email, 'membership_type': plan}) + if member_list and member_list[0]: + return member_list[0]['name'] + else: + return None + +def create_member(user_details): + member = frappe.new_doc("Member") + member.update({ + "member_name": user_details.fullname, + "email_id": user_details.email, + "pan_number": user_details.pan, + "membership_type": user_details.plan_id, + "customer": create_customer(user_details) + }) + + member.insert(ignore_permissions=True) + return member + +def create_customer(user_details): + customer = frappe.new_doc("Customer") + customer.customer_name = user_details.fullname + customer.customer_type = "Individual" + customer.insert(ignore_permissions=True) + + try: + contact = frappe.new_doc("Contact") + contact.first_name = user_details.fullname + contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) + contact.add_email(user_details.email, is_primary=1) + contact.insert(ignore_permissions=True) + + contact.append("links", { + "link_doctype": "Customer", + "link_name": customer.name + }) + + contact.insert() + except Exception: + error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + + return customer.name + +def create_membership(member): + membership = frappe.new_doc("Membership") + membership.update({ + "member": member.name, + "membership_status": "New", + "membership_type": member.membership_type, + "currency": "INR", + "amount": plan.amount + }) + + membership.insert(ignore_permissions=True) + +@frappe.whitelist(allow_guest=True) +def create_membership_subscription(user_details): + # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} + user_details = frappe._dict(user_details) + member = get_member_if_exists(user_details.email, user_details.plan_id) + plan = frappe.get_doc("Membership Type", user_details.plan_id) + if not member: + member = create_member(user_details) + + membership = create_membership(member, plan) + + return membership.setup_subscription() From 3a2c8560baa3fcfb48d902f25173c458f8316d97 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:15:51 +0530 Subject: [PATCH 06/15] feat: update desk page for Non Profits --- .../non_profit/desk_page/non_profit/non_profit.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/non_profit/desk_page/non_profit/non_profit.json b/erpnext/non_profit/desk_page/non_profit/non_profit.json index 5d759ba113..dcbd9c37a6 100644 --- a/erpnext/non_profit/desk_page/non_profit/non_profit.json +++ b/erpnext/non_profit/desk_page/non_profit/non_profit.json @@ -1,28 +1,34 @@ { "cards": [ { + "hidden": 0, "icon": "icon-list", "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]", "title": "Loan Management" }, { + "hidden": 0, "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]", "title": "Grant Application" }, { - "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Memebership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Memebership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n }\n]", + "hidden": 0, + "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]", "title": "Membership" }, { + "hidden": 0, "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]", "title": "Volunteer" }, { + "hidden": 0, "icon": "fa fa-star", "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]", "title": "Chapter" }, { + "hidden": 0, "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]", "title": "Donor" } @@ -38,7 +44,7 @@ "idx": 0, "is_standard": 1, "label": "Non Profit", - "modified": "2020-03-12 16:30:36.683012", + "modified": "2020-03-29 13:06:18.070131", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", From 4b667160a0988dfecf70fcb919e98347bc43c519 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:44:17 +0530 Subject: [PATCH 07/15] feat: added razorpay details to membership --- .../doctype/membership/membership.js | 8 +- .../doctype/membership/membership.json | 619 ++++-------------- .../doctype/membership/membership.py | 59 +- 3 files changed, 205 insertions(+), 481 deletions(-) diff --git a/erpnext/non_profit/doctype/membership/membership.js b/erpnext/non_profit/doctype/membership/membership.js index 421087995a..554549a0bd 100644 --- a/erpnext/non_profit/doctype/membership/membership.js +++ b/erpnext/non_profit/doctype/membership/membership.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('Membership', { - onload:function(frm) { + setup: function(frm) { + frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + if (val) frm.set_df_property('razorpay_details_section', 'hidden', false); + }) + }, + + onload: function(frm) { frm.add_fetch('membership_type', 'amount', 'amount'); } }); diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json index 9a204b19bf..9f10d0cfc7 100644 --- a/erpnext/non_profit/doctype/membership/membership.json +++ b/erpnext/non_profit/doctype/membership/membership.json @@ -1,501 +1,164 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "NPO-MSH-.YYYY.-.#####", - "beta": 0, - "creation": "2017-09-11 11:39:18.492184", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "NPO-MSH-.YYYY.-.#####", + "creation": "2017-09-11 11:39:18.492184", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "member", + "membership_type", + "column_break_3", + "membership_status", + "membership_validity_section", + "from_date", + "to_date", + "column_break_8", + "member_since_date", + "payment_details", + "paid", + "currency", + "amount", + "razorpay_details_section", + "subscription_id", + "payment_id", + "webhook_payload" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "member", - "fieldtype": "Link", - "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": "Member", - "length": 0, - "no_copy": 0, - "options": "Member", - "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": 0 - }, + "fieldname": "member", + "fieldtype": "Link", + "label": "Member", + "options": "Member" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Membership Type", - "length": 0, - "no_copy": 0, - "options": "Membership Type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "membership_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Membership Type", + "options": "Membership Type", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "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, - "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": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_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": "Membership Status", - "length": 0, - "no_copy": 0, - "options": "New\nCurrent\nExpired\nPending\nCancelled", - "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": 0 - }, + "fieldname": "membership_status", + "fieldtype": "Select", + "label": "Membership Status", + "options": "New\nCurrent\nExpired\nPending\nCancelled" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_validity_section", - "fieldtype": "Section Break", - "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": "Validity", - "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": 0 - }, + "fieldname": "membership_validity_section", + "fieldtype": "Section Break", + "label": "Validity" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "to_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "To", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "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, - "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": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "member_since_date", - "fieldtype": "Date", - "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": "Member Since", - "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": 0 - }, + "fieldname": "member_since_date", + "fieldtype": "Date", + "label": "Member Since" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_details", - "fieldtype": "Section Break", - "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": "Payment Details", - "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": 0 - }, + "fieldname": "payment_details", + "fieldtype": "Section Break", + "label": "Payment Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "paid", - "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": "Paid", - "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": 0 - }, + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "label": "Paid" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "currency", - "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": "Currency", - "length": 0, - "no_copy": 0, - "options": "USD\nINR", - "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": 0 - }, + "fieldname": "currency", + "fieldtype": "Select", + "label": "Currency", + "options": "USD\nINR" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Float", - "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": "Amount", - "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": 0 + "fieldname": "amount", + "fieldtype": "Float", + "label": "Amount" + }, + { + "fieldname": "razorpay_details_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Razorpay Details" + }, + { + "fieldname": "subscription_id", + "fieldtype": "Data", + "label": "Subscription ID", + "read_only": 1 + }, + { + "fieldname": "payment_id", + "fieldtype": "Data", + "label": "Payment ID", + "read_only": 1 + }, + { + "fieldname": "webhook_payload", + "fieldtype": "Code", + "label": "Webhook Payload", + "options": "JSON", + "read_only": 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": "2018-08-21 16:15:42.323446", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Membership", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-04-06 14:29:33.856060", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Membership", + "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": "Non Profit Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "share": 1, "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": "Non Profit Member", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Member", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Non Profit", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "restrict_to_domain": "Non Profit", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 409d6f3af4..360c59f4ec 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -126,20 +126,39 @@ def create_customer(user_details): return customer.name -def create_membership(member): +def create_membership(member, plan): membership = frappe.new_doc("Membership") membership.update({ "member": member.name, "membership_status": "New", "membership_type": member.membership_type, "currency": "INR", - "amount": plan.amount + "amount": plan.amount, + "from_date": getdate() }) membership.insert(ignore_permissions=True) + return membership + @frappe.whitelist(allow_guest=True) def create_membership_subscription(user_details): + """Summary + + Args: + user_details (TYPE): Description + + Returns: + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, + 'subscription_id': 'sub_EZycCvXFvqnC6p' + } + """ # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} user_details = frappe._dict(user_details) member = get_member_if_exists(user_details.email, user_details.plan_id) @@ -150,3 +169,39 @@ def create_membership_subscription(user_details): membership = create_membership(member, plan) return membership.setup_subscription() + +@frappe.whitelist(allow_guest=True) +def razorpay_subscription_started(data={}): + data = { + "entity": "event", + "event": "subscription.activated", + "payload": { + "subscription": { + "entity": { + "id": "sub_EZZsbKwt5xRYH6", + "entity": "subscription", + "plan_id": "plan_EXwyxDYDCj3X4v", + "customer_id": "cust_EZZw80dUQgID8C", + "current_start": 1585822073, + "current_end": 1588357800, + "start_at": 1585822073, + "end_at": 1646159400, + "auth_attempts": 0, + "total_count": 24, + "paid_count": 1, + } + }, + "payment": { + "entity": { + "id": "pay_EZZw7v9NEUFCVJ", + "amount": 10000, + "currency": "INR", + "method": "card", + } + } + }, + "created_at": 1585822079 + } + data = frappe._dict(data) + + pass \ No newline at end of file From d92b7ab2580b3d9062976a9950970defa6d0d964 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:44:33 +0530 Subject: [PATCH 08/15] fix: use false explicitly --- erpnext/non_profit/doctype/membership_type/membership_type.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js index ce8225e384..226981dc78 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.js +++ b/erpnext/non_profit/doctype/membership_type/membership_type.js @@ -4,7 +4,7 @@ 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'); + if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false); }) } }); From 8d6cded07f480eada7a6542a2e6b5af45104e64a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:44:42 +0530 Subject: [PATCH 09/15] feat: added subscription web hook --- .../doctype/membership/membership.py | 119 +++++++++++++----- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 360c59f4ec..574ae7fb7b 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -3,6 +3,8 @@ # For license information, please see license.txt from __future__ import unicode_literals +import json +from datetime import datetime import frappe from frappe.model.document import Document from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint @@ -168,40 +170,91 @@ def create_membership_subscription(user_details): membership = create_membership(member, plan) - return membership.setup_subscription() + subscription = membership.setup_subscription() + + membership.subscription_id = subscription.get('subscription_id') + membership.save(ignore_permissions=True) + + return subscription + +def get_membership_based_on_subscription(subscription_id, custom_filters={}): + filters = {'subscription_id': subscription.id} + filters.update(custom_filters) + + memberships = frappe.get_all("Membership", filters=filters, order_by="creation") + if not memberships: + return None + + return frappe.get_doc("Membership", memberships[0]['name']) + @frappe.whitelist(allow_guest=True) -def razorpay_subscription_started(data={}): - data = { - "entity": "event", - "event": "subscription.activated", - "payload": { - "subscription": { - "entity": { - "id": "sub_EZZsbKwt5xRYH6", - "entity": "subscription", - "plan_id": "plan_EXwyxDYDCj3X4v", - "customer_id": "cust_EZZw80dUQgID8C", - "current_start": 1585822073, - "current_end": 1588357800, - "start_at": 1585822073, - "end_at": 1646159400, - "auth_attempts": 0, - "total_count": 24, - "paid_count": 1, - } - }, - "payment": { - "entity": { - "id": "pay_EZZw7v9NEUFCVJ", - "amount": 10000, - "currency": "INR", - "method": "card", - } - } - }, - "created_at": 1585822079 - } +def trigger_razorpay_subscription(data): + if isinstance(data, six.string_types): + data = json.loads(data) data = frappe._dict(data) - pass \ No newline at end of file + subscription = data.payload.get("subscription", {}).get('entity', {}) + subscription = frappe._dict(subscription) + + payment = data.payload.get("payment", {}).get('entity', {}) + payment = frappe._dict(payment) + + if data.event == "subscription.activated": + membership = get_membership_based_on_subscription(subscription.id, {"membership_status": "New"}) + else + prev_membership = get_membership_based_on_subscription(subscription.id, {"payment_id": payment.id, "paid": 1}) + if prev_membership: + print("payment already done") + return + prev_membership = get_membership_based_on_subscription(subscription.id) + membership = frappe.new_doc("Membership") + membership.update({ + "member": prev_membership.member, + "membership_status": "Current", + "membership_type": prev_membership.membership_type, + "currency": "INR", + }) + + subscription_charged(subscription, payment, membership) + +def subscription_charged(subscription, payment, membership=None): + data = { + "subscription": subscription, + "payment": payment, + } + membership.paid = 1 + membership.payment_id = payment.id + membership.webhook_payload = json.dumps(data, indent=4, sort_keys=True) + membership.from_date = datetime.fromtimestamp(subscription.current_start) + membership.to_date = datetime.fromtimestamp(subscription.current_end) + membership.amount = payment.amount / 100 # Convert to rupees from paise + + if membership.is_new(): + membership.insert() + else: + membership.save() + + return True + + + + + + + + + + + + + + + + + + + + + + From 65e11a9640089b93c200bc5fa13c3a93007afeb2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 14:49:28 +0530 Subject: [PATCH 10/15] refactor: razorpay subscription for memberships --- erpnext/non_profit/doctype/member/member.js | 8 + erpnext/non_profit/doctype/member/member.json | 49 +++- erpnext/non_profit/doctype/member/member.py | 110 ++++++++- .../doctype/membership/membership.py | 209 ++++-------------- 4 files changed, 208 insertions(+), 168 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js index eb74dc1507..3e9d0baba5 100644 --- a/erpnext/non_profit/doctype/member/member.js +++ b/erpnext/non_profit/doctype/member/member.js @@ -2,6 +2,14 @@ // For license information, please see license.txt frappe.ui.form.on('Member', { + setup: function(frm) { + frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + if (val && (frm.doc.subscription_id || frm.doc.customer_id)) { + frm.set_df_property('razorpay_details_section', 'hidden', false); + } + }) + }, + refresh: function(frm) { frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'}; diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index c6c6dcd366..bb73a843ee 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -23,7 +23,14 @@ "address_contacts", "address_html", "column_break_9", - "contact_html" + "contact_html", + "razorpay_details_section", + "subscription_id", + "customer_id", + "subscription_activated", + "column_break_21", + "subscription_start", + "subscription_end" ], "fields": [ { @@ -127,11 +134,49 @@ "fieldname": "email_id", "fieldtype": "Data", "label": "Email Address" + }, + { + "fieldname": "subscription_id", + "fieldtype": "Data", + "label": "Subscription ID", + "read_only": 1 + }, + { + "fieldname": "customer_id", + "fieldtype": "Data", + "label": "Customer ID", + "read_only": 1 + }, + { + "fieldname": "razorpay_details_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Razorpay Details" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "subscription_activated", + "fieldtype": "Check", + "label": "Subscription Activated" + }, + { + "fieldname": "subscription_start", + "fieldtype": "Date", + "label": "Subscription Start " + }, + { + "fieldname": "subscription_end", + "fieldtype": "Date", + "label": "Subscription End" } ], "image_field": "image", "links": [], - "modified": "2020-03-30 13:42:06.488851", + "modified": "2020-04-07 14:20:33.215700", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index cec1ea0112..1ae8699db1 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -3,9 +3,12 @@ # For license information, please see license.txt from __future__ import unicode_literals +import frappe +from frappe import _ 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 class Member(Document): def onload(self): @@ -21,4 +24,107 @@ class Member(Document): def validate_email_type(self, email): from frappe.utils import validate_email_address - validate_email_address(email.strip(), True) \ No newline at end of file + validate_email_address(email.strip(), True) + + def setup_subscription(self): + membership_settings = frappe.get_doc("Membership Settings") + if not membership_settings.enable_razorpay: + frappe.throw("Please enable Razorpay to setup subscription") + + controller = get_payment_gateway_controller("Razorpay") + settings = controller.get_settings({}) + + plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") + + if not plan_id: + frappe.throw(_("Please setup Razorpay Plan ID")) + + subscription_details = { + "plan_id": plan_id, + "billing_frequency": cint(membership_settings.billing_frequency), + "customer_notify": 1 + } + + args = { + 'subscription_details': subscription_details + } + + subscription = controller.setup_subscription(settings, **args) + + return subscription + +def get_or_create_member(user_details): + member_list = frappe.get_all("Member", filters={'email': user_details.email, 'membership_type': user_details.plan_id}) + if member_list and member_list[0]: + return member_list[0]['name'] + else: + return create_member(user_details) + +def create_member(user_details): + member = frappe.new_doc("Member") + member.update({ + "member_name": user_details.fullname, + "email_id": user_details.email, + "pan_number": user_details.pan, + "membership_type": user_details.plan_id, + "customer": create_customer(user_details) + }) + + member.insert(ignore_permissions=True) + return member + +def create_customer(user_details): + customer = frappe.new_doc("Customer") + customer.customer_name = user_details.fullname + customer.customer_type = "Individual" + customer.insert(ignore_permissions=True) + + try: + contact = frappe.new_doc("Contact") + contact.first_name = user_details.fullname + contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) + contact.add_email(user_details.email, is_primary=1) + contact.insert(ignore_permissions=True) + + contact.append("links", { + "link_doctype": "Customer", + "link_name": customer.name + }) + + contact.insert() + except Exception: + error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + + return customer.name + +@frappe.whitelist(allow_guest=True) +def create_member_subscription_order(user_details): + """Summary + + Args: + user_details (TYPE): Description + + Returns: + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, + 'subscription_id': 'sub_EZycCvXFvqnC6p' + } + """ + # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} + user_details = frappe._dict(user_details) + plan = frappe.get_doc("Membership Type", user_details.plan_id) + member = get_or_create_member(user_details) + if not member: + member = create_member(user_details) + + subscription = member.setup_subscription() + + member.subscription_id = subscription.get('subscription_id') + member.save(ignore_permissions=True) + + return subscription \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 574ae7fb7b..00ecd8fbdf 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import json -from datetime import datetime import frappe +import six +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, cint -from frappe.integrations.utils import get_payment_gateway_controller from frappe import _ import erpnext @@ -56,139 +57,16 @@ class Membership(Document): self.load_from_db() self.db_set('paid', 1) - def setup_subscription(self): - membership_settings = frappe.get_doc("Membership Settings") - if not membership_settings.enable_razorpay: - frappe.throw("Please enable Razorpay to setup subscription") +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") - controller = get_payment_gateway_controller("Razorpay") - settings = controller.get_settings({}) - - plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") - - if not plan_id: - frappe.throw(_("Please setup Razorpay Plan ID")) - - subscription_details = { - "plan_id": plan_id, - "billing_frequency": cint(membership_settings.billing_frequency), - "customer_notify": 1 - } - - args = { - 'subscription_details': subscription_details - } - - subscription = controller.setup_subscription(settings, **args) - - return subscription + return frappe.get_doc("Member", members[0]['name']) -def get_member_if_exists(email, plan): - member_list = frappe.get_all("Member", filters={'email': email, 'membership_type': plan}) - if member_list and member_list[0]: - return member_list[0]['name'] - else: - return None - -def create_member(user_details): - member = frappe.new_doc("Member") - member.update({ - "member_name": user_details.fullname, - "email_id": user_details.email, - "pan_number": user_details.pan, - "membership_type": user_details.plan_id, - "customer": create_customer(user_details) - }) - - member.insert(ignore_permissions=True) - return member - -def create_customer(user_details): - customer = frappe.new_doc("Customer") - customer.customer_name = user_details.fullname - customer.customer_type = "Individual" - customer.insert(ignore_permissions=True) - - try: - contact = frappe.new_doc("Contact") - contact.first_name = user_details.fullname - contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) - contact.add_email(user_details.email, is_primary=1) - contact.insert(ignore_permissions=True) - - contact.append("links", { - "link_doctype": "Customer", - "link_name": customer.name - }) - - contact.insert() - except Exception: - error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) - - return customer.name - -def create_membership(member, plan): - membership = frappe.new_doc("Membership") - membership.update({ - "member": member.name, - "membership_status": "New", - "membership_type": member.membership_type, - "currency": "INR", - "amount": plan.amount, - "from_date": getdate() - }) - - membership.insert(ignore_permissions=True) - - return membership - -@frappe.whitelist(allow_guest=True) -def create_membership_subscription(user_details): - """Summary - - Args: - user_details (TYPE): Description - - Returns: - Dictionary: Dictionary with subscription details - { - 'subscription_details': { - 'plan_id': 'plan_EXwyxDYDCj3X4v', - 'billing_frequency': 24, - 'customer_notify': 1 - }, - 'subscription_id': 'sub_EZycCvXFvqnC6p' - } - """ - # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} - user_details = frappe._dict(user_details) - member = get_member_if_exists(user_details.email, user_details.plan_id) - plan = frappe.get_doc("Membership Type", user_details.plan_id) - if not member: - member = create_member(user_details) - - membership = create_membership(member, plan) - - subscription = membership.setup_subscription() - - membership.subscription_id = subscription.get('subscription_id') - membership.save(ignore_permissions=True) - - return subscription - -def get_membership_based_on_subscription(subscription_id, custom_filters={}): - filters = {'subscription_id': subscription.id} - filters.update(custom_filters) - - memberships = frappe.get_all("Membership", filters=filters, order_by="creation") - if not memberships: - return None - - return frappe.get_doc("Membership", memberships[0]['name']) - - -@frappe.whitelist(allow_guest=True) +@frappe.whitelist() def trigger_razorpay_subscription(data): if isinstance(data, six.string_types): data = json.loads(data) @@ -200,49 +78,52 @@ def trigger_razorpay_subscription(data): payment = data.payload.get("payment", {}).get('entity', {}) payment = frappe._dict(payment) + try: + data_json = json.dumps(data, indent=4, sort_keys=True) + 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(log) + raise e + if data.event == "subscription.activated": - membership = get_membership_based_on_subscription(subscription.id, {"membership_status": "New"}) - else - prev_membership = get_membership_based_on_subscription(subscription.id, {"payment_id": payment.id, "paid": 1}) - if prev_membership: - print("payment already done") - return - prev_membership = get_membership_based_on_subscription(subscription.id) + member.customer_id = payment.customer_id + member.subscription_start = datetime.fromtimestamp(subscription.start_at) + member.subscription_end = datetime.fromtimestamp(subscription.end_at) + member.subscription_activated = 1 + member.save(ignore_permissions=True) + elif data.event == "subscription.charged": membership = frappe.new_doc("Membership") membership.update({ - "member": prev_membership.member, + "member": member.name, "membership_status": "Current", - "membership_type": prev_membership.membership_type, + "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 }) - - subscription_charged(subscription, payment, membership) - -def subscription_charged(subscription, payment, membership=None): - data = { - "subscription": subscription, - "payment": payment, - } - membership.paid = 1 - membership.payment_id = payment.id - membership.webhook_payload = json.dumps(data, indent=4, sort_keys=True) - membership.from_date = datetime.fromtimestamp(subscription.current_start) - membership.to_date = datetime.fromtimestamp(subscription.current_end) - membership.amount = payment.amount / 100 # Convert to rupees from paise - - if membership.is_new(): - membership.insert() - else: - membership.save() + membership.insert(ignore_permissions=True) return True - - - - +def notify_failure(log): + try: + content = """Dear System Manager, +Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below + +Error Log: {0} + +Regards, +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 From 31051679c823389e061b79f9b1ca0dba49bf6f51 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 18:43:11 +0530 Subject: [PATCH 11/15] feat: remove unused fields --- .../membership_settings.json | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json index d64e392287..ce843b3e8a 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json @@ -8,11 +8,7 @@ "enable_razorpay", "razorpay_settings_section", "billing_cycle", - "billing_frequency", - "column_break_2", - "auto_capture_payment", - "create_subscription", - "auto_create_invoice" + "billing_frequency" ], "fields": [ { @@ -21,35 +17,12 @@ "label": "Billing Cycle", "options": "Monthly\nYearly" }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "enable_razorpay", "fieldtype": "Check", "label": "Enable RazorPay For Memberships" }, - { - "default": "0", - "fieldname": "create_subscription", - "fieldtype": "Check", - "label": "Create Subscription" - }, - { - "default": "0", - "depends_on": "eval:doc.enable_razorpay", - "fieldname": "auto_capture_payment", - "fieldtype": "Check", - "label": "Auto Capture Payment" - }, - { - "default": "0", - "fieldname": "auto_create_invoice", - "fieldtype": "Check", - "label": "Auto Create Invoice" - }, { "depends_on": "eval:doc.enable_razorpay", "fieldname": "razorpay_settings_section", @@ -65,7 +38,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-03-30 16:02:00.060583", + "modified": "2020-04-07 18:42:51.496807", "modified_by": "Administrator", "module": "Non Profit", "name": "Membership Settings", From affa2fba6bb5e61e416cc161a32f0bb2334e9ecc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 13 Apr 2020 13:39:36 +0530 Subject: [PATCH 12/15] feat: updated non-profit.json --- .../desk_page/non_profit/non_profit.json | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/erpnext/non_profit/desk_page/non_profit/non_profit.json b/erpnext/non_profit/desk_page/non_profit/non_profit.json index dcbd9c37a6..ebe6194893 100644 --- a/erpnext/non_profit/desk_page/non_profit/non_profit.json +++ b/erpnext/non_profit/desk_page/non_profit/non_profit.json @@ -2,35 +2,33 @@ "cards": [ { "hidden": 0, - "icon": "icon-list", - "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]", - "title": "Loan Management" + "label": "Loan Management", + "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]", - "title": "Grant Application" + "label": "Grant Application", + "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Membership" + "label": "Membership", + "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]", - "title": "Volunteer" + "label": "Volunteer", + "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-star", - "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]", - "title": "Chapter" + "label": "Chapter", + "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]", - "title": "Donor" + "label": "Donor", + "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Domains", @@ -44,7 +42,7 @@ "idx": 0, "is_standard": 1, "label": "Non Profit", - "modified": "2020-03-29 13:06:18.070131", + "modified": "2020-04-13 13:41:52.373705", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", @@ -52,5 +50,31 @@ "pin_to_bottom": 0, "pin_to_top": 0, "restrict_to_domain": "Non Profit", - "shortcuts": [] + "shortcuts": [ + { + "label": "Member", + "link_to": "Member", + "type": "DocType" + }, + { + "label": "Membership Settings", + "link_to": "Membership Settings", + "type": "DocType" + }, + { + "label": "Membership", + "link_to": "Membership", + "type": "DocType" + }, + { + "label": "Chapter", + "link_to": "Chapter", + "type": "DocType" + }, + { + "label": "Chapter Member", + "link_to": "Chapter Member", + "type": "DocType" + } + ] } \ No newline at end of file From cb354685d5c8007cf7d483dc59fe58631d9b2a95 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 14 Apr 2020 12:00:02 +0530 Subject: [PATCH 13/15] fix: linting issues --- erpnext/non_profit/doctype/member/member.py | 16 ++++++++-------- .../non_profit/doctype/membership/membership.py | 15 --------------- .../membership_settings/membership_settings.js | 6 +----- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 1ae8699db1..2f84cf5538 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -102,16 +102,16 @@ def create_member_subscription_order(user_details): """Summary Args: - user_details (TYPE): Description + user_details (TYPE): Description Returns: - Dictionary: Dictionary with subscription details - { - 'subscription_details': { - 'plan_id': 'plan_EXwyxDYDCj3X4v', - 'billing_frequency': 24, - 'customer_notify': 1 - }, + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, 'subscription_id': 'sub_EZycCvXFvqnC6p' } """ diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 00ecd8fbdf..d11f6992f9 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -124,18 +124,3 @@ 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 - - - - - - - - - - - - - - - diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js index 092c854b43..c01a0b23d5 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js @@ -3,10 +3,6 @@ frappe.ui.form.on('Membership Settings', { refresh: function(frm) { - // if (frm.doc.enable_razorpay) { - // frm.add_custom_button(__("Fetch Plans from RazorPay"), () => { - // frm.trigger("fetch_razorpay_plans") - // }); - // } + } }); From 4fdb4aa1d00f42e7c7b26e6eb6bdb73a0837f5ef Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 14 Apr 2020 12:00:15 +0530 Subject: [PATCH 14/15] feat: use int for billing frequency --- .../doctype/membership_settings/membership_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json index ce843b3e8a..56b8eac4b1 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json @@ -32,7 +32,7 @@ { "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", "fieldname": "billing_frequency", - "fieldtype": "Data", + "fieldtype": "Int", "label": "Billing Frequency" } ], From 745b632ea167e9f98cfadd36a62dd26c3e8b719a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 20 Apr 2020 18:25:51 +0530 Subject: [PATCH 15/15] fix: linting fixes --- erpnext/non_profit/doctype/member/member.py | 8 ++++---- erpnext/non_profit/doctype/membership/membership.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 2f84cf5538..571f87af87 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -92,14 +92,15 @@ def create_customer(user_details): }) contact.insert() - except Exception: - error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + except Exception as e: + frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + pass return customer.name @frappe.whitelist(allow_guest=True) def create_member_subscription_order(user_details): - """Summary + """Create Member subscription and order for payment Args: user_details (TYPE): Description @@ -117,7 +118,6 @@ def create_member_subscription_order(user_details): """ # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} user_details = frappe._dict(user_details) - plan = frappe.get_doc("Membership Type", user_details.plan_id) member = get_or_create_member(user_details) if not member: member = create_member(user_details) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index d11f6992f9..a523a238e4 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -9,7 +9,7 @@ import six 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, cint +from frappe.utils import add_days, add_years, nowdate, getdate, add_months, get_link_to_form from frappe import _ import erpnext @@ -83,7 +83,7 @@ def trigger_razorpay_subscription(data): 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(log) + notify_failure(error_log) raise e if data.event == "subscription.activated":