diff --git a/DisposalProcessing.php b/DisposalProcessing.php new file mode 100644 index 000000000..f2a846aab --- /dev/null +++ b/DisposalProcessing.php @@ -0,0 +1,1079 @@ +SiteAdmin){ + header('Location: index.php'); + exit; + } + + $subheader=__("Automated Disposal Processing"); + + if(!defined('DISPOSAL_SESSION_KEY')){ + define('DISPOSAL_SESSION_KEY','opendcim_disposal_processing'); + } + + function disposalDefaultState(){ + return array( + 'stage'=>'idle', + 'file'=>'', + 'serials'=>array(), + 'categories'=>array( + 'unknown'=>array(), + 'not_storage'=>array(), + 'storage_ready'=>array(), + 'already_disposed'=>array() + ), + 'actions'=>array( + 'unknown_ignored'=>false, + 'unknown_created'=>false, + 'not_storage_moved'=>false, + 'final_processed'=>false, + 'rollback_done'=>false + ), + 'log'=>array(), + 'disposition_id'=>0, + 'operation_date'=>date('Y-m-d'), + 'completed'=>false, + 'cleanup_selected'=>array(), + 'sheets'=>array(), + 'selected_sheets'=>array(), + 'sheet_columns'=>array() + ); + } + + function disposalSaveState($state){ + $_SESSION[DISPOSAL_SESSION_KEY]=$state; + } + + function disposalLoadState(){ + if(isset($_SESSION[DISPOSAL_SESSION_KEY]) && is_array($_SESSION[DISPOSAL_SESSION_KEY])){ + $default=disposalDefaultState(); + $state=array_merge($default,$_SESSION[DISPOSAL_SESSION_KEY]); + foreach($default['categories'] as $key=>$value){ + if(!isset($state['categories'][$key]) || !is_array($state['categories'][$key])){ + $state['categories'][$key]=array(); + } + } + foreach($default['actions'] as $key=>$value){ + if(!isset($state['actions'][$key])){ + $state['actions'][$key]=false; + } + } + if(!isset($state['log']) || !is_array($state['log'])){ + $state['log']=array(); + } + return $state; + } + $state=disposalDefaultState(); + disposalSaveState($state); + return $state; + } + + function disposalResetState(){ + $state=disposalLoadState(); + if(isset($state['file']) && $state['file']!='' && file_exists($state['file'])){ + @unlink($state['file']); + } + $state=disposalDefaultState(); + disposalSaveState($state); + return $state; + } + + function disposalFormatLocation($device){ + $cabinetLabel=''; + $rowLabel=''; + $roomLabel=''; + if($device->Cabinet>0){ + $cab=new Cabinet(); + $cab->CabinetID=$device->Cabinet; + if($cab->GetCabinet()){ + $cabinetLabel=$cab->Location; + if($cab->CabRowID>0){ + $row=new CabRow(); + $row->CabRowID=$cab->CabRowID; + if($row->GetCabRow()){ + $rowLabel=$row->Name; + } + } + $dc=new DataCenter(); + $dc->DataCenterID=$cab->DataCenterID; + if($dc->GetDataCenter()){ + $roomLabel=$dc->Name; + } + } + }elseif($device->Cabinet==-1){ + $roomLabel=__("Storage Room"); + }elseif($device->Cabinet==0){ + $roomLabel=__("Disposed"); + } + return trim(sprintf("%s / %s / %s",$rowLabel,$roomLabel,$cabinetLabel),"/ "); + } + + function disposalBuildRecord($device){ + return array( + 'deviceid'=>$device->DeviceID, + 'label'=>$device->Label, + 'serial'=>$device->SerialNo, + 'cabinet'=>$device->Cabinet, + 'position'=>$device->Position, + 'status'=>$device->Status, + 'location'=>disposalFormatLocation($device) + ); + } + + function disposalAppendLog(&$state,$record,$operation,$actionType,$extra=array()){ + $state['log'][]=array( + 'deviceid'=>isset($record['deviceid'])?$record['deviceid']:0, + 'label'=>isset($record['label'])?$record['label']:'', + 'serial'=>isset($record['serial'])?$record['serial']:'', + 'previous_state'=>isset($extra['previous_state'])?$extra['previous_state']:array(), + 'cabinet'=>isset($record['cabinet'])?$record['cabinet']:'', + 'position'=>isset($record['position'])?$record['position']:'', + 'location'=>isset($record['location'])?$record['location']:__("N/A"), + 'timestamp'=>date('c'), + 'operation'=>$operation, + 'action_type'=>$actionType, + 'disposition_id'=>isset($extra['disposition_id'])?$extra['disposition_id']:0, + 'operation_date'=>isset($extra['operation_date'])?$extra['operation_date']:'' + ); + } + + function disposalPerformRollback(&$state,&$messages,&$errors){ + if(empty($state['log'])){ + $errors[]=__("No operations to rollback."); + return; + } + for($i=count($state['log'])-1;$i>=0;$i--){ + $entry=$state['log'][$i]; + $deviceID=intval($entry['deviceid']); + $prev=isset($entry['previous_state'])?$entry['previous_state']:array(); + switch($entry['action_type']){ + case 'move_to_storage': + case 'dispose': + if($deviceID>0){ + $dev=new Device($deviceID); + if($dev->GetDevice()){ + if(isset($prev['cabinet'])){ + $dev->Cabinet=$prev['cabinet']; + } + if(array_key_exists('position',$prev)){ + $dev->Position=$prev['position']; + } + if(isset($prev['status'])){ + $dev->Status=$prev['status']; + } + $dev->UpdateDevice(); + if($entry['action_type']=='dispose'){ + DispositionMembership::removeDevice($deviceID); + } + } + } + break; + case 'create_minimal': + if($deviceID>0){ + $dev=new Device($deviceID); + if($dev->GetDevice()){ + $dev->DeleteDevice(); + } + } + break; + default: + break; + } + } + $state['actions']['rollback_done']=true; + $messages[]=__("The rollback completed successfully."); + } + + function disposalAvailableCleanupOptions(){ + return array( + 'power'=>array('label'=>__("Clean power connections (fac_PowerPorts)")), + 'network'=>array('label'=>__("Clean network connections (fac_Ports)")), + 'projects'=>array('label'=>__("Remove project assignments (fac_ProjectMembership)")), + 'tags'=>array('label'=>__("Remove tags (fac_DeviceTags)")), + 'custom'=>array('label'=>__("Remove custom attributes (fac_DeviceCustomAttribute)")), + 'vm'=>array('label'=>__("Remove VM Inventory entries (fac_VMInventory)")), + 'logs'=>array('label'=>__("Clean logs (fac_GenericLog)"),'note'=>__("Not recommended: removing logs erases historical traceability.")), + 'rack'=>array('label'=>__("Clean rack request history (fac_RackRequest)")) + ); + } + + function disposalCleanupDevice($device,$options,&$state,$record){ + global $dbh; + + if(empty($options) || !is_array($options)){ + return; + } + + foreach($options as $option){ + switch($option){ + case 'power': + $st=$dbh->prepare("DELETE FROM fac_PowerPorts WHERE DeviceID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("Power connections cleaned"),'cleanup_power',array('cleanup_option'=>'power')); + break; + case 'network': + $st=$dbh->prepare("DELETE FROM fac_Ports WHERE DeviceID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("Network connections cleaned"),'cleanup_network',array('cleanup_option'=>'network')); + break; + case 'projects': + $st=$dbh->prepare("DELETE FROM fac_ProjectMembership WHERE MemberType='Device' AND MemberID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("Project assignments removed"),'cleanup_projects',array('cleanup_option'=>'projects')); + break; + case 'tags': + $st=$dbh->prepare("DELETE FROM fac_DeviceTags WHERE DeviceID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("Device tags removed"),'cleanup_tags',array('cleanup_option'=>'tags')); + break; + case 'custom': + $st=$dbh->prepare("DELETE FROM fac_DeviceCustomValue WHERE DeviceID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("Custom attributes removed"),'cleanup_custom',array('cleanup_option'=>'custom')); + break; + case 'vm': + $st=$dbh->prepare("DELETE FROM fac_VMInventory WHERE DeviceID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("VM inventory entries removed"),'cleanup_vm',array('cleanup_option'=>'vm')); + break; + case 'logs': + $st=$dbh->prepare("DELETE FROM fac_GenericLog WHERE Class='Device' AND ObjectID=:DeviceID"); + $st->execute(array(":DeviceID"=>$device->DeviceID)); + disposalAppendLog($state,$record,__("Device logs cleaned"),'cleanup_logs',array('cleanup_option'=>'logs')); + break; + case 'rack': + if($device->SerialNo!=''){ + // RackRequest table tracks SerialNo instead of DeviceID + $st=$dbh->prepare("DELETE FROM fac_RackRequest WHERE SerialNo=:SerialNo"); + $st->execute(array(":SerialNo"=>$device->SerialNo)); + } + disposalAppendLog($state,$record,__("Rack request history cleaned"),'cleanup_rack',array('cleanup_option'=>'rack')); + break; + default: + break; + } + } + } + + function disposalNormalizeHeader($value){ + $value=trim(mb_strtolower($value ?? '')); + if(function_exists('iconv')){ + $converted=@iconv('UTF-8','ASCII//TRANSLIT//IGNORE',$value); + if($converted!==false){ + $value=$converted; + } + } + $value=preg_replace('/[^a-z0-9]+/','',$value); + return $value; + } + + function disposalGuessSerialColumn($headers){ + $references=array('sn','serialno','serialnumber','serialnum','serialnumber','numerodeserie','numerodeserie','ndeserie'); + foreach($headers as $index=>$header){ + $normalized=disposalNormalizeHeader($header); + if($normalized=='' ){ + continue; + } + if(in_array($normalized,$references)){ + return $index; + } + } + return -1; + } + + function disposalDetectSheetHeader($sheet){ + $highestColumn=\PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($sheet->getHighestColumn()); + $highestRow=min(20,$sheet->getHighestRow()); + for($row=1;$row<=$highestRow;$row++){ + $headers=array(); + $nonEmpty=0; + $textCount=0; + for($col=1;$col<=$highestColumn;$col++){ + $cell=$sheet->getCellByColumnAndRow($col,$row); + $value=''; + if(!$cell->isInMergeRange()){ + $value=trim((string)$cell->getCalculatedValue()); + } + if($value!==''){ + $nonEmpty++; + if(preg_match('/[A-Za-z]/u',$value)){ + $textCount++; + } + } + $headers[]=$value; + } + if($nonEmpty>=2 && $textCount>=1){ + $snIndex=disposalGuessSerialColumn($headers); + return array('row'=>$row,'headers'=>$headers,'sn_column'=>$snIndex); + } + } + return null; + } + + $messages=array(); + $errors=array(); + $state=disposalLoadState(); + + if($_SERVER['REQUEST_METHOD']!='POST' && !isset($_GET['downloadlog'])){ + if($state['stage']!='idle'){ + $state=disposalResetState(); + } + } + + if(isset($_GET['downloadlog']) && !empty($state['log'])){ + header('Content-Type: text/csv; charset=UTF-8'); + header('Content-Disposition: attachment;filename="disposal-log-'.date('YmdHis').'.csv"'); + echo "DeviceID,Label,SerialNumber,PreviousCabinet,PreviousPosition,Operation,Timestamp\r\n"; + foreach($state['log'] as $entry){ + $row=array( + $entry['deviceid'], + preg_replace('/"/','""',$entry['label']), + preg_replace('/"/','""',$entry['serial']), + isset($entry['previous_state']['cabinet'])?$entry['previous_state']['cabinet']:'', + isset($entry['previous_state']['position'])?$entry['previous_state']['position']:'', + preg_replace('/"/','""',$entry['operation']), + $entry['timestamp'] + ); + echo '"'.implode('","',$row).'"'."\r\n"; + } + exit; + } + + if(isset($_POST['dp-action'])){ + $action=$_POST['dp-action']; + switch($action){ + case 'upload': + $state=disposalResetState(); + if(!isset($_FILES['disposalfile']) || $_FILES['disposalfile']['error']!=UPLOAD_ERR_OK){ + $errors[]=__("Invalid file uploaded."); + break; + } + try{ + $inputType=\PhpOffice\PhpSpreadsheet\IOFactory::identify($_FILES['disposalfile']['tmp_name']); + $reader=\PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputType); + $spreadsheet=$reader->load($_FILES['disposalfile']['tmp_name']); + }catch(Exception $e){ + $errors[]=__("The uploaded file could not be read."); + break; + } + $sheetDetails=array(); + for($idx=0;$idx<$spreadsheet->getSheetCount();$idx++){ + $currentSheet=$spreadsheet->getSheet($idx); + $headerInfo=disposalDetectSheetHeader($currentSheet); + if($headerInfo){ + $sheetDetails[$idx]=array( + 'name'=>$currentSheet->getTitle(), + 'header_row'=>$headerInfo['row'], + 'headers'=>$headerInfo['headers'], + 'detected_column'=>$headerInfo['sn_column'] + ); + } + } + if(empty($sheetDetails)){ + $errors[]=__("No valid worksheets were detected in the uploaded file."); + @unlink($target); + break; + } + $tmp=tempnam(sys_get_temp_dir(),'dp_'); + @unlink($tmp); + $target=$tmp.'.xlsx'; + if(!move_uploaded_file($_FILES['disposalfile']['tmp_name'],$target)){ + $errors[]=__("Invalid file uploaded."); + break; + } + $state['file']=$target; + $state['sheets']=$sheetDetails; + $state['selected_sheets']=array_keys($sheetDetails); + $state['sheet_columns']=array(); + $state['stage']='select_sheets'; + $messages[]=__("File uploaded successfully. Please select the worksheets to process."); + break; + case 'select-sheets': + if(empty($_POST['sheets']) || !is_array($_POST['sheets'])){ + $errors[]=__("Please select at least one worksheet."); + break; + } + $selected=array(); + foreach($_POST['sheets'] as $sheetKey){ + $sheetIndex=intval($sheetKey); + if(isset($state['sheets'][$sheetIndex])){ + $selected[]=$sheetIndex; + } + } + if(empty($selected)){ + $errors[]=__("Please select at least one worksheet."); + break; + } + $state['selected_sheets']=$selected; + $state['sheet_columns']=array(); + foreach($selected as $sheetIndex){ + $detected=$state['sheets'][$sheetIndex]['detected_column']; + if($detected>=0){ + $state['sheet_columns'][$sheetIndex]=$detected; + } + } + $state['stage']='select_column'; + break; + case 'set-columns': + if($state['file']=='' || !file_exists($state['file'])){ + $errors[]=__("The uploaded file could not be read."); + break; + } + if(empty($state['selected_sheets'])){ + $errors[]=__("Please select at least one worksheet."); + break; + } + $requestedColumns=isset($_POST['sheet_columns']) && is_array($_POST['sheet_columns'])?$_POST['sheet_columns']:array(); + $sheetColumns=array(); + $columnError=false; + foreach($state['selected_sheets'] as $sheetIndex){ + if(!isset($requestedColumns[$sheetIndex]) || $requestedColumns[$sheetIndex]===''){ + $errors[]=sprintf(__("Please select a serial number column for sheet %s."),$state['sheets'][$sheetIndex]['name']); + $columnError=true; + break; + } + $colIndex=intval($requestedColumns[$sheetIndex]); + $headers=$state['sheets'][$sheetIndex]['headers']; + if($colIndex<0 || $colIndex>=count($headers) || trim($headers[$colIndex])===''){ + $errors[]=sprintf(__("Invalid column selection for sheet %s."),$state['sheets'][$sheetIndex]['name']); + $columnError=true; + break; + } + $sheetColumns[$sheetIndex]=$colIndex; + } + if($columnError){ + break; + } + $state['sheet_columns']=$sheetColumns; + try{ + $inputType=\PhpOffice\PhpSpreadsheet\IOFactory::identify($state['file']); + $reader=\PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputType); + $spreadsheet=$reader->load($state['file']); + }catch(Exception $e){ + $errors[]=__("The uploaded file could not be read."); + break; + } + $serials=array(); + foreach($state['selected_sheets'] as $sheetIndex){ + $sheet=$spreadsheet->getSheet($sheetIndex); + $headerRow=$state['sheets'][$sheetIndex]['header_row']; + $columnNumber=$sheetColumns[$sheetIndex]+1; + $highestRow=$sheet->getHighestRow(); + for($row=$headerRow+1;$row<=$highestRow;$row++){ + $cell=$sheet->getCellByColumnAndRow($columnNumber,$row); + $value=$cell->getCalculatedValue(); + $serial=trim((string)$value); + if($serial!==''){ + $serials[$serial]=$serial; + } + } + } + $serials=array_values($serials); + if(empty($serials)){ + $errors[]=__("No serial numbers were found in the selected column."); + break; + } + $categories=array( + 'unknown'=>array(), + 'not_storage'=>array(), + 'storage_ready'=>array(), + 'already_disposed'=>array() + ); + foreach($serials as $serial){ + $device=Device::GetDeviceBySerialNumber($serial,false); + if(!$device){ + $categories['unknown'][]=array('serial'=>$serial); + continue; + } + $record=disposalBuildRecord($device); + if($device->Cabinet>0){ + $categories['not_storage'][$device->DeviceID]=$record; + }elseif($device->Cabinet==-1){ + $categories['storage_ready'][$device->DeviceID]=$record; + }elseif($device->Cabinet==0){ + $record['status_note']=__("Already disposed / Skipped"); + $categories['already_disposed'][$device->DeviceID]=$record; + disposalAppendLog($state,$record,__("Already disposed / Skipped"),'skipped'); + }else{ + $categories['not_storage'][$device->DeviceID]=$record; + } + } + $state['serials']=$serials; + $state['categories']=$categories; + $state['stage']='ready'; + $messages[]=__("Serial numbers ready for processing."); + break; + case 'ignore-unknown': + if($state['actions']['unknown_ignored']){ + break; + } + if(!empty($state['categories']['unknown'])){ + foreach($state['categories']['unknown'] as $entry){ + $record=array('serial'=>$entry['serial'],'location'=>__("N/A")); + disposalAppendLog($state,$record,sprintf(__("Ignored unknown serial number %s"),$entry['serial']),'ignored'); + } + $state['categories']['unknown']=array(); + $state['actions']['unknown_ignored']=true; + $messages[]=__("Unknown serial numbers have been ignored."); + } + break; + case 'create-minimal': + if($state['actions']['unknown_created']){ + break; + } + if(!empty($state['categories']['unknown'])){ + foreach($state['categories']['unknown'] as $entry){ + $serial=$entry['serial']; + $device=new Device(); + $device->Label=$serial; + $device->SerialNo=$serial; + $device->Cabinet=-1; + $device->Position=0; + $device->Height=0; + $device->Ports=0; + $device->NominalWatts=0; + $device->PowerSupplyCount=0; + $device->FirstPortNum=1; + $device->TemplateID=0; + $device->DeviceType='Server'; + $device->ChassisSlots=0; + $device->RearChassisSlots=0; + $device->ParentDevice=0; + $device->Status='Reserved'; + $device->AuditStamp=date('Y-m-d'); + $device->MfgDate=date('Y-m-d'); + $device->InstallDate=date('Y-m-d'); + $device->WarrantyExpire=date('Y-m-d'); + if($device->CreateDevice()){ + $record=disposalBuildRecord($device); + $state['categories']['storage_ready'][$device->DeviceID]=$record; + disposalAppendLog($state,$record,__("Created minimal storage record"),'create_minimal',array('previous_state'=>array('created'=>true))); + }else{ + $errors[]=sprintf(__("Failed to create device for serial number %s."),$serial); + } + } + $state['categories']['unknown']=array(); + $state['actions']['unknown_created']=true; + $messages[]=__("Minimal device entries created successfully."); + } + break; + case 'move-to-storage': + if($state['actions']['not_storage_moved']){ + break; + } + $selected=isset($_POST['deviceids'])?$_POST['deviceids']:array(); + if(!is_array($selected) || empty($selected)){ + $errors[]=__("Please select at least one device to move."); + break; + } + foreach($selected as $deviceID){ + $deviceID=intval($deviceID); + if(!isset($state['categories']['not_storage'][$deviceID])){ + continue; + } + $record=$state['categories']['not_storage'][$deviceID]; + $dev=new Device($deviceID); + if($dev->GetDevice()){ + $prevState=array('cabinet'=>$dev->Cabinet,'position'=>$dev->Position,'status'=>$dev->Status); + $dev->MoveToStorage(); + $updated=disposalBuildRecord($dev); + $state['categories']['storage_ready'][$deviceID]=$updated; + disposalAppendLog($state,$record,__("Moved to Storage Room"),'move_to_storage',array('previous_state'=>$prevState)); + } + unset($state['categories']['not_storage'][$deviceID]); + } + $state['actions']['not_storage_moved']=true; + $messages[]=__("Devices moved to the Storage Room."); + break; + case 'start-processing': + if($state['actions']['final_processed']){ + break; + } + $cleanupSelected=array(); + if(isset($_POST['cleanup_options']) && is_array($_POST['cleanup_options'])){ + foreach($_POST['cleanup_options'] as $optionKey){ + $filtered=preg_replace('/[^a-z_]/','',$optionKey); + if($filtered!=''){ + $cleanupSelected[$filtered]=$filtered; + } + } + } + $cleanupSelected=array_values($cleanupSelected); + $state['cleanup_selected']=$cleanupSelected; + if(empty($state['categories']['storage_ready'])){ + $errors[]=__("No devices are ready in the Storage Room."); + break; + } + $dispID=isset($_POST['dispositionid'])?intval($_POST['dispositionid']):0; + $dispList=Disposition::getDisposition($dispID); + if(empty($dispList)){ + $errors[]=__("Please select a valid disposal method."); + break; + } + $operationDate=isset($_POST['operation_date'])?$_POST['operation_date']:''; + $dateCheck=DateTime::createFromFormat('Y-m-d',$operationDate); + if(!$dateCheck){ + $errors[]=__("Please provide a valid operation date."); + break; + } + global $dbh; + foreach($state['categories']['storage_ready'] as $deviceID=>$record){ + $dev=new Device($deviceID); + if($dev->GetDevice()){ + $prevState=array('cabinet'=>$dev->Cabinet,'position'=>$dev->Position,'status'=>$dev->Status); + if(!empty($cleanupSelected)){ + disposalCleanupDevice($dev,$cleanupSelected,$state,$record); + } + $dev->Dispose($dispID); + $st=$dbh->prepare("UPDATE fac_DispositionMembership SET DispositionDate=:DispositionDate WHERE DeviceID=:DeviceID"); + $st->execute(array(":DispositionDate"=>$operationDate,":DeviceID"=>$deviceID)); + disposalAppendLog($state,$record,__("Disposed from Storage Room"),'dispose',array('previous_state'=>$prevState,'disposition_id'=>$dispID,'operation_date'=>$operationDate)); + } + } + $state['categories']['storage_ready']=array(); + $state['actions']['final_processed']=true; + $state['completed']=true; + $state['disposition_id']=$dispID; + $state['operation_date']=$operationDate; + $messages[]=__("Devices disposed successfully."); + break; + case 'rollback': + if($state['actions']['rollback_done']){ + break; + } + disposalPerformRollback($state,$messages,$errors); + break; + case 'complete': + $state=disposalResetState(); + $messages[]=__("Process completed."); + break; + case 'cancel': + $state=disposalResetState(); + $messages[]=__("Operation cancelled."); + break; + default: + break; + } + disposalSaveState($state); + } + + $dList=Disposition::getDisposition(); +?> + + + + + + <?php echo __("Automated Disposal Processing"); ?> + + + + + + + + +
+ +
+
+
+ +
+
+
+ %s
',htmlspecialchars($msg)); + } + foreach($messages as $msg){ + printf('
%s
',htmlspecialchars($msg)); + } + ?> +
+
+

+ +
+ +
+ + +
+
+
+ +
+ +
+
+
+ $sheetInfo){ + $checked=(in_array($sheetIndex,$state['selected_sheets']))?' checked':''; + ?> + + +
+
+
+
+
+ + +
+ +
+ + +
+ + + +
+ +
+ +
+
+
+ + +
+ +
+
+
+ +
+

+
+
+
+
+
+ '.__("No records found.").'
'; + }else{ + foreach($state['categories']['unknown'] as $entry){ + printf('
%s
',htmlspecialchars($entry['serial'])); + } + } + ?> +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+

+
+ +
+
+
+
+
+
+
+
+ '.__("No records found.").'
'; + }else{ + foreach($state['categories']['not_storage'] as $record){ + printf('
%s
%s
%s
%s
', + $record['deviceid'], + $record['deviceid'], + htmlspecialchars($record['label']), + htmlspecialchars($record['serial']), + htmlspecialchars($record['location']) + ); + } + } + ?> +
+
+ +
+ +
+ + +
+
+
+
+
+

+
+
+
+
+
+
+
+
+ '.__("No records found.").'
'; + }else{ + foreach($state['categories']['storage_ready'] as $record){ + printf('
%s
%s
%s
%s
%s
', + $record['deviceid'], + htmlspecialchars($record['label']), + htmlspecialchars($record['serial']), + htmlspecialchars($record['location']), + htmlspecialchars($record['position']) + ); + } + } + ?> +
+

+ +
+
+
+

+

+
+
+
+
+
+
+
+
+ '.__("No records found.").'
'; + }else{ + foreach($state['categories']['already_disposed'] as $record){ + $statusNote=isset($record['status_note'])?$record['status_note']:__("Already disposed / Skipped"); + printf('
%s
%s
%s
%s
%s
', + $record['deviceid'], + htmlspecialchars($record['label']), + htmlspecialchars($record['serial']), + htmlspecialchars($statusNote), + htmlspecialchars($record['location']) + ); + } + } + ?> +
+ +
+
+

+
+ +
+
+
+
+ $optionMeta){ + $checked=(in_array($optionKey,$state['cleanup_selected']))?' checked':''; + echo ''; + } + ?> +
+
+
+ + +
+
+ + +
+ +
+
+ +
+
+ % + +
+
+
+ +
+
+
+
+ +
+

+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + + + + diff --git a/README_DisposalProcessing.md b/README_DisposalProcessing.md new file mode 100644 index 000000000..daaebd39f --- /dev/null +++ b/README_DisposalProcessing.md @@ -0,0 +1,63 @@ +# Disposal Processing Workflow + +## Overview (English) +The `DisposalProcessing.php` page, available under **Bulk Importer > Process Scrap**, guides Site Administrators through an automated disposal workflow using the logistics Excel file. It imports serial numbers, classifies devices into four categories, offers per-category actions, and records every step for auditing/rollback. + +## Vue d’ensemble (Français) +La page `DisposalProcessing.php`, accessible via **Bulk Importer > Process Scrap**, accompagne les administrateurs de site dans un workflow automatisé de traitement du rebut à partir du fichier Excel logistique. Elle importe les numéros de série, classe les équipements en quatre catégories, propose les actions adaptées et journalise chaque étape avec option de rollback. + +--- + +## Expected File (EN) +- **Format**: Excel XLSX/XLS (one per run). +- **Source**: Logistics spreadsheet listing devices slated for disposal. +- **Pivot Column**: `Serial/No` (or similar). Only this column is used—others are ignored. +- Recommendation: ensure serials exist in OpenDCIM to minimize minimal-entry creation. + +## Fichier attendu (FR) +- **Format** : Excel XLSX/XLS (un par session). +- **Origine** : Fichier logistique listant les équipements destinés à la destruction/recyclage. +- **Colonne pivot** : `Serial/No` (ou équivalente). Seule cette colonne est utilisée, les autres sont ignorées. +- Recommandation : vérifier que les numéros sont présents dans OpenDCIM afin de limiter les créations minimales. + +--- + +## Workflow Steps (EN) +1. **Upload** the Excel file; headers can be detected up to row 20. +2. **Select** the worksheet tabs to include and confirm/adjust the detected Serial Number column (auto-mapped to labels like “SN”, “Serial Number”, etc.). +3. **Categorize** devices: + - Category 1: unknown serials (not in DB). + - Category 2: devices still installed (Cabinet > 0). + - Category 3: devices in Storage Room (Cabinet = -1). + - Category 4: already disposed (Cabinet = 0). +4. **Optional cleanups**: choose power/network/project/tag/custom/VM/log/rack cleanups to run before disposal. +5. **Actions**: ignore/create minimal entries, move to storage, confirm disposal, etc. +6. **Finalize**: choose disposition method/date, run `Device::Dispose()` for confirmed devices and log each action. +7. **Log & Rollback**: download CSV log; rollback restores cabinet/position and removes fac_DispositionMembership updates. +8. **Reset**: reloading the page resets the wizard for a new file. + +## Étapes du workflow (FR) +1. **Importer** le fichier Excel ; les en-têtes sont détectées jusqu’à la ligne 20. +2. **Sélectionner** les onglets à traiter et confirmer/ajuster la colonne de numéro de série détectée (SN, Serial Number, Numéro de série, etc.). +3. **Catégoriser** les équipements : + - Catégorie 1 : numéros inconnus (absents de la base). + - Catégorie 2 : équipements encore installés (Cabinet > 0). + - Catégorie 3 : équipements présents en Storage Room (Cabinet = -1). + - Catégorie 4 : équipements déjà sortis (Cabinet = 0). +4. **Nettoyages optionnels** : choisir les actions (énergie/réseau/projets/tags/attributs/VM/journaux/historique rack) à exécuter avant la destruction. +5. **Actions** : ignorer/créer des entrées minimales, déplacer en Storage Room, confirmer la méthode de sortie, etc. +6. **Traitement final** : choisir méthode/date, lancer `Device::Dispose()` pour chaque équipement confirmé avec journalisation. +7. **Journal & rollback** : export CSV, bouton d’annulation restaurent armoires/positions et nettoient fac_DispositionMembership. +8. **Réinitialisation** : actualiser la page (GET) redémarre l’assistant pour un nouveau fichier. + +--- + +## Best Practices (EN) +- Validate serial coverage before import; use minimal entries only when required. +- Run a full DB backup before starting (warning displayed in UI). +- Download the log after each session to keep an auditable record. + +## Bonnes pratiques (FR) +- Vérifier la présence des numéros avant import pour éviter les créations minimales. +- Faire une sauvegarde complète avant le traitement automatisé (avertissement sur la page). +- Télécharger le journal après chaque session pour conserver une trace exploitable. diff --git a/classes/Device.class.php b/classes/Device.class.php index 999d048ec..1913edb87 100644 --- a/classes/Device.class.php +++ b/classes/Device.class.php @@ -1545,6 +1545,18 @@ function SearchDevicebySerialNo(){ return $deviceList; } + static function GetDeviceBySerialNumber($serial,$filterrights=true){ + global $dbh; + + $serial=sanitize($serial); + $st=$dbh->prepare("SELECT * FROM fac_Device WHERE SerialNo=:SerialNo LIMIT 1"); + $st->execute(array(":SerialNo"=>$serial)); + if(($row=$st->fetch(PDO::FETCH_ASSOC))!==false){ + return Device::RowToObject($row,$filterrights); + } + return null; + } + function SearchDevicebyAssetTag(){ global $dbh; diff --git a/locale/en_US/LC_MESSAGES/openDCIM.po b/locale/en_US/LC_MESSAGES/openDCIM.po index 360fc3acb..ea6b7a1a9 100644 --- a/locale/en_US/LC_MESSAGES/openDCIM.po +++ b/locale/en_US/LC_MESSAGES/openDCIM.po @@ -6231,3 +6231,416 @@ msgstr "First port detection failed, please report to openDCIM developers" #~ msgid "People resource for UserID=%1$s updated successfully." #~ msgstr "People resource for UserID=%1$s updated successfully." + +#: DisposalProcessing.php +msgid "Automated Disposal Processing" +msgstr "Automated Disposal Processing" + + +#: DisposalProcessing.php +msgid "N/A" +msgstr "N/A" + + +#: DisposalProcessing.php +msgid "Invalid file uploaded." +msgstr "Invalid file uploaded." + + +#: DisposalProcessing.php +msgid "The uploaded file could not be read." +msgstr "The uploaded file could not be read." + + +#: DisposalProcessing.php +msgid "File uploaded successfully. Please select the column that contains the serial numbers." +msgstr "File uploaded successfully. Please select the column that contains the serial numbers." + + +#: DisposalProcessing.php +msgid "Please select a valid column." +msgstr "Please select a valid column." + + +#: DisposalProcessing.php +msgid "No serial numbers were found in the selected column." +msgstr "No serial numbers were found in the selected column." + + +#: DisposalProcessing.php +msgid "Serial numbers ready for processing." +msgstr "Serial numbers ready for processing." + + +#: DisposalProcessing.php +msgid "Already disposed / Skipped" +msgstr "Already disposed / Skipped" + + +#: DisposalProcessing.php +msgid "Unknown serial numbers have been ignored." +msgstr "Unknown serial numbers have been ignored." + + +#: DisposalProcessing.php +msgid "Ignored unknown serial number %s" +msgstr "Ignored unknown serial number %s" + + +#: DisposalProcessing.php +msgid "Created minimal storage record" +msgstr "Created minimal storage record" + + +#: DisposalProcessing.php +msgid "Failed to create device for serial number %s." +msgstr "Failed to create device for serial number %s." + + +#: DisposalProcessing.php +msgid "Minimal device entries created successfully." +msgstr "Minimal device entries created successfully." + + +#: DisposalProcessing.php +msgid "Please select at least one device to move." +msgstr "Please select at least one device to move." + + +#: DisposalProcessing.php +msgid "Moved to Storage Room" +msgstr "Moved to Storage Room" + + +#: DisposalProcessing.php +msgid "Devices moved to the Storage Room." +msgstr "Devices moved to the Storage Room." + + +#: DisposalProcessing.php +msgid "Storage Room devices confirmed. You may proceed with the final processing." +msgstr "Storage Room devices confirmed. You may proceed with the final processing." + + +#: DisposalProcessing.php +msgid "Please confirm the Storage Room devices first." +msgstr "Please confirm the Storage Room devices first." + + +#: DisposalProcessing.php +msgid "No devices are ready in the Storage Room." +msgstr "No devices are ready in the Storage Room." + + +#: DisposalProcessing.php +msgid "Please select a valid disposal method." +msgstr "Please select a valid disposal method." + + +#: DisposalProcessing.php +msgid "Please provide a valid operation date." +msgstr "Please provide a valid operation date." + + +#: DisposalProcessing.php +msgid "Disposed from Storage Room" +msgstr "Disposed from Storage Room" + + +#: DisposalProcessing.php +msgid "Devices disposed successfully." +msgstr "Devices disposed successfully." + + +#: DisposalProcessing.php +msgid "Process completed." +msgstr "Process completed." + + +#: DisposalProcessing.php +msgid "Operation cancelled." +msgstr "Operation cancelled." + + +#: DisposalProcessing.php +msgid "Import disposal spreadsheet" +msgstr "Import disposal spreadsheet" + + +#: DisposalProcessing.php +msgid "Upload Excel File" +msgstr "Upload Excel File" + + +#: DisposalProcessing.php +msgid "Start Import" +msgstr "Start Import" + + +#: DisposalProcessing.php +msgid "Select Serial Number Column" +msgstr "Select Serial Number Column" + + +#: DisposalProcessing.php +msgid "Continue" +msgstr "Continue" + + +#: DisposalProcessing.php +msgid "Cancel operation" +msgstr "Cancel operation" + + +#: DisposalProcessing.php +msgid "Category 1: Unknown serial numbers" +msgstr "Category 1: Unknown serial numbers" + + +#: DisposalProcessing.php +msgid "Proceed without processing unknown SN" +msgstr "Proceed without processing unknown SN" + + +#: DisposalProcessing.php +msgid "Create minimal device entries automatically" +msgstr "Create minimal device entries automatically" + + +#: DisposalProcessing.php +msgid "Category 2: Devices not in the Storage Room" +msgstr "Category 2: Devices not in the Storage Room" + + +#: DisposalProcessing.php +msgid "Move selected devices to Storage Room" +msgstr "Move selected devices to Storage Room" + + +#: DisposalProcessing.php +msgid "Category 3: Devices ready in the Storage Room" +msgstr "Category 3: Devices ready in the Storage Room" + + +#: DisposalProcessing.php +msgid "Proceed with disposal method" +msgstr "Proceed with disposal method" + + +#: DisposalProcessing.php +msgid "Category 4: Already processed" +msgstr "Category 4: Already processed" + + +#: DisposalProcessing.php +msgid "Items already processed will be skipped." +msgstr "Items already processed will be skipped." + + +#: DisposalProcessing.php +msgid "Final disposal method" +msgstr "Final disposal method" + + +#: DisposalProcessing.php +msgid "Select disposition method" +msgstr "Select disposition method" + + +#: DisposalProcessing.php +msgid "Operation date" +msgstr "Operation date" + + +#: DisposalProcessing.php +msgid "Start Processing" +msgstr "Start Processing" + + +#: DisposalProcessing.php +msgid "Processing log" +msgstr "Processing log" + + +#: DisposalProcessing.php +msgid "Operation" +msgstr "Operation" + + +#: DisposalProcessing.php +msgid "Timestamp" +msgstr "Timestamp" + + +#: DisposalProcessing.php +msgid "Download full log" +msgstr "Download full log" + + +#: DisposalProcessing.php +msgid "Undo last operation" +msgstr "Undo last operation" + + +#: DisposalProcessing.php +msgid "Process Completed" +msgstr "Process Completed" + + +#: DisposalProcessing.php +msgid "Before starting this automated disposal workflow, please perform a full database backup." +msgstr "Before starting this automated disposal workflow, please perform a full database backup." + + +#: DisposalProcessing.php +msgid "No records found." +msgstr "No records found." + + +#: DisposalProcessing.php +msgid "Process Scrap" +msgstr "Process Scrap" + + +#: DisposalProcessing.php +msgid "No operations to rollback." +msgstr "No operations to rollback." + + +#: DisposalProcessing.php +msgid "The rollback completed successfully." +msgstr "The rollback completed successfully." + + +#: DisposalProcessing.php +msgid "Please select the column that contains the serial numbers." +msgstr "Please select the column that contains the serial numbers." + +#: DisposalProcessing.php +msgid "File uploaded successfully. Please select the worksheets to process." +msgstr "File uploaded successfully. Please select the worksheets to process." + +#: DisposalProcessing.php +msgid "No valid worksheets were detected in the uploaded file." +msgstr "No valid worksheets were detected in the uploaded file." + +#: DisposalProcessing.php +msgid "Please select at least one worksheet." +msgstr "Please select at least one worksheet." + +#: DisposalProcessing.php +msgid "Please select a serial number column for sheet %s." +msgstr "Please select a serial number column for sheet %s." + +#: DisposalProcessing.php +msgid "Invalid column selection for sheet %s." +msgstr "Invalid column selection for sheet %s." + +#: DisposalProcessing.php +msgid "Select worksheets to process" +msgstr "Select worksheets to process" + +#: DisposalProcessing.php +msgid "Sheet \"%s\"" +msgstr "Sheet \"%s\"" + +#: DisposalProcessing.php +msgid "Sheet \"%s\" serial column" +msgstr "Sheet \"%s\" serial column" + +#: DisposalProcessing.php +msgid "The serial number column could not be detected. Please select the correct column." +msgstr "The serial number column could not be detected. Please select the correct column." + +#: DisposalProcessing.php +msgid "Review these devices before running the final processing step." +msgstr "Review these devices before running the final processing step." + +#: DisposalProcessing.php +msgid "Clean power connections (fac_PowerPorts)" +msgstr "Clean power connections (fac_PowerPorts)" + +#: DisposalProcessing.php +msgid "Clean network connections (fac_Ports)" +msgstr "Clean network connections (fac_Ports)" + +#: DisposalProcessing.php +msgid "Remove project assignments (fac_ProjectMembership)" +msgstr "Remove project assignments (fac_ProjectMembership)" + +#: DisposalProcessing.php +msgid "Remove tags (fac_DeviceTags)" +msgstr "Remove tags (fac_DeviceTags)" + +#: DisposalProcessing.php +msgid "Remove custom attributes (fac_DeviceCustomAttribute)" +msgstr "Remove custom attributes (fac_DeviceCustomAttribute)" + +#: DisposalProcessing.php +msgid "Remove VM Inventory entries (fac_VMInventory)" +msgstr "Remove VM Inventory entries (fac_VMInventory)" + +#: DisposalProcessing.php +msgid "Clean logs (fac_GenericLog)" +msgstr "Clean logs (fac_GenericLog)" + +#: DisposalProcessing.php +msgid "Not recommended: removing logs erases historical traceability." +msgstr "Not recommended: removing logs erases historical traceability." + +#: DisposalProcessing.php +msgid "Clean rack request history (fac_RackRequest)" +msgstr "Clean rack request history (fac_RackRequest)" + +#: DisposalProcessing.php +msgid "Power connections cleaned" +msgstr "Power connections cleaned" + +#: DisposalProcessing.php +msgid "Network connections cleaned" +msgstr "Network connections cleaned" + +#: DisposalProcessing.php +msgid "Project assignments removed" +msgstr "Project assignments removed" + +#: DisposalProcessing.php +msgid "Device tags removed" +msgstr "Device tags removed" + +#: DisposalProcessing.php +msgid "Custom attributes removed" +msgstr "Custom attributes removed" + +#: DisposalProcessing.php +msgid "VM inventory entries removed" +msgstr "VM inventory entries removed" + +#: DisposalProcessing.php +msgid "Device logs cleaned" +msgstr "Device logs cleaned" + +#: DisposalProcessing.php +msgid "Rack request history cleaned" +msgstr "Rack request history cleaned" + +#: DisposalProcessing.php +msgid "Optional cleanups help remove related records before final processing." +msgstr "Optional cleanups help remove related records before final processing." + +#: DisposalProcessing.php +msgid "Optional Cleanup Operations" +msgstr "Optional Cleanup Operations" + +#: DisposalProcessing.php +msgid "Processing completed successfully." +msgstr "Processing completed successfully." + +#: DisposalProcessing.php +msgid "Processing in progress..." +msgstr "Processing in progress..." + +#: DisposalProcessing.php +msgid "Initializing" +msgstr "Initializing" + diff --git a/locale/fr_FR/LC_MESSAGES/openDCIM.po b/locale/fr_FR/LC_MESSAGES/openDCIM.po index 4b1d3370d..3c761ff9d 100644 --- a/locale/fr_FR/LC_MESSAGES/openDCIM.po +++ b/locale/fr_FR/LC_MESSAGES/openDCIM.po @@ -6359,3 +6359,416 @@ msgstr "" #~ msgid "MailTo" #~ msgstr "Envoyé à " + +#: DisposalProcessing.php +msgid "Automated Disposal Processing" +msgstr "Traitement automatisé du rebut" + + +#: DisposalProcessing.php +msgid "N/A" +msgstr "N/D" + + +#: DisposalProcessing.php +msgid "Invalid file uploaded." +msgstr "Fichier importé invalide." + + +#: DisposalProcessing.php +msgid "The uploaded file could not be read." +msgstr "Le fichier importé n'a pas pu être lu." + + +#: DisposalProcessing.php +msgid "File uploaded successfully. Please select the column that contains the serial numbers." +msgstr "Fichier importé avec succès. Veuillez sélectionner la colonne contenant les numéros de série." + + +#: DisposalProcessing.php +msgid "Please select a valid column." +msgstr "Veuillez sélectionner une colonne valide." + + +#: DisposalProcessing.php +msgid "No serial numbers were found in the selected column." +msgstr "Aucun numéro de série trouvé dans la colonne sélectionnée." + + +#: DisposalProcessing.php +msgid "Serial numbers ready for processing." +msgstr "Numéros de série prêts pour le traitement." + + +#: DisposalProcessing.php +msgid "Already disposed / Skipped" +msgstr "Déjà traité / Ignoré" + + +#: DisposalProcessing.php +msgid "Unknown serial numbers have been ignored." +msgstr "Les numéros de série inconnus ont été ignorés." + + +#: DisposalProcessing.php +msgid "Ignored unknown serial number %s" +msgstr "Numéro de série inconnu %s ignoré" + + +#: DisposalProcessing.php +msgid "Created minimal storage record" +msgstr "Enregistrement minimal de stockage créé" + + +#: DisposalProcessing.php +msgid "Failed to create device for serial number %s." +msgstr "Impossible de créer l'équipement pour le numéro de série %s." + + +#: DisposalProcessing.php +msgid "Minimal device entries created successfully." +msgstr "Les enregistrements minimaux ont été créés avec succès." + + +#: DisposalProcessing.php +msgid "Please select at least one device to move." +msgstr "Veuillez sélectionner au moins un équipement à déplacer." + + +#: DisposalProcessing.php +msgid "Moved to Storage Room" +msgstr "Déplacé vers la salle de stockage" + + +#: DisposalProcessing.php +msgid "Devices moved to the Storage Room." +msgstr "Équipements déplacés vers la salle de stockage." + + +#: DisposalProcessing.php +msgid "Storage Room devices confirmed. You may proceed with the final processing." +msgstr "Équipements de la salle de stockage confirmés. Vous pouvez poursuivre le traitement final." + + +#: DisposalProcessing.php +msgid "Please confirm the Storage Room devices first." +msgstr "Veuillez d'abord confirmer les équipements de la salle de stockage." + + +#: DisposalProcessing.php +msgid "No devices are ready in the Storage Room." +msgstr "Aucun équipement prêt dans la salle de stockage." + + +#: DisposalProcessing.php +msgid "Please select a valid disposal method." +msgstr "Veuillez sélectionner une méthode de sortie valide." + + +#: DisposalProcessing.php +msgid "Please provide a valid operation date." +msgstr "Veuillez fournir une date d'opération valide." + + +#: DisposalProcessing.php +msgid "Disposed from Storage Room" +msgstr "Sorti de la salle de stockage" + + +#: DisposalProcessing.php +msgid "Devices disposed successfully." +msgstr "Équipements traités avec succès." + + +#: DisposalProcessing.php +msgid "Process completed." +msgstr "Processus terminé." + + +#: DisposalProcessing.php +msgid "Operation cancelled." +msgstr "Opération annulée." + + +#: DisposalProcessing.php +msgid "Import disposal spreadsheet" +msgstr "Importer la feuille de calcul de rebut" + + +#: DisposalProcessing.php +msgid "Upload Excel File" +msgstr "Importer un fichier Excel" + + +#: DisposalProcessing.php +msgid "Start Import" +msgstr "Démarrer l'import" + + +#: DisposalProcessing.php +msgid "Select Serial Number Column" +msgstr "Sélectionner la colonne des numéros de série" + + +#: DisposalProcessing.php +msgid "Continue" +msgstr "Continuer" + + +#: DisposalProcessing.php +msgid "Cancel operation" +msgstr "Annuler l'opération" + + +#: DisposalProcessing.php +msgid "Category 1: Unknown serial numbers" +msgstr "Catégorie 1 : Numéros de série inconnus" + + +#: DisposalProcessing.php +msgid "Proceed without processing unknown SN" +msgstr "Continuer sans traiter les numéros de série inconnus" + + +#: DisposalProcessing.php +msgid "Create minimal device entries automatically" +msgstr "Créer automatiquement des enregistrements minimaux" + + +#: DisposalProcessing.php +msgid "Category 2: Devices not in the Storage Room" +msgstr "Catégorie 2 : Équipements hors de la salle de stockage" + + +#: DisposalProcessing.php +msgid "Move selected devices to Storage Room" +msgstr "Déplacer les équipements sélectionnés vers la salle de stockage" + + +#: DisposalProcessing.php +msgid "Category 3: Devices ready in the Storage Room" +msgstr "Catégorie 3 : Équipements prêts dans la salle de stockage" + + +#: DisposalProcessing.php +msgid "Proceed with disposal method" +msgstr "Continuer avec la méthode de sortie" + + +#: DisposalProcessing.php +msgid "Category 4: Already processed" +msgstr "Catégorie 4 : Déjà traités" + + +#: DisposalProcessing.php +msgid "Items already processed will be skipped." +msgstr "Les éléments déjà traités seront ignorés." + + +#: DisposalProcessing.php +msgid "Final disposal method" +msgstr "Méthode de sortie finale" + + +#: DisposalProcessing.php +msgid "Select disposition method" +msgstr "Sélectionner la méthode de sortie" + + +#: DisposalProcessing.php +msgid "Operation date" +msgstr "Date d'opération" + + +#: DisposalProcessing.php +msgid "Start Processing" +msgstr "Démarrer le traitement" + + +#: DisposalProcessing.php +msgid "Processing log" +msgstr "Journal de traitement" + + +#: DisposalProcessing.php +msgid "Operation" +msgstr "Opération" + + +#: DisposalProcessing.php +msgid "Timestamp" +msgstr "Horodatage" + + +#: DisposalProcessing.php +msgid "Download full log" +msgstr "Télécharger le journal complet" + + +#: DisposalProcessing.php +msgid "Undo last operation" +msgstr "Annuler la dernière opération" + + +#: DisposalProcessing.php +msgid "Process Completed" +msgstr "Processus terminé" + + +#: DisposalProcessing.php +msgid "Before starting this automated disposal workflow, please perform a full database backup." +msgstr "Avant de démarrer ce processus automatisé de rebut, veuillez effectuer une sauvegarde complète de la base de données." + + +#: DisposalProcessing.php +msgid "No records found." +msgstr "Aucun enregistrement trouvé." + + +#: DisposalProcessing.php +msgid "Process Scrap" +msgstr "Traitement du rebut" + + +#: DisposalProcessing.php +msgid "No operations to rollback." +msgstr "Aucune opération à annuler." + + +#: DisposalProcessing.php +msgid "The rollback completed successfully." +msgstr "Le retour arrière a été effectué avec succès." + + +#: DisposalProcessing.php +msgid "Please select the column that contains the serial numbers." +msgstr "Veuillez sélectionner la colonne contenant les numéros de série." + +#: DisposalProcessing.php +msgid "File uploaded successfully. Please select the worksheets to process." +msgstr "Fichier importé avec succès. Veuillez sélectionner les feuilles de calcul à traiter." + +#: DisposalProcessing.php +msgid "No valid worksheets were detected in the uploaded file." +msgstr "Aucune feuille de calcul valide n'a été détectée dans le fichier importé." + +#: DisposalProcessing.php +msgid "Please select at least one worksheet." +msgstr "Veuillez sélectionner au moins une feuille de calcul." + +#: DisposalProcessing.php +msgid "Please select a serial number column for sheet %s." +msgstr "Veuillez sélectionner la colonne des numéros de série pour la feuille %s." + +#: DisposalProcessing.php +msgid "Invalid column selection for sheet %s." +msgstr "Sélection de colonne invalide pour la feuille %s." + +#: DisposalProcessing.php +msgid "Select worksheets to process" +msgstr "Sélectionnez les feuilles de calcul à traiter" + +#: DisposalProcessing.php +msgid "Sheet \"%s\"" +msgstr "Feuille \"%s\"" + +#: DisposalProcessing.php +msgid "Sheet \"%s\" serial column" +msgstr "Colonne de numéro de série pour la feuille \"%s\"" + +#: DisposalProcessing.php +msgid "The serial number column could not be detected. Please select the correct column." +msgstr "La colonne des numéros de série n'a pas pu être détectée. Veuillez sélectionner la colonne appropriée." + +#: DisposalProcessing.php +msgid "Review these devices before running the final processing step." +msgstr "Vérifiez ces équipements avant de lancer l'étape de traitement final." + +#: DisposalProcessing.php +msgid "Clean power connections (fac_PowerPorts)" +msgstr "Nettoyer les connexions électriques (fac_PowerPorts)" + +#: DisposalProcessing.php +msgid "Clean network connections (fac_Ports)" +msgstr "Nettoyer les connexions réseau (fac_Ports)" + +#: DisposalProcessing.php +msgid "Remove project assignments (fac_ProjectMembership)" +msgstr "Supprimer les affectations de projet (fac_ProjectMembership)" + +#: DisposalProcessing.php +msgid "Remove tags (fac_DeviceTags)" +msgstr "Supprimer les tags (fac_DeviceTags)" + +#: DisposalProcessing.php +msgid "Remove custom attributes (fac_DeviceCustomAttribute)" +msgstr "Supprimer les attributs personnalisés (fac_DeviceCustomAttribute)" + +#: DisposalProcessing.php +msgid "Remove VM Inventory entries (fac_VMInventory)" +msgstr "Supprimer les entrées d'inventaire VM (fac_VMInventory)" + +#: DisposalProcessing.php +msgid "Clean logs (fac_GenericLog)" +msgstr "Nettoyer les journaux (fac_GenericLog)" + +#: DisposalProcessing.php +msgid "Not recommended: removing logs erases historical traceability." +msgstr "Non recommandé : supprimer les journaux efface la traçabilité historique." + +#: DisposalProcessing.php +msgid "Clean rack request history (fac_RackRequest)" +msgstr "Nettoyer l'historique des demandes de rack (fac_RackRequest)" + +#: DisposalProcessing.php +msgid "Power connections cleaned" +msgstr "Connexions électriques nettoyées" + +#: DisposalProcessing.php +msgid "Network connections cleaned" +msgstr "Connexions réseau nettoyées" + +#: DisposalProcessing.php +msgid "Project assignments removed" +msgstr "Affectations de projet supprimées" + +#: DisposalProcessing.php +msgid "Device tags removed" +msgstr "Tags de l'équipement supprimés" + +#: DisposalProcessing.php +msgid "Custom attributes removed" +msgstr "Attributs personnalisés supprimés" + +#: DisposalProcessing.php +msgid "VM inventory entries removed" +msgstr "Entrées d'inventaire VM supprimées" + +#: DisposalProcessing.php +msgid "Device logs cleaned" +msgstr "Journaux de l'équipement nettoyés" + +#: DisposalProcessing.php +msgid "Rack request history cleaned" +msgstr "Historique des demandes de rack nettoyé" + +#: DisposalProcessing.php +msgid "Optional cleanups help remove related records before final processing." +msgstr "Les nettoyages optionnels permettent de supprimer les données liées avant le traitement final." + +#: DisposalProcessing.php +msgid "Optional Cleanup Operations" +msgstr "Opérations de nettoyage optionnelles" + +#: DisposalProcessing.php +msgid "Processing completed successfully." +msgstr "Traitement achevé avec succès." + +#: DisposalProcessing.php +msgid "Processing in progress..." +msgstr "Traitement en cours..." + +#: DisposalProcessing.php +msgid "Initializing" +msgstr "Initialisation" + diff --git a/misc.inc.php b/misc.inc.php index 4079fc543..22d608446 100644 --- a/misc.inc.php +++ b/misc.inc.php @@ -1009,6 +1009,9 @@ function buildnavmenu($ma,&$tl){ $wamenu[__("Bulk Importer")][]=''.__("Import Network Connections").''; $wamenu[__("Bulk Importer")][]=''.__("Import Power Connections").''; $wamenu[__("Bulk Importer")][]=''.__("Process Bulk Moves").''; + if($person->SiteAdmin){ + $wamenu[__("Bulk Importer")][]=''.__("Process Scrap").''; + } } if ( $person->SiteAdmin ) { $samenu[__("Template Management")][]=''.__("Edit Manufacturers").'';