import json import jingrow from jingrow.core.utils import find from jingrow.desk.form.load import get_docinfo from jingrow.utils import flt from jingrow.utils.data import add_days, add_months, get_first_day, get_last_day, today from jcloude.guards import role_guard from jcloude.utils import get_current_team @jingrow.whitelist() @role_guard.api("partner") def approve_partner_request(key): partner_request_pg = jingrow.get_pg("Partner Approval Request", {"key": key}) if partner_request_pg and partner_request_pg.status == "Pending": if partner_request_pg.approved_by_partner: partner_request_pg.approved_by_jingrow = True partner_request_pg.status = "Approved" partner_request_pg.save(ignore_permissions=True) partner_request_pg.reload() partner_email = jingrow.db.get_value("Team", partner_request_pg.partner, "partner_email") jingrow.db.set_value( "Team", partner_request_pg.requested_by, { "partner_email": partner_email, "partnership_date": jingrow.utils.getdate(partner_request_pg.creation), }, ) jingrow.db.commit() jingrow.response.type = "redirect" jingrow.response.location = f"/app/partner-approval-request/{partner_request_pg.name}" @jingrow.whitelist() @role_guard.api("partner") def get_partner_request_status(team): return jingrow.db.get_value("Partner Approval Request", {"requested_by": team}, "status") @jingrow.whitelist() @role_guard.api("partner") def update_partnership_date(team, partnership_date): if team: team_pg = jingrow.get_pg("Team", team) team_pg.partnership_date = partnership_date team_pg.save() @jingrow.whitelist() @role_guard.api("partner") def update_website_info(website_info): from jcloude.utils.billing import get_jingrow_io_connection, is_jingrow_auth_disabled if is_jingrow_auth_disabled(): return client = get_jingrow_io_connection() try: website_info["pagetype"] = "Partner" client.update(website_info) except Exception: jingrow.log_error("Error updating website info") @jingrow.whitelist() @role_guard.api("partner") def get_partner_details(partner_email): from jcloude.utils.billing import get_jingrow_io_connection, is_jingrow_auth_disabled if is_jingrow_auth_disabled(): return None client = get_jingrow_io_connection() data = client.get_pg( "Partner", filters={"email": partner_email, "enabled": 1}, fields=[ "name", "email", "partner_type", "company_name", "partner_name", "custom_number_of_certified_members", "end_date", "partner_website", "introduction", "customers", "custom_process_maturity_level", "phone_number", "address", "custom_foundation_date", "custom_team_size", "custom_successful_projects_count", "custom_journey_blog_link", ], ) if data: return data[0] jingrow.throw("Partner Details not found") return None @jingrow.whitelist() @role_guard.api("partner") def send_link_certificate_request(user_email, certificate_type): if not jingrow.db.exists( "Partner Certificate", {"partner_member_email": user_email, "course": certificate_type} ): jingrow.throw(f"No certificate found for the {user_email} with given course") team = get_current_team(get_pg=True) jingrow.get_pg( { "pagetype": "Certificate Link Request", "partner_team": team.name, "user_email": user_email, "course": certificate_type, } ).insert() @jingrow.whitelist() @role_guard.api("partner") def approve_certificate_link_request(key): cert_req_pg = jingrow.get_pg("Certificate Link Request", {"key": key}) cert_req_pg.status = "Approved" cert_req_pg.save(ignore_permissions=True) jingrow.db.commit() jingrow.response.type = "redirect" jingrow.response.location = "/dashboard/partners/certificates" @jingrow.whitelist() @role_guard.api("partner") def get_resource_url(): return jingrow.db.get_value("Jcloude Settings", "Jcloude Settings", "drive_resource_link") @jingrow.whitelist() @role_guard.api("partner") def get_partner_name(partner_email): return jingrow.db.get_value( "Team", {"partner_email": partner_email, "enabled": 1, "jerp_partner": 1}, "billing_name", ) @jingrow.whitelist() @role_guard.api("partner") def transfer_credits(amount, customer, partner): # partner discount map DISCOUNT_MAP = {"Entry": 0, "Emerging": 0.10, "Bronze": 0.10, "Silver": 0.15, "Gold": 0.20} amt = jingrow.utils.flt(amount) partner_pg = jingrow.get_pg("Team", partner) credits_available = partner_pg.get_balance() partner_level = partner_pg.get_partner_level() discount_percent = DISCOUNT_MAP.get(partner_level[0]) if partner_level else 0 if credits_available < amt: jingrow.throw(f"Insufficient Credits to transfer. Credits Available: {credits_available}") customer_pg = jingrow.get_pg("Team", customer) credits_to_transfer = amt amt -= amt * discount_percent if customer_pg.currency != partner_pg.currency: if partner_pg.currency == "USD": credits_to_transfer = credits_to_transfer * 83 else: credits_to_transfer = credits_to_transfer / 83 try: customer_pg.allocate_credit_amount( credits_to_transfer, "Transferred Credits", f"Transferred Credits from {partner_pg.name}", ) partner_pg.allocate_credit_amount( amt * -1, "Transferred Credits", f"Transferred Credits to {customer_pg.name}" ) jingrow.db.commit() return amt except Exception: jingrow.throw("Error in transferring credits") jingrow.db.rollback() @jingrow.whitelist() @role_guard.api("partner") def get_partner_contribution_list(partner_email): partner_currency = jingrow.db.get_value( "Team", {"jerp_partner": 1, "partner_email": partner_email}, "currency" ) month_start = jingrow.utils.get_first_day(today()) month_end = jingrow.utils.get_last_day(today()) invoices = jingrow.get_all( "Invoice", { "partner_email": partner_email, "due_date": ("between", [month_start, month_end]), "type": "Subscription", }, ["due_date", "customer_name", "total_before_discount", "currency", "status"], ) for d in invoices: if partner_currency != d.currency: if partner_currency == "USD": d.update({"partner_total": flt(d.total_before_discount / 83, 2)}) else: d.update({"partner_total": flt(d.total_before_discount * 83)}) else: d.update({"partner_total": d.total_before_discount}) return invoices @jingrow.whitelist() @role_guard.api("partner") def get_partner_mrr(partner_email): partner_currency = jingrow.db.get_value( "Team", {"jerp_partner": 1, "partner_email": partner_email}, "currency" ) query = jingrow.db.sql( f""" SELECT i.due_date, SUM( CASE WHEN '{partner_currency}' = i.currency THEN i.total_before_discount WHEN '{partner_currency}' = 'INR' AND i.currency = 'USD' THEN i.total_before_discount * 83 WHEN '{partner_currency}' = 'USD' AND i.currency = 'INR' THEN i.total_before_discount / 83 ELSE i.total_before_discount END ) as total_amount FROM tabInvoice as i WHERE i.partner_email = '{partner_email}' AND i.type = 'Subscription' AND i.status = 'Paid' GROUP BY i.due_date ORDER BY i.due_date DESC LIMIT 12 """, as_dict=True, ) return [d for d in query] @jingrow.whitelist() @role_guard.api("partner") def get_dashboard_stats(): team = get_current_team(get_pg=True) data = jingrow.db.sql( f""" SELECT site.plan as plan, COUNT(site.name) as count FROM tabSite as site JOIN tabTeam as team ON site.team = team.name WHERE team.name = '{team.name}' AND site.status = 'Active' GROUP BY site.plan """, as_dict=True, ) return [d for d in data] @jingrow.whitelist() @role_guard.api("partner") def get_lead_stats(): team = get_current_team(get_pg=True) data = jingrow.db.sql( f""" SELECT COUNT(name) as total, SUM(CASE WHEN status in ('Open', 'In Process') THEN 1 ELSE 0 END) as open, SUM(CASE WHEN status = 'Won' THEN 1 ELSE 0 END) as won, SUM(CASE WHEN status = 'Lost' THEN 1 ELSE 0 END) as lost FROM `tabPartner Lead` WHERE partner_team = '{team.name}' """, as_dict=True, ) return data[0] if data else {} def get_user_by_name(email): return jingrow.get_cached_value("User", email, "full_name") @jingrow.whitelist() @role_guard.api("partner") def get_lead_activities(name): get_docinfo("", "Partner Lead", name) res = jingrow.response["docinfo"] pg_meta = jingrow.get_meta("Partner Lead") fields = {field.fieldname: {"label": field.label, "options": field.options} for field in pg_meta.fields} pg = jingrow.db.get_values("Partner Lead", name, ["creation", "owner"])[0] activities = [] activities.append( {"activity_type": "creation", "creation": pg[0], "owner": pg[1], "data": "created this lead"} ) res.versions.reverse() for version in res.versions: data = json.loads(version.data) if not data.get("changed"): continue if change := data.get("changed")[0]: field = fields.get(change[0], None) if not field or (not change[1] and not change[2]): continue field_label = field.get("label") or change[0] field_option = field.get("options") or None activity_type = "changed" data = { "field": change[0], "field_label": field_label, "old_value": change[1], "new_value": change[2], } if not change[1] and change[2]: activity_type = "added" data = { "field": change[0], "field_label": field_label, "value": change[2], } elif change[1] and not change[2]: activity_type = "removed" data = { "field": change[0], "field_label": field_label, "value": change[1], } activity = { "activity_type": activity_type, "creation": version.creation, "owner": get_user_by_name(version.owner), "data": data, "options": field_option, } activities.append(activity) for comment in res.comments: activity = { "name": comment.name, "activity_type": "comment", "creation": comment.creation, "owner": get_user_by_name(comment.owner), "content": comment.content, # "attachments": get_attachments("Comment", comment.name), } activities.append(activity) activities.sort(key=lambda x: x.get("creation"), reverse=False) activities = handle_multiple_versions(activities) return activities # noqa: RET504 def handle_multiple_versions(versions): # noqa: C901 # print(versions) activities = [] grouped_versions = [] old_version = None for version in versions: is_version = version["activity_type"] in ["changed", "added", "removed"] if not is_version: activities.append(version) if not old_version: old_version = version if is_version: grouped_versions.append(version) continue if is_version and old_version.get("owner") and version["owner"] == old_version["owner"]: grouped_versions.append(version) else: if grouped_versions: activities.append(parse_grouped_versions(grouped_versions)) grouped_versions = [] if is_version: grouped_versions.append(version) old_version = version if version == versions[-1] and grouped_versions: activities.append(parse_grouped_versions(grouped_versions)) return activities def parse_grouped_versions(versions): version = versions[0] if len(versions) == 1: return version other_versions = versions[1:] version["other_versions"] = other_versions return version @jingrow.whitelist() @role_guard.api("partner") def get_partner_invoices(due_date=None, status=None): partner_email = get_current_team(get_pg=True).partner_email filters = { "partner_email": partner_email, "type": "Subscription", } if due_date: filters["due_date"] = due_date if status: filters["status"] = status invoices = jingrow.get_all( "Invoice", filters, ["name", "due_date", "customer_name", "total_before_discount", "currency", "status"], order_by="due_date desc", ) return invoices # noqa: RET504 @jingrow.whitelist() @role_guard.api("partner") def get_invoice_items(invoice): data = jingrow.get_all( "Invoice Item", {"parent": invoice}, ["document_type", "document_name", "rate", "plan", "quantity", "amount"], ) for d in data: team = jingrow.db.get_value(d.document_type, d.document_name, "team") values = jingrow.db.get_value("Team", team, ["user", "billing_name"], as_dict=True) d["user"] = values.user d["billing_name"] = values.billing_name return data @jingrow.whitelist() @role_guard.api("partner") def get_current_month_partner_contribution(partner_email): partner_currency = jingrow.db.get_value( "Team", {"jerp_partner": 1, "partner_email": partner_email}, "currency" ) month_end = jingrow.utils.get_last_day(today()) invoice = jingrow.qb.PageType("Invoice") query = ( jingrow.qb.from_(invoice) .select(invoice.currency, invoice.total_before_discount) .where( (invoice.partner_email == partner_email) & (invoice.due_date == month_end) & (invoice.type == "Subscription") & (invoice.pagestatus < 2) ) ) invoices = query.run(as_dict=True) total = 0 for d in invoices: if partner_currency != d.currency: if partner_currency == "USD": total += flt(d.total_before_discount / 83, 2) else: total += flt(d.total_before_discount * 83, 2) else: total += d.total_before_discount return total @jingrow.whitelist() @role_guard.api("partner") def get_prev_month_partner_contribution(partner_email): partner_currency = jingrow.db.get_value( "Team", {"jerp_partner": 1, "partner_email": partner_email}, "currency" ) first_day = get_first_day(today()) two_weeks = add_days(first_day, 14) # 15th day of the month last_month_end = get_last_day(add_months(today(), -1)) invoice = jingrow.qb.PageType("Invoice") query = ( jingrow.qb.from_(invoice) .select(invoice.currency, invoice.total_before_discount) .where( (invoice.partner_email == partner_email) & (invoice.due_date == last_month_end) & (invoice.type == "Subscription") ) ) if jingrow.utils.getdate() >= first_day and jingrow.utils.getdate() <= jingrow.utils.getdate(two_weeks): # till 15th of the current month unpaid invoices can also be counted in contribution query = query.where((invoice.status).isin(["Unpaid", "Paid"])) else: query = query.where(invoice.status == "Paid") invoices = query.run(as_dict=True) total = 0 for d in invoices: if partner_currency != d.currency: if partner_currency == "USD": total += flt(d.total_before_discount / 83, 2) else: total += flt(d.total_before_discount * 83, 2) else: total += d.total_before_discount return total @jingrow.whitelist() @role_guard.api("partner") def calculate_partner_tier(contribution, currency): partner_tier = jingrow.qb.PageType("Partner Tier") query = jingrow.qb.from_(partner_tier).select(partner_tier.name) if currency == "INR": query = query.where(partner_tier.target_in_inr <= contribution).orderby( partner_tier.target_in_inr, order=jingrow.qb.desc ) else: query = query.where(partner_tier.target_in_usd <= contribution).orderby( partner_tier.target_in_usd, order=jingrow.qb.desc ) tier = query.run(as_dict=True) return tier[0] @jingrow.whitelist() @role_guard.api("partner") def add_partner(referral_code: str): team = get_current_team(get_pg=True) partner = jingrow.get_pg("Team", {"partner_referral_code": referral_code}).name if jingrow.db.exists( "Partner Approval Request", {"partner": partner, "requested_by": team.name, "status": "Pending"}, ): return "Request already sent" pg = jingrow.get_pg( { "pagetype": "Partner Approval Request", "partner": partner, "requested_by": team.name, "status": "Pending", "send_mail": True, } ) pg.insert(ignore_permissions=True) return None @jingrow.whitelist() @role_guard.api("partner") def validate_partner_code(code): partner = jingrow.db.get_value( "Team", {"enabled": 1, "jerp_partner": 1, "partner_referral_code": code}, "billing_name", ) if partner: return True, partner return False, None @jingrow.whitelist() @role_guard.api("partner") def get_partner_customers(): team = get_current_team(get_pg=True) customers = jingrow.get_all( "Team", {"enabled": 1, "jerp_partner": 0, "partner_email": team.partner_email}, ["name", "user", "payment_mode", "billing_name", "currency"], ) return customers # noqa: RET504 @jingrow.whitelist() @role_guard.api("partner") def get_partner_members(partner): from jcloude.utils.billing import get_jingrow_io_connection client = get_jingrow_io_connection() return client.get_list( "LMS Certificate", filters={"partner": partner}, fields=["member_name", "member_email", "course", "version"], ) @jingrow.whitelist() @role_guard.api("partner") def get_partner_leads(lead_name=None, status=None, engagement_stage=None, source=None): team = get_current_team() filters = {"partner_team": team} if lead_name: filters["lead_name"] = ("like", f"%{lead_name}%") if status: filters["status"] = status if engagement_stage: filters["engagement_stage"] = engagement_stage if source: filters["lead_source"] = source return jingrow.get_all( "Partner Lead", filters, ["name", "organization_name", "lead_name", "status", "lead_source", "partner_team"], ) @jingrow.whitelist() @role_guard.api("partner") def change_partner(lead_name, partner): team = get_current_team() pg = jingrow.get_pg("Partner Lead", lead_name) if pg.partner_team != team: jingrow.throw("You are not allowed to change the partner for this lead") pg.partner_team = partner pg.status = "Open" pg.save() @jingrow.whitelist() @role_guard.api("partner") def remove_partner(): team = get_current_team(get_pg=True) if team.payment_mode == "Paid By Partner": jingrow.throw( "Cannot remove partner from the team. Please change the payment mode to Prepaid Credits or Card" ) partner_user = jingrow.get_value( "Team", {"partner_email": team.partner_email, "jerp_partner": 1}, "user" ) member_to_remove = find(team.team_members, lambda x: x.user == partner_user) if member_to_remove: team.remove(member_to_remove) team.partner_email = "" team.save(ignore_permissions=True) @jingrow.whitelist() @role_guard.api("partner") def apply_for_certificate(member_name, certificate_type): team = get_current_team(get_pg=True) pg = jingrow.new_pg("Partner Certificate Request") pg.update( { "partner_team": team.name, "partner_member_email": member_name, "course": certificate_type, } ) pg.insert(ignore_permissions=True) @jingrow.whitelist() @role_guard.api("partner") def get_partner_teams(company=None, email=None, country=None, tier=None, active_only=False): filters = {"enabled": 1, "jerp_partner": 1} if company: filters["company_name"] = ("like", f"%{company}%") if email: filters["partner_email"] = ("like", f"%{email}%") if country: filters["country"] = ("like", f"%{country}%") if tier: filters["partner_tier"] = tier if active_only: filters["partner_status"] = "Active" teams = jingrow.get_all( "Team", filters, ["partner_email", "company_name", "country", "partner_tier", "name"], ) return teams # noqa: RET504 @jingrow.whitelist() @role_guard.api("partner") def get_local_payment_setup(): team = get_current_team() data = jingrow._dict() data.mpesa_setup = jingrow.db.get_value("Mpesa Setup", {"team": team}, "mpesa_setup_id") or None data.payment_gateway = jingrow.db.get_value("Payment Gateway", {"team": team}, "name") or None return data @jingrow.whitelist() @role_guard.api("partner") def get_certificate_users(): users = jingrow.get_all("Partner Certificate", ["partner_member_email", "partner_member_name"]) return users # noqa: RET504 @jingrow.whitelist() @role_guard.api("partner") def get_lead_details(lead_id): return jingrow.get_pg("Partner Lead", lead_id).as_dict() @jingrow.whitelist() @role_guard.api("partner") def update_lead_details(lead_name, lead_details): lead_details = jingrow._dict(lead_details) pg = jingrow.get_pg("Partner Lead", lead_name) pg.update( { "organization_name": lead_details.organization_name, "status": lead_details.status, "full_name": lead_details.full_name, "domain": lead_details.domain, "email": lead_details.email, "contact_no": lead_details.contact_no, "state": lead_details.state, "country": lead_details.country, "plan_proposed": lead_details.plan_proposed, "requirement": lead_details.requirement, "probability": lead_details.probability, } ) pg.save(ignore_permissions=True) pg.reload() @jingrow.whitelist() @role_guard.api("partner") def update_lead_status(lead_name, status, **kwargs): status_dict = {"status": status} if status == "In Process": status_dict.update( { "engagement_stage": kwargs.get("engagement_stage"), } ) if kwargs.get("proposed_plan") and kwargs.get("expected_close_date"): status_dict.update( { "plan_proposed": kwargs.get("proposed_plan"), "estimated_closure_date": kwargs.get("expected_close_date"), } ) elif status == "Won": status_dict.update( { "conversion_date": kwargs.get("conversion_date"), "hosting": kwargs.get("hosting"), "site_url": kwargs.get("site_url"), } ) elif status == "Lost": status_dict.update( { "lost_reason": kwargs.get("lost_reason"), "lost_reason_specify": kwargs.get("other_reason"), } ) jingrow.db.set_value("Partner Lead", lead_name, status_dict) @jingrow.whitelist() @role_guard.api("partner") def fetch_followup_details(id, lead): return jingrow.get_all( "Lead Followup", {"parent": lead, "name": id, "parenttype": "Partner Lead"}, [ "name", "date", "communication_type", "followup_by", "spoke_to", "designation", "discussion", "no_show", ], ) @jingrow.whitelist() @role_guard.api("partner") def check_certificate_exists(email, type): return jingrow.db.count("Partner Certificate", {"partner_member_email": email, "course": type}) @jingrow.whitelist() def get_fc_plans(): site_plans = jingrow.get_all( "Site Plan", {"enabled": 1, "document_type": "Site", "price_inr": (">", 0)}, pluck="name" ) return [*site_plans, "Dedicated Server", "Managed Jcloude"] @jingrow.whitelist() def update_followup_details(id, lead, followup_details): followup_details = jingrow._dict(followup_details) if id: pg = jingrow.get_pg("Lead Followup", id) pg.update( { "date": jingrow.utils.getdate(followup_details.followup_date), "communication_type": followup_details.communication_type, "followup_by": followup_details.followup_by, "spoke_to": followup_details.spoke_to, "designation": followup_details.designation, "discussion": followup_details.discussion, "no_show": followup_details.no_show, } ) pg.save(ignore_permissions=True) else: pg = jingrow.new_pg("Lead Followup") pg.update( { "parent": lead, "parenttype": "Partner Lead", "parentfield": "followup", "date": jingrow.utils.getdate(followup_details.followup_date), "communication_type": followup_details.communication_type, "followup_by": followup_details.followup_by, "spoke_to": followup_details.spoke_to, "designation": followup_details.designation, "discussion": followup_details.discussion, "no_show": followup_details.no_show, } ) pg.insert(ignore_permissions=True) pg.reload() @jingrow.whitelist() @role_guard.api("partner") def add_new_lead(lead_details): lead_details = jingrow._dict(lead_details) pg = jingrow.new_pg("Partner Lead") pg.update( { "organization_name": lead_details.organization_name, "full_name": lead_details.full_name, "domain": lead_details.domain, "email": lead_details.email, "lead_name": lead_details.lead_name, "contact_no": lead_details.contact_no, "state": lead_details.state, "country": lead_details.country, "requirement": lead_details.requirement, "partner_team": get_current_team(), "lead_source": lead_details.lead_source or "Partner Owned", "lead_type": lead_details.lead_type, "status": "Open", } ) pg.insert(ignore_permissions=True) pg.reload() @jingrow.whitelist() @role_guard.api("partner") def can_apply_for_certificate(): from jcloude.utils.billing import get_jingrow_io_connection team = get_current_team(get_pg=True) client = get_jingrow_io_connection() response = client.get_api("check_free_certificate", {"partner_email": team.partner_email}) return response # noqa: RET504 @jingrow.whitelist() @role_guard.api("partner") def delete_followup(id, lead_name): jingrow.delete_pg("Lead Followup", id)