mes-uat-changshu0609-temp-wj-250616-xisu
王杰 4 weeks ago
parent cedb437b4e
commit 12ca15bb36

@ -271,4 +271,10 @@ public interface IMesProductionDispatchContextStepService {
@ApiOperation(value = "保存站点用于缺料时进行上料绑定")
void dispatchMatchStationContext(StationRequestBean reqBean, List<MesStation> stationList);
@ApiOperation(value = "获取站点用于扣料业务中进行LOCK")
Map<String, MesStation> getLockStationContext(StationRequestBean reqBean);
@ApiOperation(value = "保存站点用于后面扣料业务中进行LOCK")
void dispatchLockStationContext(StationRequestBean reqBean, Map<String, MesStation> stationMap2Lock);
}

@ -96,12 +96,14 @@ public class MesStationContainerSnExtService implements IMesStationContainerSnEx
//获取容器条码上料明细表信息
List<MesContainerPackageDetail> containerPackageDetailList = getContainerPackageDetailList(organizeCode, containerPackageList);
if (CollectionUtils.isEmpty(containerPackageDetailList)) return null;
//站点根据名称分组
Map<String, List<MesStation>> stationMap = stationList.stream().filter(o -> null != o).collect(Collectors.groupingBy(MesStation::getStation));
//封装容器条码对应的【可扣减】的上料明细表信息
List<MesContainerPackageDetailContext> containerSnStationContextList = new ArrayList<>();
for (MesContainerPackageDetail containerPackageDetail : containerPackageDetailList) {
if (null == containerPackageDetail) continue;
MesContainerSnStation containerSnStation = containerSnStationMap.get(containerPackageDetail.getContainerSn()).get(0);
containerSnStationContextList.add(new MesContainerPackageDetailContext(containerPackageDetail).station(containerSnStation));
containerSnStationContextList.add(new MesContainerPackageDetailContext(containerPackageDetail).containerSnStation(containerSnStation).station(stationMap.get(containerSnStation.getStation()).get(0)));
}
//根据 主表seq,主表createDatetime,明细表createDatetime 正序
containerSnStationContextList = containerSnStationContextList.stream().filter(o -> null != o).sorted(Comparator.comparing(MesContainerPackageDetailContext::getSeq)

@ -68,11 +68,15 @@ public class MesFunctionDialogInputOrderQtyService extends BaseSwsService implem
reqBean.setInterfaceType(MesPcnConstWords.SHIPPING);
reqBean.setBusiType(MesPcnEnumUtil.ACTOR_RECEIVE_STRATEGY.WS_CMD_DO_SCAN.getCode());
if (!stepResult.isCompleted()) {
reqBean.setTriggerAutoFsm(true);
} else {
if (!stepResult.isCompleted()) reqBean.setTriggerAutoFsm(true);
else {
//保存弹框输入的工单数量
productionDispatchContextStepService.dispatchOrderQtyDialogContext(reqBean, buttonDynamicModel.getFunctionValue());
reqBean.setButtonCode(buttonDynamicModel.getButtonCode());
reqBean.setStepDialogStatus(true);
}
shippingDispatchService.sendScanQueueNextExec(reqBean);

@ -0,0 +1,154 @@
package cn.estsh.i3plus.ext.mes.pcn.apiservice.serviceimpl.step;
import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesProductionCustomContextStepService;
import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesProductionDispatchContextStepService;
import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesProductionProcessContextStepService;
import cn.estsh.i3plus.ext.mes.pcn.api.busi.IMesStationContainerSnExtService;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesProdRuleContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.context.MesProductionProcessContext;
import cn.estsh.i3plus.ext.mes.pcn.pojo.util.MesPcnExtConstWords;
import cn.estsh.i3plus.mes.pcn.actor.shipping.dispatch.IFsmCommonService;
import cn.estsh.i3plus.mes.pcn.serviceimpl.fsm.BaseStepService;
import cn.estsh.i3plus.pojo.base.enumutil.MesPcnEnumUtil;
import cn.estsh.i3plus.pojo.mes.bean.MesStation;
import cn.estsh.i3plus.pojo.mes.model.StationRequestBean;
import cn.estsh.i3plus.pojo.mes.model.StationResultBean;
import cn.estsh.i3plus.pojo.mes.model.StepResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description :
* @Author : wangjie
**/
@Slf4j
@Service("mesStationDeductionAssemblyStepService")
public class MesStationDeductionAssemblyStepService extends BaseStepService {
@Autowired
private IMesProductionProcessContextStepService productionProcessContextStepService;
@Autowired
private IMesProductionDispatchContextStepService productionDispatchContextStepService;
@Autowired
private IMesProductionCustomContextStepService productionCustomContextStepService;
@Autowired
private IMesStationContainerSnExtService stationContainerSnExtService;
@Autowired
private IFsmCommonService fsmCommonService;
private final static Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
@Override
public StepResult init(StationRequestBean reqBean) {
StepResult stepResult = StepResult.getSuccessComplete();
String endlessLoopReadTimes = fsmCommonService.handleFsmWcpcMapDataForDoScanThenBackValue(reqBean, MesPcnExtConstWords.ENDLESS_LOOP_READ_TIMES);
if (StringUtils.isEmpty(endlessLoopReadTimes)) endlessLoopReadTimes = MesPcnExtConstWords.ENDLESS_LOOP_READ_TIMES_DEFAULT;
if (productionDispatchContextStepService.dispatchOverEndlessLoopReadTimes(reqBean, endlessLoopReadTimes)) {
stepThreadSleepAndSendTaskCompleteAndThrowEx(reqBean, new StationResultBean().isWs(false).writeDbLog().checkRepeat(),
stepResult.isCompleted(false).msg(String.format("当前未能执行完成站点扣减装配件超过[%s]次!", endlessLoopReadTimes)),
MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, getStepParams(reqBean), MesPcnExtConstWords.READ_FAILURE_SLEEP, MesPcnExtConstWords.READ_FAILURE_SLEEP_DEFAULT_TIME);
}
//发送工步内容
productionCustomContextStepService.sendStepContextMessage(reqBean);
return stepResult;
}
@Override
public StepResult execute(StationRequestBean reqBean) {
StationResultBean resultBean = new StationResultBean();
StepResult stepResult = StepResult.getSuccessComplete();
//获取工位当前设备信息
MesProductionProcessContext productionProcessContext = productionProcessContextStepService.dispatchCurCellEquipment(reqBean);
//配置错误 抛出异常
if (!productionProcessContext.getSuccess()) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(), productionProcessContext.getMessage());
//获取上下文产品加工规则数据信息集合
List<MesProdRuleContext> prodRuleContextList = productionDispatchContextStepService.getProdRuleDataContext(reqBean);
if (CollectionUtils.isEmpty(prodRuleContextList)) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(), "当前不存在产品加工规则信息,请重置工序解决!");
//验证是否存在装配件容器匹配
Optional<MesProdRuleContext> optional = prodRuleContextList.stream().filter(o -> (null != o && !StringUtils.isEmpty(o.getIsMatchContainer()))).findFirst();
if (null == optional || !optional.isPresent()) {
return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult, true,
MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "验证每腔不存在容器匹配的装配件,默认跳过站点扣减装配件!");
}
//获取站点用于扣料业务中进行LOCK
Map<String, MesStation> stationMap2Lock = productionDispatchContextStepService.getLockStationContext(reqBean);
List<String> stationNameList = CollectionUtils.isEmpty(stationMap2Lock) ? null : new ArrayList<>(stationMap2Lock.keySet());
if (CollectionUtils.isEmpty(stationNameList)) stepExpSendMsgAndThrowEx(reqBean, resultBean.writeDbLog(), "当前未获取到缓存的扣料站点信息,请重置工序解决!");
List<ReentrantLock> acquiredLocks = new ArrayList<>();
try {
if (!tryLock(acquiredLocks, stationNameList)) {
return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog().checkRepeat(), stepResult, false,
MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "站点扣减装配件扣减失败,即将重试!");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
unLockAll(acquiredLocks);
}
return stepResult;
}
private Boolean tryLock(List<ReentrantLock> acquiredLocks, List<String> stationNameList) throws InterruptedException {
Collections.sort(stationNameList);
long remaining = TimeUnit.MILLISECONDS.toNanos(3000);
long endTime = System.nanoTime() + remaining;
for (String stationName : stationNameList) {
if (StringUtils.isEmpty(stationName)) continue;
ReentrantLock lock = lockMap.computeIfAbsent(stationName, o -> new ReentrantLock());
if (!lock.tryLock(remaining, TimeUnit.NANOSECONDS)) return false;
acquiredLocks.add(lock);
remaining = endTime - System.nanoTime();
}
return true;
}
private void unLockAll(List<ReentrantLock> acquiredLocks) {
for (int i = acquiredLocks.size() - 1; i >= 0; i --) acquiredLocks.get(i).unlock();
}
}

@ -51,9 +51,6 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
@Autowired
private IMesTimeEfficientCfgMatchService timeEfficientCfgMatchService;
@Autowired
private MesAssemblyShowNosortStepService assemblyShowNosortStepService;
@Override
public StepResult execute(StationRequestBean reqBean) {
@ -75,7 +72,7 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
Optional<MesProdRuleContext> optional = prodRuleContextList.stream().filter(o -> (null != o && !StringUtils.isEmpty(o.getIsMatchContainer()))).findFirst();
if (null == optional || !optional.isPresent()) {
return stepDynamicsCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult, true,
MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "验证每腔不存在容器匹配的装配件,默认跳过容器条码扣减装配件!");
MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, "验证每腔不存在容器匹配的装配件,默认跳过容器扣减匹配站点!");
}
//处理设备站点【装配件站点&&混料站点】
@ -90,7 +87,8 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
//获取容器条码上料明细表信息【可扣减】【根据 主表seq,主表createDatetime,明细表createDatetime 正序】
List<MesContainerPackageDetailContext> containerSnStationList = stationContainerSnExtService.getContainerSnStationContext(reqBean.getOrganizeCode(), stationList);
if (CollectionUtils.isEmpty(containerSnStationList)) return stepNonCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult, "容器条码扣减装配件时验证当前无可扣减的原材料信息!");
if (CollectionUtils.isEmpty(containerSnStationList)) return stepNonCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(),
stepResult.nextTriggerEvent(MesPcnExtConstWords.NEXT_TRIGGER_EVENT_FEEDING), "容器条码扣减装配件时验证当前无可扣减的原材料信息!");
//将可扣减的条码进行原子性缓存, 并获取最新的实际库存
productionCustomContextStepService.dispatchContainerSnAtomicity(reqBean.getOrganizeCode(), containerSnStationList);
@ -100,12 +98,18 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
MesWorkCenter workCenter = productionProcessContext.getWorkCenter();
//站点信息用于后面扣料业务中进行LOCK
Map<String, MesStation> stationMap2Lock = new HashMap<>();
//匹配装配件的工序用量
dispatchStationMatchAssembly(reqBean, resultBean, stepResult, workCenter, prodRuleContextList, containerSnStationList, orderQtyDialogContext);
dispatchStationMatchAssembly(reqBean, resultBean, stepResult, workCenter, prodRuleContextList, containerSnStationList, orderQtyDialogContext, stationMap2Lock);
if (!stepResult.isCompleted()) return stepNonCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult, stepResult.getMsg());
if (!stepResult.isCompleted()) return stepNonCompleteAndSendMsgReturn(reqBean, resultBean.writeDbLog(), stepResult.nextTriggerEvent(MesPcnExtConstWords.NEXT_TRIGGER_EVENT_FEEDING), stepResult.getMsg());
//保存上下文产品加工规则信息集合
productionDispatchContextStepService.dispatchProdRuleDataContext(reqBean, prodRuleContextList);
//保存站点用于后面扣料业务中进行LOCK
productionDispatchContextStepService.dispatchLockStationContext(reqBean, stationMap2Lock);
if (workCenter.getCenterType().compareTo(MesExtEnumUtil.WORK_CENTER_TYPE.NOSORT.getValue()) == 0)
((MesAssemblyShowNosortStepService) SpringContextsUtil.getBean("mesAssemblyShowNosortStepService")).showProductionAssembly(reqBean, resultBean, workCenter, prodRuleContextList);
@ -118,10 +122,11 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
//匹配装配件的工序用量【遍历加工规则】
private void dispatchStationMatchAssembly(StationRequestBean reqBean, StationResultBean resultBean, StepResult stepResult, MesWorkCenter workCenter,
List<MesProdRuleContext> prodRuleContextList, List<MesContainerPackageDetailContext> containerSnStationList, Double orderQtyDialogContext) {
List<MesProdRuleContext> prodRuleContextList, List<MesContainerPackageDetailContext> containerSnStationList,
Double orderQtyDialogContext, Map<String, MesStation> stationMap2Lock) {
for (MesProdRuleContext prodRuleContext : prodRuleContextList) {
if (null == prodRuleContext || StringUtils.isEmpty(prodRuleContext.getIsMatchContainer())) continue;
List<MesProductionAssemblyContext> productionAssemblyContextList = dispatchStationMatchAssembly(reqBean, resultBean, stepResult, workCenter, prodRuleContext, containerSnStationList, orderQtyDialogContext);
List<MesProductionAssemblyContext> productionAssemblyContextList = dispatchStationMatchAssembly(reqBean, resultBean, stepResult, workCenter, prodRuleContext, containerSnStationList, orderQtyDialogContext, stationMap2Lock);
if (!stepResult.isCompleted()) break;
prodRuleContext.assemblyDataJson(productionAssemblyContextList);
}
@ -129,7 +134,8 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
//匹配装配件的工序用量【遍历装配件】
private List<MesProductionAssemblyContext> dispatchStationMatchAssembly(StationRequestBean reqBean, StationResultBean resultBean, StepResult stepResult, MesWorkCenter workCenter,
MesProdRuleContext prodRuleContext, List<MesContainerPackageDetailContext> containerSnStationList, Double orderQtyDialogContext) {
MesProdRuleContext prodRuleContext, List<MesContainerPackageDetailContext> containerSnStationList,
Double orderQtyDialogContext, Map<String, MesStation> stationMap2Lock) {
List<MesProductionAssemblyContext> productionAssemblyContextList = prodRuleContext.getAssemblyDataContext(workCenter);
for (MesProductionAssemblyContext productionAssemblyContext : productionAssemblyContextList) {
if (null == productionAssemblyContext || productionAssemblyContext.getMatchType().compareTo(MesExtEnumUtil.ASSEMBLY_MATCH_TYPE.MATCH_TYPE_80.getValue()) != 0) continue;
@ -171,6 +177,7 @@ public class MesStationMatchAssemblyStepService extends BaseStepService {
}
containerSnDataList.add(productionCustomContextStepService.getContainerSnAtomicityKey(reqBean.getOrganizeCode(), containerPackageDetailContext));
containerSnList.add(containerPackageDetailContext.getContainerSn());
if (!stationMap2Lock.containsKey(containerPackageDetailContext.getStation())) stationMap2Lock.put(containerPackageDetailContext.getStation(), containerPackageDetailContext.getStationInfo());
}
if (MathOperation.compareTo(unMatchQty, new Double(0)) > 0) {

@ -63,8 +63,8 @@ public class MesWorkOrderQueueAcceptStepService extends BaseStepService {
@Autowired
private IShippingDispatchService shippingDispatchService;
private static Map<String, String> lockTimeMap = new HashMap<>();
private static Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private final static Map<String, String> lockTimeMap = new HashMap<>();
private final static Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
@Override
public void title(StationRequestBean reqBean) {
@ -79,7 +79,7 @@ public class MesWorkOrderQueueAcceptStepService extends BaseStepService {
String endlessLoopReadTimes = fsmCommonService.handleFsmWcpcMapDataForDoScanThenBackValue(reqBean, MesPcnExtConstWords.ENDLESS_LOOP_READ_TIMES);
if (StringUtils.isEmpty(endlessLoopReadTimes)) endlessLoopReadTimes = MesPcnExtConstWords.ENDLESS_LOOP_READ_TIMES_DEFAULT;
if (productionDispatchContextStepService.dispatchOverEndlessLoopReadTimes(reqBean, endlessLoopReadTimes)) {
stepThreadSleepAndSendTaskCompleteAndThrowEx(reqBean, new StationResultBean().isWs(false),
stepThreadSleepAndSendTaskCompleteAndThrowEx(reqBean, new StationResultBean().isWs(false).writeDbLog().checkRepeat(),
stepResult.isCompleted(false).msg(String.format("当前未获取到工位工单队列超过[%s]次!", endlessLoopReadTimes)),
MesPcnEnumUtil.STATION_BUSI_TYPE.RUNNING_INFO, MesPcnEnumUtil.STATION_DATA_TYPE.TEXT, getStepParams(reqBean), MesPcnExtConstWords.READ_FAILURE_SLEEP, MesPcnExtConstWords.READ_FAILURE_SLEEP_DEFAULT_TIME);
}

@ -606,7 +606,7 @@ public class MesProductionDispatchContextStepService extends BaseStepService imp
@Override
public List<MesStation> getMatchStationContext(StationRequestBean reqBean) {
String stationContext = getFsmBusiData(reqBean.getOrganizeCode(), getContextKey(reqBean), MesPcnExtConstWords.MATCH_STATION_CONTEXT);
return StringUtils.isEmpty(stationContext) ? null : JSONObject.parseArray(stationContext, MesStation.class);
return !StringUtils.isEmpty(stationContext) ? JSONObject.parseArray(stationContext, MesStation.class) : null;
}
//保存站点用于缺料时进行上料绑定
@ -616,4 +616,18 @@ public class MesProductionDispatchContextStepService extends BaseStepService imp
dispatchFsmBusiData(reqBean.getOrganizeCode(), getContextKey(reqBean), MesPcnExtConstWords.MATCH_STATION_CONTEXT, JSONObject.toJSONString(stationList));
}
//获取站点用于扣料业务中进行LOCK
@Override
public Map<String, MesStation> getLockStationContext(StationRequestBean reqBean) {
String stationContext = getFsmBusiData(reqBean.getOrganizeCode(), getContextKey(reqBean), MesPcnExtConstWords.LOCK_STATION_CONTEXT);
return !StringUtils.isEmpty(stationContext) ? JSONObject.parseObject(stationContext, new TypeReference<Map<String, MesStation>>() {}) : null;
}
//保存站点用于后面扣料业务中进行LOCK
@Override
public void dispatchLockStationContext(StationRequestBean reqBean, Map<String, MesStation> stationMap2Lock) {
if (CollectionUtils.isEmpty(stationMap2Lock)) return;
dispatchFsmBusiData(reqBean.getOrganizeCode(), getContextKey(reqBean), MesPcnExtConstWords.LOCK_STATION_CONTEXT, JSONObject.toJSONString(stationMap2Lock));
}
}

@ -3,6 +3,7 @@ package cn.estsh.i3plus.ext.mes.pcn.pojo.context;
import cn.estsh.i3plus.ext.mes.pcn.pojo.util.MesPcnExtConstWords;
import cn.estsh.i3plus.pojo.mes.bean.MesContainerPackageDetail;
import cn.estsh.i3plus.pojo.mes.bean.MesContainerSnStation;
import cn.estsh.i3plus.pojo.mes.bean.MesStation;
import io.swagger.annotations.ApiParam;
import lombok.Data;
import org.springframework.beans.BeanUtils;
@ -30,19 +31,38 @@ public class MesContainerPackageDetailContext extends MesContainerPackageDetail
@ApiParam("库存")
private Double remainQty;
@ApiParam("库存")
private Double deductionQty;
@ApiParam(name = "设备代码")
private String equipmentCode;
@ApiParam(name = "站点类型")
private Integer stationType;
@ApiParam(name = "加工模式")
private Integer processMethod;
public MesContainerPackageDetailContext() {}
public MesContainerPackageDetailContext(MesContainerPackageDetail containerPackageDetail) {
BeanUtils.copyProperties(containerPackageDetail, this);
}
public MesContainerPackageDetailContext station(MesContainerSnStation containerSnStation) {
public MesContainerPackageDetailContext containerSnStation(MesContainerSnStation containerSnStation) {
this.station = containerSnStation.getStation();
this.seq = !StringUtils.isEmpty(containerSnStation.getSeq()) ? containerSnStation.getSeq() : MesPcnExtConstWords.ZERO;
this.createDatetime2Package = containerSnStation.getCreateDatetime();
return this;
}
public MesContainerPackageDetailContext station(MesStation station) {
this.equipmentCode = station.getEquipmentCode();
this.stationType = station.getStationType();
this.processMethod = station.getProcessMethod();
return this;
}
public MesContainerPackageDetailContext remainQty(String remainQty) {
return remainQty(!StringUtils.isEmpty(remainQty) ? new Double(remainQty) : new Double(MesPcnExtConstWords.ZERO));
}
@ -52,4 +72,12 @@ public class MesContainerPackageDetailContext extends MesContainerPackageDetail
return this;
}
public MesStation getStationInfo() {
MesStation station = new MesStation();
station.setEquipmentCode(this.equipmentCode);
station.setStationType(this.stationType);
station.setProcessMethod(this.processMethod);
return station;
}
}

@ -551,6 +551,8 @@ public class MesPcnExtConstWords {
public static final String NEXT_TRIGGER_EVENT_PRODUCT_SN = "PRODUCT_SN";
// 自定义触发事件: 产出零件
public static final String NEXT_TRIGGER_EVENT_PART_NO = "PART_NO";
// 自定义触发事件: 站点补料
public static final String NEXT_TRIGGER_EVENT_FEEDING = "FEEDING";
// 0
public static final String ZERO_STR = "0";
@ -744,6 +746,8 @@ public class MesPcnExtConstWords {
public static final String ORDER_QTY_DIALOG_CONTEXT = "ORDER_QTY_DIALOG_CONTEXT";
// 用于缺料时进行上料绑定的站点
public static final String MATCH_STATION_CONTEXT = "MATCH_STATION_CONTEXT";
// 用于扣料业务中进行LOCK
public static final String LOCK_STATION_CONTEXT = "LOCK_STATION_CONTEXT";
//OPC_API_PARAM

Loading…
Cancel
Save