feat: add article and production modules
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use plugin\produce\app\model\ExceptionRecord;
|
||||
|
||||
class ExceptionAnalysisService
|
||||
{
|
||||
public function overview(array $params): array
|
||||
{
|
||||
$query = $this->query($params);
|
||||
|
||||
return [
|
||||
'total' => (clone $query)->count(),
|
||||
'pending' => (clone $query)->where('status', 0)->count(),
|
||||
'processing' => (clone $query)->where('status', 1)->count(),
|
||||
'completed' => (clone $query)->where('status', 2)->count(),
|
||||
'by_type' => $this->group($params, 'type_code', 'type_name'),
|
||||
'by_reason' => $this->group($params, 'reason_code', 'reason_name'),
|
||||
'by_issue' => $this->group($params, 'issue_code', 'issue_name'),
|
||||
'by_date' => $this->byDate($params),
|
||||
];
|
||||
}
|
||||
|
||||
private function group(array $params, string $codeColumn, string $nameColumn): array
|
||||
{
|
||||
return $this->query($params)
|
||||
->selectRaw("{$codeColumn} as code, {$nameColumn} as name, COUNT(*) as total")
|
||||
->groupBy($codeColumn, $nameColumn)
|
||||
->orderByDesc('total')
|
||||
->orderBy($nameColumn)
|
||||
->limit(20)
|
||||
->get()
|
||||
->map(static fn (object $item): array => [
|
||||
'code' => (string) $item->code,
|
||||
'name' => (string) $item->name,
|
||||
'total' => (int) $item->total,
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
private function byDate(array $params): array
|
||||
{
|
||||
$model = new ExceptionRecord();
|
||||
$connection = $model->getConnection();
|
||||
$table = $connection->getTablePrefix() . $model->getTable();
|
||||
|
||||
return $this->query($params)
|
||||
->selectRaw("DATE_FORMAT(FROM_UNIXTIME({$table}.reported_at), '%Y-%m-%d') as date, COUNT(*) as total")
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->get()
|
||||
->map(static fn (object $item): array => [
|
||||
'date' => (string) $item->date,
|
||||
'total' => (int) $item->total,
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
private function query(array $params): Builder
|
||||
{
|
||||
$model = new ExceptionRecord();
|
||||
$query = $model->getConnection()->table($model->getTable())->whereNull('deleted_at');
|
||||
|
||||
foreach (['type_code', 'reason_code', 'issue_code', 'status'] as $field) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($field, $params[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($params['reported_at_range']) && is_array($params['reported_at_range']) && count($params['reported_at_range']) === 2) {
|
||||
$query->whereBetween('reported_at', [
|
||||
strtotime((string) $params['reported_at_range'][0] . ' 00:00:00'),
|
||||
strtotime((string) $params['reported_at_range'][1] . ' 23:59:59'),
|
||||
]);
|
||||
} else {
|
||||
$query->whereBetween('reported_at', [strtotime('-29 days 00:00:00'), time()]);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ExceptionRecordDao;
|
||||
|
||||
class ExceptionRecordService extends BaseService
|
||||
{
|
||||
public function __construct(ExceptionRecordDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ExceptionSettingDao;
|
||||
|
||||
class ExceptionSettingService extends BaseService
|
||||
{
|
||||
public function __construct(ExceptionSettingDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use plugin\produce\app\dao\HardwareFaultDescriptionDao;
|
||||
use plugin\produce\app\model\HardwareFaultDescription;
|
||||
use plugin\produce\app\model\MaterialCategory;
|
||||
|
||||
class HardwareFaultDescriptionService extends BaseService
|
||||
{
|
||||
public function __construct(HardwareFaultDescriptionDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
public function importFromExcel(string $filePath): array
|
||||
{
|
||||
$sheet = IOFactory::load($filePath)->getActiveSheet();
|
||||
$rows = $sheet->toArray('', true, true, false);
|
||||
if (count($rows) < 2) {
|
||||
throw new \InvalidArgumentException('导入文件至少应包含表头和一条硬件故障描述数据');
|
||||
}
|
||||
|
||||
$headers = array_map(static fn ($value) => trim((string) $value), $rows[0]);
|
||||
$indexes = array_flip($headers);
|
||||
foreach (['分类', '描述'] as $header) {
|
||||
if (!array_key_exists($header, $indexes)) {
|
||||
throw new \InvalidArgumentException('导入表头必须包含:分类、描述');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->transaction(function () use ($rows, $indexes) {
|
||||
$created = 0;
|
||||
$updated = 0;
|
||||
$categories = [];
|
||||
|
||||
foreach (array_slice($rows, 1) as $rowNumber => $row) {
|
||||
$categoryName = trim((string) ($row[$indexes['分类']] ?? ''));
|
||||
$description = trim((string) ($row[$indexes['描述']] ?? ''));
|
||||
if ($categoryName === '' && $description === '') {
|
||||
continue;
|
||||
}
|
||||
if ($categoryName === '' || $description === '') {
|
||||
throw new \InvalidArgumentException('第 ' . ($rowNumber + 2) . ' 行的分类、描述均不能为空');
|
||||
}
|
||||
|
||||
$category = $categories[$categoryName] ?? null;
|
||||
if (!$category) {
|
||||
$category = MaterialCategory::query()->withTrashed()->where('category_name', $categoryName)->first();
|
||||
if ($category?->trashed()) {
|
||||
$category->restore();
|
||||
}
|
||||
$category ??= MaterialCategory::query()->create(['category_name' => $categoryName]);
|
||||
$categories[$categoryName] = $category;
|
||||
}
|
||||
|
||||
$faultDescription = HardwareFaultDescription::query()
|
||||
->withTrashed()
|
||||
->where('category_id', $category->id)
|
||||
->where('description', $description)
|
||||
->first();
|
||||
if ($faultDescription) {
|
||||
if ($faultDescription->trashed()) {
|
||||
$faultDescription->restore();
|
||||
}
|
||||
$updated++;
|
||||
continue;
|
||||
}
|
||||
|
||||
HardwareFaultDescription::query()->create([
|
||||
'category_id' => $category->id,
|
||||
'description' => $description,
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
|
||||
return ['created' => $created, 'updated' => $updated];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\MaterialCategoryDao;
|
||||
|
||||
class MaterialCategoryService extends BaseService
|
||||
{
|
||||
public function __construct(MaterialCategoryDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use plugin\produce\app\dao\MaterialDao;
|
||||
use plugin\produce\app\model\Material;
|
||||
use plugin\produce\app\model\MaterialCategory;
|
||||
|
||||
class MaterialService extends BaseService
|
||||
{
|
||||
public function __construct(MaterialDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Excel 导入物料;首行必须为:分类、商品编码、商品名称。
|
||||
* 相同商品编码会更新分类和名称,以便重复导入。
|
||||
*/
|
||||
public function importFromExcel(string $filePath): array
|
||||
{
|
||||
$sheet = IOFactory::load($filePath)->getActiveSheet();
|
||||
$rows = $sheet->toArray('', true, true, false);
|
||||
if (count($rows) < 2) {
|
||||
throw new \InvalidArgumentException('导入文件至少应包含表头和一条物料数据');
|
||||
}
|
||||
|
||||
$headers = array_map(static fn ($value) => trim((string) $value), $rows[0]);
|
||||
$indexes = array_flip($headers);
|
||||
foreach (['分类', '商品编码', '商品名称'] as $header) {
|
||||
if (!array_key_exists($header, $indexes)) {
|
||||
throw new \InvalidArgumentException('导入表头必须包含:分类、商品编码、商品名称');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->transaction(function () use ($rows, $indexes) {
|
||||
$created = 0;
|
||||
$updated = 0;
|
||||
$categories = [];
|
||||
|
||||
foreach (array_slice($rows, 1) as $rowNumber => $row) {
|
||||
$categoryName = trim((string) ($row[$indexes['分类']] ?? ''));
|
||||
$productCode = trim((string) ($row[$indexes['商品编码']] ?? ''));
|
||||
$productName = trim((string) ($row[$indexes['商品名称']] ?? ''));
|
||||
if ($categoryName === '' && $productCode === '' && $productName === '') {
|
||||
continue;
|
||||
}
|
||||
if ($categoryName === '' || $productCode === '' || $productName === '') {
|
||||
throw new \InvalidArgumentException('第 ' . ($rowNumber + 2) . ' 行的分类、商品编码、商品名称均不能为空');
|
||||
}
|
||||
|
||||
$category = $categories[$categoryName] ?? null;
|
||||
if (!$category) {
|
||||
$category = MaterialCategory::query()->withTrashed()->where('category_name', $categoryName)->first();
|
||||
if ($category?->trashed()) {
|
||||
$category->restore();
|
||||
}
|
||||
$category ??= MaterialCategory::query()->create(['category_name' => $categoryName]);
|
||||
$categories[$categoryName] = $category;
|
||||
}
|
||||
$material = Material::query()->withTrashed()->where('product_code', $productCode)->first();
|
||||
if ($material) {
|
||||
if ($material->trashed()) {
|
||||
$material->restore();
|
||||
}
|
||||
$material->fill(['category_id' => $category->id, 'product_name' => $productName])->save();
|
||||
$updated++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Material::query()->create([
|
||||
'category_id' => $category->id,
|
||||
'product_code' => $productCode,
|
||||
'product_name' => $productName,
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
|
||||
return ['created' => $created, 'updated' => $updated];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ProcessDao;
|
||||
|
||||
class ProcessService extends BaseService
|
||||
{
|
||||
public function __construct(ProcessDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ProcessStepDao;
|
||||
|
||||
class ProcessStepService extends BaseService
|
||||
{
|
||||
public function __construct(ProcessStepDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ProduceConfigDao;
|
||||
|
||||
class ProduceConfigService extends BaseService
|
||||
{
|
||||
public function __construct(ProduceConfigDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
public function getConfigByGroup(string $groupCode): array
|
||||
{
|
||||
return $this->dao->getByGroupCode($groupCode);
|
||||
}
|
||||
|
||||
public function saveConfig(string $groupCode, array $configs): bool
|
||||
{
|
||||
return $this->dao->batchSave($groupCode, $configs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ProductRecordDao;
|
||||
|
||||
class ProductRecordService extends BaseService
|
||||
{
|
||||
public function __construct(ProductRecordDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\RepairRecordDao;
|
||||
use plugin\produce\app\model\HardwareFaultDescription;
|
||||
use plugin\produce\app\model\MaterialCategory;
|
||||
use plugin\produce\app\model\RepairRecord;
|
||||
|
||||
class RepairRecordService extends BaseService
|
||||
{
|
||||
public function __construct(RepairRecordDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
public function save(array $data): RepairRecord
|
||||
{
|
||||
return $this->transaction(function () use ($data) {
|
||||
$data = $this->prepareData($data);
|
||||
$record = RepairRecord::query()->create($data);
|
||||
$this->changeFaultUsage($record->fault_description_id, 1);
|
||||
return $record;
|
||||
});
|
||||
}
|
||||
|
||||
public function update(int|string $id, array $data): bool
|
||||
{
|
||||
return $this->transaction(function () use ($id, $data) {
|
||||
$record = RepairRecord::query()->findOrFail($id);
|
||||
$previousFaultId = $record->fault_description_id;
|
||||
$data = $this->prepareData($data);
|
||||
$record->fill($data)->save();
|
||||
if ((string) $previousFaultId !== (string) $record->fault_description_id) {
|
||||
$this->changeFaultUsage($previousFaultId, -1);
|
||||
$this->changeFaultUsage($record->fault_description_id, 1);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteRecord(int|string $id): bool
|
||||
{
|
||||
return $this->transaction(function () use ($id) {
|
||||
$record = RepairRecord::query()->findOrFail($id);
|
||||
$this->changeFaultUsage($record->fault_description_id, -1);
|
||||
return (bool) $record->delete();
|
||||
});
|
||||
}
|
||||
|
||||
private function prepareData(array $data): array
|
||||
{
|
||||
$hasCategoryIds = array_key_exists('category_ids', $data);
|
||||
$categoryIds = array_values(array_unique(array_filter(array_map(static fn ($id): string => trim((string) $id), (array) ($data['category_ids'] ?? [])), static fn (string $id): bool => $id !== '')));
|
||||
if ($hasCategoryIds) {
|
||||
$data['category_ids'] = $categoryIds ?: null;
|
||||
$data['category_id'] = $categoryIds[0] ?? null;
|
||||
if (!$categoryIds) {
|
||||
$data['category_names'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$categoryId = $data['category_id'] ?? null;
|
||||
$faultId = $data['fault_description_id'] ?? null;
|
||||
|
||||
if (isset($data['repaired_at']) && $data['repaired_at'] !== null && (int) $data['repaired_at'] > 9999999999) {
|
||||
$data['repaired_at'] = (int) floor((int) $data['repaired_at'] / 1000);
|
||||
}
|
||||
if (array_key_exists('category_id', $data) && !$categoryId) {
|
||||
$data['category_name'] = null;
|
||||
$data['category_names'] = null;
|
||||
}
|
||||
if (array_key_exists('fault_description_id', $data) && !$faultId) {
|
||||
$data['fault_description'] = null;
|
||||
}
|
||||
if ($categoryIds) {
|
||||
$categories = MaterialCategory::query()->withTrashed()->whereIn('id', $categoryIds)->get(['id', 'category_name'])->keyBy('id');
|
||||
if ($categories->count() !== count($categoryIds)) {
|
||||
throw new \InvalidArgumentException('物料分类不存在');
|
||||
}
|
||||
$data['category_names'] = array_map(static fn (string $id): string => (string) $categories[$id]->category_name, $categoryIds);
|
||||
$data['category_name'] = $data['category_names'][0];
|
||||
} elseif ($categoryId) {
|
||||
$data['category_name'] = MaterialCategory::query()->withTrashed()->whereKey($categoryId)->value('category_name');
|
||||
}
|
||||
if ($faultId) {
|
||||
$fault = HardwareFaultDescription::query()->withTrashed()->find($faultId);
|
||||
if (!$fault) {
|
||||
throw new \InvalidArgumentException('硬件故障描述不存在');
|
||||
}
|
||||
if ($categoryId && (string) $fault->category_id !== (string) $categoryId) {
|
||||
throw new \InvalidArgumentException('硬件故障描述与物料分类不匹配');
|
||||
}
|
||||
$data['category_id'] = $fault->category_id;
|
||||
$data['category_ids'] = [$fault->category_id];
|
||||
$data['category_name'] = $fault->category?->category_name ?? $data['category_name'] ?? '';
|
||||
$data['category_names'] = [$data['category_name']];
|
||||
$data['fault_description'] = $fault->description;
|
||||
}
|
||||
if (isset($data['parts']) && is_string($data['parts'])) {
|
||||
$data['parts'] = json_decode($data['parts'], true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function changeFaultUsage(int|string|null $faultId, int $delta): void
|
||||
{
|
||||
if (!$faultId) {
|
||||
return;
|
||||
}
|
||||
$fault = HardwareFaultDescription::query()->withTrashed()->find($faultId);
|
||||
if (!$fault) {
|
||||
return;
|
||||
}
|
||||
$fault->use_count = max(0, (int) $fault->use_count + $delta);
|
||||
$fault->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\RepairSettingDao;
|
||||
|
||||
class RepairSettingService extends BaseService
|
||||
{
|
||||
public function __construct(RepairSettingDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\ScanDataDao;
|
||||
|
||||
class ScanDataService extends BaseService
|
||||
{
|
||||
public function __construct(ScanDataDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\SpecificRuleDao;
|
||||
|
||||
class SpecificRuleService extends BaseService
|
||||
{
|
||||
public function __construct(SpecificRuleDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\StatusTransitionDao;
|
||||
|
||||
class StatusTransitionService extends BaseService
|
||||
{
|
||||
public function __construct(StatusTransitionDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\StepRelationDao;
|
||||
|
||||
class StepRelationService extends BaseService
|
||||
{
|
||||
public function __construct(StepRelationDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\StepStatusDao;
|
||||
|
||||
class StepStatusService extends BaseService
|
||||
{
|
||||
public function __construct(StepStatusDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use core\base\BaseService;
|
||||
use plugin\produce\app\dao\TransitionLogDao;
|
||||
|
||||
class TransitionLogService extends BaseService
|
||||
{
|
||||
public function __construct(TransitionLogDao $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace plugin\produce\app\service\admin;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use plugin\produce\app\model\ProductRecord;
|
||||
use plugin\produce\app\model\ScanData;
|
||||
|
||||
class WorkStatisticsService
|
||||
{
|
||||
public function monthly(array $params): array
|
||||
{
|
||||
$month = $this->normalizeMonth($params['month'] ?? null);
|
||||
$days = (int) date('t', strtotime($month . '-01'));
|
||||
$start = strtotime($month . '-01 00:00:00');
|
||||
$end = strtotime(date('Y-m-t 23:59:59', $start));
|
||||
$rows = $this->collectRows($params, $start, $end, static fn (string $column): string => "DATE_FORMAT(FROM_UNIXTIME({$column}), '%Y-%m-%d')");
|
||||
|
||||
return $this->formatRows($rows, $days, static fn (int $index): string => (string) $index);
|
||||
}
|
||||
|
||||
public function daily(array $params): array
|
||||
{
|
||||
$date = $this->normalizeDate($params['date'] ?? null);
|
||||
$start = strtotime($date . ' 00:00:00');
|
||||
$end = strtotime($date . ' 23:59:59');
|
||||
$rows = $this->collectRows($params, $start, $end, static fn (string $column): string => "HOUR(FROM_UNIXTIME({$column}))");
|
||||
|
||||
return $this->formatRows($rows, 24, static fn (int $index): string => str_pad((string) $index, 2, '0', STR_PAD_LEFT) . ':00');
|
||||
}
|
||||
|
||||
private function collectRows(array $params, int $start, int $end, callable $periodExpression): array
|
||||
{
|
||||
$dataType = (string) ($params['data_type'] ?? 'all');
|
||||
$rows = [];
|
||||
|
||||
if ($dataType === 'all' || $dataType === 'order') {
|
||||
$rows = array_merge($rows, $this->queryScanData($params, $start, $end, $periodExpression)->get()->all());
|
||||
}
|
||||
|
||||
if ($dataType === 'all' || $dataType === 'machine') {
|
||||
$rows = array_merge($rows, $this->queryProductRecord($params, $start, $end, $periodExpression)->get()->all());
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function queryScanData(array $params, int $start, int $end, callable $periodExpression): Builder
|
||||
{
|
||||
$scanDataModel = new ScanData();
|
||||
$connection = $scanDataModel->getConnection();
|
||||
$scanDataTable = $scanDataModel->getTable();
|
||||
$processTable = 'produce_process';
|
||||
$stepTable = 'produce_process_step';
|
||||
|
||||
$query = $connection->table($scanDataTable)
|
||||
->leftJoin($processTable, $processTable . '.id', '=', $scanDataTable . '.process_id')
|
||||
->leftJoin($stepTable, $stepTable . '.id', '=', $scanDataTable . '.step_id')
|
||||
->whereBetween($scanDataTable . '.scan_time', [$start, $end])
|
||||
->whereNull($scanDataTable . '.deleted_at');
|
||||
|
||||
foreach (['process_id', 'step_id', 'scan_result'] as $field) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($scanDataTable . '.' . $field, $params[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['user_code', 'operator'] as $field) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($scanDataTable . '.' . $field, 'like', '%' . $params[$field] . '%');
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['process_name' => $processTable . '.process_name', 'step_name' => $stepTable . '.step_name'] as $field => $column) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($column, 'like', '%' . $params[$field] . '%');
|
||||
}
|
||||
}
|
||||
|
||||
return $query->select([
|
||||
$scanDataTable . '.user_code',
|
||||
$scanDataTable . '.operator',
|
||||
$connection->raw($connection->getTablePrefix() . $processTable . '.process_name as process_name'),
|
||||
$connection->raw($connection->getTablePrefix() . $stepTable . '.step_name as step_name'),
|
||||
])
|
||||
->selectRaw($periodExpression($connection->getTablePrefix() . $scanDataTable . '.scan_time') . ' as period')
|
||||
->selectRaw('SUM(' . $connection->getTablePrefix() . $scanDataTable . '.quantity) as amount')
|
||||
->groupBy('user_code', 'operator', 'process_name', 'step_name', 'period')
|
||||
->orderBy('user_code')
|
||||
->orderBy('process_name')
|
||||
->orderBy('step_name');
|
||||
}
|
||||
|
||||
private function queryProductRecord(array $params, int $start, int $end, callable $periodExpression): Builder
|
||||
{
|
||||
$productRecordModel = new ProductRecord();
|
||||
$connection = $productRecordModel->getConnection();
|
||||
$productRecordTable = $productRecordModel->getTable();
|
||||
$processTable = 'produce_process';
|
||||
$stepTable = 'produce_process_step';
|
||||
|
||||
$query = $connection->table($productRecordTable)
|
||||
->leftJoin($processTable, $processTable . '.id', '=', $productRecordTable . '.process_id')
|
||||
->leftJoin($stepTable, $stepTable . '.id', '=', $productRecordTable . '.step_id')
|
||||
->whereBetween($productRecordTable . '.created_at', [$start, $end])
|
||||
->whereNull($productRecordTable . '.deleted_at');
|
||||
|
||||
foreach (['process_id', 'step_id', 'scan_result'] as $field) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($productRecordTable . '.' . $field, $params[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['user_code', 'operator'] as $field) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($productRecordTable . '.' . $field, 'like', '%' . $params[$field] . '%');
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['process_name' => $processTable . '.process_name', 'step_name' => $stepTable . '.step_name'] as $field => $column) {
|
||||
if (($params[$field] ?? '') !== '') {
|
||||
$query->where($column, 'like', '%' . $params[$field] . '%');
|
||||
}
|
||||
}
|
||||
|
||||
return $query->select([
|
||||
$productRecordTable . '.user_code',
|
||||
$productRecordTable . '.operator',
|
||||
$connection->raw($connection->getTablePrefix() . $processTable . '.process_name as process_name'),
|
||||
$connection->raw($connection->getTablePrefix() . $stepTable . '.step_name as step_name'),
|
||||
])
|
||||
->selectRaw($periodExpression($connection->getTablePrefix() . $productRecordTable . '.created_at') . ' as period')
|
||||
->selectRaw('SUM(' . $connection->getTablePrefix() . $productRecordTable . '.quantity) as amount')
|
||||
->groupBy('user_code', 'operator', 'process_name', 'step_name', 'period')
|
||||
->orderBy('user_code')
|
||||
->orderBy('process_name')
|
||||
->orderBy('step_name');
|
||||
}
|
||||
|
||||
private function formatRows(iterable $rows, int $periodCount, callable $periodLabel): array
|
||||
{
|
||||
$grouped = [];
|
||||
$summaryRows = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$productionProcess = $this->formatProductionProcess((string) $row->process_name, (string) $row->step_name);
|
||||
$key = implode('|', [(string) $row->user_code, (string) $row->operator, (string) $row->process_name, (string) $row->step_name]);
|
||||
if (!isset($grouped[$key])) {
|
||||
$grouped[$key] = [
|
||||
'user_code' => $row->user_code,
|
||||
'operator' => $row->operator,
|
||||
'production_process' => $productionProcess,
|
||||
'process_name' => $row->process_name,
|
||||
'step_name' => $row->step_name,
|
||||
'total' => 0,
|
||||
];
|
||||
for ($index = 1; $index <= $periodCount; $index++) {
|
||||
$grouped[$key][$periodLabel($index)] = 0;
|
||||
}
|
||||
}
|
||||
if (!isset($summaryRows[$productionProcess])) {
|
||||
$summaryRows[$productionProcess] = [
|
||||
'user_code' => '总计',
|
||||
'operator' => '',
|
||||
'production_process' => $productionProcess,
|
||||
'process_name' => $row->process_name,
|
||||
'step_name' => $row->step_name,
|
||||
'total' => 0,
|
||||
];
|
||||
for ($index = 1; $index <= $periodCount; $index++) {
|
||||
$summaryRows[$productionProcess][$periodLabel($index)] = 0;
|
||||
}
|
||||
}
|
||||
$period = (string) $row->period;
|
||||
$period = $periodCount === 24 ? str_pad($period, 2, '0', STR_PAD_LEFT) . ':00' : (string) ((int) substr($period, -2));
|
||||
if (array_key_exists($period, $grouped[$key])) {
|
||||
$amount = (int) $row->amount;
|
||||
$grouped[$key][$period] += $amount;
|
||||
$grouped[$key]['total'] += $amount;
|
||||
$summaryRows[$productionProcess][$period] += $amount;
|
||||
$summaryRows[$productionProcess]['total'] += $amount;
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_values($grouped);
|
||||
if ($items !== []) {
|
||||
$items = array_merge($items, array_values($summaryRows));
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function formatProductionProcess(string $processName, string $stepName): string
|
||||
{
|
||||
if ($processName !== '' && $stepName !== '') {
|
||||
return $processName . '-' . $stepName;
|
||||
}
|
||||
|
||||
return $processName !== '' ? $processName : $stepName;
|
||||
}
|
||||
|
||||
private function normalizeMonth(mixed $month): string
|
||||
{
|
||||
$month = is_string($month) && preg_match('/^\d{4}-\d{2}$/', $month) ? $month : date('Y-m');
|
||||
return date('Y-m', strtotime($month . '-01'));
|
||||
}
|
||||
|
||||
private function normalizeDate(mixed $date): string
|
||||
{
|
||||
$date = is_string($date) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) ? $date : date('Y-m-d');
|
||||
return date('Y-m-d', strtotime($date));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user