SCTMES_V5/mes_in_sct/app/action/SetMaterialInput.php
2025-06-14 18:55:09 +08:00

350 lines
20 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\action;
use libs\listener\Event;
use libs\db\Db;
use Exception;
// 物料投入
class SetMaterialInput
{
public function execute($post)
{
return $this->do_set_material_input($post);
}
/*
制片段设备物料投料出现以下场景:
一、一个或多个原材料 => 半成品
例子:镍钴锰酸锂+导电剂 => 正极搅拌半成品
二、半成品+原材料 => 新半成品
例子:正极搅拌半成品 + 铝箔 => 涂布半成品
三、半成品 => 新半成品
例子:涂布半成品 => 辊压半成品
接口api示例
{
"action": "set_material_input",
"param": {
"batch": "", #当batch不为空代表正极制片段、负极制片段、装配段第一个工序当batch为空时代表正极制片段、负极制片段、装配段除了第一个工序其他工序batch以半成品wip的批次为准
"workingsubclass": "ZJ", # 工序单元
"device_code": "FJZJ-1", # 设备编码
"in": [{
"item_code":"tongbo", #物料编码
"item_batch": "fengezi-1", #物料批次
"item_quantity":"345.34", #投入数量
"item_id": "-1" #物料虚拟码当item_id=-1时代表原材料当item_id!=-1代表半成品wip
}]
}
}
*/
/*
* 通过batch->flow->model->bom->workingsubclass->BomRelationship对应的bom结构
* 1.获取请求体设备编码参数
* 2.判断工序单元、设备编码是存在?根据设备编码查询批次工序日志表,判断该设备是否已经存在生产中物料信息
* 3.获取生产批次关联工艺流程工艺流程关联型号型号关联bombom关联bom关系bom关系关联物料信息
* 4.通过以上关联信息查询物料编码
* 4.1.获取bom的物料编码
* 4.2.获取param_in结构的物料编码
* 5.检查上传param参数中in结构是否缺少物料
* 6.剔除上传param参数中in结构多余物料信息获取最终需要处理param_in
* 7.判断原材料或半成品是否已经投入批次工序日志缓存表,不允许重复再投,只能先把上一次投入物料结束,才能允许再投入;
* 8.拼接存储结构上传数据到批次工序日志缓存表原材料新增数据半成品更改半成品状态为0
* 9.半成品更改半成品状态为0表示进行设备投料
* 10.原材料数组数据写入缓存表
*/
private function do_set_material_input($post)
{
// 验证数据
$param = $post['param'];
//获取上一工作站
try {
if (empty($param["workingsubclass"])) {
throw new Exception("缺少workingsubclass参数");
}
if (empty($param["device_code"])) {
throw new Exception("缺少device_code参数");
}
if (empty($param["in"]) || count($param["in"]) < 0) {
throw new Exception("缺少in参数");
}
//1.获取请求体设备编码参数
$workingsubclass = $param["workingsubclass"];
$batch = $param["batch"];
$device_code = $param["device_code"];
$param_in = $param["in"];
// 缓存wip的item_id,为后续通过item_id获取batch
$wip_item_id = '';
// 判断in结构参数是否上传
foreach ($param_in as $key => $values) {
if (empty($values["item_code"])) {
throw new Exception("上传的in结构内" . ($key + 1) . "个item_code参数缺少数据");
}
if (empty($values["item_batch"])) {
throw new Exception("上传的in结构内" . ($key + 1) . "个item_batch参数缺少数据");
}
if (!isset($values["item_quantity"]) && $values["item_quantity"] == "") {
throw new Exception("上传的in结构内" . ($key + 1) . "个item_quantity参数缺少数据");
}
if (empty($values["item_id"])) {
throw new Exception("上传的in结构内" . ($key + 1) . "个item_id参数缺少数据");
}
if ($wip_item_id == '' && $values["item_id"] != '-1') {
$wip_item_id = $values["item_id"];
}
}
if ($batch == '' && $wip_item_id == '') {
throw new Exception("不允许上传batch为空同时param参数in结构内容item_id为-1情况当batch为空时代表除了工序段第一个工序其他工序item_id必须有一个不为-1的值");
}
// 判断in结构内item_code、item_id是否有重复值同时忽略键值对[['key'=> 'item_id','value'=> '-1']
// 指定需要校验的键名
$check_keys = ['item_code', 'item_id'];
// 忽略条件,可能存在多个原材料投入所以需要忽略item=-1
$ignore_key_value_pairs = [
['key' => 'item_id', 'value' => '-1'],
];
$duplicates = findDuplicates($param_in, $check_keys, $ignore_key_value_pairs);
if (!empty($duplicates)) {
$duplicates_str = '上传的in结构内';
foreach ($duplicates as $key => $value) {
$duplicates_str .= '参数' . $key . '存在重复值【' . implode("", $value) . '】,';
}
throw new Exception($duplicates_str);
}
//2.判断工序单元、设备编码是存在?
$sql = "SELECT id FROM hf_mes_process_workingsubclass WHERE code='" . $workingsubclass . "'";
$ret = Db::fetch($sql);
if (empty($ret)) {
throw new Exception("工序单元[$workingsubclass]不存在");
}
$sql = "SELECT id FROM hf_mes_device WHERE code='" . $device_code . "'";
$ret = Db::fetch($sql);
if (empty($ret)) {
throw new Exception("设备编码[$device_code]不存在");
}
//判断是否存在已经上料的物料
$param_in_item_code = array_column($param_in, 'item_code');
$sql = "SELECT item_code,batch FROM hf_mes_bkv_batch_process_log WHERE device_code='" . $device_code . "' AND workingsubclass='" . $workingsubclass . "' AND item_code IN ('" . implode("','", $param_in_item_code) . "') AND status=0";
$ret = Db::fetchAll($sql);
if ($ret) {
throw new Exception('工序单元[' . $workingsubclass . ']下设备编码[' . $device_code . ']在批次[' . implode(',', array_column($ret, 'batch')) . ']中已经存在投入[' . implode(',', array_column($ret, 'item_code')) . ']物料,请先下料后再上料!');
}
//3.获取生产批次关联工艺流程工艺流程关联型号型号关联bombom关联bom关系bom关系关联物料信息
if (empty($batch)) {
// 当batch为空时代表正极制片段、负极制片段、装配段除了第一个工序其他工序batch以半成品wip的批次为准
$sql = "SELECT batch FROM hf_mes_production_battery_map WHERE battery_id='" . $wip_item_id . "' ORDER BY id DESC LIMIT 1";
$ret = Db::fetch($sql);
if (empty($ret)) {
throw new Exception('半成品' . $wip_item_id . '找不到对应批次');
}
$batch = $ret['batch'];
}
$subbatch = '';
$process_code = '';
$sql = "SELECT b.subbatch,c.process FROM hf_mes_production_planning_management_batch a
INNER JOIN hf_mes_production_planning_management_subbatch b ON a.id = b.batch_id
INNER JOIN hf_mes_technology_flow c ON b.flow_id = c.id
WHERE batch='" . $batch . "' ORDER BY b.id DESC LIMIT 1";
$ret = Db::fetch($sql);
if (empty($ret)) {
throw new Exception("批次[" . $batch . "]不存在子批次信息,请检查批次是否已创建");
}
$subbatch = $ret['subbatch'];
// 拿到工序编码
foreach (json_decode($ret['process'], true) as $val) {
if ($val[4] == $workingsubclass) {
$process_code = $val[2];
}
}
$param['process_code'] = $process_code;
$param['class_name'] = get_class($this);
// 写入process_log的数据(同步)
$response = Event::emit('ExtScriptEvent.extScriptBegin', $param);
if ($response['ext_script_code'] != 0) {
throw new Exception($response['ext_script_msg']);
}
//4.通过以上关联信息查询物料编码
$sql = "SELECT e.code as bom_source_code,e.name as bom_source_name,d.bom_source_id,d.bom_id,f.code as bom_source_category_code FROM hf_mes_production_planning_management_batch a
INNER JOIN hf_mes_technology_flow b ON a.flow_id = b.id
INNER JOIN hf_mes_product_bom c ON b.product_model_id = c.product_model_id
INNER JOIN hf_mes_product_bom_relationship d ON c.id = d.bom_id
INNER JOIN hf_mes_product_bom_source e ON d.bom_source_id = e.id
INNER JOIN hf_mes_product_bom_source_category f ON e.bom_source_category_id = f.id
WHERE a.batch='" . $batch . "' AND d.in_workingsubclass='" . $workingsubclass . "'";
$ret = Db::fetchAll($sql);
if (empty($ret)) {
throw new Exception('批次' . $batch . '下的工序单元' . $workingsubclass . 'bom关系不存在in结构物料编码');
}
//4.1获取bom的物料编码
$bom_relationship_array = $ret;
$bom_relationship_item_code = array_column($ret, 'bom_source_code');
//4.2获取param_in结构的物料编码
$param_in_item_code = array_column($param_in, 'item_code');
//5.检查上传param参数中in结构的物料是否正确
$missing_material = [];
foreach ($param_in_item_code as $key => $val) {
if (!in_array($val, $bom_relationship_item_code)) {
$missing_material[] = $val;
}
}
if (count($missing_material) > 0) {
throw new Exception("上传param中in结构的item_code物料[" . implode(',', $missing_material) . "]不在本生产批次[$batch]的[$workingsubclass]工序中投料");
}
$uuid = [];
$forward_tracing_uuid = [];
$wip_uuid = [];
$pid = [];
// 6、剔除上传param参数中in结构多余物料信息获取最终需要处理param_in
foreach ($param_in as $key => $value) {
if (!in_array($value['item_code'], $bom_relationship_item_code)) {
unset($param_in[$key]);
} else {
// 7、判断原材料或半成品是否已经投入批次工序日志缓存表不允许重复再投只能先把上一次投入物料结束才能允许再投入
// 如果是某一物料追加再投,用其他接口进行处理
#物料虚拟码当item_id=-1时代表原材料当item_id!=-1代表半成品wip
if ($value['item_id'] == '-1') {
// 判断原材料是否已投入但还未结束,如果是则报警不给投入
$sql = "SELECT id FROM hf_mes_bkv_batch_process_log WHERE workingsubclass='" . $workingsubclass . "' AND item_device_code='" . $device_code . "' AND item_code='" . $value['item_code'] . "' AND status=0 ORDER BY id DESC LIMIT 1";
$ret = Db::fetch($sql);
if (!empty($ret)) {
throw new Exception("工序单元[$workingsubclass]下设备[$device_code]已有正在生产中的物料[" . $value['item_code'] . "],如需投料请先结束生产中的物料");
}
//同一个物料编码和物料批次只能对应一个UUID如果直接获取 一开始就追加forward_tracing_uuid生成需要保证forward_tracing_uuid的唯一性
//通过物料编码和物料批次获取forward_tracing_uuid如果不存再数据则直接生成
$sql = "SELECT forward_tracing_uuid FROM hf_mes_bkv_batch_process_log WHERE item_batch='" . $value['item_batch'] . "' AND item_code='" . $value['item_code'] . "'ORDER BY id DESC LIMIT 1";
$ret = Db::fetch($sql);
if (empty($ret)) {
//直接生成UUID和forward_tracing_uui
$forward_tracing_uuid[] = [generateUniqueId()];
} else {
$forward_tracing_uuid[] = json_decode($ret["forward_tracing_uuid"]);
}
} else {
//判警不给投断半成品是否存在,且状态是未结束;如果不存在或状态是结束,则报入
//追加正向追溯uuidforward_tracing_uuid数据写入
$sql = "SELECT id,uuid,status,pid,forward_tracing_uuid FROM hf_mes_bkv_batch_process_log WHERE item_id='" . $value['item_id'] . "' ORDER BY id DESC LIMIT 1";
$ret = Db::fetch($sql);
if (empty($ret)) {
throw new Exception("物料[" . $value['item_code'] . "]不存在半成品信息[" . $value['item_id'] . "],请检查半成品信息是否存在批次工序日志缓存表");
}
if ($ret['status'] != '-1' && $ret['status'] != '-2') {
throw new Exception("物料[" . $value['item_code'] . "]对应半成品信息[" . $value['item_id'] . "已投料或已生产完成,不允许重复投料,请检查物料状态信息");
}
$forward_tracing_uuid[] = json_decode($ret["forward_tracing_uuid"]);
$uuid = array_merge($uuid, json_decode($ret["uuid"]));
$wip_uuid[$value['item_id']] = $ret["uuid"];
$pid[$value['item_id']] = $ret["pid"];
}
// 把bom_source_id、bom_id、bom_source_category_code加进param为后续拼接insert sql做准备
foreach ($bom_relationship_array as $k => $v) {
if ($v['bom_source_code'] == $value['item_code']) {
$param_in[$key]['bom_source_id'] = $v['bom_source_id'];
$param_in[$key]['bom_source_name'] = $v['bom_source_name'];
$param_in[$key]['bom_id'] = $v['bom_id'];
$param_in[$key]['bom_source_category_code'] = $v['bom_source_category_code'];
}
}
// 冻结解冻列表设置拦截工序调用该接口就会反馈给class和classname到设备
if ($value['item_id'] != '-1') {
$freeze_data = Event::emit('FreezeInterceptionEvent.handle', [
'battery_id' => $v['item_id'],
'workingsubclass' => $ret['next_workingsubclass'],
'ropes_time' => date("Y-m-d H:i:s"),
]);
$h = $k + 1;
if (!empty($freeze_data)) {
// 检查 status 是否为 5 和 workingsubclass为ALLPROCESS
if ($freeze_data['status'] == 5 && $freeze_data['workingsubclass'] = 'ALLPROCESS') {
throw new Exception("(!!!紧急冻结全工序!!!)第[{$h}]位置半成品物料{$value['item_id']}在工序[{$workingsubclass}]已被标记[{$freeze_data['classname']}]冻结,需要设备排出或人工取出!!!");
// 检查 status 是否为 1
} elseif ($freeze_data['status'] == 1) {
throw new Exception("(!!!请先解冻再复投!!!)第[{$h}]位置半成品物料{$value['item_id']}在工序[{$workingsubclass}]已被标记[{$freeze_data['classname']}]冻结,需要设备排出或人工取出!!!");
}
// 默认冻结报错逻辑
throw new Exception("第[{$h}]位置半成品物料{$value['item_id']}在工序[{$workingsubclass}]已被标记[{$freeze_data['classname']}]冻结,需要设备排出或人工取出!!!");
}
}
}
}
// 如果存在半成品uuid则用半成品uuid否则默认为[]
$uuid = json_encode(array_unique($uuid));
$finish_time = date("Y-m-d H:i:s");
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
// 开启事务
Db::beginTrans();
try {
// 8、拼接存储结构上传数据到批次工序日志缓存表原材料新增数据半成品更改半成品状态为0
$material_insert_sql_head = 'INSERT INTO "hf_mes_bkv_batch_process_log" (item_id, batch, subbatch, workingsubclass, process_code, device_code, item_device_code, status, start_time, finish_time, bom_source_id, item_batch, item_quantity, pid, uuid, bom_id, item_code,item_name, lot, tray,forward_tracing_uuid) VALUES';
$material_insert_sql_val = [];
foreach ($param_in as $key => $value) {
$item_id = $value['item_id'];
if ($value['item_id'] == '-1') {
// 生成MES内部item_id
$item_id = generate_material_item_id($workingsubclass, $value['bom_source_category_code'], $value['item_code']);
}
$material_insert_sql_val[] = sprintf(
"('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s','%s','%s')",
$item_id,
$batch,
$subbatch,
$workingsubclass,
$process_code,
$device_code,
$device_code,
0,
$finish_time,
$finish_time,
$value['bom_source_id'],
$value['item_batch'],
$value['item_quantity'],
$value['item_id'] == '-1' ? -1 : $pid[$value['item_id']],
$value['item_id'] == '-1' ? $uuid : $wip_uuid[$value['item_id']],
$value['bom_id'],
$value['item_code'],
$value['bom_source_name'],
strtotime(date('Y-m-d H:i:s')),
'-1',
json_encode($forward_tracing_uuid[$key])
);
}
//10.原材料数组数据写入缓存表
$sql = $material_insert_sql_head . implode(',', $material_insert_sql_val);
$row = Db::query($sql);
if ($row === NULL) {
throw new Exception("原材料新增批次工序日志缓存表失败");
}
} catch (Exception $e) {
Db::rollBackTrans();
throw new Exception($e->getMessage());
}
Db::commitTrans();
return '';
}
}