import functools import jingrow import phonenumbers import requests from jingrow import _ from jingrow.model.pagestatus import DocStatus from jingrow.model.dynamic_links import get_dynamic_link_map from jingrow.utils import floor from phonenumbers import NumberParseException from phonenumbers import PhoneNumberFormat as PNF def parse_phone_number(phone_number, default_country="IN"): try: # Parse the number number = phonenumbers.parse(phone_number, default_country) # Get various information about the number result = { "is_valid": phonenumbers.is_valid_number(number), "country_code": number.country_code, "national_number": str(number.national_number), "formats": { "international": phonenumbers.format_number(number, PNF.INTERNATIONAL), "national": phonenumbers.format_number(number, PNF.NATIONAL), "E164": phonenumbers.format_number(number, PNF.E164), "RFC3966": phonenumbers.format_number(number, PNF.RFC3966), }, "type": phonenumbers.number_type(number), "country": phonenumbers.region_code_for_number(number), "is_possible": phonenumbers.is_possible_number(number), } return {"success": True, **result} except NumberParseException as e: return {"success": False, "error": str(e)} def are_same_phone_number(number1, number2, default_region="IN", validate=True): """ Check if two phone numbers are the same, regardless of their format. Args: number1 (str): First phone number number2 (str): Second phone number default_region (str): Default region code for parsing ambiguous numbers Returns: bool: True if numbers are same, False otherwise """ try: # Parse both numbers parsed1 = phonenumbers.parse(number1, default_region) parsed2 = phonenumbers.parse(number2, default_region) # Check if both numbers are valid if validate and not (phonenumbers.is_valid_number(parsed1) and phonenumbers.is_valid_number(parsed2)): return False # Convert both to E164 format and compare formatted1 = phonenumbers.format_number(parsed1, phonenumbers.PhoneNumberFormat.E164) formatted2 = phonenumbers.format_number(parsed2, phonenumbers.PhoneNumberFormat.E164) return formatted1 == formatted2 except phonenumbers.NumberParseException: return False def seconds_to_duration(seconds): if not seconds: return "0s" hours = floor(seconds // 3600) minutes = floor((seconds % 3600) // 60) seconds = floor((seconds % 3600) % 60) # 1h 0m 0s -> 1h # 0h 1m 0s -> 1m # 0h 0m 1s -> 1s # 1h 1m 0s -> 1h 1m # 1h 0m 1s -> 1h 1s # 0h 1m 1s -> 1m 1s # 1h 1m 1s -> 1h 1m 1s if hours and minutes and seconds: return f"{hours}h {minutes}m {seconds}s" elif hours and minutes: return f"{hours}h {minutes}m" elif hours and seconds: return f"{hours}h {seconds}s" elif minutes and seconds: return f"{minutes}m {seconds}s" elif hours: return f"{hours}h" elif minutes: return f"{minutes}m" elif seconds: return f"{seconds}s" else: return "0s" # Extracted from jingrow core jingrow/model/delete_pg.py/check_if_pg_is_linked def get_linked_docs(pg, method="Delete"): from jingrow.model.rename_pg import get_link_fields link_fields = get_link_fields(pg.pagetype) ignored_doctypes = set() if method == "Cancel" and (pg_ignore_flags := pg.get("ignore_linked_doctypes")): ignored_doctypes.update(pg_ignore_flags) if method == "Delete": ignored_doctypes.update(jingrow.get_hooks("ignore_links_on_delete")) docs = [] for lf in link_fields: link_dt, link_field, issingle = lf["parent"], lf["fieldname"], lf["issingle"] if link_dt in ignored_doctypes or (link_field == "amended_from" and method == "Cancel"): continue try: meta = jingrow.get_meta(link_dt) except jingrow.DoesNotExistError: jingrow.clear_last_message() # This mostly happens when app do not remove their customizations, we shouldn't # prevent link checks from failing in those cases continue if issingle: if jingrow.db.get_single_value(link_dt, link_field) == pg.name: docs.append({"pg": pg.name, "link_dt": link_dt, "link_field": link_field}) continue fields = ["name", "pagestatus"] if meta.istable: fields.extend(["parent", "parenttype"]) for item in jingrow.db.get_values(link_dt, {link_field: pg.name}, fields, as_dict=True): # available only in child table cases item_parent = getattr(item, "parent", None) linked_parent_pagetype = item.parenttype if item_parent else link_dt if linked_parent_pagetype in ignored_doctypes: continue if method != "Delete" and (method != "Cancel" or not DocStatus(item.pagestatus).is_submitted()): # don't raise exception if not # linked to a non-cancelled pg when deleting or to a submitted pg when cancelling continue elif link_dt == pg.pagetype and (item_parent or item.name) == pg.name: # don't raise exception if not # linked to same item or pg having same name as the item continue else: reference_docname = item_parent or item.name docs.append( { "pg": pg.name, "reference_pagetype": linked_parent_pagetype, "reference_docname": reference_docname, } ) return docs # Extracted from jingrow core jingrow/model/delete_pg.py/check_if_pg_is_dynamically_linked def get_dynamic_linked_docs(pg, method="Delete"): docs = [] for df in get_dynamic_link_map().get(pg.pagetype, []): ignore_linked_doctypes = pg.get("ignore_linked_doctypes") or [] if df.parent in jingrow.get_hooks("ignore_links_on_delete") or ( df.parent in ignore_linked_doctypes and method == "Cancel" ): # don't check for communication and todo! continue meta = jingrow.get_meta(df.parent) if meta.issingle: # dynamic link in single pg refdoc = jingrow.db.get_singles_dict(df.parent) if ( refdoc.get(df.options) == pg.pagetype and refdoc.get(df.fieldname) == pg.name and ( # linked to an non-cancelled pg when deleting (method == "Delete" and not DocStatus(refdoc.pagestatus).is_cancelled()) # linked to a submitted pg when cancelling or (method == "Cancel" and DocStatus(refdoc.pagestatus).is_submitted()) ) ): docs.append({"pg": pg.name, "reference_pagetype": df.parent, "reference_docname": df.parent}) else: # dynamic link in table df["table"] = ", `parent`, `parenttype`, `idx`" if meta.istable else "" for refdoc in jingrow.db.sql( """select `name`, `pagestatus` {table} from `tab{parent}` where `{options}`=%s and `{fieldname}`=%s""".format(**df), (pg.pagetype, pg.name), as_dict=True, ): # linked to an non-cancelled pg when deleting # or linked to a submitted pg when cancelling if (method == "Delete" and not DocStatus(refdoc.pagestatus).is_cancelled()) or ( method == "Cancel" and DocStatus(refdoc.pagestatus).is_submitted() ): reference_pagetype = refdoc.parenttype if meta.istable else df.parent reference_docname = refdoc.parent if meta.istable else refdoc.name if reference_pagetype in jingrow.get_hooks("ignore_links_on_delete") or ( reference_pagetype in ignore_linked_doctypes and method == "Cancel" ): # don't check for communication and todo! continue at_position = f"at Row: {refdoc.idx}" if meta.istable else "" docs.append( { "pg": pg.name, "reference_pagetype": reference_pagetype, "reference_docname": reference_docname, "at_position": at_position, } ) return docs def is_admin(user: str | None = None) -> bool: """ Check whether `user` is an admin :param user: User to check against, defaults to current user :return: Whether `user` is an admin """ user = user or jingrow.session.user return user == "Administrator" def is_sales_user(user: str | None = None) -> bool: """ Check whether `user` is an agent :param user: User to check against, defaults to current user :return: Whether `user` is an agent """ user = user or jingrow.session.user return is_admin() or "Sales Manager" in jingrow.get_roles(user) or "Sales User" in jingrow.get_roles(user) def sales_user_only(fn): """Decorator to validate if user is an agent.""" @functools.wraps(fn) def wrapper(*args, **kwargs): if not is_sales_user(): jingrow.throw( msg=_("You are not permitted to access this resource."), title=_("Not Allowed"), exc=jingrow.PermissionError, ) return fn(*args, **kwargs) return wrapper