| | |
| | | <template>
|
| | | <div>
|
| | | <!-- 手动创建换盘机械手任务弹窗 -->
|
| | | <vol-box v-model="showManualCreate" :lazy="true" width="500px" :padding="15" title="手动创建换盘机械手任务">
|
| | | <!-- 手动创建机器人任务弹窗 -->
|
| | | <vol-box v-model="showManualCreate" :lazy="true" width="550px" :padding="15" title="手动创建机器人任务">
|
| | | <el-form :model="manualFormData" ref="form" label-width="120px">
|
| | | <!-- 任务类型选择 -->
|
| | | <el-form-item label="任务类型" prop="robotRoadway" required>
|
| | | <el-select v-model="manualFormData.robotRoadway" placeholder="请选择任务类型" @change="onTaskTypeChange">
|
| | | <el-option label="组盘任务" value="GroupPallet"></el-option>
|
| | | <el-option label="换盘任务" value="ChangePallet"></el-option>
|
| | | <el-option label="拆盘任务" value="SplitPallet"></el-option>
|
| | | </el-select>
|
| | | </el-form-item>
|
| | |
|
| | | <!-- 组盘任务:仅需目标地址托盘码和任务总数 -->
|
| | | <template v-if="manualFormData.robotRoadway === 'GroupPallet'">
|
| | | <el-form-item label="目标地址托盘码" prop="robotTargetAddressPalletCode" required>
|
| | | <el-input v-model="manualFormData.robotTargetAddressPalletCode" placeholder="请输入目标地址托盘码"></el-input>
|
| | | </el-form-item>
|
| | | <el-form-item label="任务总数" prop="robotTaskTotalNum" required>
|
| | | <el-input-number v-model="manualFormData.robotTaskTotalNum" :min="1" :max="999" label="任务总数"></el-input-number>
|
| | | </el-form-item>
|
| | | </template>
|
| | |
|
| | | <!-- 换盘任务:需来源、目标托盘码和4个方向 -->
|
| | | <template v-if="manualFormData.robotRoadway === 'ChangePallet'">
|
| | | <el-form-item label="来源地址托盘码" prop="robotSourceAddressPalletCode" required>
|
| | | <el-input v-model="manualFormData.robotSourceAddressPalletCode" placeholder="请输入来源地址托盘码"></el-input>
|
| | | </el-form-item>
|
| | |
| | | </el-form-item>
|
| | | <el-form-item label="方向" prop="forward" required>
|
| | | <el-radio-group v-model="manualFormData.forward">
|
| | | <el-radio :label="1">去化成(源:1→目标:3)</el-radio>
|
| | | <el-radio :label="2">去化成(源:2→目标:4)</el-radio>
|
| | | <el-radio :label="3">回高温(源:3→目标:1)</el-radio>
|
| | | <el-radio :label="4">回高温(源:4→目标:2)</el-radio>
|
| | | <el-radio :label="1">去化成1: 源:1(11010) → 目标:3(2103)</el-radio>
|
| | | <el-radio :label="2">去化成2: 源:2(11001) → 目标:4(2101)</el-radio>
|
| | | <el-radio :label="3">回高温3: 源:4(2103) → 目标:2(11010)</el-radio>
|
| | | <el-radio :label="4">回高温4: 源:4(2101) → 目标:2(11001)</el-radio>
|
| | | </el-radio-group>
|
| | | </el-form-item>
|
| | | <el-form-item label="任务总数" prop="robotTaskTotalNum" required>
|
| | | <el-input-number v-model="manualFormData.robotTaskTotalNum" :min="1" :max="999" label="任务总数"></el-input-number>
|
| | | </el-form-item>
|
| | | </template>
|
| | |
|
| | | <!-- 拆盘任务:仅需来源地址托盘码和任务总数 -->
|
| | | <template v-if="manualFormData.robotRoadway === 'SplitPallet'">
|
| | | <el-form-item label="来源地址托盘码" prop="robotSourceAddressPalletCode" required>
|
| | | <el-input v-model="manualFormData.robotSourceAddressPalletCode" placeholder="请输入来源地址托盘码"></el-input>
|
| | | </el-form-item>
|
| | | <el-form-item label="任务总数" prop="robotTaskTotalNum" required>
|
| | | <el-input-number v-model="manualFormData.robotTaskTotalNum" :min="1" :max="999" label="任务总数"></el-input-number>
|
| | | </el-form-item>
|
| | | </template>
|
| | | </el-form>
|
| | | <template #footer>
|
| | | <el-button type="primary" size="small" @click="submitManualCreate">确定</el-button>
|
| | | <el-button type="danger" size="small" @click="showManualCreate = false">关闭</el-button>
|
| | | <el-button type="danger" size="small" @click="closeDialog">关闭</el-button>
|
| | | </template>
|
| | | </vol-box>
|
| | | </div>
|
| | |
| | | data() {
|
| | | return {
|
| | | showManualCreate: false,
|
| | | // 任务类型映射(用于显示中文名称)
|
| | | taskTypeMap: {
|
| | | GroupPallet: "组盘任务",
|
| | | ChangePallet: "换盘任务",
|
| | | SplitPallet: "拆盘任务",
|
| | | },
|
| | | // 方向描述映射
|
| | | directionMap: {
|
| | | 1: "去化成1: 源:1(11010) → 目标:3(2103)",
|
| | | 2: "去化成2: 源:2(11001) → 目标:4(2101)",
|
| | | 3: "回高温3: 源:4(2103) → 目标:2(11010)",
|
| | | 4: "回高温4: 源:4(2101) → 目标:2(11001)",
|
| | | },
|
| | | manualFormData: {
|
| | | robotSourceAddressPalletCode: "",
|
| | | robotTargetAddressPalletCode: "",
|
| | | forward: 1, // 1=去化成(1→3), 2=去化成(2→4), 3=回高温(3→1), 4=回高温(4→2)
|
| | | robotRoadway: "", // 机器人名称/任务类型
|
| | | robotSourceAddressPalletCode: "", // 来源地址托盘码
|
| | | robotTargetAddressPalletCode: "", // 目标地址托盘码
|
| | | forward: 1, // 方向(换盘任务使用,1-4)
|
| | | robotTaskTotalNum: 1, // 任务总数
|
| | | },
|
| | | };
|
| | | },
|
| | |
| | | this.resetManualForm();
|
| | | },
|
| | |
|
| | | // 关闭弹窗
|
| | | closeDialog() {
|
| | | this.showManualCreate = false;
|
| | | },
|
| | |
|
| | | // 重置表单
|
| | | resetManualForm() {
|
| | | this.manualFormData = {
|
| | | robotRoadway: "",
|
| | | robotSourceAddressPalletCode: "",
|
| | | robotTargetAddressPalletCode: "",
|
| | | forward: 1,
|
| | | robotTaskTotalNum: 1,
|
| | | };
|
| | | },
|
| | |
|
| | | // 任务类型切换时重置相关字段
|
| | | onTaskTypeChange(value) {
|
| | | this.manualFormData.robotSourceAddressPalletCode = "";
|
| | | this.manualFormData.robotTargetAddressPalletCode = "";
|
| | | this.manualFormData.forward = 1;
|
| | | },
|
| | |
|
| | | // 表单验证
|
| | | validateForm() {
|
| | | const data = this.manualFormData;
|
| | |
|
| | | // 1. 必选任务类型
|
| | | if (!data.robotRoadway) {
|
| | | this.$message.error("请选择任务类型");
|
| | | return false;
|
| | | }
|
| | |
|
| | | // 2. 根据任务类型验证必填字段
|
| | | const taskType = data.robotRoadway;
|
| | |
|
| | | if (taskType === "GroupPallet") {
|
| | | if (!data.robotTargetAddressPalletCode) {
|
| | | this.$message.error("组盘任务:请输入目标地址托盘码");
|
| | | return false;
|
| | | }
|
| | | } else if (taskType === "ChangePallet") {
|
| | | if (!data.robotSourceAddressPalletCode) {
|
| | | this.$message.error("换盘任务:请输入来源地址托盘码");
|
| | | return false;
|
| | | }
|
| | | if (!data.robotTargetAddressPalletCode) {
|
| | | this.$message.error("换盘任务:请输入目标地址托盘码");
|
| | | return false;
|
| | | }
|
| | | // 验证方向 1-4
|
| | | if (![1, 2, 3, 4].includes(data.forward)) {
|
| | | this.$message.error("换盘任务:请选择有效的方向(1-4)");
|
| | | return false;
|
| | | }
|
| | | } else if (taskType === "SplitPallet") {
|
| | | if (!data.robotSourceAddressPalletCode) {
|
| | | this.$message.error("拆盘任务:请输入来源地址托盘码");
|
| | | return false;
|
| | | }
|
| | | }
|
| | |
|
| | | // 3. 验证任务总数
|
| | | if (data.robotTaskTotalNum < 1) {
|
| | | this.$message.error("任务总数不能小于1");
|
| | | return false;
|
| | | }
|
| | |
|
| | | return true;
|
| | | },
|
| | |
|
| | | // 提交手动创建任务
|
| | | submitManualCreate() {
|
| | | // 表单验证
|
| | | if (!this.manualFormData.robotSourceAddressPalletCode) {
|
| | | return this.$message.error("请输入来源地址托盘码");
|
| | | }
|
| | | if (!this.manualFormData.robotTargetAddressPalletCode) {
|
| | | return this.$message.error("请输入目标地址托盘码");
|
| | | }
|
| | | if (this.manualFormData.forward === undefined || this.manualFormData.forward === null) {
|
| | | return this.$message.error("请选择方向");
|
| | | if (!this.validateForm()) {
|
| | | return;
|
| | | }
|
| | |
|
| | | // 调用后端API创建换盘机械手任务
|
| | | this.http
|
| | | .post("api/RobotTask/AddRobotTask", {
|
| | | // 构建请求参数(与后端 ManualRobotTaskDto 对应)
|
| | | const params = {
|
| | | robotRoadway: this.manualFormData.robotRoadway,
|
| | | robotSourceAddressPalletCode: this.manualFormData.robotSourceAddressPalletCode,
|
| | | robotTargetAddressPalletCode: this.manualFormData.robotTargetAddressPalletCode,
|
| | | forward: this.manualFormData.forward, // 注意字段名改为forward,类型为整数
|
| | | }, "创建换盘机械手任务中...")
|
| | | robotTaskTotalNum: this.manualFormData.robotTaskTotalNum,
|
| | | forward: this.manualFormData.forward, // 方向,整型 1-4
|
| | | };
|
| | |
|
| | | // 获取任务类型中文名称用于提示
|
| | | const taskTypeName = this.taskTypeMap[params.robotRoadway] || "机器人";
|
| | |
|
| | | // 获取方向描述(仅换盘任务)
|
| | | let directionDesc = "";
|
| | | if (params.robotRoadway === "ChangePallet") {
|
| | | directionDesc = this.directionMap[params.forward] || "";
|
| | | }
|
| | |
|
| | | // 调用后端API创建机器人任务
|
| | | this.http
|
| | | .post("api/RobotTask/CreateRobotTaskManually", params, `创建${taskTypeName}${directionDesc ? ' - ' + directionDesc : ''}中...`)
|
| | | .then((res) => {
|
| | | if (!res.status) {
|
| | | return this.$message.error(res.message);
|
| | | }
|
| | | this.$message.success("换盘机械手任务创建成功");
|
| | | this.$message.success(`${taskTypeName}${directionDesc ? ' - ' + directionDesc : ''}创建成功`);
|
| | | this.showManualCreate = false;
|
| | | // 刷新父页面数据
|
| | | this.$emit("parentCall", ($vue) => {
|
| | |
| | | });
|
| | | })
|
| | | .catch((err) => {
|
| | | this.$message.error("创建换盘机械手任务失败:" + (err.message || "未知错误"));
|
| | | this.$message.error(`创建${taskTypeName}失败:` + (err.message || "未知错误"));
|
| | | });
|
| | | },
|
| | | },
|
| | |
| | | /// <summary> |
| | | /// 拆盘任务 |
| | | /// </summary> |
| | | [Description("换盘任务")] |
| | | [Description("拆盘任务")] |
| | | SplitPallet = 520 |
| | | } |
| | | |
| | |
| | | using System; |
| | | using SqlSugar; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.ComponentModel.DataAnnotations; |
| | | using System.Linq; |
| | |
| | | /// </summary> |
| | | public int TaskType { get; set; } |
| | | } |
| | |
|
| | | /// <summary> |
| | | /// 手动创建机器人任务 |
| | | /// </summary> |
| | | public class ManualRobotTaskDto
|
| | | {
|
| | | /// <summary> |
| | | /// 机器人名称 |
| | | /// </summary> |
| | | public string RobotRoadway { get; set; }
|
| | |
|
| | | /// <summary> |
| | | /// 机器人来源地址输送线托盘号 |
| | | /// </summary>
|
| | | public String RobotSourceAddressPalletCode { get; set; }
|
| | |
|
| | | /// <summary> |
| | | /// 机器人目标地址线托盘号 |
| | | /// </summary>
|
| | | public String RobotTargetAddressPalletCode { get; set; }
|
| | |
|
| | |
|
| | | /// <summary> |
| | | /// 机器人任务总数 |
| | | /// </summary> |
| | | public int RobotTaskTotalNum { get; set; }
|
| | |
|
| | | /// <summary> |
| | | /// 方向 |
| | | /// </summary>
|
| | | public int Forward { get; set; }
|
| | | } |
| | | } |
| | |
| | | |
| | | int MapWarehouseIdConfigKey(string? targetAddress); |
| | | string ResolveRobotRuleValue(string? targetAddress, string addressSectionName, string? fallback); |
| | | WebResponseContent CreateRobotTaskManually(ManualRobotTaskDto request);
|
| | | } |
| | | } |
| | |
| | | using Microsoft.AspNetCore.Authorization;
|
| | | using Microsoft.AspNetCore.Mvc;
|
| | | using System.Threading.Tasks;
|
| | | using WIDESEAWCS_Common.TaskEnum;
|
| | | using WIDESEAWCS_Core;
|
| | | using WIDESEAWCS_Core.BaseController;
|
| | | using WIDESEAWCS_Core.Enums;
|
| | |
| | | return WebResponseContent.Instance.Error();
|
| | | }
|
| | |
|
| | | // 暂时创建换盘机械手任务
|
| | | [HttpGet, HttpPost, Route("AddRobotTask"), AllowAnonymous]
|
| | | public WebResponseContent AddRobotTask([FromBody] RobotMoveRequest request) |
| | | // 手动机械手任务
|
| | | [HttpGet, HttpPost, Route("CreateRobotTaskManually"), AllowAnonymous]
|
| | | public WebResponseContent CreateRobotTaskManually([FromBody] ManualRobotTaskDto request) |
| | | {
|
| | | try
|
| | | {
|
| | | Dt_RobotTask robotTask = new Dt_RobotTask();
|
| | | robotTask.RobotTaskNum = Random.Shared.StrictNext();
|
| | | robotTask.RobotRoadway = "换盘机械手";
|
| | | robotTask.RobotTaskType = 510;
|
| | | robotTask.RobotTaskState = 300;
|
| | | robotTask.RobotTaskTotalNum = 48;
|
| | | robotTask.RobotGrade = 1;
|
| | | robotTask.RobotDispatchertime = DateTime.Now;
|
| | | robotTask.RobotRemark = "人工手动创建";
|
| | | robotTask.RobotSourceAddressPalletCode = request.robotSourceAddressPalletCode;
|
| | | robotTask.RobotTargetAddressPalletCode = request.robotTargetAddressPalletCode;
|
| | | // 根据方向设置源和目标
|
| | | switch (request.Forward)
|
| | | {
|
| | | case 1:
|
| | | robotTask.RobotSourceAddress = "1";
|
| | | robotTask.RobotSourceAddressLineCode = "11010";
|
| | | robotTask.RobotTargetAddress = "3";
|
| | | robotTask.RobotTargetAddressLineCode = "2103";
|
| | | break;
|
| | | case 2:
|
| | | robotTask.RobotSourceAddress = "2";
|
| | | robotTask.RobotSourceAddressLineCode = "11001";
|
| | | robotTask.RobotTargetAddress = "4";
|
| | | robotTask.RobotTargetAddressLineCode = "2101";
|
| | | break;
|
| | | case 3:
|
| | | robotTask.RobotSourceAddress = "3";
|
| | | robotTask.RobotSourceAddressLineCode = "2103";
|
| | | robotTask.RobotTargetAddress = "1";
|
| | | robotTask.RobotTargetAddressLineCode = "11010";
|
| | | break;
|
| | | case 4:
|
| | | robotTask.RobotSourceAddress = "4";
|
| | | robotTask.RobotSourceAddressLineCode = "2101";
|
| | | robotTask.RobotTargetAddress = "2";
|
| | | robotTask.RobotTargetAddressLineCode = "11001";
|
| | | break;
|
| | | default:
|
| | | return WebResponseContent.Instance.Error($"添加机器人任务失败");
|
| | | }
|
| | |
|
| | | return Service.AddData(robotTask);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"添加机器人任务失败: {ex.Message}");
|
| | | return Service.CreateRobotTaskManually(request);
|
| | | }
|
| | | }
|
| | |
|
| | | }
|
| | | }
|
| | | public class RobotMoveRequest
|
| | | {
|
| | | public int Forward { get; set; }
|
| | | public String robotSourceAddressPalletCode { get; set; }
|
| | | public String robotTargetAddressPalletCode { get; set; }
|
| | | } |
| | |
| | | "EnableConsoleOutput": false, //是否输出到控制台 |
| | | "EnableFloderByLevel": true //是否按日志级别生成不同的文件夹 |
| | | }, |
| | | "ApiLogIgnore": "", //记录日志时,忽略的API名称,多个用逗号分隔,配置的不记录到数据库中 |
| | | "ApiLogIgnore": "Export,Get,get", //记录日志时,忽略的API名称,多个用逗号分隔,配置的不记录到数据库中 |
| | | "ApiName": "WIDESEAWCS", |
| | | "ExpMinutes": 120, |
| | | "QuartzJobAutoStart": true, |
| | |
| | | }, |
| | | "RedisConfig": { |
| | | "Enabled": true, //是否启用Redis,false时仅使用内存缓存 |
| | | "ConnectionString": "127.0.0.1:6379,password=P@ssw0rd,defaultDatabase=0,connectTimeout=5000,abortConnect=false", //Redis连接字符串 |
| | | "ConnectionString": "127.0.0.1:6379,password=,defaultDatabase=0,connectTimeout=5000,abortConnect=false", //Redis连接字符串 |
| | | "InstanceName": "WIDESEAWCS:", //实例名称,用于区分不同应用 |
| | | "DefaultDatabase": 0, //默认数据库索引(0-15) |
| | | "EnableSentinel": false, //是否启用哨兵模式 |
| | |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using Serilog; |
| | | using Serilog; |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using WIDESEA_Core; |
| | | using WIDESEAWCS_Common.HttpEnum; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEA_Core; |
| | | using WIDESEAWCS_Core; |
| | | using WIDESEAWCS_Core.Enums; |
| | | using WIDESEAWCS_Core.Helper; |
| | | using WIDESEAWCS_DTO; |
| | | using WIDESEAWCS_DTO.TaskInfo; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_Model.Models; |
| | | using WIDESEAWCS_QuartzJob.Models; |
| | |
| | | public class InboundTaskFlowService : IInboundTaskFlowService |
| | | { |
| | | private readonly IRouterService _routerService; |
| | | private readonly ITaskRepository _taskRepository; |
| | | private readonly HttpClientHelper _httpClientHelper; |
| | | private readonly ILogger _logger; |
| | | |
| | |
| | | /// </summary> |
| | | /// <param name="routerService">路由服务。</param> |
| | | /// <param name="httpClientHelper">WMS接口调用帮助类。</param> |
| | | public InboundTaskFlowService(IRouterService routerService, HttpClientHelper httpClientHelper, ILogger logger) |
| | | public InboundTaskFlowService(IRouterService routerService, ITaskRepository taskRepository, HttpClientHelper httpClientHelper, ILogger logger) |
| | | { |
| | | _routerService = routerService; |
| | | _taskRepository = taskRepository; |
| | | _httpClientHelper = httpClientHelper; |
| | | _logger = logger; |
| | | } |
| | |
| | | /// <returns>同步结果。</returns> |
| | | private WebResponseContent UpdateWMSTaskStatus(Dt_Task task) |
| | | { |
| | | DateTime startTime = DateTime.Now;
|
| | |
|
| | | // 处理入库完成状态的特殊同步
|
| | | if (task.TaskStatus == (int)TaskInStatusEnum.InFinish)
|
| | | {
|
| | | string InboundFinishKey = nameof(ConfigKey.InboundFinishTaskAsync);
|
| | | var requestDto = new CreateTaskDto()
|
| | | {
|
| | | PalletCode = task.PalletCode,
|
| | | SourceAddress = task.SourceAddress,
|
| | | TargetAddress = task.TargetAddress,
|
| | | Roadway = task.Roadway,
|
| | | TaskType = task.TaskType,
|
| | | }.ToJson();
|
| | |
|
| | | var resultFinish = _httpClientHelper.Post<WebResponseContent>(InboundFinishKey, requestDto);
|
| | | if (!resultFinish.IsSuccess || !resultFinish.Data.Status)
|
| | | {
|
| | | QuartzLogHelper.LogError(_logger, $"调用WMS接口失败,接口:【{resultFinish}】,请求参数:【{requestDto}】,错误信息:【{resultFinish.Data?.Message}】", "InboundTaskFlowService");
|
| | | return WebResponseContent.Instance.Error($"调用WMS接口更新任务状态失败,任务号:【{task.TaskNum}】,错误信息:【{resultFinish.Data?.Message}】");
|
| | | }
|
| | |
|
| | | QuartzLogHelper.LogInfo(_logger, $"调用WMS接口成功,接口:【{InboundFinishKey}】,响应数据:【{resultFinish.Data?.Data}】,耗时:{(DateTime.Now - startTime).TotalMilliseconds}ms", "InboundTaskFlowService");
|
| | | _taskRepository.DeleteAndMoveIntoHty(task, OperateTypeEnum.人工完成);
|
| | |
|
| | | return WebResponseContent.Instance.OK();
|
| | | }
|
| | |
|
| | | string configKey = nameof(ConfigKey.UpdateTaskByStatus); |
| | | string requestParam = new UpdateTaskDto { Id = task.TaskNum, NewStatus = task.TaskStatus, NextAddress = task.NextAddress, CurrentAddress = task.CurrentAddress }.ToJson(); |
| | | DateTime startTime = DateTime.Now; |
| | | |
| | | var result = _httpClientHelper.Post<WebResponseContent>( |
| | | configKey, |
| | |
| | | using Newtonsoft.Json; |
| | | using Masuit.Tools.Hardware; |
| | | using Newtonsoft.Json; |
| | | using Serilog; |
| | | using System.Diagnostics.CodeAnalysis; |
| | | using WIDESEA_Core; |
| | |
| | | /// <returns>同步结果。</returns> |
| | | private WebResponseContent UpdateWMSTaskStatus(Dt_Task task) |
| | | { |
| | | DateTime startTime = DateTime.Now;
|
| | | // 处理出库完成状态的特殊同步
|
| | | if (task.TaskStatus == (int)TaskOutStatusEnum.SC_OutFinish)
|
| | | {
|
| | | string OutboundFinishKey = nameof(ConfigKey.OutboundFinishTaskAsync);
|
| | | var requestDto = new CreateTaskDto()
|
| | | {
|
| | | PalletCode = task.PalletCode,
|
| | | SourceAddress = task.SourceAddress,
|
| | | TargetAddress = task.TargetAddress,
|
| | | Roadway = task.Roadway,
|
| | | TaskType = task.TaskType,
|
| | | }.ToJson();
|
| | |
|
| | | var resultFinish = _httpClientHelper.Post<WebResponseContent>(OutboundFinishKey, requestDto);
|
| | | if (!resultFinish.IsSuccess || !resultFinish.Data.Status)
|
| | | {
|
| | | QuartzLogHelper.LogError(_logger, $"调用WMS接口失败,接口:【{resultFinish}】,请求参数:【{requestDto}】,错误信息:【{resultFinish.Data?.Message}】", "OutboundTaskFlowService");
|
| | | return WebResponseContent.Instance.Error($"调用WMS接口更新任务状态失败,任务号:【{task.TaskNum}】,错误信息:【{resultFinish.Data?.Message}】");
|
| | | }
|
| | |
|
| | | QuartzLogHelper.LogInfo(_logger, $"调用WMS接口成功,接口:【{OutboundFinishKey}】,响应数据:【{resultFinish.Data?.Data}】,耗时:{(DateTime.Now - startTime).TotalMilliseconds}ms", "OutboundTaskFlowService");
|
| | |
|
| | | return WebResponseContent.Instance.OK();
|
| | | } |
| | | |
| | | string configKey = nameof(ConfigKey.UpdateTaskByStatus); |
| | | string requestParam = new UpdateTaskDto { Id = task.TaskNum, NewStatus = task.TaskStatus, NextAddress = task.NextAddress, CurrentAddress = task.CurrentAddress }.ToJson(); |
| | | DateTime startTime = DateTime.Now; |
| | | |
| | | |
| | | var result = _httpClientHelper.Post<WebResponseContent>( |
| | | configKey, |
| | |
| | |
|
| | | #endregion << 版 本 注 释 >>
|
| | |
|
| | | using Autofac.Core;
|
| | | using MapsterMapper;
|
| | | using Masuit.Tools;
|
| | | using Microsoft.Extensions.Configuration;
|
| | | using Newtonsoft.Json;
|
| | | using Serilog;
|
| | |
| | | .Where(x => !string.IsNullOrWhiteSpace(x.Key) && !string.IsNullOrWhiteSpace(x.Value))
|
| | | .ToDictionary(x => x.Key.Trim(), x => x.Value!.Trim());
|
| | | }
|
| | |
|
| | | public WebResponseContent CreateRobotTaskManually(ManualRobotTaskDto request)
|
| | | {
|
| | | try
|
| | | {
|
| | | Dt_RobotTask robotTask = new Dt_RobotTask();
|
| | | robotTask.RobotTaskNum = Random.Shared.StrictNext();
|
| | | robotTask.RobotRoadway = request.RobotRoadway;
|
| | | if (request.RobotRoadway == RobotTaskTypeEnum.GroupPallet.ToString())
|
| | | {
|
| | | robotTask.RobotTaskType = (int)RobotTaskTypeEnum.GroupPallet;
|
| | | robotTask.RobotTaskState = (int)TaskRobotStatusEnum.RobotNew;
|
| | | robotTask.RobotTaskTotalNum = request.RobotTaskTotalNum;
|
| | | robotTask.RobotDispatchertime = DateTime.Now;
|
| | | robotTask.RobotRemark = "人工手动创建";
|
| | | robotTask.RobotTargetAddressPalletCode = request.RobotTargetAddressPalletCode;
|
| | | robotTask.RobotTargetAddressLineCode = "11068";
|
| | | }
|
| | | else if (request.RobotRoadway == RobotTaskTypeEnum.ChangePallet.ToString())
|
| | | {
|
| | | switch (request.Forward)
|
| | | {
|
| | | case 1:
|
| | | robotTask.RobotSourceAddress = "1";
|
| | | robotTask.RobotSourceAddressLineCode = "11010";
|
| | | robotTask.RobotTargetAddress = "3";
|
| | | robotTask.RobotTargetAddressLineCode = "2103";
|
| | | break;
|
| | | case 2:
|
| | | robotTask.RobotSourceAddress = "2";
|
| | | robotTask.RobotSourceAddressLineCode = "11001";
|
| | | robotTask.RobotTargetAddress = "4";
|
| | | robotTask.RobotTargetAddressLineCode = "2101";
|
| | | break;
|
| | | case 3:
|
| | | robotTask.RobotSourceAddress = "3";
|
| | | robotTask.RobotSourceAddressLineCode = "2103";
|
| | | robotTask.RobotTargetAddress = "1";
|
| | | robotTask.RobotTargetAddressLineCode = "11010";
|
| | | break;
|
| | | case 4:
|
| | | robotTask.RobotSourceAddress = "4";
|
| | | robotTask.RobotSourceAddressLineCode = "2101";
|
| | | robotTask.RobotTargetAddress = "2";
|
| | | robotTask.RobotTargetAddressLineCode = "11001";
|
| | | break;
|
| | | default:
|
| | | return WebResponseContent.Instance.Error($"添加机器人任务失败,方向不对");
|
| | | }
|
| | | robotTask.RobotTaskType = (int)RobotTaskTypeEnum.ChangePallet;
|
| | | robotTask.RobotTaskState = (int)TaskRobotStatusEnum.RobotNew;
|
| | | robotTask.RobotTaskTotalNum = request.RobotTaskTotalNum;
|
| | | robotTask.RobotDispatchertime = DateTime.Now;
|
| | | robotTask.RobotRemark = "人工手动创建";
|
| | | robotTask.RobotSourceAddressPalletCode = request.RobotSourceAddressPalletCode;
|
| | | robotTask.RobotTargetAddressPalletCode = request.RobotTargetAddressPalletCode;
|
| | | }
|
| | | else if (request.RobotRoadway == RobotTaskTypeEnum.SplitPallet.ToString())
|
| | | {
|
| | | robotTask.RobotTaskType = (int)RobotTaskTypeEnum.SplitPallet;
|
| | | robotTask.RobotTaskState = (int)TaskRobotStatusEnum.RobotNew;
|
| | | robotTask.RobotTaskTotalNum = request.RobotTaskTotalNum;
|
| | | robotTask.RobotDispatchertime = DateTime.Now;
|
| | | robotTask.RobotRemark = "人工手动创建";
|
| | | robotTask.RobotSourceAddressPalletCode = request.RobotSourceAddressPalletCode;
|
| | | }
|
| | | else
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"添加机器人任务失败,机器人名称错误{request.RobotRoadway}");
|
| | | }
|
| | | return base.AddData(robotTask);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"添加机器人任务失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | | }
|
| | | } |
| | |
| | | if (state.ChangePalletPhase == 5) |
| | | { |
| | | // FlowB 最终阶段:假电芯取完,源空托盘回库 HCSC1 |
| | | //if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true, isRoadway: "HCSC1")) |
| | | //{ |
| | | // return false; |
| | | //} |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true, isRoadway: "HCSC1")) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (_taskProcessor.DeleteTask(currentTask.RobotTaskId) != true) |
| | | { |
| | |
| | | <template> |
| | | <div class="dashboard-container"> |
| | | <!-- 顶部:本月出入库趋势 (全宽) --> |
| | | <!-- 各仓库月度出入库对比图 --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">每月出入库趋势</div> |
| | | <div id="chart-monthly-trend" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第二行:每日出入库趋势 (全宽) --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">每日出入库趋势</div> |
| | | <div id="chart-daily" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第四行:仓库分布 --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">各仓库库存分布</div> |
| | | <div id="chart-warehouse" class="chart-content"></div> |
| | | <div class="card-title">各仓库月度出入库对比</div> |
| | | <div id="chart-warehouse-monthly" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | dailyData: [], |
| | | monthlyData: [], |
| | | warehouseData: [] |
| | | warehouseNames: ['FJSC1', 'ZJSC1', 'GWSC1', 'CWSC1', 'HCSC1'] |
| | | }; |
| | | }, |
| | | mounted() { |
| | |
| | | }, |
| | | |
| | | initCharts() { |
| | | this.charts.monthlyTrend = echarts.init(document.getElementById("chart-monthly-trend")); |
| | | this.charts.daily = echarts.init(document.getElementById("chart-daily")); |
| | | this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse")); |
| | | this.charts.warehouseMonthly = echarts.init(document.getElementById("chart-warehouse-monthly")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | await this.loadMonthlyStats(); |
| | | await this.loadDailyStats(); |
| | | await this.loadStockByWarehouse(); |
| | | }, |
| | | |
| | | async loadMonthlyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/MonthlyStats", { months: 12 }); |
| | | if (res.status && res.data) { |
| | | console.log("每月统计数据:", res.data); |
| | | this.monthlyData = res.data; |
| | | this.updateMonthlyTrendChart(); |
| | | } |
| | | const promises = this.warehouseNames.map(warehouse => |
| | | this.http.get("/api/Dashboard/MonthlyStats", { |
| | | months: 6, |
| | | Roadway: warehouse |
| | | }) |
| | | ); |
| | | |
| | | const results = await Promise.all(promises); |
| | | |
| | | this.monthlyData = results.map((res, index) => ({ |
| | | warehouse: this.warehouseNames[index], |
| | | warehouseName: this.getWarehouseName(this.warehouseNames[index]), |
| | | data: res.data || [] |
| | | })); |
| | | |
| | | this.updateWarehouseMonthlyChart(); |
| | | } catch (e) { |
| | | console.error("加载每月统计失败", e); |
| | | } |
| | | }, |
| | | |
| | | async loadDailyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/DailyStats", { days: 30 }); |
| | | if (res.status && res.data) { |
| | | console.log("每日统计数据:", res.data); |
| | | this.dailyData = res.data; |
| | | this.updateDailyChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载每日统计失败", e); |
| | | } |
| | | }, |
| | | |
| | | async loadStockByWarehouse() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockByWarehouse"); |
| | | if (res.status && res.data) { |
| | | console.log("仓库分布数据:", res.data); |
| | | this.warehouseData = res.data.data || res.data; |
| | | this.updateWarehouseChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载仓库分布失败", e); |
| | | } |
| | | }, |
| | | |
| | | updateMonthlyTrendChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.monthlyData.map(m => m.month), |
| | | axisLabel: { color: "#fff", rotate: 45 } |
| | | }, |
| | | yAxis: [ |
| | | { |
| | | type: "value", |
| | | name: "数量", |
| | | axisLabel: { color: "#fff" } |
| | | } |
| | | ], |
| | | series: [ |
| | | { name: "入库", type: "bar", data: this.monthlyData.map(m => m.inbound), itemStyle: { color: "#5470c6" } }, |
| | | { name: "出库", type: "line", data: this.monthlyData.map(m => m.outbound), itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | getWarehouseName(code) { |
| | | const nameMap = { |
| | | 'FJSC1': '负极卷1号仓库', |
| | | 'ZJSC1': '正极卷1号仓库', |
| | | 'GWSC1': '高温1号仓库', |
| | | 'CWSC1': '常温1号仓库', |
| | | 'HCSC1': '分容1号仓库' |
| | | }; |
| | | this.charts.monthlyTrend.setOption(option, true); |
| | | return nameMap[code] || code; |
| | | }, |
| | | |
| | | updateDailyChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["入库", "出库"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.dailyData.map(d => d.date), |
| | | axisLabel: { |
| | | color: "#fff", |
| | | interval: 0, |
| | | rotate: 45, |
| | | fontSize: 12, |
| | | margin: 10 |
| | | updateWarehouseMonthlyChart() { |
| | | // 获取所有月份 |
| | | const months = this.monthlyData[0]?.data.map(d => `${d.month}月`) || []; |
| | | |
| | | // 为每个仓库生成系列数据 |
| | | const series = []; |
| | | |
| | | this.monthlyData.forEach((warehouseData, index) => { |
| | | const data = warehouseData.data; |
| | | |
| | | series.push({ |
| | | name: warehouseData.warehouseName, |
| | | type: 'bar', |
| | | data: data.map(d => ({ |
| | | value: (d.inbound || 0) + (d.outbound || 0), |
| | | inbound: d.inbound || 0, |
| | | outbound: d.outbound || 0, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: function(params) { |
| | | return `入:${params.data.inbound}\n出:${params.data.outbound}`; |
| | | }, |
| | | axisTick: { |
| | | alignWithLabel: true |
| | | fontSize: 10, |
| | | color: '#fff', |
| | | lineHeight: 15 |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '10%', |
| | | containLabel: true |
| | | }, |
| | | series: [ |
| | | { name: "入库", type: "bar", data: this.dailyData.map(d => d.inbound), itemStyle: { color: "#5470c6" } }, |
| | | { name: "出库", type: "bar", data: this.dailyData.map(d => d.outbound), itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.daily.setOption(option, true); |
| | | }, |
| | | |
| | | updateWarehouseChart() { |
| | | const warehouseNames = this.warehouseData.map(w => w.warehouse); |
| | | const totalStocks = this.warehouseData.map(w => w.total); |
| | | const hasStocks = this.warehouseData.map(w => w.hasStock); |
| | | const noStocks = this.warehouseData.map(w => w.noStock); |
| | | const hasStockPercentages = this.warehouseData.map(w => w.hasStockPercentage); |
| | | const noStockPercentages = this.warehouseData.map(w => w.noStockPercentage); |
| | | })), |
| | | barWidth: '15%', |
| | | barGap: '10%', |
| | | itemStyle: { |
| | | color: this.getBarColor(index), |
| | | borderRadius: [3, 3, 0, 0] |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | const option = { |
| | | title: { |
| | | text: '各仓库月度出入库对比', |
| | | textStyle: { |
| | | color: '#00ffff', |
| | | fontSize: 16 |
| | | }, |
| | | left: 'center', |
| | | top: 10 |
| | | }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: function(params) { |
| | | let tip = params[0].name + '<br/>'; |
| | | let tip = `<strong>${params[0].axisValue}</strong><br/>`; |
| | | params.forEach(param => { |
| | | const dataIndex = param.dataIndex; |
| | | const warehouse = window.homeComponent.warehouseData[dataIndex]; |
| | | |
| | | if (param.seriesName === '已用容量') { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.hasStockPercentage})<br/>`; |
| | | tip += `有库存: ${warehouse.hasStock}<br/>`; |
| | | tip += `无库存: ${warehouse.noStock}<br/>`; |
| | | tip += `总容量: ${warehouse.total}`; |
| | | } else if (param.seriesName === '剩余容量') { |
| | | tip += `${param.marker}${param.seriesName}: ${param.value} (${warehouse.noStockPercentage})<br/>`; |
| | | tip += `有库存: ${warehouse.hasStock}<br/>`; |
| | | tip += `无库存: ${warehouse.noStock}<br/>`; |
| | | tip += `总容量: ${warehouse.total}`; |
| | | } |
| | | tip += `<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${param.color};margin-right:5px;"></span>`; |
| | | tip += `${param.seriesName}: `; |
| | | tip += `入库:${param.data.inbound} | 出库:${param.data.outbound} | 总计:${param.value}<br/>`; |
| | | }); |
| | | return tip; |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['已用容量', '剩余容量'], |
| | | textStyle: { color: '#fff' } |
| | | data: this.monthlyData.map(d => d.warehouseName), |
| | | textStyle: { color: '#fff', fontSize: 11 }, |
| | | top: 45, |
| | | left: 'center', |
| | | type: 'scroll' |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '10%', |
| | | top: '20%', |
| | | containLabel: true |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: warehouseNames, |
| | | axisLabel: { color: '#fff', rotate: 30 } |
| | | data: months, |
| | | axisLabel: { |
| | | color: '#fff', |
| | | fontSize: 11 |
| | | }, |
| | | axisLine: { |
| | | lineStyle: { color: 'rgba(255,255,255,0.3)' } |
| | | }, |
| | | splitLine: { |
| | | show: true, |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | axisLabel: { color: '#fff' } |
| | | name: '数量', |
| | | nameTextStyle: { color: '#fff' }, |
| | | axisLabel: { color: '#fff' }, |
| | | splitLine: { |
| | | lineStyle: { |
| | | color: 'rgba(255,255,255,0.1)', |
| | | type: 'dashed' |
| | | } |
| | | } |
| | | }, |
| | | series: [ |
| | | dataZoom: [ |
| | | { |
| | | name: '已用容量', |
| | | type: 'bar', |
| | | data: hasStocks.map((value, index) => ({ |
| | | value: value, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c} {a|' + hasStockPercentages[index] + '}', |
| | | rich: { |
| | | a: { |
| | | lineHeight: 20, |
| | | borderColor: '#91cc75', |
| | | color: '#91cc75' |
| | | } |
| | | } |
| | | } |
| | | })), |
| | | itemStyle: { color: '#91cc75' } |
| | | type: 'inside', |
| | | start: 0, |
| | | end: 100 |
| | | }, |
| | | { |
| | | name: '剩余容量', |
| | | type: 'bar', |
| | | data: noStocks.map((value, index) => ({ |
| | | value: value, |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c} {a|' + noStockPercentages[index] + '}', |
| | | rich: { |
| | | a: { |
| | | lineHeight: 20, |
| | | borderColor: '#fac858', |
| | | color: '#fac858' |
| | | start: 0, |
| | | end: 100, |
| | | height: 20, |
| | | bottom: 0, |
| | | borderColor: 'rgba(255,255,255,0.3)', |
| | | fillerColor: 'rgba(0,255,255,0.1)', |
| | | handleStyle: { |
| | | color: '#00ffff', |
| | | borderColor: '#00ffff' |
| | | }, |
| | | textStyle: { |
| | | color: '#fff' |
| | | } |
| | | } |
| | | } |
| | | })), |
| | | itemStyle: { color: '#fac858' } |
| | | } |
| | | ] |
| | | ], |
| | | series: series |
| | | }; |
| | | |
| | | window.homeComponent = this; |
| | | this.charts.warehouseMonthly.setOption(option, true); |
| | | }, |
| | | |
| | | this.charts.warehouse.setOption(option, true); |
| | | getBarColor(index) { |
| | | const colors = [ |
| | | '#5470c6', // 蓝 |
| | | '#fac858', // 黄 |
| | | '#73c0de', // 天蓝 |
| | | '#fc8452', // 橙 |
| | | '#ea7ccc' // 粉 |
| | | ]; |
| | | return colors[index] || '#5470c6'; |
| | | } |
| | | } |
| | | }; |
| | |
| | | box-shadow: 2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | |
| | | .chart-card::before, |
| | | .chart-card::after { |
| | | animation: neon-flicker 2s infinite alternate; |
| | | } |
| | | |
| | | @keyframes neon-flicker { |
| | | 0%, 100% { |
| | | opacity: 1; |
| | | box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | 50% { |
| | | opacity: 0.8; |
| | | box-shadow: -2px -2px 5px #00ffff, 0 0 5px rgba(0, 255, 255, 0.5); |
| | | } |
| | | } |
| | | |
| | | .card-title { |
| | | color: #00ffff; |
| | | font-size: 16px; |
| | |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 280px; |
| | | height: 500px; |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 全宽图表 */ |
| | | .full-width .chart-card { |
| | | flex: none; |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-width .chart-content { |
| | | height: 350px; |
| | | height: 500px; |
| | | } |
| | | |
| | | /* 添加网格线效果 */ |
| | | .dashboard-container::before { |
| | | content: ""; |
| | | position: fixed; |
| | |
| | | using System.Threading.Tasks; |
| | | using WIDESEA_Common.TaskEnum; |
| | | using WIDESEA_Core; |
| | | using WIDESEA_Core.Enums; |
| | | using WIDESEA_DTO.Task; |
| | | using WIDESEA_IStockService; |
| | | using WIDESEA_Model.Models; |
| | | |
| | | namespace WIDESEA_TaskInfoService |
| | | { |
| | |
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// 每日统计
|
| | | /// 每日统计(按巷道号分组,指定仓库)
|
| | | /// </summary>
|
| | | [HttpGet("DailyStats"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30)
|
| | | public async Task<WebResponseContent> DailyStats([FromQuery] int days = 10)
|
| | | {
|
| | | try
|
| | | {
|
| | |
| | | var startDate = DateTime.Today.AddDays(-days + 1);
|
| | | var endDate = DateTime.Today; // 包含今天
|
| | |
|
| | | // 指定要统计的仓库(巷道号)
|
| | | var specifiedRoadways = new List<string>
|
| | | {
|
| | | "GWSC1", "CWSC1", "HCSC1", "ZJSC1", "FJSC1"
|
| | | };
|
| | |
|
| | | var query = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= startDate && t.InsertTime <= endDate)
|
| | | .Select(t => new { t.InsertTime, t.TaskType })
|
| | | .Where(t => specifiedRoadways.Contains(t.Roadway)) // 只查询指定巷道号的数据
|
| | | .Select(t => new { t.InsertTime, t.TaskType, t.Roadway })
|
| | | .ToListAsync();
|
| | |
|
| | | // 生成日期范围
|
| | |
| | | allDates.Add(date);
|
| | | }
|
| | |
|
| | | // 按日期分组统计
|
| | | // 按巷道号和日期分组统计
|
| | | var groupedData = query
|
| | | .GroupBy(t => t.InsertTime.Date)
|
| | | .GroupBy(t => new { t.Roadway, Date = t.InsertTime.Date })
|
| | | .Select(g => new
|
| | | {
|
| | | Date = g.Key,
|
| | | Roadway = g.Key.Roadway,
|
| | | Date = g.Key.Date,
|
| | | Inbound = g.Count(t => t.TaskType >= 200 && t.TaskType < 300),
|
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
|
| | | })
|
| | | .ToList();
|
| | |
|
| | | // 构建结果:每个指定仓库对应一个日期列表
|
| | | var result = specifiedRoadways.Select(roadway =>
|
| | | {
|
| | | // 获取该巷道号的分组数据字典
|
| | | var roadwayData = groupedData
|
| | | .Where(g => g.Roadway == roadway)
|
| | | .ToDictionary(x => x.Date, x => x);
|
| | |
|
| | | // 补全缺失日期
|
| | | var result = allDates.Select(date =>
|
| | | // 补全缺失日期,确保每天都有数据(默认为0)
|
| | | var dailyStats = allDates.Select(date =>
|
| | | {
|
| | | if (groupedData.TryGetValue(date, out var data))
|
| | | if (roadwayData.TryGetValue(date, out var data))
|
| | | {
|
| | | return new
|
| | | {
|
| | |
| | | }
|
| | | })
|
| | | .OrderBy(x => x.Date)
|
| | | .ToList();
|
| | |
|
| | | return new
|
| | | {
|
| | | Roadway = roadway,
|
| | | DailyStats = dailyStats
|
| | | };
|
| | | })
|
| | | .ToList();
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, result);
|
| | |
| | | return $"{monday.Year}-W{weekNum:D2}";
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// 每月统计
|
| | | /// </summary>
|
| | | /// <remarks>
|
| | | /// 按年月统计入站和出站任务数量
|
| | | /// </remarks>
|
| | | [HttpGet("MonthlyStats"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12)
|
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12, string Roadway = null)
|
| | | {
|
| | | try
|
| | | {
|
| | |
| | | var startDate = DateTime.Today.AddMonths(-months + 1);
|
| | | startDate = new DateTime(startDate.Year, startDate.Month, 1);
|
| | |
|
| | | var monthlyStats = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= startDate)
|
| | | // 仓库名称映射
|
| | | var roadwayNames = new Dictionary<string, string>
|
| | | {
|
| | | { "FJSC1", "负极卷1号仓库" },
|
| | | { "ZJSC1", "正极卷1号仓库" },
|
| | | { "GWSC1", "高温1号仓库" },
|
| | | { "CWSC1", "常温1号仓库" },
|
| | | { "HCSC1", "分容1号仓库" }
|
| | | };
|
| | |
|
| | | // 构建查询
|
| | | var query = _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= startDate);
|
| | |
|
| | | // 如果指定了道路,添加道路过滤条件
|
| | | if (!string.IsNullOrEmpty(Roadway))
|
| | | {
|
| | | query = query.Where(t => t.Roadway == Roadway);
|
| | | }
|
| | |
|
| | | var monthlyStats = await query
|
| | | .GroupBy(t => new { t.InsertTime.Year, t.InsertTime.Month })
|
| | | .Select(t => new
|
| | | {
|
| | |
| | | {
|
| | | Month = monthKey,
|
| | | Inbound = stat.Inbound,
|
| | | Outbound = stat.Outbound
|
| | | Outbound = stat.Outbound,
|
| | | Roadway = Roadway,
|
| | | RoadwayName = !string.IsNullOrEmpty(Roadway) && roadwayNames.ContainsKey(Roadway)
|
| | | ? roadwayNames[Roadway]
|
| | | : null
|
| | | });
|
| | | }
|
| | | else
|
| | |
| | | {
|
| | | Month = monthKey,
|
| | | Inbound = 0,
|
| | | Outbound = 0
|
| | | Outbound = 0,
|
| | | Roadway = Roadway,
|
| | | RoadwayName = !string.IsNullOrEmpty(Roadway) && roadwayNames.ContainsKey(Roadway)
|
| | | ? roadwayNames[Roadway]
|
| | | : null
|
| | | });
|
| | | }
|
| | | }
|
| | |
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | // 记录异常日志(实际项目中建议使用日志框架)
|
| | | // _logger.LogError(ex, "每月统计获取失败");
|
| | |
|
| | | return WebResponseContent.Instance.Error($"每月统计获取失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// 库存库龄分布
|
| | | /// </summary>
|
| | |
| | | "EnableConsoleOutput": false, //是否输出到控制台 |
| | | "EnableFloderByLevel": true //是否按日志级别生成不同的文件夹 |
| | | }, |
| | | "ApiLogIgnore": "Export,Get,get", |
| | | "LogAopEnable": false, |
| | | "PrintSql": false, //打印SQL语句 |
| | | "ApiName": "WIDESEA", |