diff --git a/asset_lite/api/custom_api.py b/asset_lite/api/custom_api.py index 360d94f..bb57dab 100644 --- a/asset_lite/api/custom_api.py +++ b/asset_lite/api/custom_api.py @@ -21,13 +21,19 @@ def get_user_details(user_id=None): response_data = { "user_id": user_id, "full_name": user.full_name, + "first_name": user.first_name or "", + "middle_name": user.middle_name or "", + "last_name": user.last_name or "", + "username": user.username or "", "email": user.email, + "mobile_no": user.mobile_no or "", "user_image": user.user_image, "roles": roles, "last_login": user.last_login, "enabled": user.enabled, "creation": user.creation, "modified": user.modified, + "custom_user_id": user.get("custom_user_id") or "", "custom_site_name": user.get("custom_site_name") or "", "custom_phcc_site_name": user.get("custom_phcc_site_name") or "", "role_profile_name": user.get("role_profile_name") or "", diff --git a/asset_lite/api/data_import_api.py b/asset_lite/api/data_import_api.py new file mode 100644 index 0000000..e0b37b6 --- /dev/null +++ b/asset_lite/api/data_import_api.py @@ -0,0 +1,121 @@ +import csv +import os +from io import StringIO + +import frappe +from frappe import _ +from frappe.utils.csvutils import read_csv_content +from frappe.utils.file_manager import save_file +from frappe.utils.xlsxutils import ( + make_xlsx, + read_xls_file_from_attached_file, + read_xlsx_file_from_attached_file, +) + + +def _trim_cell(value): + if isinstance(value, str): + trimmed = value.strip() + return trimmed if trimmed else None + return value + + +def _trim_rows(rows): + trimmed_cells = 0 + out = [] + for row in rows: + new_row = [] + for cell in row: + if isinstance(cell, str): + original = cell + trimmed = _trim_cell(cell) + if original != (trimmed or ""): + trimmed_cells += 1 + new_row.append(trimmed) + else: + new_row.append(cell) + out.append(new_row) + return out, trimmed_cells + + +def _read_rows_from_content(content, extension): + extension = (extension or "").lower() + if extension == "csv": + return read_csv_content(content) + if extension == "xlsx": + return read_xlsx_file_from_attached_file(fcontent=content) + if extension == "xls": + return read_xls_file_from_attached_file(content) + frappe.throw(_("Import file must be .csv, .xlsx, or .xls")) + + +def _write_trimmed_content(rows, extension): + extension = (extension or "").lower() + if extension == "csv": + buffer = StringIO() + writer = csv.writer(buffer) + for row in rows: + writer.writerow(["" if cell is None else cell for cell in row]) + return buffer.getvalue().encode("utf-8"), "csv" + + if extension == "xls": + extension = "xlsx" + + if extension == "xlsx": + return make_xlsx(rows, "Import").getvalue(), "xlsx" + + frappe.throw(_("Import file must be .csv, .xlsx, or .xls")) + + +def _trimmed_filename(original_name, output_extension): + base, _ = os.path.splitext(original_name or "import") + return f"{base}_trimmed.{output_extension}" + + +@frappe.whitelist() +def trim_and_upload_import_file(data_import): + doc = frappe.get_doc("Data Import", data_import) + doc.check_permission("write") + + if not doc.import_file: + frappe.throw(_("Attach an import file first.")) + + if doc.google_sheets_url: + frappe.throw(_("Trim and Upload is only available for attached files, not Google Sheets URLs.")) + + file_name = frappe.db.get_value("File", {"file_url": doc.import_file}, "name") + if not file_name: + frappe.throw(_("Import file not found in File attachments.")) + + source_file = frappe.get_doc("File", file_name) + content = source_file.get_content() + extension = os.path.splitext(source_file.file_name or doc.import_file)[1][1:].lower() + + rows = _read_rows_from_content(content, extension) + if not rows: + frappe.throw(_("Import file is empty.")) + + trimmed_rows, trimmed_cells = _trim_rows(rows) + output_content, output_extension = _write_trimmed_content(trimmed_rows, extension) + output_name = _trimmed_filename(source_file.file_name or os.path.basename(doc.import_file), output_extension) + + new_file = save_file( + output_name, + output_content, + dt=doc.doctype, + dn=doc.name, + is_private=source_file.is_private, + df="import_file", + ) + + doc.import_file = new_file.file_url + doc.template_options = "" + doc.template_warnings = "" + doc.save() + + return { + "import_file": new_file.file_url, + "file_name": new_file.file_name, + "trimmed_cells": trimmed_cells, + "summary": _("Trimmed {0} cell(s) and uploaded {1}.").format(trimmed_cells, new_file.file_name), + } diff --git a/asset_lite/api/ppm_generator_api.py b/asset_lite/api/ppm_generator_api.py index 49818d6..8109cd6 100644 --- a/asset_lite/api/ppm_generator_api.py +++ b/asset_lite/api/ppm_generator_api.py @@ -42,6 +42,56 @@ PM_ENTRY_LINE_FIELDS = [ 'model' ] +PPM_PLANNER_ASSET_FIELDS = [ + "name", + "asset_name", + "custom_modality", + "company", + "custom_manufacturer", + "custom_device_status", + "custom_model", + "department", +] + + +@frappe.whitelist() +def get_ppm_planner_assets( + company=None, + custom_modality=None, + custom_manufacturer=None, + custom_device_status=None, + custom_model=None, + department=None, +): + """Fetch submitted assets for PPM Planner bulk scheduling.""" + filters = {"docstatus": 1} + + if company: + filters["company"] = company + if custom_modality: + filters["custom_modality"] = custom_modality + if custom_manufacturer: + filters["custom_manufacturer"] = custom_manufacturer + if custom_device_status: + filters["custom_device_status"] = custom_device_status + if custom_model: + filters["custom_model"] = custom_model + if department: + filters["department"] = department + + assets = frappe.get_all("Asset", filters=filters, fields=PPM_PLANNER_ASSET_FIELDS) + + existing_maintenance_assets = set( + frappe.get_all( + "Asset Maintenance", + filters={"docstatus": ["<", 2]}, + pluck="name", + ) + ) + + return [asset for asset in assets if asset.name not in existing_maintenance_assets] + + @frappe.whitelist() def create_bulk_schedules(asset_names, start_date, end_date, maintenance_team=None, periodicity='Monthly', maintenance_type='Preventive'): """Create maintenance schedules for multiple assets""" diff --git a/asset_lite/fixtures/custom_field.json b/asset_lite/fixtures/custom_field.json index 0df3be8..2e36481 100644 --- a/asset_lite/fixtures/custom_field.json +++ b/asset_lite/fixtures/custom_field.json @@ -55,62 +55,6 @@ "unique": 0, "width": null }, - { - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "User", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_department", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_preview": 0, - "in_standard_filter": 1, - "insert_after": "Username", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Department", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2025-08-26 15:24:16.357765", - "module": "Asset Lite", - "name": "User-custom_department", - "no_copy": 0, - "non_negative": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, { "allow_in_quick_entry": 0, "allow_on_submit": 0, @@ -12153,7 +12097,7 @@ }, { "allow_in_quick_entry": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "collapsible_depends_on": null, @@ -12185,7 +12129,7 @@ "length": 0, "link_filters": null, "mandatory_depends_on": null, - "modified": "2025-04-22 12:52:15.979629", + "modified": "2026-08-31 09:21:30.053035", "module": "Asset Lite", "name": "Asset-custom_installation_date", "no_copy": 0, diff --git a/asset_lite/fixtures/role.json b/asset_lite/fixtures/role.json index 6199201..e0ea171 100644 --- a/asset_lite/fixtures/role.json +++ b/asset_lite/fixtures/role.json @@ -1468,5 +1468,47 @@ "timeline": 0, "two_factor_auth": 0, "view_switcher": 0 + }, + { + "bulk_actions": 1, + "dashboard": 1, + "desk_access": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Role", + "form_sidebar": 1, + "home_page": null, + "is_custom": 0, + "list_sidebar": 1, + "modified": "2026-08-28 12:49:21.664589", + "name": "BME Director", + "notifications": 1, + "restrict_to_domain": null, + "role_name": "BME Director", + "search_bar": 1, + "timeline": 1, + "two_factor_auth": 0, + "view_switcher": 1 + }, + { + "bulk_actions": 1, + "dashboard": 1, + "desk_access": 1, + "disabled": 0, + "docstatus": 0, + "doctype": "Role", + "form_sidebar": 1, + "home_page": null, + "is_custom": 0, + "list_sidebar": 1, + "modified": "2026-08-28 12:49:45.525864", + "name": "Engineer", + "notifications": 1, + "restrict_to_domain": null, + "role_name": "Engineer", + "search_bar": 1, + "timeline": 1, + "two_factor_auth": 0, + "view_switcher": 1 } ] \ No newline at end of file diff --git a/asset_lite/fixtures/server_script.json b/asset_lite/fixtures/server_script.json index 1b5abfc..30e8310 100644 --- a/asset_lite/fixtures/server_script.json +++ b/asset_lite/fixtures/server_script.json @@ -674,13 +674,13 @@ "doctype_event": "Before Insert", "enable_rate_limit": 0, "event_frequency": "All", - "modified": "2026-01-09 13:21:21.936865", + "modified": "2026-08-25 12:15:18.912036", "module": "Asset Lite", "name": "Update Item Defaults", "rate_limit_count": 5, "rate_limit_seconds": 86400, "reference_doctype": "Item", - "script": "row = doc.append(\"item_defaults\", {})\r\nrow.company = \"Iman General Hospital\"\r\nrow.default_warehouse = \"Iman Central Warehouse - IGH\"", + "script": "#row = doc.append(\"item_defaults\", {})\r\n#row.company = \"Iman General Hospital\"\r\n#row.default_warehouse = \"Iman Central Warehouse - IGH\"\r\n\r\n\r\nif doc.custom_hospital_name:\r\n company = frappe.get_doc(\"Company\", doc.custom_hospital_name)\r\n\r\n if company.abbr:\r\n warehouse = \"Stores - \" + company.abbr\r\n\r\n # If no Item Defaults rows exist, create one\r\n if not doc.item_defaults:\r\n row = doc.append(\"item_defaults\")\r\n row.company = doc.custom_hospital_name\r\n row.default_warehouse = warehouse\r\n\r\n else:\r\n # Update existing Item Defaults rows\r\n for row in doc.item_defaults:\r\n row.company = doc.custom_hospital_name\r\n row.default_warehouse = warehouse", "script_type": "DocType Event" }, { diff --git a/asset_lite/fixtures/web_form.json b/asset_lite/fixtures/web_form.json new file mode 100644 index 0000000..de84180 --- /dev/null +++ b/asset_lite/fixtures/web_form.json @@ -0,0 +1,142 @@ +[ + { + "accept_payment": 0, + "allow_comments": 0, + "allow_delete": 0, + "allow_edit": 0, + "allow_incomplete": 0, + "allow_multiple": 0, + "allow_print": 0, + "amount": 0.0, + "amount_based_on_field": 0, + "amount_field": null, + "anonymous": 0, + "apply_document_permissions": 0, + "banner_image": null, + "breadcrumbs": null, + "button_label": "Save", + "client_script": null, + "condition_json": "[]", + "currency": "SAR", + "custom_css": null, + "doc_type": "WO Feedback", + "docstatus": 0, + "doctype": "Web Form", + "introduction_text": "
Work Order is Closed Please Give Your Feedback Here