diff --git a/employee_self_service/mobile/v2/common/__init__.py b/employee_self_service/mobile/v2/common/__init__.py index 7eea503..ac72bed 100644 --- a/employee_self_service/mobile/v2/common/__init__.py +++ b/employee_self_service/mobile/v2/common/__init__.py @@ -1,305 +1,28 @@ -import frappe -from frappe import _ -from employee_self_service.mobile.v2.utils import ( - gen_response, - exception_handler, - ess_validate, +from employee_self_service.mobile.v2.common.assignment import ( + assign_document, + get_assignments, + remove_assignment, + clear_assignments +) +from employee_self_service.mobile.v2.common.attachment import ( + upload_documents, + delete_file, + get_attachments +) +from employee_self_service.mobile.v2.common.master import ( + get_option_list, + get_sort_option_list, + get_link_option_list, + get_ess_custom_fields, + get_company_list, + get_cost_center, + get_approver, + get_default_company_cost_center, + get_comments, + get_customer_list, + get_user_list +) +from employee_self_service.mobile.v2.common.permission import ( + get_ess_doctype_permission ) -from frappe.desk.form import assign_to -from frappe.handler import upload_file -from frappe.utils import cint - -@frappe.whitelist() -@ess_validate(methods=["POST"]) -def assign_document( - doctype, - docname, - users, - description=None -): - try: - if not frappe.db.exists(doctype, docname): - return gen_response(404, "Document not found") - - assign_to.add( - dict( - assign_to=users, - doctype=doctype, - name=docname, - description=description or "Assigned From Mobile App" - ) - ) - return gen_response( - 200, - "Assignment completed successfully" - ) - except Exception as e: - return exception_handler(e) - -@frappe.whitelist() -@ess_validate(methods=["GET"]) -def get_assignments(doctype, docname): - try: - assignments = assign_to.get({ - "doctype": doctype, - "name": docname - }) - - if not assignments: - return gen_response( - 200, - "Assignments fetched successfully", - { - "total_assignments": 0, - "assignments": [] - } - ) - - users = [d.owner for d in assignments if d.owner] - - user_details = frappe.get_all( - "User", - filters={"name": ["in", users]}, - fields=["name", "full_name", "user_image"] - ) - - user_map = { - user.name: user - for user in user_details - } - - for row in assignments: - user = user_map.get(row.owner) - - row["full_name"] = user.full_name if user else None - row["user_image"] = user.user_image if user else None - - return gen_response( - 200, - "Assignments fetched successfully", - { - "total_assignments": len(assignments), - "assignments": assignments - } - ) - - except Exception as e: - return exception_handler(e) - -@frappe.whitelist() -@ess_validate(methods=["POST"]) -def remove_assignment( - doctype, - docname, - user -): - try: - assign_to.remove( - doctype=doctype, - name=docname, - assign_to=user - ) - return gen_response( - 200, - "Assignment removed successfully" - ) - except Exception as e: - return exception_handler(e) - -@frappe.whitelist() -@ess_validate(methods=["POST"]) -def clear_assignments( - doctype, - docname -): - try: - assign_to.clear( - doctype, - docname - ) - return gen_response( - 200, - "All assignments cleared successfully" - ) - except Exception as e: - return exception_handler(e) - - -@frappe.whitelist() -@ess_validate(methods=["POST"]) -def upload_documents(): - try: - form_dict = frappe.form_dict - - reference_doctype = form_dict.get("reference_doctype") - reference_docname = form_dict.get("reference_docname") - - if not reference_doctype: - return gen_response(400, "Please provide a reference document type.") - - if not reference_docname: - return gen_response(400, "Please provide a reference document name.") - - if "file" not in frappe.request.files: - return gen_response(400, "Please upload a file for attachment.") - - file_doc = upload_file() - - file_doc.update({ - "attached_to_doctype": reference_doctype, - "attached_to_name": reference_docname, - "is_private": cint(form_dict.get("is_private", 1)) - }) - - file_doc.save() - - return gen_response( - 200, - "File uploaded successfully.", - { - "name": file_doc.name, - "file_url": file_doc.file_url, - "file_name": file_doc.file_name, - "is_private": file_doc.is_private, - } - ) - - except frappe.PermissionError: - return gen_response(403, "Not permitted to upload this file.") - except Exception as e: - return exception_handler(e) - -@frappe.whitelist() -@ess_validate(methods=["POST"]) -def delete_file(file_name): - try: - if not file_name: - return gen_response(500, "Please provide the file name.") - - if not frappe.db.exists("File", file_name): - return gen_response(404, "File not found.") - - frappe.delete_doc("File", file_name) - - return gen_response(200, "File deleted successfully.") - - except frappe.PermissionError: - return gen_response(403, "Not permitted to delete this file.") - except Exception as e: - return exception_handler(e) - - -@frappe.whitelist() -@ess_validate(methods=["GET"]) -def get_attachments(reference_doctype, reference_name): - try: - if not reference_doctype: - return gen_response(500, "Please provide a reference document type.") - - if not reference_name: - return gen_response(500, "Please provide a reference document name.") - - files = frappe.get_all( - "File", - filters={ - "attached_to_doctype": reference_doctype, - "attached_to_name": reference_name, - }, - fields=["name", "file_name", "file_url", "is_private", "file_size"], - ) - - return gen_response(200, "Attachments fetched successfully.", files) - except frappe.PermissionError: - return gen_response(403, "Not permitted to view attachments.") - except Exception as e: - return exception_handler(e) - -@frappe.whitelist() -@ess_validate(methods=["GET"]) -def get_option_list(doctype,field_name): - try: - meta = frappe.get_meta(doctype) - status_field_meta = meta.get_field(field_name) - status_options = [] - - if status_field_meta and status_field_meta.options: - status_options = [ - opt.strip() - for opt in status_field_meta.options.split("\n") - if opt.strip() - ] - - return gen_response( - 200, - "Options fetched successfully", - status_options, - ) - - except Exception as e: - return exception_handler(e) - - -@frappe.whitelist() -@ess_validate(methods=["GET"]) -def get_sort_option_list(doctype): - try: - meta = frappe.get_meta(doctype) - - options = [ - {"fieldname": "modified", "label": "Last Updated On"}, - {"fieldname": "name", "label": "ID"}, - {"fieldname": "creation", "label": "Created On"}, - ] - - layout_fieldtypes = { - "Section Break", "Column Break", "Tab Break", - "HTML", "Table", "Table MultiSelect", - "Button", "Image", "Fold", "Heading", - } - - for df in meta.fields: - if df.in_list_view and df.fieldtype not in layout_fieldtypes: - options.append({"fieldname": df.fieldname, "label": df.label}) - - return gen_response(200, "Sort options fetched successfully", options) - - except Exception as e: - return exception_handler(e) - -@frappe.whitelist() -@ess_validate(methods=["GET"]) -def get_link_option_list( - doctype, - fields=None, - filters=None, - start=0, - page_length=20, -): - try: - if not frappe.db.exists("DocType", doctype): - return gen_response(404, f"DocType '{doctype}' not found") - - link_options = frappe.get_all( - doctype, - fields=fields or ["name"], - filters=filters or [], - start=cint(start), - page_length=cint(page_length), - order_by=f"`tab{doctype}`.modified desc", - ) - - return gen_response( - 200, - "Link options fetched successfully", - link_options, - ) - - except frappe.PermissionError: - return gen_response( - 403, - "Not permitted to access this DocType." - ) - except Exception as e: - return exception_handler(e) - diff --git a/employee_self_service/mobile/v2/common/assignment.py b/employee_self_service/mobile/v2/common/assignment.py new file mode 100644 index 0000000..66ba8b3 --- /dev/null +++ b/employee_self_service/mobile/v2/common/assignment.py @@ -0,0 +1,122 @@ +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + ess_validate, +) +import frappe +from frappe.desk.form import assign_to + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def assign_document( + doctype, + docname, + users, + description=None +): + try: + if not frappe.db.exists(doctype, docname): + return gen_response(404, "Document not found") + + assign_to.add( + dict( + assign_to=users, + doctype=doctype, + name=docname, + description=description or "Assigned From Mobile App" + ) + ) + return gen_response( + 200, + "Assignment completed successfully" + ) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_assignments(doctype, docname): + try: + assignments = assign_to.get({ + "doctype": doctype, + "name": docname + }) + + if not assignments: + return gen_response( + 200, + "Assignments fetched successfully", + { + "total_assignments": 0, + "assignments": [] + } + ) + + users = [d.owner for d in assignments if d.owner] + + user_details = frappe.get_all( + "User", + filters={"name": ["in", users]}, + fields=["name", "full_name", "user_image"] + ) + + user_map = { + user.name: user + for user in user_details + } + + for row in assignments: + user = user_map.get(row.owner) + + row["full_name"] = user.full_name if user else None + row["user_image"] = user.user_image if user else None + + return gen_response( + 200, + "Assignments fetched successfully", + { + "total_assignments": len(assignments), + "assignments": assignments + } + ) + + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def remove_assignment( + doctype, + docname, + user +): + try: + assign_to.remove( + doctype=doctype, + name=docname, + assign_to=user + ) + return gen_response( + 200, + "Assignment removed successfully" + ) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def clear_assignments( + doctype, + docname +): + try: + assign_to.clear( + doctype, + docname + ) + return gen_response( + 200, + "All assignments cleared successfully" + ) + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/common/attachment.py b/employee_self_service/mobile/v2/common/attachment.py new file mode 100644 index 0000000..c2c7d89 --- /dev/null +++ b/employee_self_service/mobile/v2/common/attachment.py @@ -0,0 +1,98 @@ +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + ess_validate, +) +import frappe +from frappe.utils import cint +from frappe.handler import upload_file + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def upload_documents(): + try: + form_dict = frappe.form_dict + + reference_doctype = form_dict.get("reference_doctype") + reference_docname = form_dict.get("reference_docname") + + if not reference_doctype: + return gen_response(400, "Please provide a reference document type.") + + if not reference_docname: + return gen_response(400, "Please provide a reference document name.") + + if "file" not in frappe.request.files: + return gen_response(400, "Please upload a file for attachment.") + + file_doc = upload_file() + + file_doc.update({ + "attached_to_doctype": reference_doctype, + "attached_to_name": reference_docname, + "is_private": cint(form_dict.get("is_private", 1)) + }) + + file_doc.save() + + return gen_response( + 200, + "File uploaded successfully.", + { + "name": file_doc.name, + "file_url": file_doc.file_url, + "file_name": file_doc.file_name, + "is_private": file_doc.is_private, + } + ) + + except frappe.PermissionError: + return gen_response(403, "Not permitted to upload this file.") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def delete_file(file_name): + try: + if not file_name: + return gen_response(500, "Please provide the file name.") + + if not frappe.db.exists("File", file_name): + return gen_response(404, "File not found.") + + frappe.delete_doc("File", file_name) + + return gen_response(200, "File deleted successfully.") + + except frappe.PermissionError: + return gen_response(403, "Not permitted to delete this file.") + except Exception as e: + return exception_handler(e) + + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_attachments(reference_doctype, reference_name): + try: + if not reference_doctype: + return gen_response(500, "Please provide a reference document type.") + + if not reference_name: + return gen_response(500, "Please provide a reference document name.") + + files = frappe.get_all( + "File", + filters={ + "attached_to_doctype": reference_doctype, + "attached_to_name": reference_name, + }, + fields=["name", "file_name", "file_url", "is_private", "file_size"], + ) + + return gen_response(200, "Attachments fetched successfully.", files) + + except frappe.PermissionError: + return gen_response(403, "Not permitted to view attachments.") + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/common/master.py b/employee_self_service/mobile/v2/common/master.py new file mode 100644 index 0000000..f496f46 --- /dev/null +++ b/employee_self_service/mobile/v2/common/master.py @@ -0,0 +1,235 @@ +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + ess_validate, + get_employee_by_user +) +import frappe,erpnext +from frappe.utils import cint,pretty_date + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_option_list(doctype,field_name): + try: + meta = frappe.get_meta(doctype) + status_field_meta = meta.get_field(field_name) + status_options = [] + + if status_field_meta and status_field_meta.options: + status_options = [ + opt.strip() + for opt in status_field_meta.options.split("\n") + if opt.strip() + ] + + return gen_response( + 200, + "Options fetched successfully", + status_options, + ) + + except Exception as e: + return exception_handler(e) + + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_sort_option_list(doctype): + try: + meta = frappe.get_meta(doctype) + + options = [ + {"fieldname": "modified", "label": "Last Updated On"}, + {"fieldname": "name", "label": "ID"}, + {"fieldname": "creation", "label": "Created On"}, + ] + + layout_fieldtypes = { + "Section Break", "Column Break", "Tab Break", + "HTML", "Table", "Table MultiSelect", + "Button", "Image", "Fold", "Heading", + } + + for df in meta.fields: + if df.in_list_view and df.fieldtype not in layout_fieldtypes: + options.append({"fieldname": df.fieldname, "label": df.label}) + + return gen_response(200, "Sort options fetched successfully", options) + + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_link_option_list( + doctype, + fields=None, + filters=None, + start=0, + page_length=20, +): + try: + if not frappe.db.exists("DocType", doctype): + return gen_response(404, f"DocType '{doctype}' not found") + + link_options = frappe.get_all( + doctype, + fields=fields or ["name"], + filters=filters or [], + start=cint(start), + page_length=cint(page_length), + order_by=f"`tab{doctype}`.modified desc", + ) + + return gen_response( + 200, + "Link options fetched successfully", + link_options, + ) + + except frappe.PermissionError: + return gen_response( + 403, + "Not permitted to access this DocType." + ) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_ess_custom_fields(doctype_name): + try: + if not frappe.db.exists("ESS Custom Field", doctype_name): + return gen_response(200, "No custom fields found for this form", []) + custom_field_doc = frappe.get_doc("ESS Custom Field", doctype_name) + return gen_response( + 200, "Custom fields retrieved successfully", custom_field_doc + ) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_company_list(): + try: + company_list = frappe.get_list("Company", pluck="name") + return gen_response( + 200, + "Company List get successfully", + company_list, + ) + except frappe.PermissionError: + return gen_response(500, frappe.flags.error_message) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_cost_center(company): + try: + cost_centers = frappe.get_list( + "Cost Center", filters={"company": company, "is_group": 0}, fields=["name"] + ) + return gen_response(200, "Cost Center list get successfully", cost_centers) + except frappe.PermissionError: + return gen_response(500, frappe.flags.error_message) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_approver(document_type): + try: + from hrms.hr.doctype.department_approver.department_approver import ( + get_approvers, + ) + + emp_data = get_employee_by_user(frappe.session.user) + leave_approver = get_approvers( + doctype=document_type, + txt="", + searchfield="", + start=0, + page_len=20, + filters={"employee": emp_data.name, "doctype": document_type}, + ) + keys = ["user", "first_name", "last_name"] + mapped_data = [dict(zip(keys, row)) for row in leave_approver] + return gen_response(200, "approver receive successfully", mapped_data) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_default_company_cost_center(company): + try: + return gen_response( + 200, + "default cost center get successfully", + erpnext.get_default_cost_center(company), + ) + except frappe.PermissionError: + return gen_response(500, frappe.flags.error_message) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_comments(reference_doctype=None, reference_name=None): + """ + reference_doctype: doctype + reference_name: docname + """ + try: + filters = [ + ["Comment", "reference_doctype", "=", f"{reference_doctype}"], + ["Comment", "reference_name", "=", f"{reference_name}"], + ["Comment", "comment_type", "=", "Comment"], + ] + comments = frappe.get_all( + "Comment", + filters=filters, + fields=[ + "content as comment", + "comment_by", + "creation", + "comment_email", + ], + ) + + for comment in comments: + user_image = frappe.get_value( + "User", comment.comment_email, "user_image", cache=True + ) + comment["user_image"] = user_image + comment["commented"] = pretty_date(comment["creation"]) + comment["creation"] = comment["creation"].strftime("%I:%M %p") + + return gen_response(200, "Comments get successfully", comments) + + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_customer_list(): + try: + customer = frappe.get_list("Customer", ["name", "customer_name"]) + return gen_response(200, "Customer list Getting Successfully", customer) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_user_list(): + try: + user_list = frappe.get_all( + "User", + filters={"user_type": "System User", "enabled": 1}, + fields=["name", "full_name", "user_image"], + ) + return gen_response(200, "User List getting Successfully", user_list) + except frappe.PermissionError: + return gen_response(500, "Not permitted read user") + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/common/permission.py b/employee_self_service/mobile/v2/common/permission.py new file mode 100644 index 0000000..f6f0825 --- /dev/null +++ b/employee_self_service/mobile/v2/common/permission.py @@ -0,0 +1,56 @@ +import frappe +from frappe import _ +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + get_actions +) +from frappe.permissions import get_doc_permissions + +@frappe.whitelist() +def get_ess_doctype_permission(doctype_name, doctype_reference_name): + """ + API to get current user's permissions and actions for a given document. + Args: + doctype_name (str): The DocType name (e.g. "Sales Order"). + doctype_reference_name (str): The document name/ID (e.g. "SO-0001"). + """ + try: + # Fetch the document + doc = frappe.get_doc(doctype_name, doctype_reference_name) + + # Get user's permissions + perms = get_doc_permissions(doc) + permissions_summary = { + key: perms.get(key, 0) for key in ("write", "delete", "submit", "cancel") + } + if doc.docstatus == 0: + permissions_summary["cancel"] = 0 + if doc.docstatus == 1: + permissions_summary["write"] = 0 + permissions_summary["delete"] = 0 + permissions_summary["submit"] = 0 + if doc.docstatus == 2: + permissions_summary["write"] = 0 + permissions_summary["delete"] = 0 + permissions_summary["submit"] = 0 + permissions_summary["cancel"] = 0 + + # Get available actions + actions = get_actions(doc) + if len(actions) >= 1: + permissions_summary["action"] = 1 + permissions_summary["submit"] = 0 + permissions_summary["cancel"] = 0 + else: + permissions_summary["action"] = 0 + # Success response + return gen_response( + 200, + "Permission and actions retrieved successfully", + {"permissions": permissions_summary, "actions": actions}, + ) + except frappe.PermissionError: + return gen_response(403, "Not permitted for item") + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/common/utils.py b/employee_self_service/mobile/v2/common/utils.py index fb89fc6..3fc08b2 100644 --- a/employee_self_service/mobile/v2/common/utils.py +++ b/employee_self_service/mobile/v2/common/utils.py @@ -1,2 +1,2 @@ import frappe -from frappe import _ \ No newline at end of file +from frappe import _ diff --git a/employee_self_service/mobile/v2/module/__init__.py b/employee_self_service/mobile/v2/module/__init__.py new file mode 100644 index 0000000..f2fbe62 --- /dev/null +++ b/employee_self_service/mobile/v2/module/__init__.py @@ -0,0 +1,28 @@ +from employee_self_service.mobile.v2.module.expense import ( + get_expense_claim_type_totals, + get_expense_claims, + update_expense, + apply_expense, + get_expense, + get_expense_type + +) +from employee_self_service.mobile.v2.module.order import ( + get_order_status, + get_order_list, + get_warehouse_list, + get_item_group_list, + prepare_order_totals, + get_item_list, + create_order, + scan_item, + get_uoms, + get_order +) +from employee_self_service.mobile.v2.module.leave import ( + get_leave_application_list, + get_leave_type, + update_leave_application, + make_leave_application, + get_leave_application +) diff --git a/employee_self_service/mobile/v2/module/expense/__init__.py b/employee_self_service/mobile/v2/module/expense/__init__.py new file mode 100644 index 0000000..168b3ad --- /dev/null +++ b/employee_self_service/mobile/v2/module/expense/__init__.py @@ -0,0 +1,237 @@ +import frappe +from frappe import _ +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + ess_validate, + get_employee_by_user, + get_global_defaults, + validate_employee_data +) +from employee_self_service.mobile.v2.module.expense.utils import ( + get_month_year_details, + get_payable_account, + get_attachments +) +from frappe.utils import fmt_money,today +import json + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_expense_claim_type_totals(): + try: + global_defaults = get_global_defaults() + emp_data = get_employee_by_user(frappe.session.user) + if not len(emp_data) >= 1: + return gen_response(500, "Employee does not exists") + validate_employee_data(emp_data) + filters = frappe._dict() + filters.employee = emp_data.get("name") + filters.approval_status = "Approved" + fields = [ + "`tabExpense Claim`.name", + "`tabExpense Claim`.employee", + "`tabExpense Claim`.employee_name", + "`tabExpense Claim Detail`.expense_type", + "sum(`tabExpense Claim Detail`.amount) as total_amount", + ] + + claims = frappe.get_list( + "Expense Claim", + fields=fields, + filters=filters, + order_by="`tabExpense Claim`.posting_date desc", + group_by="`tabExpense Claim Detail`.expense_type", + ) + + for claim in claims: + claim["total_amount_currency"] = fmt_money( + claim["total_amount"], currency=global_defaults.get("default_currency") + ) + + return gen_response(200, "Expense date get successfully", claims) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_expense_claims(): + try: + global_defaults = get_global_defaults() + emp_data = get_employee_by_user(frappe.session.user) + if not len(emp_data) >= 1: + return gen_response(500, "Employee does not exists") + validate_employee_data(emp_data) + filters = frappe._dict() + filters.employee = emp_data.get("name") + fields = [ + "`tabExpense Claim`.name", + "`tabExpense Claim`.employee", + "`tabExpense Claim`.employee_name", + "`tabExpense Claim`.approval_status", + "`tabExpense Claim`.status", + "`tabExpense Claim`.expense_approver", + "`tabExpense Claim`.total_claimed_amount", + "`tabExpense Claim`.posting_date", + "`tabExpense Claim`.company", + "`tabExpense Claim Detail`.expense_type", + "count(`tabExpense Claim Detail`.expense_type) as total_expenses", + ] + + claims = frappe.get_list( + "Expense Claim", + fields=fields, + filters=filters, + order_by="`tabExpense Claim`.posting_date desc", + group_by="`tabExpense Claim`.name", + ) + expense_data = {} + for expense in claims: + expense["total_claimed_amount"] = fmt_money( + expense["total_claimed_amount"], + currency=global_defaults.get("default_currency"), + ) + + month_year = get_month_year_details(expense) + expense["posting_date"] = expense.get("posting_date").strftime("%d-%m-%Y") + if month_year not in list(expense_data.keys())[::-1]: + expense_data[month_year] = [expense] + else: + expense_data[month_year].append(expense) + + return gen_response(200, "Expense date get successfully", expense_data) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def update_expense(**data): + try: + emp_data = get_employee_by_user(frappe.session.user, fields=["name", "company"]) + + if not len(emp_data) >= 1: + return gen_response(500, "Employee does not exists") + validate_employee_data(emp_data) + + if not frappe.db.exists( + "Expense Claim", {"name": data.get("id"), "employee": emp_data.name} + ): + return gen_response(500, "Invalid ID") + + expense_doc = frappe.get_doc("Expense Claim", data.get("id")) + expense_doc.update(data) + expense_doc.save(ignore_permissions=True) + + if data.get("attachments") is not None: + for file in data.get("attachments"): + frappe.get_doc( + doctype="File", + file_url=file.get("file_url"), + attached_to_doctype="Expense Claim", + attached_to_name=expense_doc.name, + ).insert(ignore_permissions=True) + + return gen_response(200, "Expense updated Successfully", expense_doc) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def apply_expense(**data): + try: + emp_data = get_employee_by_user( + frappe.session.user, fields=["name", "company", "expense_approver"] + ) + + if not len(emp_data) >= 1: + return gen_response(500, "Employee does not exists") + validate_employee_data(emp_data) + + # expenses=[ + # { + # "expense_date": frappe.form_dict.expense_date, + # "expense_type": frappe.form_dict.expense_type, + # "description": frappe.form_dict.description, + # "amount": frappe.form_dict.amount, + # } + # ], + + payable_account = get_payable_account(emp_data.get("company")) + expense_doc = frappe.get_doc( + doctype="Expense Claim", + employee=emp_data.name, + expense_approver=emp_data.expense_approver, + posting_date=today(), + company=emp_data.get("company"), + payable_account=payable_account, + items=frappe.form_dict.items, + ) + expense_doc.update(data) + expense_doc.insert() + + if data.get("attachments") is not None: + for file in data.get("attachments"): + file_doc = frappe.get_doc( + { + "doctype": "File", + "file_url": file.get("file_url"), + "attached_to_doctype": "Expense Claim", + "attached_to_name": expense_doc.name, + } + ) + file_doc.insert(ignore_permissions=True) + + return gen_response(200, "Expense applied Successfully", expense_doc) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_expense(*args, **kwargs): + try: + data = kwargs + global_defaults = get_global_defaults() + expense_doc = json.loads( + frappe.get_doc("Expense Claim", data.get("id")).as_json() + ) + for expense_item in expense_doc.get("expenses"): + expense_item["amount_in_currency"] = fmt_money( + expense_item.get("amount"), + currency=global_defaults.get("default_currency"), + ) + expense_item["section_amount_in_currency"] = fmt_money( + expense_item.get("sanctioned_amount"), + currency=global_defaults.get("default_currency"), + ) + expense_doc["attachments"] = get_attachments(data.get("id")) + expense_doc["total_claimed_amount"] = fmt_money( + expense_doc["total_claimed_amount"], + currency=global_defaults.get("default_currency"), + ) + expense_doc["total_sanctioned_amount_in_currency"] = fmt_money( + expense_doc["total_sanctioned_amount"], + currency=global_defaults.get("default_currency"), + ) + if expense_doc.get("project"): + expense_doc["project_name"] = frappe.db.get_value( + "Project", expense_doc.get("project"), "project_name" + ) + if expense_doc.get("task"): + expense_doc["task_name"] = frappe.db.get_value( + "Task", expense_doc.get("task"), "subject" + ) + gen_response(200, "Expense detail get successfully.", expense_doc) + except frappe.PermissionError: + return gen_response(500, "Not permitted for Expense") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +def get_expense_type(): + try: + expense_types = frappe.get_all( + "Expense Claim Type", filters={}, fields=["name"] + ) + return gen_response(200, "Expense type get successfully", expense_types) + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/module/expense/utils.py b/employee_self_service/mobile/v2/module/expense/utils.py new file mode 100644 index 0000000..d933d51 --- /dev/null +++ b/employee_self_service/mobile/v2/module/expense/utils.py @@ -0,0 +1,35 @@ +from employee_self_service.mobile.v2.utils import ( + gen_response, + get_ess_settings +) +import frappe +from frappe.utils import getdate + +def get_month_year_details(expense): + date = getdate(expense.get("posting_date")) + month = date.strftime("%B") + year = date.year + return f"{month} {year}" + +def get_payable_account(company): + ess_settings = get_ess_settings() + default_payable_account = ess_settings.get("default_payable_account") + if not default_payable_account: + default_payable_account = frappe.db.get_value( + "Company", company, "default_payable_account" + ) + if not default_payable_account: + return gen_response( + 500, + "Set Default Payable Account Either In ESS Settings or Company Settings", + ) + else: + return default_payable_account + return default_payable_account + +def get_attachments(id): + return frappe.get_all( + "File", + filters={"attached_to_doctype": "Expense Claim", "attached_to_name": id}, + fields=["file_url", "file_name"], + ) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/module/leave/__init__.py b/employee_self_service/mobile/v2/module/leave/__init__.py new file mode 100644 index 0000000..1aee04c --- /dev/null +++ b/employee_self_service/mobile/v2/module/leave/__init__.py @@ -0,0 +1,184 @@ +import frappe +from frappe import _ +from erpnext.accounts.utils import get_fiscal_year +from frappe.utils import today,nowdate +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + ess_validate, + get_employee_by_user, + validate_employee_data +) +from employee_self_service.mobile.v2.module.leave.utils import ( + get_leave_balance_report +) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_leave_application_list(): + """ + Get Leave Application which is already applied. Get Leave Balance Report + """ + try: + emp_data = get_employee_by_user(frappe.session.user) + validate_employee_data(emp_data) + leave_application_fields = [ + "name", + "leave_type", + "DATE_FORMAT(from_date, '%d-%m-%Y') as from_date", + "DATE_FORMAT(to_date, '%d-%m-%Y') as to_date", + "total_leave_days", + "description", + "status", + "DATE_FORMAT(posting_date, '%d-%m-%Y') as posting_date", + "half_day", + "DATE_FORMAT(half_day_date, '%d-%m-%Y') as half_day_date", + ] + upcoming_leaves = frappe.get_all( + "Leave Application", + filters={"from_date": [">", today()], "employee": emp_data.get("name")}, + fields=leave_application_fields, + ) + + taken_leaves = frappe.get_all( + "Leave Application", + fields=leave_application_fields, + filters={"from_date": ["<=", today()], "employee": emp_data.get("name")}, + ) + fiscal_year = get_fiscal_year(nowdate())[0] + if not fiscal_year: + return gen_response(500, "Fiscal year not set") + res = get_leave_balance_report( + emp_data.get("name"), emp_data.get("company"), fiscal_year + ) + leave_applications = { + "upcoming": upcoming_leaves, + "taken": taken_leaves, + "balance": res, + } + return gen_response(200, "Leave data getting successfully", leave_applications) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_leave_type(from_date=None, to_date=None): + try: + from hrms.hr.doctype.leave_application.leave_application import ( + get_leave_balance_on, + ) + + if not from_date: + from_date = today() + emp_data = get_employee_by_user(frappe.session.user) + leave_types = frappe.get_all( + "Leave Type", filters={}, fields=["name", "'0' as balance"] + ) + for leave_type in leave_types: + leave_type["balance"] = get_leave_balance_on( + emp_data.get("name"), + leave_type.get("name"), + from_date, + consider_all_leaves_in_the_allocation_period=True, + ) + return gen_response(200, "Leave type get successfully", leave_types) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def update_leave_application(*args, **kwargs): + try: + emp_data = get_employee_by_user(frappe.session.user) + if not len(emp_data) >= 1: + return gen_response(500, "Employee does not exists!") + validate_employee_data(emp_data) + + leave_id = kwargs.get("name") + if not leave_id: + return gen_response(500, "Leave ID is required!") + + if not frappe.db.exists("Leave Application", kwargs.get("name")): + return gen_response(500, "Leave application does not exists!") + + leave_application_doc = frappe.get_doc("Leave Application", leave_id) + leave_application_doc.update(kwargs) + leave_application_doc.save() + gen_response(200, "Leave application successfully updated!") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def make_leave_application(*args, **kwargs): + try: + from hrms.hr.doctype.leave_application.leave_application import ( + get_leave_approver, + ) + + emp_data = get_employee_by_user(frappe.session.user) + if not len(emp_data) >= 1: + return gen_response(500, "Employee does not exists!") + validate_employee_data(emp_data) + + leave_application_doc = frappe.get_doc( + doctype="Leave Application", + employee=emp_data.get("name"), + company=emp_data.company, + leave_approver=get_leave_approver(emp_data.name), + ) + leave_application_doc.update(kwargs) + leave_application_doc.insert() + gen_response(200, "Leave application successfully added!", leave_application_doc.name) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_leave_application(name): + """ + Get Leave Application which is already applied. Get Leave Balance Report + """ + try: + emp_data = get_employee_by_user(frappe.session.user) + validate_employee_data(emp_data) + + if not frappe.db.exists( + "Leave Application", {"name": name, "employee": emp_data.get("name")} + ): + return gen_response(500, "Leave application does not exists!") + + leave_application_fields = [ + "name", + "leave_type", + "total_leave_days", + "description", + "status", + "half_day", + "from_date", + "to_date", + "posting_date", + "half_day_date" + ] + + leave_application = frappe.db.get_value( + "Leave Application", name, leave_application_fields, as_dict=True + ) + + file_data = frappe.db.get_value( + "File", + { + "attached_to_doctype": "Leave Application", + "attached_to_name": name, + "attached_to_field": "medical_supporting_document", + }, + ["file_name","name","file_url"], + as_dict=True, + ) + + leave_application["medical_supporting_document"] = file_data + + return gen_response(200, "Leave data getting successfully", leave_application) + except Exception as e: + return exception_handler(e) + diff --git a/employee_self_service/mobile/v2/module/leave/utils.py b/employee_self_service/mobile/v2/module/leave/utils.py new file mode 100644 index 0000000..3a43f0b --- /dev/null +++ b/employee_self_service/mobile/v2/module/leave/utils.py @@ -0,0 +1,34 @@ +import frappe +from frappe import _ +from frappe.utils import getdate + +def get_leave_balance_report(employee, company, fiscal_year): + """ + Returns a map of leave type and balance details like: + { + 'Casual Leave': {'allocated_leaves': 10.0, 'balance_leaves': 5.0}, + 'Earned Leave': {'allocated_leaves': 3.0, 'balance_leaves': 3.0}, + } + """ + from hrms.hr.doctype.leave_application.leave_application import get_leave_details + + date = getdate() + leave_balance = [] + + leave_details = get_leave_details(employee, date) + allocation = leave_details["leave_allocation"] + + for leave_type, details in allocation.items(): + leave_balance.append( + { + "leave_type": leave_type, + "total_leaves": details.get("total_leaves"), + "leaves_allocated": details.get("remaining_leaves"), + "leaves_taken": details.get("leaves_taken"), + "expired_leaves": details.get("expired_leaves"), + "employee": employee, + "closing_balance": details.get("remaining_leaves"), + } + ) + + return leave_balance diff --git a/employee_self_service/mobile/v2/module/order/__init__.py b/employee_self_service/mobile/v2/module/order/__init__.py new file mode 100644 index 0000000..b98e800 --- /dev/null +++ b/employee_self_service/mobile/v2/module/order/__init__.py @@ -0,0 +1,467 @@ +import frappe +import json +from frappe import _ +from datetime import datetime +from erpnext.accounts.party import get_dashboard_info +from frappe.utils import fmt_money,getdate +from employee_self_service.mobile.v2.utils import ( + gen_response, + exception_handler, + ess_validate, + get_global_defaults, + check_workflow_exists, + get_date_range, + get_ess_settings, + prepare_json_data, + get_actions +) +from employee_self_service.mobile.v2.module.order.utils import ( + get_order_details_with_currency, + get_items_rate, + _create_update_order, + _get_item_price, + get_uom_item_price, + get_default_price_list, + get_attachments +) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_order_status(): + try: + # Get status options from Sales Order doctype meta + meta = frappe.get_meta("Sales Order") + status_field_meta = meta.get_field("status") + status_options = [] + + if status_field_meta and status_field_meta.options: + # Remove empty/blank options + status_options = [ + opt.strip() + for opt in status_field_meta.options.split("\n") + if opt.strip() + ] + + return gen_response( + 200, + "Order status options fetched successfully", + status_options, + ) + + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_order_list( + start=0, + page_length=10, + order_by="modified", + sort_order="desc", + filters=None, + posting_date_type=None, + posting_date_from=None, + posting_date_to=None, + delivery_date_type=None, + delivery_date_from=None, + delivery_date_to=None, +): + try: + if isinstance(filters, str): + filters = frappe.parse_json(filters) + + global_defaults = get_global_defaults() + status_field = check_workflow_exists("Sales Order") or "status" + + if posting_date_type and not posting_date_type == "Custom Date": + posting_duration_details = get_date_range(posting_date_type) + posting_date_from = posting_duration_details.get("from_date") + posting_date_to = posting_duration_details.get("to_date") + + if delivery_date_type and not delivery_date_type == "Custom Date": + delivery_duration_details = get_date_range(delivery_date_type) + delivery_date_from = delivery_duration_details.get("from_date") + delivery_date_to = delivery_duration_details.get("to_date") + + # Move 'status' into dynamic status field + if filters and filters.get("status"): + filters[status_field] = filters.pop("status") + + # Handle 'item' filter separately (joins with Sales Order Item) + updated_filters = [] + + if filters: + for key, value in filters.items(): + if key == "item_name": + updated_filters.append( + ["Sales Order Item", "item_code", "=", value] + ) + else: + updated_filters.append(["Sales Order", key, "=", value]) + + if posting_date_type and posting_date_from and posting_date_to: + updated_filters.append( + [ + "Sales Order", + "transaction_date", + "Between", + [posting_date_from, posting_date_to], + ] + ) + if delivery_date_type and delivery_date_from and delivery_date_to: + updated_filters.append( + [ + "Sales Order", + "delivery_date", + "Between", + [delivery_date_from, delivery_date_to], + ] + ) + + order_list = frappe.get_list( + "Sales Order", + fields=[ + "name", + "customer", + "customer_name", + "transaction_date", + "grand_total", + f"{status_field} as status", + "total_qty", + ], + start=start, + page_length=page_length, + order_by=f"{order_by} {sort_order}", + filters=updated_filters, + ) + + for order in order_list: + order["grand_total"] = fmt_money( + order["grand_total"], currency=global_defaults.get("default_currency") + ) + order["transaction_date"] = datetime.strftime( + order["transaction_date"], "%d-%m-%Y" + ) + + return gen_response(200, "Order list fetched successfully", order_list) + + except frappe.PermissionError: + return gen_response(403, "Not permitted for Sales Order") + + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_warehouse_list(filters=None): + try: + if not filters: + filters = [] + filters.append(["Warehouse", "is_group", "=", 0]) + item_group_list = frappe.get_list("Warehouse", fields=["name"], filters=filters) + return gen_response(200, "Warehouse list get successfully", item_group_list) + except frappe.PermissionError: + return gen_response(500, "Not permitted for item") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET", "POST"]) +def get_item_group_list(filters=None): + try: + if not filters: + filters = [] + filters.append(["Item Group", "show_in_mobile", "=", 1]) + item_group_list = frappe.get_list( + "Item Group", fields=["name"], filters=filters + ) + gen_response(200, "Item group list get successfully", item_group_list) + except frappe.PermissionError: + return gen_response(500, "Not permitted for item") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def prepare_order_totals(*args, **kwargs): + try: + data = kwargs + if not data.get("customer"): + return gen_response(500, "Customer is required.") + ess_settings = get_ess_settings() + for item in data.get("items"): + item["delivery_date"] = data.get("delivery_date") + item["warehouse"] = ess_settings.get("default_warehouse") + global_defaults = get_global_defaults() + sales_order_doc = frappe.get_doc( + doctype="Sales Order", company=global_defaults.get("default_company") + ) + sales_order_doc.update(data) + sales_order_doc.apply_discount_on = "Grand Total" + + sales_order_doc.run_method("set_missing_values") + sales_order_doc.run_method("calculate_taxes_and_totals") + sales_order_doc = json.loads(sales_order_doc.as_json()) + gen_response( + 200, + "Order details get successfully", + get_order_details_with_currency( + sales_order_doc, global_defaults.get("default_currency") + ), + ) + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_item_list( + start=0, + page_length=10, + filters=None, + customer=None, + for_filters=None, + or_filters=None, + warehouse=None, +): + try: + if not filters: + filters = [] + filters.append(["Item", "show_in_mobile", "=", 1]) + item_list = frappe.get_list( + "Item", + fields=["name", "item_name", "item_code", "image", "sales_uom", "stock_uom"], + filters=filters, + start=start, + or_filters=or_filters, + page_length=page_length, + ) + for item in item_list: + item["uom"] = item.get("sales_uom") or item.get("stock_uom") + if for_filters: + items = item_list + else: + items = get_items_rate(item_list, customer=customer, warehouse=warehouse) + gen_response(200, "Item list get successfully", items) + except frappe.PermissionError: + return gen_response(500, "Not permitted for item") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["POST"]) +def create_order(*args, **kwargs): + try: + data = kwargs + if not data.get("customer"): + return gen_response(500, "Customer is required.") + if not data.get("items") or len(data.get("items")) == 0: + return gen_response(500, "Please select items to proceed.") + if not data.get("delivery_date"): + return gen_response(500, "Please select delivery date to proceed.") + global_defaults = get_global_defaults() + # ess_settings = get_ess_settings() + if data.get("order_id"): + if not frappe.db.exists("Sales Order", data.get("order_id"), cache=True): + return gen_response(500, "Invalid order id.") + sales_order_doc = frappe.get_doc("Sales Order", data.get("order_id")) + _create_update_order( + data=data, + sales_order_doc=sales_order_doc, + default_warehouse=data.get("set_warehouse"), + ) + if data.get("attachments") is not None: + for file in data.get("attachments"): + file_doc = frappe.get_doc( + { + "doctype": "File", + "file_url": file.get("file_url"), + "attached_to_doctype": "Sales Order", + "attached_to_name": sales_order_doc.name, + } + ) + file_doc.insert(ignore_permissions=True) + gen_response(200, "Order updated successfully.", sales_order_doc.name) + else: + sales_order_doc = frappe.get_doc( + doctype="Sales Order", + company=global_defaults.get("default_company"), + ) + _create_update_order( + data=data, + sales_order_doc=sales_order_doc, + default_warehouse=data.get("set_warehouse"), + ) + if data.get("attachments") is not None: + for file in data.get("attachments"): + file_doc = frappe.get_doc( + { + "doctype": "File", + "file_url": file.get("file_url"), + "attached_to_doctype": "Sales Order", + "attached_to_name": sales_order_doc.name, + } + ) + file_doc.insert(ignore_permissions=True) + + gen_response(200, "Order created successfully.", sales_order_doc.name) + except frappe.PermissionError: + return gen_response(500, "Not permitted for create sales order") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +def scan_item(barcode): + try: + from erpnext.stock.utils import scan_barcode + + item_details = scan_barcode(barcode) + item_list = frappe.get_list( + "Item", + filters={"name": item_details.get("item_code")}, + fields=["name", "item_name", "item_code", "image", "sales_uom", "stock_uom"], + ) + for item in item_list: + item["uom"] = item.get("sales_uom") or item.get("stock_uom") + items = get_items_rate(item_list) + if len(items) >= 1: + gen_response(200, "Item list get successfully", items[0]) + else: + gen_response(500, "Item does not exists") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_uoms(customer, item): + try: + global_defaults = get_global_defaults() + sales_price_list = get_default_price_list(customer=customer) + item_doc = frappe.get_doc("Item", item) + default_uom = item_doc.get("sales_uom") or item_doc.get("stock_uom") + stock_uom = item_doc.get("stock_uom") + uoms = [] + default_uom_price = _get_item_price( + item_code=item_doc.get("name"), + price_list=sales_price_list, + uom=default_uom, + ) + for uom_row in item_doc.get("uoms"): + uom_hint = f"1 {uom_row.get('uom')} = {uom_row.get('conversion_factor')} {stock_uom}" + uom_details = dict( + uom=uom_row.get("uom"), + conversion_factor=uom_row.get("conversion_factor"), + uom_hint=uom_hint, + is_default=uom_row.get("uom") == default_uom, + ) + get_uom_item_price( + sales_price_list, item, uom_details, default_uom_price, global_defaults + ) + uoms.append(uom_details) + # Put sales_uom first in the list + uoms.sort(key=lambda u: u["uom"] != default_uom) + return gen_response(200, "uom details get successfully", uoms) + except frappe.PermissionError: + return gen_response(500, "Not permitted for item") + except Exception as e: + return exception_handler(e) + +@frappe.whitelist() +@ess_validate(methods=["GET"]) +def get_order(*args, **kwargs): + try: + data = kwargs + order_doc = json.loads( + frappe.get_doc("Sales Order", data.get("order_id")).as_json() + ) + global_defaults = get_global_defaults() + transaction_date = getdate(order_doc["transaction_date"]) + delivery_date = getdate(order_doc["delivery_date"]) + order_doc["transaction_date"] = transaction_date.strftime("%d-%m-%Y") + order_doc["delivery_date"] = delivery_date.strftime("%d-%m-%Y") + order_data = get_order_details_with_currency( + order_doc, global_defaults.get("default_currency") + ) + for response_field in [ + "name", + "customer", + "transaction_date", + "delivery_date", + "workflow_state", + "total_qty", + "customer_name", + "shipping_address", + "contact_email", + "contact_mobile", + "contact_phone", + "cost_center", + "company", + "set_warehouse", + "discount_amount", + "po_no", + "project", + "order_type", + "commission_rate", + ]: + order_data[response_field] = order_doc.get(response_field) + item_list = [] + for item in order_doc.get("items"): + item["amount"] = fmt_money( + item.get("amount"), currency=global_defaults.get("default_currency") + ) + item["rate_currency"] = fmt_money( + item.get("rate"), currency=global_defaults.get("default_currency") + ) + item["price_list_rate_currency"] = fmt_money( + item.get("price_list_rate"), + currency=global_defaults.get("default_currency"), + ) + if item.get("price_list_rate") == 0: + item["price_list_rate"] = item.get("rate") + item["price_list_rate_currency"] = fmt_money( + item.get("rate"), currency=global_defaults.get("default_currency") + ) + item_list.append( + prepare_json_data( + [ + "item_name", + "item_code", + "qty", + "amount", + "rate", + "image", + "rate_currency", + "discount_amount", + "discount_percentage", + "price_list_rate", + "price_list_rate_currency", + "uom", + ], + item, + ) + ) + order_data["items"] = item_list + order_data["next_action"] = get_actions(order_doc, order_data) + order_data["allow_edit"] = True if order_doc.get("docstatus") == 0 else False + order_data["created_by"] = frappe.get_cached_value( + "User", order_doc.get("owner"), "full_name" + ) + dashboard_info = get_dashboard_info("Customer", order_doc.get("customer")) + order_data["annual_billing"] = fmt_money( + dashboard_info[0].get("billing_this_year") if dashboard_info else 0.0, + currency=global_defaults.get("default_currency"), + ) + order_data["total_unpaid"] = fmt_money( + dashboard_info[0].get("total_unpaid") if dashboard_info else 0.0, + currency=global_defaults.get("default_currency"), + ) + order_data["discount"] = order_data["discount_amount"] + order_data["discount_amount"] = fmt_money( + order_data["discount_amount"] if order_data["discount_amount"] else 0.0, + currency=global_defaults.get("default_currency"), + ) + order_data["attachments"] = get_attachments(data.get("order_id")) + gen_response(200, "Order detail get successfully.", order_data) + except frappe.PermissionError: + return gen_response(500, "Not permitted for sales order") + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/module/order/utils.py b/employee_self_service/mobile/v2/module/order/utils.py new file mode 100644 index 0000000..18dc00d --- /dev/null +++ b/employee_self_service/mobile/v2/module/order/utils.py @@ -0,0 +1,153 @@ +import frappe +from frappe import _ +from frappe.utils import fmt_money +from erpnext.stock.utils import get_stock_balance +from employee_self_service.mobile.v2.utils import ( + get_global_defaults, + get_ess_settings, + get_sales_person_by_customer +) + +def get_order_details_with_currency(sales_order_doc, currency): + order_response_dict = {} + for response_fields in [ + "total_taxes_and_charges", + "net_total", + "discount_amount", + "grand_total", + "total", + ]: + order_response_dict[response_fields] = fmt_money( + sales_order_doc.get(response_fields), + currency=currency, + ) + return order_response_dict + +def get_items_rate(items, customer=None, warehouse=None): + global_defaults = get_global_defaults() + ess_settings = get_ess_settings() + price_list = get_default_price_list(customer=customer) + if not price_list: + frappe.throw( + _( + "Please set a price list for the customer or define a default in the Selling Settings." + ) + ) + + # Check if stock balance should be included + show_stock_balance = ess_settings.get("show_stock_balance_in_item_list", 0) + + for item in items: + uom = item.get("uom") + item_price = _get_item_price( + item_code=item.name, + price_list=price_list, + uom=uom, + ) + item_price_currency = fmt_money( + item_price if item_price else 0.0, + currency=global_defaults.get("default_currency"), + ) + item["rate_currency"] = item_price_currency + item["rate"] = item_price if item_price else 0.0 + item["price_list_rate"] = item_price if item_price else 0.0 + item["price_list_rate_currency"] = item_price_currency + + # Add stock balance if enabled and warehouse is provided + if show_stock_balance and warehouse: + stock_balance = get_stock_balance(item.name, warehouse) + item["stock_balance"] = stock_balance + elif show_stock_balance: + # If no warehouse specified, show 0 or get from default warehouse + default_warehouse = ess_settings.get("default_warehouse") + if default_warehouse: + stock_balance = get_stock_balance(item.name, default_warehouse) + item["stock_balance"] = stock_balance + else: + item["stock_balance"] = 0.0 + + return items + +def _create_update_order(data, sales_order_doc, default_warehouse): + enable_target_management = frappe.db.get_single_value( + "ESS Target Settings", "enable_target_management" + ) + delivery_date = data.get("delivery_date") + for item in data.get("items"): + item["delivery_date"] = delivery_date + item["warehouse"] = default_warehouse + sales_order_doc.apply_discount_on = "Grand Total" + sales_order_doc.update(data) + sales_order_doc.run_method("set_missing_values") + sales_order_doc.run_method("calculate_taxes_and_totals") + + # Populate sales team from Customer if not already set + if not sales_order_doc.get("sales_team"): + sales_persons = get_sales_person_by_customer(data.get("customer")) + if sales_persons: + sales_order_doc.set("sales_team", sales_persons) + + if enable_target_management: + sales_persons = get_sales_person_by_customer(data.get("customer")) + if sales_persons: + sales_order_doc.set("sales_team", sales_persons) + + sales_order_doc.save() + +def get_default_price_list(customer=None): + if customer: + price_list, customer_group = frappe.db.get_value( + "Customer", customer, ["default_price_list", "customer_group"] + ) + if price_list: + return price_list + price_list = frappe.db.get_value( + "Customer Group", customer_group, "default_price_list" + ) + if price_list: + return price_list + return frappe.db.get_single_value("Selling Settings", "selling_price_list") + +def _get_item_price(item_code, price_list, uom): + item_price = frappe.db.get_value( + "Item Price", + {"price_list": price_list, "item_code": item_code, "uom": uom}, + "price_list_rate", + order_by="valid_from desc", + ) + return item_price or 0.0 + +def get_uom_item_price( + price_list, item_code, uom=None, default_uom_price=0.0, global_defaults=None +): + item_price = _get_item_price( + item_code=item_code, + price_list=price_list, + uom=uom.get("uom"), + ) + if item_price: + item_price_currency = fmt_money( + item_price if item_price else 0.0, + currency=global_defaults.get("default_currency"), + ) + uom["rate_currency"] = item_price_currency + uom["rate"] = item_price if item_price else 0.0 + uom["price_list_rate"] = item_price if item_price else 0.0 + uom["price_list_rate_currency"] = item_price_currency + else: + item_price = default_uom_price * uom.get("conversion_factor") + item_price_currency = fmt_money( + item_price if item_price else 0.0, + currency=global_defaults.get("default_currency"), + ) + uom["rate_currency"] = item_price_currency + uom["rate"] = item_price if item_price else 0.0 + uom["price_list_rate"] = item_price if item_price else 0.0 + uom["price_list_rate_currency"] = item_price_currency + +def get_attachments(id): + return frappe.get_all( + "File", + filters={"attached_to_doctype": "Sales Order", "attached_to_name": id}, + fields=["name", "file_url", "file_name"], + ) \ No newline at end of file diff --git a/employee_self_service/mobile/v2/utils/__init__.py b/employee_self_service/mobile/v2/utils/__init__.py index fe3b1d1..5318e44 100644 --- a/employee_self_service/mobile/v2/utils/__init__.py +++ b/employee_self_service/mobile/v2/utils/__init__.py @@ -2,6 +2,8 @@ import wrapt from bs4 import BeautifulSoup from frappe import _ +from erpnext.accounts.utils import get_fiscal_year +from frappe.utils import add_days,add_months, get_first_day, today def gen_response(status, message, data=[]): frappe.response["http_status_code"] = status @@ -58,6 +60,14 @@ def get_employee_by_user(user, fields=["name"]): return emp_data +def validate_employee_data(employee_data): + if not employee_data.get("company"): + return gen_response( + 500, + "Company not set in employee doctype. Contact HR manager for set company", + ) + + def get_ess_settings(): return frappe.get_doc( "Employee Self Service Settings", "Employee Self Service Settings" @@ -87,4 +97,102 @@ def remove_default_fields(data): ]: if data.get(row): del data[row] - return data \ No newline at end of file + return data + +def check_workflow_exists(doctype): + doc_workflow = frappe.get_all( + "Workflow", + filters={"document_type": doctype, "is_active": 1}, + fields=["workflow_state_field"], + ) + if doc_workflow: + return doc_workflow[0].workflow_state_field + else: + return False + + +# Duration Types Supported in `get_date_range`: +# - "Current Month" +# - "Last Month" +# - "Last 3 Month" +# - "Last 6 Month" +# - "Current Financial Year" +# - "Last Financial Year" +@frappe.whitelist() +def get_date_range(duration_type): + if duration_type == "Current Month": + return {"from_date": get_first_day(today()), "to_date": today()} + if duration_type == "Last Month": + last_month_end_date = add_days(get_first_day(today()), -1) + return { + "from_date": get_first_day(last_month_end_date), + "to_date": last_month_end_date, + } + if duration_type == "Last 3 Month": + last_month_end_date = add_days(get_first_day(today()), -1) + last_month_start_month_end_date = add_months(last_month_end_date, -2) + return { + "from_date": get_first_day(last_month_start_month_end_date), + "to_date": last_month_end_date, + } + if duration_type == "Last 6 Month": + last_month_end_date = add_days(get_first_day(today()), -1) + last_month_start_month_end_date = add_months(last_month_end_date, -5) + return { + "from_date": get_first_day(last_month_start_month_end_date), + "to_date": last_month_end_date, + } + if duration_type == "Current Financial Year": + fiscal_year = get_fiscal_year(today(), as_dict=1) + if not fiscal_year: + frappe.throw(_("No Any Financial Year Active")) + return { + "from_date": fiscal_year.get("year_start_date"), + "to_date": fiscal_year.get("year_end_date"), + } + if duration_type == "Last Financial Year": + current_fiscal_year = get_fiscal_year(today(), as_dict=1) + if not current_fiscal_year: + frappe.throw(_("No Any Financial Year Active")) + last_fiscal_year = get_fiscal_year( + add_days(current_fiscal_year.get("year_start_date"), -1), as_dict=1 + ) + if not last_fiscal_year: + frappe.throw(_("No Any Data In Last Financial Year")) + return { + "from_date": last_fiscal_year.get("year_start_date"), + "to_date": last_fiscal_year.get("year_end_date"), + } + +def get_sales_person_by_customer(party): + sales_persons = frappe.get_all( + "Sales Team", + filters={"parenttype": "Customer", "parent": party}, + fields=["sales_person", "allocated_percentage", "commission_rate"], + ) + return sales_persons + +def prepare_json_data(key_list, data): + return_data = {} + for key in data: + if key in key_list: + return_data[key] = data.get(key) + return return_data + +def get_actions(doc, doc_data=None): + from frappe.model.workflow import get_transitions + + if not frappe.db.exists( + "Workflow", dict(document_type=doc.get("doctype"), is_active=1) + ): + if doc_data: + doc_data["workflow_state"] = doc.get("status") + return [] + try: + transitions = get_transitions(doc) + except Exception: + return [] + actions = [] + for row in transitions: + actions.append(row.get("action")) + return actions