完善生产异常维修与备件管理功能

This commit is contained in:
2026-09-01 19:17:09 +08:00
parent 02a429af66
commit 32f1486f5e
49 changed files with 1765 additions and 121 deletions
@@ -40,6 +40,7 @@ final class DataEntryController extends Base
{
protected ?BaseValidate $validate = null;
protected ?BaseValidate $productRecordValidate = null;
private ?array $repairFlowCache = null;
public function __construct(
protected CurrentMember $currentMember,
@@ -124,6 +125,61 @@ final class DataEntryController extends Base
]);
}
#[OA\Get(path: '/produce/material/options', summary: '获取维修备件选项', tags: ['produce-web'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
public function materialOptions(Request $request): Response
{
$productCode = trim((string) $request->input('product_code', ''));
$productName = trim((string) $request->input('product_name', ''));
$categoryId = (int) $request->input('category_id', 0);
$page = max(1, (int) $request->input('page', 1));
$limit = min(50, max(1, (int) $request->input('limit', 10)));
$query = Material::query()->with('category:id,category_name');
$query->when($productCode !== '', static fn ($builder) => $builder->where('product_code', 'like', "%{$productCode}%"));
$query->when($productName !== '', static fn ($builder) => $builder->where('product_name', 'like', "%{$productName}%"));
$query->when($categoryId > 0, static fn ($builder) => $builder->where('category_id', $categoryId));
$usageCounts = $this->materialUsageCounts();
$materials = $query->get(['id', 'category_id', 'product_code', 'product_name'])
->sort(function (Material $left, Material $right) use ($usageCounts): int {
$leftCount = $usageCounts[(string) $left->product_code] ?? 0;
$rightCount = $usageCounts[(string) $right->product_code] ?? 0;
return $leftCount === $rightCount
? strcmp((string) $left->product_code, (string) $right->product_code)
: $rightCount <=> $leftCount;
});
$total = $materials->count();
$items = $materials->forPage($page, $limit)
->map(static fn (Material $material): array => [
'id' => (string) $material->id,
'category_id' => (string) $material->category_id,
'category_name' => (string) $material->category_name,
'product_code' => (string) $material->product_code,
'product_name' => (string) $material->product_name,
])
->values()
->all();
return Json::success('获取成功', compact('items', 'total', 'page', 'limit'));
}
private function materialUsageCounts(): array
{
$counts = [];
RepairRecord::query()->without('exception')->whereNotNull('parts')->get(['parts'])->each(function (RepairRecord $record) use (&$counts): void {
foreach ((array) $record->parts as $part) {
$code = trim((string) ($part['product_code'] ?? ''));
if ($code === '') {
continue;
}
$counts[$code] = ($counts[$code] ?? 0) + max(1, (int) ($part['quantity'] ?? 1));
}
});
return $counts;
}
#[OA\Get(path: '/produce/scanapp/material-category', summary: '获取扫码端物料分类选项', tags: ['produce-web'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
@@ -644,7 +700,7 @@ final class DataEntryController extends Base
$query = match ($recordType) {
'scan' => ScanData::query()->with(['process', 'step', 'status']),
'product' => ProductRecord::query()->with(['process', 'step', 'status']),
'exception' => ExceptionRecord::query(),
'exception' => ExceptionRecord::query()->with(['process', 'step']),
'repair' => RepairRecord::query()->with('exception'),
};
@@ -731,7 +787,7 @@ final class DataEntryController extends Base
ProductRecord::query()->with(['process', 'step', 'status'])->where('process_order_no', $orderNo)->get()->each(function (ProductRecord $item) use ($timeline): void {
$timeline->push($this->formatTimelineRecord('product', $item, (int) $item->created_at));
});
ExceptionRecord::query()->where('order_no', $orderNo)->get()->each(function (ExceptionRecord $item) use ($timeline): void {
ExceptionRecord::query()->with(['process', 'step'])->where('order_no', $orderNo)->get()->each(function (ExceptionRecord $item) use ($timeline): void {
$timeline->push($this->formatTimelineRecord('exception', $item, (int) $item->reported_at));
});
RepairRecord::query()->with('exception')->where('order_no', $orderNo)->get()->each(function (RepairRecord $item) use ($timeline): void {
@@ -1063,6 +1119,22 @@ final class DataEntryController extends Base
return Json::fail('用户编码不存在');
}
$config = $this->getConfigGroupData('basic_setting');
$processId = (int) ($config['exception_process_id'] ?? 0);
$stepId = (int) ($config['exception_step_id'] ?? 0);
if ($processId <= 0 || $stepId <= 0) {
return Json::fail('异常提报工序步骤尚未配置,请联系管理员');
}
$step = ProcessStep::query()
->whereKey($stepId)
->where('process_id', $processId)
->where('status', 1)
->first();
if (!$step) {
return Json::fail('异常提报工序步骤配置无效,请联系管理员');
}
$type = ExceptionSetting::query()
->whereKey($typeId)
->where('setting_type', 'type')
@@ -1099,24 +1171,26 @@ final class DataEntryController extends Base
return is_string($image) && preg_match('/^https?:\/\//', $image) === 1;
}));
$record = ExceptionRecord::query()->create([
'order_no' => $orderNo,
'user_code' => $userCode,
'reporter' => (string) ($member->nickname ?: $member->username),
'reporter_id' => (int) $member->id,
'type_id' => $type->id,
'type_code' => $type->code,
'type_name' => $type->name,
'reason_id' => $reason->id,
'reason_code' => $reason->code,
'reason_name' => $reason->name,
'issue_id' => $issue?->id,
'issue_code' => $issue?->code,
'issue_name' => $issue?->name,
'images' => $images,
'remark' => trim((string) ($data['remark'] ?? '')),
'status' => 0,
'reported_at' => time(),
]);
'order_no' => $orderNo,
'process_id' => $processId,
'step_id' => $stepId,
'user_code' => $userCode,
'reporter' => (string) ($member->nickname ?: $member->username),
'reporter_id' => (int) $member->id,
'type_id' => $type->id,
'type_code' => $type->code,
'type_name' => $type->name,
'reason_id' => $reason->id,
'reason_code' => $reason->code,
'reason_name' => $reason->name,
'issue_id' => $issue?->id,
'issue_code' => $issue?->code,
'issue_name' => $issue?->name,
'images' => $images,
'remark' => trim((string) ($data['remark'] ?? '')),
'status' => 0,
'reported_at' => time(),
]);
return Json::success('提交成功', ['id' => $record->id]);
}
@@ -1223,13 +1297,13 @@ final class DataEntryController extends Base
'record_type_name' => '异常提报',
'order_no' => (string) $item->order_no,
'product_code' => '',
'process_name' => '',
'step_name' => '',
'process_name' => (string) $item->process_name,
'step_name' => (string) $item->step_name,
'status_name' => (string) $item->status_name,
'result_name' => (string) $item->type_name,
'operator' => (string) $item->reporter,
'user_code' => (string) $item->user_code,
'quantity' => null,
'quantity' => 1,
'exception_reason' => (string) $item->reason_name,
'exception_content' => (string) $item->issue_name,
'event_at' => (int) $item->reported_at,
@@ -1242,13 +1316,13 @@ final class DataEntryController extends Base
'record_type_name' => '维修记录',
'order_no' => (string) $item->order_no,
'product_code' => (string) $item->product_code,
'process_name' => '',
'step_name' => '',
'process_name' => $this->getRepairFlowNodeNames()['process_name'],
'step_name' => $this->getRepairFlowNodeNames()['step_name'],
'status_name' => '已维修',
'result_name' => (string) $item->repair_result_name,
'operator' => (string) $item->operator,
'user_code' => '',
'quantity' => null,
'user_code' => (string) ($item->exception?->user_code ?? ''),
'quantity' => 1,
'material_category' => (string) ($item->category_name ?: $item->category_names),
'fault_description' => (string) ($item->fault_description_name ?: $item->fault_description),
'event_at' => (int) ($item->repaired_at ?: $item->created_at),
@@ -1258,6 +1332,28 @@ final class DataEntryController extends Base
};
}
/** 获取维修记录配置的工序与步骤名称。 */
private function getRepairFlowNodeNames(): array
{
if ($this->repairFlowCache !== null) {
return $this->repairFlowCache;
}
$basic = $this->getConfigGroupData('basic_setting');
$flow = $this->getConfigGroupData('repair_flow');
$processId = (int) ($basic['repair_process_id'] ?? 0);
$stepId = (int) ($basic['repair_step_id'] ?? 0);
$processId = $processId > 0 ? $processId : (int) ($flow['repair_process_id'] ?? 0);
$stepId = $stepId > 0 ? $stepId : (int) ($flow['repair_step_id'] ?? 0);
$process = $processId > 0 ? Process::query()->withTrashed()->find($processId) : null;
$step = $stepId > 0 ? ProcessStep::query()->withTrashed()->find($stepId) : null;
return $this->repairFlowCache = [
'process_name' => (string) ($process?->process_name ?? ''),
'step_name' => (string) ($step?->step_name ?? ''),
];
}
private function formatTimelineRecord(string $recordType, object $item, int $eventAt): array
{
$record = $this->formatQueryRecord($recordType, $item);