Merge pull request #23865 from scmmishra/membership-enhancements-2

feat: enhancements to erpnext membership
This commit is contained in:
Rucha Mahabal 2021-01-21 21:10:53 +05:30 committed by GitHub
commit 8d60d301c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 386 additions and 120 deletions

View File

@ -132,16 +132,10 @@ def allow_regional(fn):
return caller return caller
def get_last_membership(): def get_last_membership(member):
'''Returns last membership if exists''' '''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type', last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1) dict(member=member, paid=1), order_by='to_date desc', limit=1)
return last_membership and last_membership[0] if last_membership:
return last_membership[0]
def is_member():
'''Returns true if the user is still a member'''
last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate():
return True
return False

View File

@ -341,7 +341,8 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email" "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
"erpnext.non_profit.doctype.membership.membership.set_expired_status"
], ],
"daily_long": [ "daily_long": [
"erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.setup.doctype.email_digest.email_digest.send",

View File

@ -12,7 +12,6 @@
"membership_expiry_date", "membership_expiry_date",
"column_break_5", "column_break_5",
"membership_type", "membership_type",
"email",
"email_id", "email_id",
"image", "image",
"customer_section", "customer_section",
@ -64,13 +63,6 @@
"options": "Membership Type", "options": "Membership Type",
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "email",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User"
},
{ {
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach Image", "fieldtype": "Attach Image",
@ -178,7 +170,7 @@
], ],
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2020-09-16 23:44:13.596948", "modified": "2020-11-09 12:12:10.174647",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Non Profit", "module": "Non Profit",
"name": "Member", "name": "Member",

View File

@ -18,8 +18,6 @@ class Member(Document):
def validate(self): def validate(self):
if self.email:
self.validate_email_type(self.email)
if self.email_id: if self.email_id:
self.validate_email_type(self.email_id) self.validate_email_type(self.email_id)
@ -57,14 +55,16 @@ class Member(Document):
def make_customer_and_link(self): def make_customer_and_link(self):
if self.customer: if self.customer:
frappe.msgprint(_("A customer is already linked to this Member")) frappe.msgprint(_("A customer is already linked to this Member"))
cust = create_customer(frappe._dict({
customer = create_customer(frappe._dict({
'fullname': self.member_name, 'fullname': self.member_name,
'email': self.email_id or self.email, 'email': self.email_id,
'phone': None 'phone': None
})) }))
self.customer = cust self.customer = customer
self.save() self.save()
frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
def get_or_create_member(user_details): def get_or_create_member(user_details):

View File

@ -4,16 +4,25 @@
frappe.ui.form.on('Membership', { frappe.ui.form.on('Membership', {
setup: function(frm) { setup: function(frm) {
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
if (val) frm.set_df_property('razorpay_details_section', 'hidden', false); if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
}) })
}, },
refresh: function(frm) { refresh: function(frm) {
if (frm.doc.__islocal)
return;
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => { !frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
frm.call("generate_invoice", { frm.call({
save: true doc: frm.doc,
}).then(() => { method: "generate_invoice",
frm.reload_doc(); args: {save: true},
freeze: true,
freeze_message: __("Creating Membership Invoice"),
callback: function(r) {
if (r.invoice)
frm.reload_doc();
}
}); });
}); });
@ -27,6 +36,6 @@ frappe.ui.form.on('Membership', {
}, },
onload: function(frm) { onload: function(frm) {
frm.add_fetch('membership_type', 'amount', 'amount'); frm.add_fetch("membership_type", "amount", "amount");
} }
}); });

View File

@ -7,6 +7,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"member", "member",
"member_name",
"membership_type", "membership_type",
"column_break_3", "column_break_3",
"membership_status", "membership_status",
@ -46,6 +47,8 @@
{ {
"fieldname": "membership_status", "fieldname": "membership_status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Membership Status", "label": "Membership Status",
"options": "New\nCurrent\nExpired\nPending\nCancelled" "options": "New\nCurrent\nExpired\nPending\nCancelled"
}, },
@ -122,11 +125,18 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Invoice", "label": "Invoice",
"options": "Sales Invoice" "options": "Sales Invoice"
},
{
"fetch_from": "member.member_name",
"fieldname": "member_name",
"fieldtype": "Data",
"label": "Member Name",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-09-19 14:28:11.532696", "modified": "2021-01-21 16:31:20.032656",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Non Profit", "module": "Non Profit",
"name": "Membership", "name": "Membership",
@ -158,7 +168,9 @@
} }
], ],
"restrict_to_domain": "Non Profit", "restrict_to_domain": "Non Profit",
"search_fields": "member, member_name",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "member_name",
"track_changes": 1 "track_changes": 1
} }

View File

@ -14,33 +14,43 @@ from erpnext.non_profit.doctype.member.member import create_member
from frappe import _ from frappe import _
import erpnext import erpnext
class Membership(Document): class Membership(Document):
def validate(self): def validate(self):
if not self.member or not frappe.db.exists("Member", self.member): if not self.member or not frappe.db.exists("Member", self.member):
member_name = frappe.get_value('Member', dict(email=frappe.session.user)) # for web forms
user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
if user_type == "Website User":
self.create_member_from_website_user()
else:
frappe.throw(_("Please select a Member"))
if not member_name: self.validate_membership_period()
user = frappe.get_doc('User', frappe.session.user)
member = frappe.get_doc(dict(
doctype='Member',
email=frappe.session.user,
membership_type=self.membership_type,
member_name=user.get_fullname()
)).insert(ignore_permissions=True)
member_name = member.name
if self.get("__islocal"): def create_member_from_website_user(self):
self.member = member_name member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
if not member_name:
user = frappe.get_doc("User", frappe.session.user)
member = frappe.get_doc(dict(
doctype="Member",
email_id=frappe.session.user,
membership_type=self.membership_type,
member_name=user.get_fullname()
)).insert(ignore_permissions=True)
member_name = member.name
if self.get("__islocal"):
self.member = member_name
def validate_membership_period(self):
# get last membership (if active) # get last membership (if active)
last_membership = erpnext.get_last_membership() last_membership = erpnext.get_last_membership(self.member)
# if person applied for offline membership # if person applied for offline membership
if last_membership and not frappe.session.user == "Administrator": if last_membership and not frappe.session.user == "Administrator":
# if last membership does not expire in 30 days, then do not allow to renew # if last membership does not expire in 30 days, then do not allow to renew
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
frappe.throw(_('You can only renew if your membership expires within 30 days')) frappe.throw(_("You can only renew if your membership expires within 30 days"))
self.from_date = add_days(last_membership.to_date, 1) self.from_date = add_days(last_membership.to_date, 1)
elif frappe.session.user == "Administrator": elif frappe.session.user == "Administrator":
@ -54,11 +64,16 @@ class Membership(Document):
self.to_date = add_months(self.from_date, 1) self.to_date = add_months(self.from_date, 1)
def on_payment_authorized(self, status_changed_to=None): def on_payment_authorized(self, status_changed_to=None):
if status_changed_to in ("Completed", "Authorized"): if status_changed_to not in ("Completed", "Authorized"):
self.load_from_db() return
self.db_set('paid', 1) self.load_from_db()
self.db_set("paid", 1)
settings = frappe.get_doc("Membership Settings")
if settings.enable_invoicing and settings.create_for_web_forms:
self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True)
def generate_invoice(self, save=True):
def generate_invoice(self, save=True, with_payment_entry=False):
if not (self.paid or self.currency or self.amount): if not (self.paid or self.currency or self.amount):
frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details")) frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
@ -66,34 +81,64 @@ class Membership(Document):
frappe.throw(_("An invoice is already linked to this document")) frappe.throw(_("An invoice is already linked to this document"))
member = frappe.get_doc("Member", self.member) member = frappe.get_doc("Member", self.member)
plan = frappe.get_doc("Membership Type", self.membership_type)
settings = frappe.get_doc("Membership Settings")
if not member.customer: if not member.customer:
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member))) frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
if not settings.debit_account: plan = frappe.get_doc("Membership Type", self.membership_type)
frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings")) settings = frappe.get_doc("Membership Settings")
self.validate_membership_type_and_settings(plan, settings)
if not settings.company:
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
invoice = make_invoice(self, member, plan, settings) invoice = make_invoice(self, member, plan, settings)
self.invoice = invoice.name self.invoice = invoice.name
if with_payment_entry:
self.make_payment_entry(settings, invoice)
if save: if save:
self.save() self.save()
return invoice return invoice
def validate_membership_type_and_settings(self, plan, settings):
settings_link = get_link_to_form("Membership Type", self.membership_type)
if not settings.debit_account:
frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
if not settings.company:
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in {0}").format(settings_link))
if not plan.linked_item:
frappe.throw(_("Please set a Linked Item for the Membership Type {0}").format(
get_link_to_form("Membership Type", self.membership_type)))
def make_payment_entry(self, settings, invoice):
if not settings.payment_account:
frappe.throw(_("You need to set <b>Payment Account</b> in {0}").format(
get_link_to_form("Membership Type", self.membership_type)))
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
frappe.flags.ignore_account_permission = True
pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
frappe.flags.ignore_account_permission=False
pe.paid_to = settings.payment_account
pe.reference_no = self.name
pe.reference_date = getdate()
pe.save(ignore_permissions=True)
pe.submit()
def send_acknowlement(self): def send_acknowlement(self):
settings = frappe.get_doc("Membership Settings") settings = frappe.get_doc("Membership Settings")
if not settings.send_email: if not settings.send_email:
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in Membership Settings")) frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
get_link_to_form("Membership Settings", "Membership Settings")))
member = frappe.get_doc("Member", self.member) member = frappe.get_doc("Member", self.member)
if not member.email_id:
frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
plan = frappe.get_doc("Membership Type", self.membership_type) plan = frappe.get_doc("Membership Type", self.membership_type)
email = member.email_id if member.email_id else member.email email = member.email_id
attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)] attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
if self.invoice and settings.send_invoice: if self.invoice and settings.send_invoice:
@ -112,48 +157,56 @@ class Membership(Document):
} }
if not frappe.flags.in_test: if not frappe.flags.in_test:
frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args) frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
else: else:
frappe.sendmail(**email_args) frappe.sendmail(**email_args)
def generate_and_send_invoice(self): def generate_and_send_invoice(self):
invoice = self.generate_invoice(False) self.generate_invoice(save=False)
self.send_acknowlement() self.send_acknowlement()
def make_invoice(membership, member, plan, settings): def make_invoice(membership, member, plan, settings):
invoice = frappe.get_doc({ invoice = frappe.get_doc({
'doctype': 'Sales Invoice', "doctype": "Sales Invoice",
'customer': member.customer, "customer": member.customer,
'debit_to': settings.debit_account, "debit_to": settings.debit_account,
'currency': membership.currency, "currency": membership.currency,
'is_pos': 0, "company": settings.company,
'items': [ "is_pos": 0,
"items": [
{ {
'item_code': plan.linked_item, "item_code": plan.linked_item,
'rate': membership.amount, "rate": membership.amount,
'qty': 1 "qty": 1
} }
] ]
}) })
invoice.set_missing_values()
invoice.insert(ignore_permissions=True) invoice.insert(ignore_permissions=True)
invoice.submit() invoice.submit()
frappe.msgprint(_("Sales Invoice created successfully"))
return invoice return invoice
def get_member_based_on_subscription(subscription_id, email): def get_member_based_on_subscription(subscription_id, email):
members = frappe.get_all("Member", filters={ members = frappe.get_all("Member", filters={
'subscription_id': subscription_id, "subscription_id": subscription_id,
'email_id': email "email_id": email
}, order_by="creation desc") }, order_by="creation desc")
try: try:
return frappe.get_doc("Member", members[0]['name']) return frappe.get_doc("Member", members[0]["name"])
except: except:
return None return None
def verify_signature(data): def verify_signature(data):
signature = frappe.request.headers.get('X-Razorpay-Signature') if frappe.flags.in_test:
return True
signature = frappe.request.headers.get("X-Razorpay-Signature")
settings = frappe.get_doc("Membership Settings") settings = frappe.get_doc("Membership Settings")
key = settings.get_webhook_secret() key = settings.get_webhook_secret()
@ -162,6 +215,7 @@ def verify_signature(data):
controller.verify_signature(data, signature, key) controller.verify_signature(data, signature, key)
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs): def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True) data = frappe.request.get_data(as_text=True)
@ -170,16 +224,16 @@ def trigger_razorpay_subscription(*args, **kwargs):
except Exception as e: except Exception as e:
log = frappe.log_error(e, "Webhook Verification Error") log = frappe.log_error(e, "Webhook Verification Error")
notify_failure(log) notify_failure(log)
return { 'status': 'Failed', 'reason': e} return { "status": "Failed", "reason": e}
if isinstance(data, six.string_types): if isinstance(data, six.string_types):
data = json.loads(data) data = json.loads(data)
data = frappe._dict(data) data = frappe._dict(data)
subscription = data.payload.get("subscription", {}).get('entity', {}) subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription) subscription = frappe._dict(subscription)
payment = data.payload.get("payment", {}).get('entity', {}) payment = data.payload.get("payment", {}).get("entity", {})
payment = frappe._dict(payment) payment = frappe._dict(payment)
try: try:
@ -189,15 +243,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
member = get_member_based_on_subscription(subscription.id, payment.email) member = get_member_based_on_subscription(subscription.id, payment.email)
if not member: if not member:
member = create_member(frappe._dict({ member = create_member(frappe._dict({
'fullname': payment.email, "fullname": payment.email,
'email': payment.email, "email": payment.email,
'plan_id': get_plan_from_razorpay_id(subscription.plan_id) "plan_id": get_plan_from_razorpay_id(subscription.plan_id)
})) }))
member.subscription_id = subscription.id member.subscription_id = subscription.id
member.customer_id = payment.customer_id member.customer_id = payment.customer_id
if subscription.notes and type(subscription.notes) == dict: if subscription.notes and type(subscription.notes) == dict:
notes = '\n'.join("{}: {}".format(k, v) for k, v in subscription.notes.items()) notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items())
member.add_comment("Comment", notes) member.add_comment("Comment", notes)
elif subscription.notes and type(subscription.notes) == str: elif subscription.notes and type(subscription.notes) == str:
member.add_comment("Comment", subscription.notes) member.add_comment("Comment", subscription.notes)
@ -227,28 +281,39 @@ def trigger_razorpay_subscription(*args, **kwargs):
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id) message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log) notify_failure(log)
return { 'status': 'Failed', 'reason': e} return { "status": "Failed", "reason": e}
return { 'status': 'Success' } return { "status": "Success" }
def notify_failure(log): def notify_failure(log):
try: try:
content = """Dear System Manager, content = """
Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below 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))
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) sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
except: except:
pass pass
def get_plan_from_razorpay_id(plan_id): def get_plan_from_razorpay_id(plan_id):
plan = frappe.get_all("Membership Type", filters={'razorpay_plan_id': plan_id}, order_by="creation desc") plan = frappe.get_all("Membership Type", filters={"razorpay_plan_id": plan_id}, order_by="creation desc")
try: try:
return plan[0]['name'] return plan[0]["name"]
except: except:
return None return None
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabMembership` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled') AND `to_date` < %s
""", (nowdate()))

View File

@ -0,0 +1,15 @@
frappe.listview_settings['Membership'] = {
get_indicator: function(doc) {
if (doc.membership_status == 'New') {
return [__('New'), 'blue', 'membership_status,=,New'];
} else if (doc.membership_status === 'Current') {
return [__('Current'), 'green', 'membership_status,=,Current'];
} else if (doc.membership_status === 'Pending') {
return [__('Pending'), 'yellow', 'membership_status,=,Pending'];
} else if (doc.membership_status === 'Expired') {
return [__('Expired'), 'grey', 'membership_status,=,Expired'];
} else {
return [__('Cancelled'), 'red', 'membership_status,=,Cancelled'];
}
}
};

View File

@ -2,8 +2,110 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe
import erpnext
from erpnext.non_profit.doctype.member.member import create_member
from frappe.utils import nowdate, add_months
class TestMembership(unittest.TestCase): class TestMembership(unittest.TestCase):
pass def setUp(self):
# Get default company
company = frappe.get_doc("Company", erpnext.get_default_company())
# update membership settings
settings = frappe.get_doc("Membership Settings")
# Enable razorpay
settings.enable_razorpay = 1
settings.billing_cycle = "Monthly"
settings.billing_frequency = 24
# Enable invoicing
settings.enable_invoicing = 1
settings.make_payment_entry = 1
settings.company = company.name
settings.payment_account = company.default_cash_account
settings.debit_account = company.default_receivable_account
settings.save()
# make test plan
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
plan = frappe.new_doc("Membership Type")
plan.membership_type = "_rzpy_test_milythm"
plan.amount = 100
plan.razorpay_plan_id = "_rzpy_test_milythm"
plan.linked_item = create_item("_Test Item for Non Profit Membership").name
plan.insert()
else:
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
# make test member
self.member_doc = create_member(frappe._dict({
'fullname': "_Test_Member",
'email': "_test_member_erpnext@example.com",
'plan_id': plan.name
}))
self.member_doc.make_customer_and_link()
self.member = self.member_doc.name
def test_auto_generate_invoice_and_payment_entry(self):
entry = make_membership(self.member)
# Naive test to see if at all invoice was generated and attached to member
# In any case if details were missing, the invoicing would throw an error
invoice = entry.generate_invoice(save=True)
self.assertEqual(invoice.name, entry.invoice)
def test_renew_within_30_days(self):
# create a membership for two months
# Should work fine
make_membership(self.member, { "from_date": nowdate() })
make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
from frappe.utils.user import add_role
add_role("test@example.com", "Non Profit Manager")
frappe.set_user("test@example.com")
# create next membership with expiry not within 30 days
self.assertRaises(frappe.ValidationError, make_membership, self.member, {
"from_date": add_months(nowdate(), 2),
})
frappe.set_user("Administrator")
# create the same membership but as administrator
make_membership(self.member, {
"from_date": add_months(nowdate(), 2),
"to_date": add_months(nowdate(), 3),
})
def set_config(key, value):
frappe.db.set_value("Membership Settings", None, key, value)
def make_membership(member, payload={}):
data = {
"doctype": "Membership",
"member": member,
"membership_status": "Current",
"membership_type": "_rzpy_test_milythm",
"currency": "INR",
"paid": 1,
"from_date": nowdate(),
"amount": 100
}
data.update(payload)
membership = frappe.get_doc(data)
membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
return membership
def create_item(item_code):
if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item")
item.item_code = item_code
item.item_name = item_code
item.stock_uom = "Nos"
item.description = item_code
item.item_group = "All Item Groups"
item.is_stock_item = 0
item.save()
else:
item = frappe.get_doc("Item", item_code)
return item

View File

@ -11,7 +11,7 @@ frappe.ui.form.on("Membership Settings", {
}); });
} }
frm.set_query('inv_print_format', function(doc) { frm.set_query("inv_print_format", function() {
return { return {
filters: { filters: {
"doc_type": "Sales Invoice" "doc_type": "Sales Invoice"
@ -19,7 +19,7 @@ frappe.ui.form.on("Membership Settings", {
}; };
}); });
frm.set_query('membership_print_format', function(doc) { frm.set_query("membership_print_format", function() {
return { return {
filters: { filters: {
"doc_type": "Membership" "doc_type": "Membership"
@ -27,12 +27,23 @@ frappe.ui.form.on("Membership Settings", {
}; };
}); });
frm.set_query('debit_account', function(doc) { frm.set_query("debit_account", function() {
return { return {
filters: { filters: {
'account_type': 'Receivable', "account_type": "Receivable",
'is_group': 0, "is_group": 0,
'company': frm.doc.company "company": frm.doc.company
}
};
});
frm.set_query("payment_account", function () {
var account_types = ["Bank", "Cash"];
return {
filters: {
"account_type": ["in", account_types],
"is_group": 0,
"company": frm.doc.company
} }
}; };
}); });

View File

@ -11,9 +11,12 @@
"billing_frequency", "billing_frequency",
"webhook_secret", "webhook_secret",
"column_break_6", "column_break_6",
"enable_auto_invoicing", "enable_invoicing",
"create_for_web_forms",
"make_payment_entry",
"company", "company",
"debit_account", "debit_account",
"payment_account",
"column_break_9", "column_break_9",
"send_email", "send_email",
"send_invoice", "send_invoice",
@ -58,14 +61,7 @@
"label": "Invoicing" "label": "Invoicing"
}, },
{ {
"default": "0", "depends_on": "eval:doc.enable_invoicing",
"fieldname": "enable_auto_invoicing",
"fieldtype": "Check",
"label": "Enable Auto Invoicing",
"mandatory_depends_on": "eval:doc.send_invoice"
},
{
"depends_on": "eval:doc.enable_auto_invoicing",
"fieldname": "debit_account", "fieldname": "debit_account",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Debit Account", "label": "Debit Account",
@ -77,7 +73,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:doc.enable_auto_invoicing", "depends_on": "eval:doc.enable_invoicing",
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
@ -86,7 +82,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.enable_auto_invoicing && doc.send_email", "depends_on": "eval:doc.enable_invoicing && doc.send_email",
"fieldname": "send_invoice", "fieldname": "send_invoice",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Send Invoice with Email" "label": "Send Invoice with Email"
@ -119,11 +115,43 @@
"label": "Email Template", "label": "Email Template",
"mandatory_depends_on": "eval:doc.send_email", "mandatory_depends_on": "eval:doc.send_email",
"options": "Email Template" "options": "Email Template"
},
{
"default": "0",
"fieldname": "enable_invoicing",
"fieldtype": "Check",
"label": "Enable Invoicing",
"mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
},
{
"default": "0",
"depends_on": "eval:doc.enable_invoicing",
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
"fieldname": "make_payment_entry",
"fieldtype": "Check",
"label": "Make Payment Entry"
},
{
"depends_on": "eval:doc.make_payment_entry",
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Payment To",
"mandatory_depends_on": "eval:doc.make_payment_entry",
"options": "Account"
},
{
"default": "0",
"depends_on": "eval:doc.enable_invoicing",
"description": "Automatically create an invoice when payment is authorized from a web form entry",
"fieldname": "create_for_web_forms",
"fieldtype": "Check",
"label": "Auto Create Invoice for Web Forms"
} }
], ],
"index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-08-05 17:26:37.287395", "modified": "2021-01-21 19:57:53.213286",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Non Profit", "module": "Non Profit",
"name": "Membership Settings", "name": "Membership Settings",

View File

@ -2,13 +2,21 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Membership Type', { frappe.ui.form.on('Membership Type', {
refresh: function(frm) { refresh: function (frm) {
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { 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);
}); });
frappe.db.get_single_value("Membership Settings", "enable_auto_invoicing").then(val => { frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => {
if (val) frm.set_df_property('linked_item', 'hidden', false); if (val) frm.set_df_property('linked_item', 'hidden', false);
}); });
frm.set_query('linked_item', () => {
return {
filters: {
is_stock_item: 0
}
};
});
} }
}); });

View File

@ -5,9 +5,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from frappe.model.document import Document from frappe.model.document import Document
import frappe import frappe
from frappe import _
class MembershipType(Document): class MembershipType(Document):
pass def validate(self):
if self.linked_item:
is_stock_item = frappe.db.get_value("Item", self.linked_item, "is_stock_item")
if is_stock_item:
frappe.throw(_("The Linked Item should be a service item"))
def get_membership_type(razorpay_id): def get_membership_type(razorpay_id):
return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id}) return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})

View File

@ -736,8 +736,9 @@ erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.update_reason_for_resignation_in_employee erpnext.patches.v13_0.update_reason_for_resignation_in_employee
erpnext.patches.v13_0.update_custom_fields_for_shopify
execute:frappe.delete_doc("Report", "Quoted Item Comparison") execute:frappe.delete_doc("Report", "Quoted Item Comparison")
erpnext.patches.v13_0.update_member_email_address
erpnext.patches.v13_0.update_custom_fields_for_shopify
erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.add_po_to_global_search

View File

@ -0,0 +1,23 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
"""add value to email_id column from email"""
if frappe.db.has_column("Member", "email"):
# Get all members
for member in frappe.db.get_all("Member", pluck="name"):
# Check if email_id already exists
if not frappe.db.get_value("Member", member, "email_id"):
# fetch email id from the user linked field email
email = frappe.db.get_value("Member", member, "email")
# Set the value for it
frappe.db.set_value("Member", member, "email_id", email)
if frappe.db.exists("DocType", "Membership Settings"):
rename_field("Membership Settings", "enable_auto_invoicing", "enable_invoicing")