Merge pull request #23865 from scmmishra/membership-enhancements-2
feat: enhancements to erpnext membership
This commit is contained in:
commit
8d60d301c8
@ -132,16 +132,10 @@ def allow_regional(fn):
|
||||
|
||||
return caller
|
||||
|
||||
def get_last_membership():
|
||||
def get_last_membership(member):
|
||||
'''Returns last membership if exists'''
|
||||
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]
|
||||
|
||||
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
|
||||
if last_membership:
|
||||
return last_membership[0]
|
||||
|
@ -341,7 +341,8 @@ scheduler_events = {
|
||||
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_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": [
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
|
@ -12,7 +12,6 @@
|
||||
"membership_expiry_date",
|
||||
"column_break_5",
|
||||
"membership_type",
|
||||
"email",
|
||||
"email_id",
|
||||
"image",
|
||||
"customer_section",
|
||||
@ -64,13 +63,6 @@
|
||||
"options": "Membership Type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach Image",
|
||||
@ -178,7 +170,7 @@
|
||||
],
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-09-16 23:44:13.596948",
|
||||
"modified": "2020-11-09 12:12:10.174647",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Member",
|
||||
|
@ -18,8 +18,6 @@ class Member(Document):
|
||||
|
||||
|
||||
def validate(self):
|
||||
if self.email:
|
||||
self.validate_email_type(self.email)
|
||||
if self.email_id:
|
||||
self.validate_email_type(self.email_id)
|
||||
|
||||
@ -57,14 +55,16 @@ class Member(Document):
|
||||
def make_customer_and_link(self):
|
||||
if self.customer:
|
||||
frappe.msgprint(_("A customer is already linked to this Member"))
|
||||
cust = create_customer(frappe._dict({
|
||||
|
||||
customer = create_customer(frappe._dict({
|
||||
'fullname': self.member_name,
|
||||
'email': self.email_id or self.email,
|
||||
'email': self.email_id,
|
||||
'phone': None
|
||||
}))
|
||||
|
||||
self.customer = cust
|
||||
self.customer = customer
|
||||
self.save()
|
||||
frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
|
||||
|
||||
|
||||
def get_or_create_member(user_details):
|
||||
|
@ -4,16 +4,25 @@
|
||||
frappe.ui.form.on('Membership', {
|
||||
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);
|
||||
if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
|
||||
})
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.__islocal)
|
||||
return;
|
||||
|
||||
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
|
||||
frm.call("generate_invoice", {
|
||||
save: true
|
||||
}).then(() => {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "generate_invoice",
|
||||
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) {
|
||||
frm.add_fetch('membership_type', 'amount', 'amount');
|
||||
frm.add_fetch("membership_type", "amount", "amount");
|
||||
}
|
||||
});
|
||||
|
@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"member",
|
||||
"member_name",
|
||||
"membership_type",
|
||||
"column_break_3",
|
||||
"membership_status",
|
||||
@ -46,6 +47,8 @@
|
||||
{
|
||||
"fieldname": "membership_status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Membership Status",
|
||||
"options": "New\nCurrent\nExpired\nPending\nCancelled"
|
||||
},
|
||||
@ -122,11 +125,18 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "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,
|
||||
"links": [],
|
||||
"modified": "2020-09-19 14:28:11.532696",
|
||||
"modified": "2021-01-21 16:31:20.032656",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Membership",
|
||||
@ -158,7 +168,9 @@
|
||||
}
|
||||
],
|
||||
"restrict_to_domain": "Non Profit",
|
||||
"search_fields": "member, member_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "member_name",
|
||||
"track_changes": 1
|
||||
}
|
@ -14,17 +14,26 @@ from erpnext.non_profit.doctype.member.member import create_member
|
||||
from frappe import _
|
||||
import erpnext
|
||||
|
||||
|
||||
class Membership(Document):
|
||||
def validate(self):
|
||||
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"))
|
||||
|
||||
self.validate_membership_period()
|
||||
|
||||
def create_member_from_website_user(self):
|
||||
member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
|
||||
|
||||
if not member_name:
|
||||
user = frappe.get_doc('User', frappe.session.user)
|
||||
user = frappe.get_doc("User", frappe.session.user)
|
||||
member = frappe.get_doc(dict(
|
||||
doctype='Member',
|
||||
email=frappe.session.user,
|
||||
doctype="Member",
|
||||
email_id=frappe.session.user,
|
||||
membership_type=self.membership_type,
|
||||
member_name=user.get_fullname()
|
||||
)).insert(ignore_permissions=True)
|
||||
@ -33,14 +42,15 @@ class Membership(Document):
|
||||
if self.get("__islocal"):
|
||||
self.member = member_name
|
||||
|
||||
def validate_membership_period(self):
|
||||
# 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 last_membership and not frappe.session.user == "Administrator":
|
||||
# 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()) :
|
||||
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)
|
||||
elif frappe.session.user == "Administrator":
|
||||
@ -54,11 +64,16 @@ class Membership(Document):
|
||||
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"):
|
||||
if status_changed_to not in ("Completed", "Authorized"):
|
||||
return
|
||||
self.load_from_db()
|
||||
self.db_set('paid', 1)
|
||||
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):
|
||||
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"))
|
||||
|
||||
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:
|
||||
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
||||
|
||||
if not settings.debit_account:
|
||||
frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
|
||||
|
||||
if not settings.company:
|
||||
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
|
||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
self.validate_membership_type_and_settings(plan, settings)
|
||||
|
||||
invoice = make_invoice(self, member, plan, settings)
|
||||
self.invoice = invoice.name
|
||||
|
||||
if with_payment_entry:
|
||||
self.make_payment_entry(settings, invoice)
|
||||
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
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):
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
if self.invoice and settings.send_invoice:
|
||||
@ -112,48 +157,56 @@ class Membership(Document):
|
||||
}
|
||||
|
||||
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:
|
||||
frappe.sendmail(**email_args)
|
||||
|
||||
def generate_and_send_invoice(self):
|
||||
invoice = self.generate_invoice(False)
|
||||
self.generate_invoice(save=False)
|
||||
self.send_acknowlement()
|
||||
|
||||
|
||||
def make_invoice(membership, member, plan, settings):
|
||||
invoice = frappe.get_doc({
|
||||
'doctype': 'Sales Invoice',
|
||||
'customer': member.customer,
|
||||
'debit_to': settings.debit_account,
|
||||
'currency': membership.currency,
|
||||
'is_pos': 0,
|
||||
'items': [
|
||||
"doctype": "Sales Invoice",
|
||||
"customer": member.customer,
|
||||
"debit_to": settings.debit_account,
|
||||
"currency": membership.currency,
|
||||
"company": settings.company,
|
||||
"is_pos": 0,
|
||||
"items": [
|
||||
{
|
||||
'item_code': plan.linked_item,
|
||||
'rate': membership.amount,
|
||||
'qty': 1
|
||||
"item_code": plan.linked_item,
|
||||
"rate": membership.amount,
|
||||
"qty": 1
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
invoice.set_missing_values()
|
||||
invoice.insert(ignore_permissions=True)
|
||||
invoice.submit()
|
||||
|
||||
frappe.msgprint(_("Sales Invoice created successfully"))
|
||||
|
||||
return invoice
|
||||
|
||||
|
||||
def get_member_based_on_subscription(subscription_id, email):
|
||||
members = frappe.get_all("Member", filters={
|
||||
'subscription_id': subscription_id,
|
||||
'email_id': email
|
||||
"subscription_id": subscription_id,
|
||||
"email_id": email
|
||||
}, order_by="creation desc")
|
||||
|
||||
try:
|
||||
return frappe.get_doc("Member", members[0]['name'])
|
||||
return frappe.get_doc("Member", members[0]["name"])
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
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")
|
||||
key = settings.get_webhook_secret()
|
||||
@ -162,6 +215,7 @@ def verify_signature(data):
|
||||
|
||||
controller.verify_signature(data, signature, key)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def trigger_razorpay_subscription(*args, **kwargs):
|
||||
data = frappe.request.get_data(as_text=True)
|
||||
@ -170,16 +224,16 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
except Exception as e:
|
||||
log = frappe.log_error(e, "Webhook Verification Error")
|
||||
notify_failure(log)
|
||||
return { 'status': 'Failed', 'reason': e}
|
||||
return { "status": "Failed", "reason": e}
|
||||
|
||||
if isinstance(data, six.string_types):
|
||||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
|
||||
subscription = data.payload.get("subscription", {}).get('entity', {})
|
||||
subscription = data.payload.get("subscription", {}).get("entity", {})
|
||||
subscription = frappe._dict(subscription)
|
||||
|
||||
payment = data.payload.get("payment", {}).get('entity', {})
|
||||
payment = data.payload.get("payment", {}).get("entity", {})
|
||||
payment = frappe._dict(payment)
|
||||
|
||||
try:
|
||||
@ -189,15 +243,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
member = get_member_based_on_subscription(subscription.id, payment.email)
|
||||
if not member:
|
||||
member = create_member(frappe._dict({
|
||||
'fullname': payment.email,
|
||||
'email': payment.email,
|
||||
'plan_id': get_plan_from_razorpay_id(subscription.plan_id)
|
||||
"fullname": payment.email,
|
||||
"email": payment.email,
|
||||
"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
|
||||
}))
|
||||
|
||||
member.subscription_id = subscription.id
|
||||
member.customer_id = payment.customer_id
|
||||
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)
|
||||
elif subscription.notes and type(subscription.notes) == str:
|
||||
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)
|
||||
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
||||
notify_failure(log)
|
||||
return { 'status': 'Failed', 'reason': e}
|
||||
return { "status": "Failed", "reason": e}
|
||||
|
||||
return { 'status': 'Success' }
|
||||
return { "status": "Success" }
|
||||
|
||||
|
||||
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
|
||||
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
return plan[0]['name']
|
||||
return plan[0]["name"]
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def set_expired_status():
|
||||
frappe.db.sql("""
|
||||
UPDATE
|
||||
`tabMembership` SET `status` = 'Expired'
|
||||
WHERE
|
||||
`status` not in ('Cancelled') AND `to_date` < %s
|
||||
""", (nowdate()))
|
15
erpnext/non_profit/doctype/membership/membership_list.js
Normal file
15
erpnext/non_profit/doctype/membership/membership_list.js
Normal 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'];
|
||||
}
|
||||
}
|
||||
};
|
@ -2,8 +2,110 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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):
|
||||
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
|
||||
|
@ -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 {
|
||||
filters: {
|
||||
"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 {
|
||||
filters: {
|
||||
"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 {
|
||||
filters: {
|
||||
'account_type': 'Receivable',
|
||||
'is_group': 0,
|
||||
'company': frm.doc.company
|
||||
"account_type": "Receivable",
|
||||
"is_group": 0,
|
||||
"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
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -11,9 +11,12 @@
|
||||
"billing_frequency",
|
||||
"webhook_secret",
|
||||
"column_break_6",
|
||||
"enable_auto_invoicing",
|
||||
"enable_invoicing",
|
||||
"create_for_web_forms",
|
||||
"make_payment_entry",
|
||||
"company",
|
||||
"debit_account",
|
||||
"payment_account",
|
||||
"column_break_9",
|
||||
"send_email",
|
||||
"send_invoice",
|
||||
@ -58,14 +61,7 @@
|
||||
"label": "Invoicing"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_auto_invoicing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Auto Invoicing",
|
||||
"mandatory_depends_on": "eval:doc.send_invoice"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_invoicing",
|
||||
"depends_on": "eval:doc.enable_invoicing",
|
||||
"fieldname": "debit_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Debit Account",
|
||||
@ -77,7 +73,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_invoicing",
|
||||
"depends_on": "eval:doc.enable_invoicing",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
@ -86,7 +82,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
|
||||
"depends_on": "eval:doc.enable_invoicing && doc.send_email",
|
||||
"fieldname": "send_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Invoice with Email"
|
||||
@ -119,11 +115,43 @@
|
||||
"label": "Email Template",
|
||||
"mandatory_depends_on": "eval:doc.send_email",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2020-08-05 17:26:37.287395",
|
||||
"modified": "2021-01-21 19:57:53.213286",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Membership Settings",
|
||||
|
@ -3,12 +3,20 @@
|
||||
|
||||
frappe.ui.form.on('Membership Type', {
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
frm.set_query('linked_item', () => {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -5,9 +5,14 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
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):
|
||||
return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
|
@ -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.v13_0.updates_for_multi_currency_payroll
|
||||
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")
|
||||
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.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||
erpnext.patches.v13_0.add_po_to_global_search
|
||||
|
23
erpnext/patches/v13_0/update_member_email_address.py
Normal file
23
erpnext/patches/v13_0/update_member_email_address.py
Normal 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")
|
Loading…
Reference in New Issue
Block a user