|
<?php |
|
|
|
namespace App\Traits\Service\Batch; |
|
|
|
use App\Traits\Repository\Collectable; |
|
use App\Validate\UtilDate; |
|
use App\Exceptions\Batch\{ItemIdNotFoundException, ItemIdMissingException}; |
|
use Exception; |
|
use Illuminate\Support\{Arr, Collection}; |
|
|
|
|
|
/** |
|
* BatchBase |
|
*/ |
|
abstract class BatchBase |
|
{ |
|
use Collectable; |
|
|
|
/** |
|
* @param string $pkName; |
|
*/ |
|
public $pkName = ''; |
|
|
|
/** |
|
* @param object $existingModels; |
|
*/ |
|
public $existingModels; |
|
|
|
/** |
|
* @param object $dbParentItemModels; |
|
*/ |
|
public $dbParentItemModels; |
|
|
|
/** |
|
* @param array $availableFields; |
|
*/ |
|
public $availableFields; |
|
|
|
/** |
|
* getAllowedUserInputColumns |
|
* |
|
* @return array |
|
*/ |
|
abstract public function getAllowedUserInputColumns(); |
|
|
|
/** |
|
* getModelId |
|
* |
|
* @return array |
|
*/ |
|
abstract public function getModelId(); |
|
|
|
/** |
|
* getExistingModelByIds |
|
* |
|
* @param array $ids |
|
* |
|
* @return array |
|
*/ |
|
abstract public function getExistingModelByIds(array $ids); |
|
|
|
/** |
|
* construct |
|
*/ |
|
public function __construct() |
|
{ |
|
$this->init(); |
|
} |
|
|
|
/** |
|
* init |
|
* |
|
* @return void |
|
*/ |
|
public function init() |
|
{ |
|
$this->availableFields = $this->model->getFillable(); |
|
} |
|
|
|
/** |
|
* setDbParentItemModels |
|
* |
|
* @param object $dbParentItemModels |
|
* |
|
* @return object |
|
*/ |
|
public function setDbParentItemModels($dbParentItemModels) |
|
{ |
|
// convert to collection |
|
if (gettype($dbParentItemModels) == 'array') { |
|
$dbParentItemModels = collect($dbParentItemModels); |
|
} |
|
$this->dbParentItemModels = $dbParentItemModels; |
|
} |
|
|
|
/** |
|
* getFilteredAllowInput |
|
* |
|
* @return array |
|
*/ |
|
public function getFilteredAllowInput($excludes = ['ID']) |
|
{ |
|
return array_filter($this->getAllowedUserInputColumns(), function($item) use ($excludes) { |
|
return !in_array($item, $excludes); |
|
}); |
|
} |
|
|
|
/** |
|
* validateItemUpdateByFilterOptions |
|
* |
|
* @param array $inputItems |
|
* @param array $filterOptions |
|
* @param string $keyBy = 'ID' |
|
* |
|
* @return array |
|
*/ |
|
public function validateItemUpdateByFilterOptions($inputItems, $filterOptions, $keyBy = 'ID') |
|
{ |
|
$isUpdate = count($filterOptions) == 0; |
|
if ($isUpdate) { |
|
// validate item id not exists |
|
array_filter($inputItems, function($rowItem) use ($keyBy) { |
|
if (!array_key_exists($keyBy, $rowItem)) { |
|
throw new ItemIdMissingException("item明細設定有誤:".$this->pkName); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
/** |
|
* addValidatedDateToUserInputs |
|
* |
|
* @param array $inputs |
|
* @param array $dateFields |
|
* |
|
* @return array |
|
*/ |
|
public function addValidatedDateToUserInputs(array $inputs, array $dateFields) |
|
{ |
|
$inputItems = $inputs['ITEMS']; |
|
foreach ($inputItems as $rowIndex => $rowItems) { |
|
foreach ($dateFields as $rowDate) { |
|
if (isset($rowItems[$rowDate])) { |
|
UtilDate::get($rowItems, ['field' => $rowDate, 'hint' => $rowDate.'日期格式有誤']); |
|
} |
|
} |
|
$inputItems[$rowIndex] = $rowItems; |
|
} |
|
|
|
$inputs['ITEMS'] = $inputItems; |
|
return $inputs; |
|
} |
|
|
|
/** |
|
* addCodeToUserInputs |
|
* |
|
* @param array $data |
|
* |
|
* @return array |
|
*/ |
|
public function addCodeToUserInputs(array $data) |
|
{ |
|
// count how many records in database |
|
$dbSn = $this->model->get()->count(); |
|
|
|
$this->setExistingModelsFromInput($data['ITEMS']); |
|
|
|
$existingData = $this->existingModels->toArray(); |
|
|
|
foreach ($data['ITEMS'] as $rowIndex => $rowItem) { |
|
$hasId = Arr::get($rowItem, 'ID', false); |
|
$hasExistingData = $hasId ? Arr::get($existingData, $hasId, false) : false; |
|
|
|
// default using existing data or get a new serial number |
|
$dbSnCode = $hasExistingData ? $hasExistingData['CODE'] : SerialNumber::getSnLeftPadFromLength($rowIndex + $dbSn + 1, $this->getSerialNumberLength()); |
|
|
|
// replace with user CODE from input |
|
if (array_key_exists('CODE', $rowItem)) { |
|
$dbSnCode = $rowItem['CODE']; |
|
} |
|
|
|
$data['ITEMS'][$rowIndex]['CODE'] = $dbSnCode; |
|
} |
|
|
|
return $data; |
|
} |
|
|
|
/** |
|
* setExistingModelsFromInput |
|
* |
|
* @param array $data |
|
* |
|
* @return array |
|
*/ |
|
public function setExistingModelsFromInput(array $rawItemInputs) |
|
{ |
|
if (!$this->existingModels) { |
|
$collectInputs = collect($rawItemInputs); |
|
$ids = $collectInputs->keyBy('ID') |
|
->filter(function($item, $rowKey) { return $rowKey != ""; }) |
|
->keys() |
|
->toArray(); |
|
|
|
// get existing data by ids |
|
$this->existingModels = $this->getExistingModelByIds($ids)->keyBy('ID'); |
|
} |
|
} |
|
|
|
/** |
|
* getItemInputs |
|
* |
|
* @param array $rawItemInputs |
|
* |
|
* @return array |
|
*/ |
|
public function getItemInputs(array $rawItemInputs) |
|
{ |
|
if (!method_exists($this, 'getAllowedUserInputColumns')) { |
|
throw new Exception("[BatchBase] getAllowedUserInputColumns not defined!"); |
|
} |
|
|
|
if (!method_exists($this, 'getModelId')) { |
|
throw new Exception("[BatchBase] getModelId not defined!"); |
|
} |
|
|
|
if (!method_exists($this, 'getExistingModelByIds')) { |
|
throw new Exception("[BatchBase] getExistingModelByIds not defined"); |
|
} |
|
|
|
$cleans = []; |
|
$cleans['insert'] = []; |
|
$cleans['update'] = []; |
|
$cleans['delete'] = []; |
|
|
|
// get all ids for existing model |
|
$collectInputs = collect($rawItemInputs); |
|
|
|
$this->setExistingModelsFromInput($rawItemInputs); |
|
$existingData = $this->existingModels->toArray(); |
|
|
|
// build format inputs by fillabe fields start |
|
$model = $this->model; |
|
$formattedInputs = []; |
|
foreach ($this->availableFields as $rowField) { |
|
$collectInputs->map(function($item, $itemIndex) use(&$formattedInputs, $rowField, $model) { |
|
$formattedInputs[$itemIndex][$rowField] = Arr::get($item, $rowField, $model->$rowField); |
|
}); |
|
} |
|
|
|
unset($rawItemInputs); |
|
unset($collectInputs); |
|
// build format inputs by fillabe fields end |
|
|
|
foreach ($formattedInputs as $rowIndex => $rowItemInput) { |
|
$hasId = Arr::get($rowItemInput, 'ID', true); |
|
$rowId = is_null($hasId) ? $this->getModelId() : $hasId; |
|
// merge existing data |
|
$rowExistingData = Arr::get($existingData, $rowId, []); |
|
$isInsert = count($rowExistingData) == 0; |
|
$rowItemInput = array_merge($rowExistingData, $rowItemInput); |
|
|
|
$rowAction = ($isInsert === true) ? 'insert' : 'update'; |
|
$cleans[$rowAction][$rowIndex] = $this->input($rowItemInput, $this->getAllowedUserInputColumns()); |
|
$cleans[$rowAction][$rowIndex]['ID'] = $rowId; |
|
|
|
if ($isInsert === true) { |
|
$cleans[$rowAction][$rowIndex]['INSERT_USER'] = $this->getInsertUser(); |
|
$cleans[$rowAction][$rowIndex]['INSERT_DATE'] = $this->getCurrentDateFromFormat("Y-m-d H:i:s"); |
|
$cleans[$rowAction][$rowIndex]['MODIFY_USER'] = NULL; |
|
$cleans[$rowAction][$rowIndex]['MODIFY_DATE'] = NULL; |
|
} else { |
|
$cleans[$rowAction][$rowIndex]['INSERT_USER'] = $rowItemInput['INSERT_USER']; |
|
$cleans[$rowAction][$rowIndex]['INSERT_DATE'] = $rowItemInput['INSERT_DATE']; |
|
$cleans[$rowAction][$rowIndex]['MODIFY_USER'] = $this->getInsertUser(); |
|
$cleans[$rowAction][$rowIndex]['MODIFY_DATE'] = $this->getCurrentDateFromFormat("Y-m-d H:i:s"); |
|
} |
|
|
|
// sort with key |
|
ksort($cleans[$rowAction][$rowIndex]); |
|
} |
|
|
|
// find extra item to delete |
|
if ($this->dbParentItemModels) { |
|
$dbItemIds = $this->dbParentItemModels->keyBy('ID')->keys()->toArray(); |
|
$inputItemIds = collect($formattedInputs)->keyBy('ID')->keys()->toArray(); |
|
$cleans['delete'] = array_diff($dbItemIds, $inputItemIds); |
|
} |
|
|
|
return $cleans; |
|
} |
|
|
|
/** |
|
* updateBatch |
|
* |
|
* @param array $data |
|
* |
|
* @return array |
|
*/ |
|
public function updateBatch(array $data) |
|
{ |
|
$result = []; |
|
$updateData = collect($data)->keyBy('ID'); |
|
|
|
$this->existingModels->map(function($item, $rowIndex) use ($updateData, &$result) { |
|
$rowItemId = $item->ID; |
|
$isUpdate = $item->update($updateData[$rowItemId]); |
|
$result[$rowItemId] = ['status' => $isUpdate, 'item' => $item->toArray()]; |
|
}); |
|
|
|
return $result; |
|
} |
|
|
|
/** |
|
* execute |
|
* |
|
* @param array $cleanInputs |
|
* |
|
* @return array |
|
*/ |
|
public function execute(array $cleanInputs) |
|
{ |
|
$deleteInputs = $cleanInputs['delete']; |
|
$insertOrUpdateInputs = array_merge($cleanInputs['insert'], $cleanInputs['update']); |
|
$isDeleteResult = false; |
|
$insertOrUpdateResult = false; |
|
|
|
// delete for update |
|
if (count($deleteInputs) > 0) { |
|
$isDeleteResult = $this->model->destroy($deleteInputs); |
|
} |
|
|
|
if (count($insertOrUpdateInputs) > 0) { |
|
$insertOrUpdateResult = $this->model::insertOnDuplicateKey($insertOrUpdateInputs, array_keys(reset($insertOrUpdateInputs))); |
|
} |
|
|
|
return [ |
|
'is_delete' => [ |
|
'status' => $isDeleteResult, |
|
'data' => $deleteInputs |
|
], |
|
'insert_or_update' => [ |
|
'status' => $insertOrUpdateResult, |
|
'data' => $insertOrUpdateInputs |
|
] |
|
]; |
|
} |
|
} |