asset-lite latest fixtures and data import code

This commit is contained in:
Duradundi 2026-09-10 11:00:50 +00:00
parent eab723d294
commit fcb2167953
10 changed files with 457 additions and 78 deletions

View File

@ -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 "",

View File

@ -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),
}

View File

@ -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"""

View File

@ -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,

View File

@ -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
}
]

View File

@ -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"
},
{

View File

@ -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": "<div class=\"ql-editor read-mode\"><p>Work Order is Closed Please Give Your Feedback Here</p></div>",
"is_standard": 0,
"list_columns": [],
"list_title": null,
"login_required": 0,
"max_attachment_size": 0,
"meta_description": null,
"meta_image": null,
"meta_title": null,
"modified": "2026-08-18 13:35:38.853748",
"module": "Asset Lite",
"name": "wo-feedback",
"payment_button_help": null,
"payment_button_label": "Buy Now",
"payment_gateway": null,
"print_format": null,
"published": 1,
"route": "wo-feedback",
"show_attachments": 0,
"show_list": 0,
"show_sidebar": 0,
"success_message": "Your Feedback Submitted successfully",
"success_title": "Feedback Submitted",
"success_url": null,
"title": "WO Feedback",
"web_form_fields": [
{
"allow_read_on_all_link_options": 0,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "work_order",
"fieldtype": "Link",
"hidden": 0,
"label": "Work Order",
"mandatory_depends_on": null,
"max_length": 0,
"max_value": 0,
"options": "Work_Order",
"parent": "wo-feedback",
"parentfield": "web_form_fields",
"parenttype": "Web Form",
"precision": "",
"read_only": 0,
"read_only_depends_on": null,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "requester_name",
"fieldtype": "Data",
"hidden": 0,
"label": "Requester Name",
"mandatory_depends_on": null,
"max_length": 0,
"max_value": 0,
"options": null,
"parent": "wo-feedback",
"parentfield": "web_form_fields",
"parenttype": "Web Form",
"precision": "",
"read_only": 0,
"read_only_depends_on": null,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "rating",
"fieldtype": "Select",
"hidden": 0,
"label": "Rating",
"mandatory_depends_on": null,
"max_length": 0,
"max_value": 0,
"options": "\nExcellent / ممتاز\nGood / جيد\nAverage / متوسط\nPoor / ضعيف",
"parent": "wo-feedback",
"parentfield": "web_form_fields",
"parenttype": "Web Form",
"precision": "",
"read_only": 0,
"read_only_depends_on": null,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "comments",
"fieldtype": "Small Text",
"hidden": 0,
"label": "Comments",
"mandatory_depends_on": null,
"max_length": 0,
"max_value": 0,
"options": null,
"parent": "wo-feedback",
"parentfield": "web_form_fields",
"parenttype": "Web Form",
"precision": "",
"read_only": 0,
"read_only_depends_on": null,
"reqd": 0,
"show_in_filter": 0
}
],
"website_sidebar": null
}
]

View File

@ -80,15 +80,6 @@
"style": "",
"workflow_state_name": "Sent To Site Manager"
},
{
"docstatus": 0,
"doctype": "Workflow State",
"icon": "",
"modified": "2026-08-11 11:37:18.893891",
"name": "Sent To Project Manager",
"style": "",
"workflow_state_name": "Sent To Project Manager"
},
{
"docstatus": 0,
"doctype": "Workflow State",
@ -116,6 +107,15 @@
"style": "",
"workflow_state_name": "Pending Purchase"
},
{
"docstatus": 0,
"doctype": "Workflow State",
"icon": "",
"modified": "2026-08-11 11:37:18.893891",
"name": "Sent To Project Manager",
"style": "",
"workflow_state_name": "Sent To Project Manager"
},
{
"docstatus": 0,
"doctype": "Workflow State",
@ -138,19 +138,19 @@
"docstatus": 0,
"doctype": "Workflow State",
"icon": "",
"modified": "2025-08-11 12:01:35.474110",
"name": "Closed",
"style": "",
"workflow_state_name": "Closed"
"modified": "2024-09-13 13:50:24.315353",
"name": "Pending Approval",
"style": "Primary",
"workflow_state_name": "Pending Approval"
},
{
"docstatus": 0,
"doctype": "Workflow State",
"icon": "",
"modified": "2024-09-13 13:50:24.315353",
"name": "Pending Approval",
"style": "Primary",
"workflow_state_name": "Pending Approval"
"modified": "2025-08-11 12:01:35.474110",
"name": "Closed",
"style": "",
"workflow_state_name": "Closed"
},
{
"docstatus": 0,

View File

@ -44,7 +44,10 @@ app_include_js = "/assets/asset_lite/js/dashboard_embed.js?v=1.0.28"
# page_js = {"page" : "public/js/file.js"}
# include js in doctype views
doctype_js = {"Asset Maintenance Log" : "public/js/custom_asset_maintenance_log.js"}
doctype_js = {
"Asset Maintenance Log" : "public/js/custom_asset_maintenance_log.js",
"Data Import" : "public/js/custom_data_import.js"
}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
@ -281,6 +284,7 @@ fixtures = [
{"doctype": "Server Script", "filters": [["module","=","Asset Lite"]]},
{"doctype": "Notification", "filters": [["is_standard","=",0]]},
{"doctype": "Report", "filters": [["module","=","Asset Lite"]]},
{"doctype": "Web Form", "filters": [["module","=","Asset Lite"]]},
{
"doctype": "Dashboard Chart",
"filters": [

View File

@ -0,0 +1,70 @@
function trim_and_upload_import_file(frm) {
frappe.confirm(
__(
'Trim leading and trailing spaces from all cells, upload the cleaned file, and update Import File?'
),
() => {
frappe.call({
method: 'asset_lite.api.data_import_api.trim_and_upload_import_file',
args: { data_import: frm.doc.name },
freeze: true,
freeze_message: __('Trimming and uploading file...'),
callback(r) {
if (!r.message) {
return;
}
frappe.show_alert(
{
message: r.message.summary || __('File trimmed and uploaded.'),
indicator: 'green',
},
5
);
frm.reload_doc().then(() => frm.trigger('import_file'));
},
});
}
);
}
function add_trim_and_upload_button(frm) {
if (frm.is_new() || !frm.doc.import_file || frm.doc.google_sheets_url) {
frm.fields_dict.import_file?.$wrapper?.find('.trim-upload-btn').remove();
return;
}
const label = __('Trim and Upload file');
const action = () => trim_and_upload_import_file(frm);
frm.add_custom_button(label, action, __('Actions'));
frm.page.add_action_item(label, action);
const field = frm.get_field('import_file');
if (!field?.$wrapper) {
return;
}
field.$wrapper.find('.trim-upload-btn').remove();
const $btn = $(`<button type="button" class="btn btn-default btn-sm trim-upload-btn" style="margin-left: 8px;">
${label}
</button>`);
$btn.on('click', action);
const $attachRow = field.$wrapper.find('.attached-file, .file-preview, .control-input-wrapper').last();
if ($attachRow.length) {
$attachRow.append($btn);
} else {
field.$wrapper.append($btn);
}
}
frappe.ui.form.on('Data Import', {
refresh(frm) {
add_trim_and_upload_button(frm);
},
onload_post_render(frm) {
add_trim_and_upload_button(frm);
},
});