FEAT_EXCEL MERGE HELPER ADDED
This commit is contained in:
parent
2094e680d7
commit
e8dd72da0e
@ -126,6 +126,10 @@ class JobWorker extends AdminController
|
||||
'employeesEnrollmentInsert' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\EmployeeServiceController',
|
||||
],
|
||||
'calculateMembersDemography' => [
|
||||
'type' => 'CC', // Handler Category
|
||||
'handler' => 'App\Controllers\LeadsController',
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,90 +4,215 @@ namespace App\Helpers;
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class ExcelMergeHelper
|
||||
{
|
||||
/**
|
||||
* Merge multiple Excel files into a single spreadsheet.
|
||||
*
|
||||
* @param array $filesToMerge Array of files to merge.
|
||||
* @param string $outputPath Path to save the merged file.
|
||||
* @return bool True if merge successful, false otherwise.
|
||||
*/
|
||||
public static function mergeExcelFiles(array $filesToMerge, string $outputPath): bool
|
||||
|
||||
protected static $logger;
|
||||
|
||||
public static function initialize()
|
||||
{
|
||||
self::$logger = \Config\Services::mylogger();
|
||||
}
|
||||
public static function mergeExcelFiles(array $filesToMerge, string $outputPath)
|
||||
{
|
||||
try {
|
||||
// Validate input: If no files are provided, throw an error.
|
||||
self::initialize();
|
||||
if (empty($filesToMerge)) {
|
||||
throw new Exception("No files provided for merging");
|
||||
throw new Exception("No files provided for merging.");
|
||||
}
|
||||
|
||||
// Initialize the base spreadsheet (starting with null, will be created with the first file)
|
||||
$spreadsheet = null;
|
||||
|
||||
// Loop through the files to merge
|
||||
foreach ($filesToMerge as $filePath => $sheetsToMerge) {
|
||||
// Load the current file
|
||||
$currentSpreadsheet = IOFactory::load($filePath);
|
||||
|
||||
// Debugging: Log the number of sheets in the current file
|
||||
error_log("File: $filePath has " . $currentSpreadsheet->getSheetCount() . " sheets.");
|
||||
|
||||
// If this is the first file, initialize the base spreadsheet
|
||||
if ($spreadsheet === null) {
|
||||
$spreadsheet = $currentSpreadsheet;
|
||||
|
||||
// If specific sheets are provided for the first file, remove the others
|
||||
if (!is_null($sheetsToMerge) && !empty($sheetsToMerge)) {
|
||||
$sheetCount = $spreadsheet->getSheetCount();
|
||||
for ($i = $sheetCount - 1; $i >= 0; $i--) {
|
||||
if (!in_array($i, $sheetsToMerge)) {
|
||||
$spreadsheet->removeSheetByIndex($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we are not working with the first file, we need to merge specified sheets
|
||||
if (is_null($sheetsToMerge) || empty($sheetsToMerge)) {
|
||||
// If no specific sheets, merge all sheets
|
||||
$sheetsToMerge = range(0, $currentSpreadsheet->getSheetCount() - 1);
|
||||
}
|
||||
|
||||
// Merge the specified sheets from the current file
|
||||
foreach ($sheetsToMerge as $sheetIndex) {
|
||||
// Check if sheet index is within bounds
|
||||
if ($sheetIndex < 0 || $sheetIndex >= $currentSpreadsheet->getSheetCount()) {
|
||||
throw new Exception("Invalid sheet index $sheetIndex in file $filePath");
|
||||
}
|
||||
|
||||
// Retrieve the sheet to copy
|
||||
$sheetToCopy = $currentSpreadsheet->getSheet($sheetIndex);
|
||||
|
||||
// Handle sheet name collision by renaming the sheet with a counter
|
||||
$baseName = $sheetToCopy->getTitle();
|
||||
$sheetName = $baseName;
|
||||
$counter = 1;
|
||||
while ($spreadsheet->sheetNameExists($sheetName)) {
|
||||
$sheetName = $baseName . '_merged_' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
$sheetToCopy->setTitle($sheetName);
|
||||
|
||||
// Clone and add the sheet to the base spreadsheet
|
||||
$spreadsheet->addSheet(clone $sheetToCopy);
|
||||
}
|
||||
// Validate files exist and are readable
|
||||
foreach ($filesToMerge as $fileData) {
|
||||
if (!file_exists($fileData['file_path'])) {
|
||||
throw new Exception("File not found: " . $fileData['file_path']);
|
||||
}
|
||||
if (!is_readable($fileData['file_path'])) {
|
||||
throw new Exception("File not readable: " . $fileData['file_path']);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the merged file
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save($outputPath);
|
||||
self::$logger->logme('error',"Creating new spreadsheet...");
|
||||
$mergedSpreadsheet = new Spreadsheet();
|
||||
$firstSheet = true;
|
||||
|
||||
foreach ($filesToMerge as $fileIndex => $fileData) {
|
||||
$filePath = $fileData['file_path'];
|
||||
$sheetsToMerge = $fileData['sheets'];
|
||||
self::$logger->logme('error',"Processing file $fileIndex: $filePath");
|
||||
|
||||
try {
|
||||
self::$logger->logme('error',"Attempting to load file: $filePath");
|
||||
$reader = IOFactory::createReaderForFile($filePath);
|
||||
$reader->setReadDataOnly(true); // This can help with memory usage
|
||||
self::$logger->logme('error',"Reader created successfully");
|
||||
|
||||
$currentSpreadsheet = $reader->load($filePath);
|
||||
self::$logger->logme('error',"File loaded successfully");
|
||||
|
||||
$sheetCount = $currentSpreadsheet->getSheetCount();
|
||||
self::$logger->logme('error',"File $filePath has $sheetCount sheets.");
|
||||
|
||||
if (empty($sheetsToMerge)) {
|
||||
$sheetsToMerge = range(0, $sheetCount - 1);
|
||||
}
|
||||
|
||||
foreach ($sheetsToMerge as $sourceSheetIndex) {
|
||||
self::$logger->logme('error',"Processing sheet index: $sourceSheetIndex");
|
||||
|
||||
if ($sourceSheetIndex < 0 || $sourceSheetIndex >= $sheetCount) {
|
||||
self::$logger->logme('error',"Invalid sheet index $sourceSheetIndex in file $filePath");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$sourceSheet = $currentSpreadsheet->getSheet($sourceSheetIndex);
|
||||
self::$logger->logme('error',"Source sheet loaded");
|
||||
|
||||
$baseName = $sourceSheet->getTitle();
|
||||
self::$logger->logme('error',"Processing sheet: $baseName");
|
||||
|
||||
$newSheetName = $baseName;
|
||||
$counter = 1;
|
||||
|
||||
while ($mergedSpreadsheet->sheetNameExists($newSheetName)) {
|
||||
$newSheetName = $baseName . '_' . $fileIndex . '_' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
self::$logger->logme('error',"New sheet name will be: $newSheetName");
|
||||
|
||||
if ($firstSheet) {
|
||||
$targetSheet = $mergedSpreadsheet->getActiveSheet();
|
||||
$targetSheet->setTitle($newSheetName);
|
||||
$firstSheet = false;
|
||||
self::$logger->logme('error',"Using first sheet");
|
||||
} else {
|
||||
$targetSheet = new Worksheet($mergedSpreadsheet, $newSheetName);
|
||||
$mergedSpreadsheet->addSheet($targetSheet);
|
||||
self::$logger->logme('error',"Created new sheet");
|
||||
}
|
||||
|
||||
self::$logger->logme('error',"Starting content copy for sheet: $newSheetName");
|
||||
self::copyWorksheetContent($sourceSheet, $targetSheet);
|
||||
self::$logger->logme('error',"Content copied successfully for sheet: $newSheetName");
|
||||
|
||||
} catch (Exception $e) {
|
||||
self::$logger->logme('error',"Error processing sheet $sourceSheetIndex: " . $e->getMessage());
|
||||
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Free up memory
|
||||
$currentSpreadsheet->disconnectWorksheets();
|
||||
unset($currentSpreadsheet);
|
||||
self::$logger->logme('error',"Cleaned up spreadsheet resources");
|
||||
|
||||
} catch (Exception $e) {
|
||||
self::$logger->logme('error',"Error processing file $filePath: " . $e->getMessage());
|
||||
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$logger->logme('error',"Current sheet names in merged spreadsheet:");
|
||||
foreach ($mergedSpreadsheet->getSheetNames() as $name) {
|
||||
self::$logger->logme('error'," - $name");
|
||||
}
|
||||
}
|
||||
|
||||
if ($mergedSpreadsheet->getSheetCount() === 0) {
|
||||
throw new Exception("No sheets were successfully merged.");
|
||||
}
|
||||
|
||||
self::$logger->logme('error',"Setting active sheet to index 0");
|
||||
$mergedSpreadsheet->setActiveSheetIndex(0);
|
||||
|
||||
self::$logger->logme('error',"Preparing to save merged file to: $outputPath");
|
||||
|
||||
// Ensure output directory exists
|
||||
$outputDir = dirname($outputPath);
|
||||
if (!is_dir($outputDir)) {
|
||||
mkdir($outputDir, 0777, true);
|
||||
}
|
||||
|
||||
if (!is_writable($outputDir)) {
|
||||
throw new Exception("Output directory is not writable: $outputDir");
|
||||
}
|
||||
|
||||
if (ob_get_length()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
self::$logger->logme('error',"Creating Excel writer");
|
||||
$writer = new Xlsx($mergedSpreadsheet);
|
||||
|
||||
self::$logger->logme('error',"Saving file...");
|
||||
$writer->save($outputPath);
|
||||
|
||||
// Free up memory
|
||||
$mergedSpreadsheet->disconnectWorksheets();
|
||||
unset($mergedSpreadsheet);
|
||||
|
||||
self::$logger->logme('error',"Merge successful. File saved at: $outputPath");
|
||||
return $outputPath;
|
||||
|
||||
return true; // Return true if merge was successful
|
||||
} catch (Exception $e) {
|
||||
// Log the error (optional) and return false on failure
|
||||
error_log("Excel Merge Error: " . $e->getMessage());
|
||||
return false;
|
||||
self::$logger->logme('error',"Excel Merge Error: " . $e->getMessage());
|
||||
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function copyWorksheetContent($sourceSheet, $targetSheet)
|
||||
{
|
||||
try {
|
||||
self::$logger->logme('error',"Starting copyWorksheetContent");
|
||||
|
||||
// Get the highest row and column indexes
|
||||
$highestRow = $sourceSheet->getHighestRow();
|
||||
$highestColumn = $sourceSheet->getHighestColumn();
|
||||
self::$logger->logme('error',"Copying data from range A1:{$highestColumn}{$highestRow}");
|
||||
|
||||
// Copy cell values only first for better performance
|
||||
foreach ($sourceSheet->getRowIterator() as $row) {
|
||||
$cellIterator = $row->getCellIterator();
|
||||
$cellIterator->setIterateOnlyExistingCells(true);
|
||||
foreach ($cellIterator as $cell) {
|
||||
$column = $cell->getColumn();
|
||||
$rowIndex = $cell->getRow();
|
||||
$targetSheet->setCellValue($column . $rowIndex, $cell->getValue());
|
||||
}
|
||||
}
|
||||
self::$logger->logme('error',"Basic cell values copied");
|
||||
|
||||
// Copy column dimensions
|
||||
foreach ($sourceSheet->getColumnDimensions() as $columnId => $sourceDim) {
|
||||
$targetSheet->getColumnDimension($columnId)
|
||||
->setWidth($sourceDim->getWidth())
|
||||
->setVisible($sourceDim->getVisible())
|
||||
->setAutoSize($sourceDim->getAutoSize());
|
||||
}
|
||||
self::$logger->logme('error',"Column dimensions copied");
|
||||
|
||||
// Copy row dimensions
|
||||
foreach ($sourceSheet->getRowDimensions() as $rowId => $sourceDim) {
|
||||
$targetSheet->getRowDimension($rowId)
|
||||
->setRowHeight($sourceDim->getRowHeight())
|
||||
->setVisible($sourceDim->getVisible());
|
||||
}
|
||||
self::$logger->logme('error',"Row dimensions copied");
|
||||
|
||||
// Copy basic worksheet properties
|
||||
$targetSheet->setShowGridlines($sourceSheet->getShowGridlines());
|
||||
$targetSheet->setShowRowColHeaders($sourceSheet->getShowRowColHeaders());
|
||||
self::$logger->logme('error',"Worksheet properties copied");
|
||||
|
||||
} catch (Exception $e) {
|
||||
self::$logger->logme('error',"Error in copyWorksheetContent: " . $e->getMessage());
|
||||
self::$logger->logme('error',"Error trace: " . $e->getTraceAsString());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,16 +10,28 @@ class ExcelMergeHelperTest extends TestCase
|
||||
{
|
||||
// Use the paths you provided
|
||||
$filePaths = [
|
||||
'/home/venba/Downloads/Financial_Sample.xlsx' => [1,3,2],
|
||||
'/home/venba/Downloads/enrollment.xlsx' => []
|
||||
// ['file_path' =>WRITEPATH.'uploads/lead_files/Member_Data.xlsx','sheets' => []],
|
||||
// ['file_path' =>WRITEPATH.'/tmp/RFQ_ABC_GMC_20241220140640.xlsx','sheets' => []],
|
||||
|
||||
['file_path' =>'/home/venba/Downloads/enrollment (1).xlsx','sheets' => []],
|
||||
['file_path' =>'/home/venba/Downloads/Financial_Sample.xlsx','sheets' => []],
|
||||
];
|
||||
$outputPath = '/home/venba/Documents/merged_file.xlsx'; // Output path for the merged file
|
||||
|
||||
// Call the mergeExcelFiles function
|
||||
$result = ExcelMergeHelper::mergeExcelFiles($filePaths, $outputPath);
|
||||
var_dump($result);
|
||||
var_dump($result);
|
||||
// Check if the result is true (indicating success)
|
||||
$this->assertTrue($result, 'Expected true, but got false');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// $temp_file_path = WRITEPATH.'tmp/RFQ_ABC_GMC_20241219172307.xlsx';
|
||||
// $temp_file_name = 'RFQ_ABC_GMC_20241219172307.xlsx';
|
||||
// $lead_file_path = WRITEPATH.'uploads/lead_files/Member_Data.xlsx';
|
||||
|
||||
// $filePaths = [
|
||||
// ['file_path' => $temp_file_path,'sheets' =>[]],
|
||||
// ['file_path' =>$lead_file_path ,'sheets' =>[]]
|
||||
// ];
|
||||
// $outputPath = dirname($temp_file_path).'/'.$temp_file_name.'_merged';
|
||||
|
||||
183
tests/unit/LeadsControllerTest.php
Normal file
183
tests/unit/LeadsControllerTest.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\unit;
|
||||
|
||||
use Exception;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use App\Controllers\LeadsController;
|
||||
|
||||
class LeadsControllerTest extends CIUnitTestCase
|
||||
{
|
||||
protected $leadsController;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->leadsController = new LeadsController();
|
||||
}
|
||||
|
||||
public function testGetDemographyDataBasicScenario()
|
||||
{
|
||||
echo('passed 0');
|
||||
// Arrange
|
||||
$members = [
|
||||
// Header row
|
||||
['Name', 'Age', 'Relationship', 'SI Enhancement'],
|
||||
// Data rows
|
||||
['John', '25', 'Self', '100000'],
|
||||
['Jane', '23', 'Spouse', '100000'],
|
||||
['Kid', '5', 'Child', '50000']
|
||||
];
|
||||
echo('passed 1');
|
||||
$age_band_data = [
|
||||
['0-17', '18-35', '36-45', 'Above 46']
|
||||
];
|
||||
echo('passed 2');
|
||||
$members_heading = ['Name', 'Age', 'Relationship', 'SI Enhancement'];
|
||||
$available_col = 'age';print_rr($available_col);
|
||||
$col_index = 1;
|
||||
echo('passed 3');
|
||||
try{
|
||||
echo('Inside try');
|
||||
// Act
|
||||
echo('passed 3.5');
|
||||
$result = $this->leadsController->getDemographyData($members,$age_band_data,$members_heading,$available_col,$col_index);
|
||||
|
||||
echo('passed 4');
|
||||
} catch (Exception $e) {
|
||||
echo "Message: " . $e->getMessage() . "\n";
|
||||
echo "File: " . $e->getFile() . "\n";
|
||||
echo "Line: " . $e->getLine() . "\n";
|
||||
// Optionally, print the full stack trace
|
||||
echo "Stack trace: " . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
var_dump($result);
|
||||
// Assert
|
||||
$this->assertIsArray($result);
|
||||
$this->assertArrayHasKey('general', $result);
|
||||
$this->assertArrayHasKey('100000', $result);
|
||||
$this->assertArrayHasKey('50000', $result);
|
||||
|
||||
// Check counts for general category
|
||||
$this->assertEquals(1, $result['general']['Self']['18-35']);
|
||||
$this->assertEquals(1, $result['general']['Spouse']['18-35']);
|
||||
$this->assertEquals(1, $result['general']['Child']['0-17']);
|
||||
}
|
||||
|
||||
public function testGetDemographyDataWithDOB()
|
||||
{
|
||||
// Arrange
|
||||
$members = [
|
||||
// Header row
|
||||
['Name', 'DOB', 'Relationship', 'SI Enhancement'],
|
||||
// Data rows
|
||||
['John', '01-Jan-1995', 'Self', '100000'],
|
||||
['Jane', '01-Jan-1998', 'Spouse', '100000']
|
||||
];
|
||||
|
||||
$age_band_data = [
|
||||
['18-35', '36-45', '46+']
|
||||
];
|
||||
|
||||
$members_heading = ['Name', 'DOB', 'Relationship', 'SI Enhancement'];
|
||||
$available_col = 'dob';
|
||||
$col_index = 1;
|
||||
|
||||
// Act
|
||||
$result = $this->leadsController->getDemographyData(
|
||||
$members,
|
||||
$age_band_data,
|
||||
$members_heading,
|
||||
$available_col,
|
||||
$col_index
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertIsArray($result);
|
||||
$this->assertArrayHasKey('general', $result);
|
||||
$this->assertArrayHasKey('100000', $result);
|
||||
|
||||
// Both members should be in 18-35 age band
|
||||
$this->assertEquals(1, $result['general']['Self']['18-35']);
|
||||
$this->assertEquals(1, $result['general']['Spouse']['18-35']);
|
||||
}
|
||||
|
||||
public function testGetDemographyDataWithEmptyMembers()
|
||||
{
|
||||
// Arrange
|
||||
$members = [
|
||||
// Only header row
|
||||
['Name', 'Age', 'Relationship', 'SI Enhancement']
|
||||
];
|
||||
|
||||
$age_band_data = [
|
||||
['0-17', '18-35', '36-45', '46+']
|
||||
];
|
||||
|
||||
$members_heading = ['Name', 'Age', 'Relationship', 'SI Enhancement'];
|
||||
$available_col = 'age';
|
||||
$col_index = 1;
|
||||
|
||||
// Act
|
||||
$result = $this->leadsController->getDemographyData(
|
||||
$members,
|
||||
$age_band_data,
|
||||
$members_heading,
|
||||
$available_col,
|
||||
$col_index
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertIsArray($result);
|
||||
$this->assertArrayHasKey('general', $result);
|
||||
|
||||
// Check that all totals are zero
|
||||
$this->assertEquals(0, $result['general']['Grand Total']['Grand Total']);
|
||||
}
|
||||
|
||||
public function testGetDemographyDataWithMultipleSIBands()
|
||||
{
|
||||
// Arrange
|
||||
$members = [
|
||||
// Header row
|
||||
['Name', 'Age', 'Relationship', 'SI Enhancement'],
|
||||
// Data rows with different SI amounts
|
||||
['John', '25', 'Self', '100000'],
|
||||
['Jane', '23', 'Spouse', '200000'],
|
||||
['Kid1', '5', 'Child', '100000'],
|
||||
['Kid2', '7', 'Child', '200000']
|
||||
];
|
||||
|
||||
$age_band_data = [
|
||||
['0-17', '18-35', '36-45', '46+']
|
||||
];
|
||||
|
||||
$members_heading = ['Name', 'Age', 'Relationship', 'SI Enhancement'];
|
||||
$available_col = 'age';
|
||||
$col_index = 1;
|
||||
|
||||
// Act
|
||||
$result = $this->leadsController->getDemographyData(
|
||||
$members,
|
||||
$age_band_data,
|
||||
$members_heading,
|
||||
$available_col,
|
||||
$col_index
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertIsArray($result);
|
||||
$this->assertArrayHasKey('100000', $result);
|
||||
$this->assertArrayHasKey('200000', $result);
|
||||
|
||||
// Check counts for different SI bands
|
||||
$this->assertEquals(1, $result['100000']['Self']['18-35']);
|
||||
$this->assertEquals(1, $result['200000']['Spouse']['18-35']);
|
||||
$this->assertEquals(1, $result['100000']['Child']['0-17']);
|
||||
$this->assertEquals(1, $result['200000']['Child']['0-17']);
|
||||
|
||||
// Check general category totals
|
||||
$this->assertEquals(2, $result['general']['Child']['0-17']);
|
||||
$this->assertEquals(2, $result['general']['Child']['Grand Total']);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user