| | |
| | | { |
| | | "tool_name": "Read", |
| | | "tool_input_preview": "{\"file_path\":\"D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WMS\\\\WIDESEA_WMSServer\\\\WIDESEA_TaskInfoService\\\\WCS\\\\TaskService.cs\"}", |
| | | "error": "File does not exist. Note: your current working directory is D:\\Git\\ShanMeiXinNengYuan\\Code.", |
| | | "timestamp": "2026-04-18T07:45:29.125Z", |
| | | "tool_name": "Bash", |
| | | "tool_input_preview": "{\"command\":\"cd D:\\\\Git\\\\ShanMeiXinNengYuan\\\\Code\\\\WCS\\\\WIDESEAWCS_Client && npm run build 2>&1 | head -30\",\"timeout\":120000}", |
| | | "error": "Exit code 1\n/usr/bin/bash: line 1: cd: D:GitShanMeiXinNengYuanCodeWCSWIDESEAWCS_Client: No such file or directory", |
| | | "timestamp": "2026-04-19T07:15:17.022Z", |
| | | "retry_count": 1 |
| | | } |
| | |
| | | "total_spawned": 83, |
| | | "total_completed": 83, |
| | | "total_failed": 0, |
| | | "last_updated": "2026-04-18T14:04:59.113Z" |
| | | "last_updated": "2026-04-18T14:41:25.052Z" |
| | | } |
| | |
| | | ] |
| | | } |
| | | }, |
| | | "hotPaths": [], |
| | | "hotPaths": [ |
| | | { |
| | | "path": "src\\views\\system\\Sys_User.vue", |
| | | "accessCount": 1, |
| | | "lastAccessed": 1776583114779, |
| | | "type": "file" |
| | | }, |
| | | { |
| | | "path": "src\\views\\taskinfo\\robotTask.vue", |
| | | "accessCount": 1, |
| | | "lastAccessed": 1776583136953, |
| | | "type": "file" |
| | | } |
| | | ], |
| | | "userDirectives": [] |
| | | } |
| | |
| | | axios.defaults.baseURL = 'http://127.0.0.1:9292/'; |
| | | } |
| | | else if (process.env.NODE_ENV == 'debug') { |
| | | axios.defaults.baseURL = 'http://127.0.0.1:8098/'; |
| | | axios.defaults.baseURL = 'http://127.0.0.1:9292/'; |
| | | } |
| | | |
| | | else if (process.env.NODE_ENV == 'production') { |
| | | axios.defaults.baseURL = 'http://115.159.85.185:9292/'; |
| | | axios.defaults.baseURL = 'http://192.168.60.30:9292/'; |
| | | } |
| | | if (!axios.defaults.baseURL.endsWith('/')) { |
| | | axios.defaults.baseURL+="/"; |
| | |
| | | sortName: "createDate", // é»è®¤æåºå段 |
| | | }); |
| | | |
| | | // ç¼è¾è¡¨ååæ®µï¼åå§ä¸ºç©ºï¼æ ¹æ®å®é
ç¼è¾éæ±é
ç½®ï¼ |
| | | const editFormFields = ref({}); |
| | | const editFormOptions = ref([]); |
| | | // ç¼è¾è¡¨ååæ®µ |
| | | const editFormFields = ref({ |
| | | robotTaskNum: "", |
| | | robotRoadway: "", |
| | | robotTaskType: "", |
| | | robotTaskState: "", |
| | | robotTaskTotalNum: "", |
| | | robotSourceAddress: "", |
| | | robotTargetAddress: "", |
| | | robotSourceAddressLineCode: "", |
| | | robotTargetAddressLineCode: "", |
| | | robotSourceAddressPalletCode: "", |
| | | robotTargetAddressPalletCode: "", |
| | | robotGrade: 2, |
| | | }); |
| | | |
| | | // ç¼è¾è¡¨åé
ç½® |
| | | const editFormOptions = ref([ |
| | | [ |
| | | { title: "ä»»å¡ç¼å·", field: "robotTaskNum", type: "int", required: true }, |
| | | { title: "å··é", field: "robotRoadway", type: "string", required: true }, |
| | | { title: "任塿»æ°", field: "robotTaskTotalNum", type: "int", required: true }, |
| | | { |
| | | title: "ä¼å
级", |
| | | field: "robotGrade", |
| | | type: "select", |
| | | data: [ |
| | | { key: 1, value: "ä½" }, |
| | | { key: 2, value: "æ®é" }, |
| | | { key: 3, value: "é«" }, |
| | | { key: 4, value: "ç´§æ¥" }, |
| | | ], |
| | | }, |
| | | ], |
| | | [ |
| | | { |
| | | title: "ä»»å¡ç±»å", |
| | | field: "robotTaskType", |
| | | type: "select", |
| | | dataKey: "taskType", |
| | | data: [], |
| | | required: true, |
| | | }, |
| | | { |
| | | title: "ä»»å¡ç¶æ", |
| | | field: "robotTaskState", |
| | | type: "select", |
| | | dataKey: "taskState", |
| | | data: [], |
| | | required: true, |
| | | }, |
| | | { title: "æ¥æºå°å", field: "robotSourceAddress", type: "string", required: true }, |
| | | { title: "ç®æ å°å", field: "robotTargetAddress", type: "string", required: true }, |
| | | ], |
| | | [ |
| | | { title: "æ¥æºçº¿ä»£ç ", field: "robotSourceAddressLineCode", type: "string" }, |
| | | { title: "ç®æ 线代ç ", field: "robotTargetAddressLineCode", type: "string" }, |
| | | { title: "æ¥æºæç代ç ", field: "robotSourceAddressPalletCode", type: "string" }, |
| | | { title: "ç®æ æç代ç ", field: "robotTargetAddressPalletCode", type: "string" }, |
| | | ], |
| | | ]); |
| | | |
| | | // æç´¢è¡¨ååæ®µ |
| | | const searchFormFields = ref({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | using WIDESEAWCS_Model.Models; |
| | | |
| | | namespace WIDESEAWCS_ITaskInfoRepository |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶æä»å¨æ¥å£ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// å®ä¹æºæ¢°æç¶æçæ°æ®åºè®¿é®æä½ã |
| | | /// å¤æå¯¹è±¡ï¼RobotCraneãCurrentTaskãæ°ç»çï¼å¨è°ç¨æ¹ä½¿ç¨å¼ºç±»åï¼ |
| | | /// 卿¤æ¥å£å±é¢ä»¥ Dt_RobotState å®ä½ä¸ºæä½åä½ã |
| | | /// </remarks> |
| | | public interface IRobotStateRepository |
| | | { |
| | | /// <summary> |
| | | /// æ ¹æ® IP å°åè·åæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <returns>ç¶æå®ä½ï¼ä¸åå¨åè¿å null</returns> |
| | | Dt_RobotState? GetByIp(string ipAddress); |
| | | |
| | | /// <summary> |
| | | /// è·åæåå»ºæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="robotCrane">æºå¨äººè®¾å¤ä¿¡æ¯ï¼ç¨äºåå§åæ°ç¶æ</param> |
| | | /// <returns>ç¶æå®ä½</returns> |
| | | Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane); |
| | | |
| | | /// <summary> |
| | | /// å®å
¨æ´æ°æºæ¢°æç¶æï¼ä¹è§éï¼ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="newState">æ°ç¶æå®ä½ï¼RowVersion ä¼è¢«æ´æ°ï¼</param> |
| | | /// <param name="expectedRowVersion">ææçè¡çæ¬å·ï¼æ´æ°åççæ¬ï¼</param> |
| | | /// <returns>æ¯å¦æ´æ°æåï¼false è¡¨ç¤ºçæ¬å²çªæè®°å½ä¸åå¨</returns> |
| | | bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion); |
| | | |
| | | /// <summary> |
| | | /// å° Dt_RobotState å®ä½è½¬æ¢ä¸º RobotSocketState å
å对象 |
| | | /// </summary> |
| | | /// <param name="entity">æ°æ®åºå®ä½</param> |
| | | /// <returns>å
åç¶æå¯¹è±¡</returns> |
| | | RobotSocketState ToSocketState(Dt_RobotState entity); |
| | | |
| | | /// <summary> |
| | | /// å° RobotSocketState å
å对象转æ¢ä¸º Dt_RobotState å®ä½ |
| | | /// </summary> |
| | | /// <param name="state">å
åç¶æå¯¹è±¡</param> |
| | | /// <returns>æ°æ®åºå®ä½</returns> |
| | | Dt_RobotState ToEntity(RobotSocketState state); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | using Newtonsoft.Json; |
| | | using SqlSugar; |
| | | using WIDESEAWCS_Core.DB.Models; |
| | | |
| | | namespace WIDESEAWCS_Model.Models |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶ææ°æ®åºå®ä½ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// å¯¹åºæ°æ®åºè¡¨ Dt_RobotStateï¼ä½¿ç¨ RowVersion å®ç°ä¹è§å¹¶åæ§å¶ã |
| | | /// å¤æå¯¹è±¡ï¼RobotCraneãCurrentTaskãæ°ç»çï¼ä»¥ JSON å符串åå¨ã |
| | | /// </remarks> |
| | | [SugarTable(nameof(Dt_RobotState), "æºæ¢°æç¶æè¡¨")] |
| | | public class Dt_RobotState : BaseEntity |
| | | { |
| | | /// <summary> |
| | | /// ä¸»é® ID |
| | | /// </summary> |
| | | [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主é®ID")] |
| | | public int Id { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æ IP å°åï¼å¯ä¸ç´¢å¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 50, ColumnDescription = "æºæ¢°æIPå°å", IsJsonKey = true)] |
| | | public string IPAddress { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// è¡çæ¬ï¼ç¨äºä¹è§å¹¶åæ§å¶ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// SqlSugar ä¼èªå¨ç®¡çæ¤åæ®µï¼æ¯æ¬¡æ´æ°æ¶æ°æ®åºèªå¨éå¢ã |
| | | /// æ´æ°æ¶ WHERE RowVersion = @expectedRowVersionï¼æ£æ¥å½±åè¡æ°å¤ææ¯å¦å²çªã |
| | | /// </remarks> |
| | | [SugarColumn(ColumnDescription = "è¡çæ¬ï¼ä¹è§éï¼", IsJsonKey = true)] |
| | | public byte[] RowVersion { get; set; } = Array.Empty<byte>(); |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦å·²è®¢é
æ¶æ¯äºä»¶ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦å·²è®¢é
æ¶æ¯äºä»¶", IsJsonKey = true)] |
| | | public bool IsEventSubscribed { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æè¿è¡æ¨¡å¼ |
| | | /// </summary> |
| | | /// <remarks>1: æå¨æ¨¡å¼, 2: èªå¨æ¨¡å¼</remarks> |
| | | [SugarColumn(ColumnDescription = "è¿è¡æ¨¡å¼", IsNullable = true, IsJsonKey = true)] |
| | | public int? RobotRunMode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°ææ§å¶æ¨¡å¼ |
| | | /// </summary> |
| | | /// <remarks>1: 客æ·ç«¯æ§å¶, 2: å
¶ä»</remarks> |
| | | [SugarColumn(ColumnDescription = "æ§å¶æ¨¡å¼", IsNullable = true, IsJsonKey = true)] |
| | | public int? RobotControlMode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°ææèæåå¯¹è±¡ç¶æ |
| | | /// </summary> |
| | | /// <remarks>0: æ ç©æï¼æè空é²ï¼, 1: æç©æï¼å·²æåè´§ç©ï¼</remarks> |
| | | [SugarColumn(ColumnDescription = "æèæåç¶æ", IsNullable = true, IsJsonKey = true)] |
| | | public int? RobotArmObject { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æè®¾å¤åºç¡ä¿¡æ¯ï¼JSON åºååï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "设å¤ä¿¡æ¯JSON", IsJsonKey = true)] |
| | | public string RobotCraneJson { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æåå§å宿åå°å¾
æºä½ç¶æ |
| | | /// </summary> |
| | | /// <remarks>Possible values: "Homed", "Homing"</remarks> |
| | | [SugarColumn(Length = 50, ColumnDescription = "åé¶ç¶æ", IsNullable = true, IsJsonKey = true)] |
| | | public string? Homed { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åæ£å¨æ§è¡çå¨ä½ |
| | | /// </summary> |
| | | [SugarColumn(Length = 50, ColumnDescription = "å½åå¨ä½", IsNullable = true, IsJsonKey = true)] |
| | | public string? CurrentAction { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åè¿è¡ç¶æ |
| | | /// </summary> |
| | | [SugarColumn(Length = 50, ColumnDescription = "è¿è¡ç¶æ", IsNullable = true, IsJsonKey = true)] |
| | | public string? OperStatus { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æè¿ä¸æ¬¡åè´§å®æçä½ç½®æ°ç»ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 500, ColumnDescription = "åè´§ä½ç½®æ°ç»JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? LastPickPositionsJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æè¿ä¸æ¬¡æ¾è´§å®æçä½ç½®æ°ç»ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 500, ColumnDescription = "æ¾è´§ä½ç½®æ°ç»JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? LastPutPositionsJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// çµæ± /è´§ä½æ¡ç å表ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "çµè¯æ¡ç å表JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? CellBarcodeJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åæ£å¨æ§è¡çä»»å¡ï¼JSON åºååï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "å½åä»»å¡JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? CurrentTaskJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦éè¦æ§è¡æçä»»å¡ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦æçä»»å¡", IsJsonKey = true)] |
| | | public bool IsSplitPallet { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦éè¦æ§è¡ç»çä»»å¡ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦ç»çä»»å¡", IsJsonKey = true)] |
| | | public bool IsGroupPallet { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººå·²å¤çç任塿»æ° |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "å·²å¤ç任塿»æ°", IsJsonKey = true)] |
| | | public int RobotTaskTotalNum { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦å¤äºåçµè¯è¡¥å
æ¨¡å¼ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦åçµè¯æ¨¡å¼", IsJsonKey = true)] |
| | | public bool IsInFakeBatteryMode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// å½åæ¹æ¬¡èµ·å§ç¼å· |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "å½åæ¹æ¬¡ç¼å·", IsJsonKey = true)] |
| | | public int CurrentBatchIndex { get; set; } = 1; |
| | | |
| | | /// <summary> |
| | | /// æ¢çä»»å¡å½åé¶æ®µ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¢çé¶æ®µ", IsJsonKey = true)] |
| | | public int ChangePalletPhase { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦æ«ç NG |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦æ«ç NG", IsJsonKey = true)] |
| | | public bool IsScanNG { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦çµè¯å°ä½ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "çµè¯æ¯å¦å°ä½", IsJsonKey = true)] |
| | | public bool BatteryArrived { get; set; } |
| | | } |
| | | } |
| | |
| | | public string RobotTargetAddress { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººæ¥æºå°å线代ç |
| | | /// æºå¨äººæ¥æºå°åè¾é线ç¼å· |
| | | /// </summary> |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººæ¥æºå°å线代ç ")] |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººæ¥æºå°åè¾é线ç¼å·")] |
| | | public string RobotSourceAddressLineCode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººç®æ å°å线代ç |
| | | /// æºå¨äººç®æ å°åè¾é线ç¼å· |
| | | /// </summary> |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººç®æ å°å线代ç ")] |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººç®æ å°åè¾é线ç¼å·")] |
| | | public string RobotTargetAddressLineCode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººæ¥æºå°å线æç代ç |
| | | /// æºå¨äººæ¥æºå°åè¾é线æçå· |
| | | /// </summary> |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººæ¥æºå°å线æç代ç ")] |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººæ¥æºå°åè¾é线æçå·")] |
| | | public string RobotSourceAddressPalletCode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººç®æ å°å线æç代ç |
| | | /// æºå¨äººç®æ å°å线æçå· |
| | | /// </summary> |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººç®æ å°å线æç代ç ")] |
| | | [SugarColumn(Length = 20, ColumnDescription = "æºå¨äººç®æ å°å线æçå·")] |
| | | public string RobotTargetAddressPalletCode { get; set; } |
| | | |
| | | /// <summary> |
| | |
| | | //throw new Exception($"读åPLCå符串å®ä¹é¿åº¦ã{Content[index]}ãä¸å¯¹è±¡ã{GetType().Name}ã屿§ã{propertyInfo.Name}ãç¹æ§å®ä¹å符串é¿åº¦ã{dataLength}ãä¸ä¸è´"); |
| | | //QuartzLogger.Debug($"读åPLCå符串å®ä¹é¿åº¦ã{Content[index]}ãä¸å¯¹è±¡ã{GetType().Name}ã屿§ã{propertyInfo.Name}ãç¹æ§å®ä¹å符串é¿åº¦ã{dataLength}ãä¸ä¸è´"); |
| | | |
| | | propertyInfo.SetValue(this, Encoding.Default.GetString(Content, index, dataLength).Trim().Replace("\0", "").Replace("\\u000","").Trim()); |
| | | try |
| | | { |
| | | propertyInfo.SetValue(this, Encoding.Default.GetString(Content, index + 2, Content[index + 1] > 0 ? Content[index + 1] : dataLength - 2).Trim().Replace("\0", "").Replace("\\u000", "").Trim()); |
| | | } |
| | | catch |
| | | { |
| | | propertyInfo.SetValue(this, Encoding.Default.GetString(Content, index, dataLength).Trim().Replace("\0", "").Replace("\\u000", "").Trim()); |
| | | } |
| | | index += dataLength; |
| | | break; |
| | | } |
| | |
| | | |
| | | public Task StartAsync(CancellationToken cancellationToken) |
| | | { |
| | | const string cacheKey = $"{RedisPrefix.Code}"; |
| | | //const string cacheKey = $"{RedisPrefix.Code}"; |
| | | |
| | | _cache.RemoveByPrefix($"{cacheKey}"); |
| | | //_cache.RemoveByPrefix($"{cacheKey}"); |
| | | |
| | | |
| | | |
| | |
| | | --> |
| | | <Project> |
| | | <PropertyGroup> |
| | | <DeleteExistingFiles>false</DeleteExistingFiles> |
| | | <DeleteExistingFiles>true</DeleteExistingFiles> |
| | | <ExcludeApp_Data>false</ExcludeApp_Data> |
| | | <LaunchSiteAfterPublish>true</LaunchSiteAfterPublish> |
| | | <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration> |
| | | <LastUsedBuildConfiguration>Debug</LastUsedBuildConfiguration> |
| | | <LastUsedPlatform>Any CPU</LastUsedPlatform> |
| | | <PublishProvider>FileSystem</PublishProvider> |
| | | <PublishUrl>bin\Release\net6.0\publish\</PublishUrl> |
| | | <PublishUrl>bin\Debug\net6.0\publish\</PublishUrl> |
| | | <WebPublishMethod>FileSystem</WebPublishMethod> |
| | | <_TargetId>Folder</_TargetId> |
| | | <SiteUrlToLaunchAfterPublish /> |
| | | <TargetFramework>net8.0</TargetFramework> |
| | | <ProjectGuid>487fa45b-ea1a-4aca-bb5b-0f6708f462c0</ProjectGuid> |
| | | <SelfContained>false</SelfContained> |
| | | </PropertyGroup> |
| | | </Project> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | { |
| | | "version": 1, |
| | | "isRoot": true, |
| | | "tools": {} |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | using Newtonsoft.Json; |
| | | using SqlSugar; |
| | | using WIDESEAWCS_Core.BaseRepository; |
| | | using WIDESEAWCS_Core.UnitOfWork; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | | using WIDESEAWCS_Model.Models; |
| | | |
| | | namespace WIDESEAWCS_TaskInfoRepository |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶æ SqlSugar ä»å¨å®ç° |
| | | /// </summary> |
| | | public class RobotStateRepository : IUnitOfWork, IRobotStateRepository |
| | | { |
| | | private readonly IUnitOfWorkManage _unitOfWork; |
| | | private readonly SqlSugarClient _db; |
| | | |
| | | public RobotStateRepository(IUnitOfWorkManage unitOfWork) |
| | | { |
| | | _unitOfWork = unitOfWork; |
| | | _db = unitOfWork.GetDbClient(); |
| | | } |
| | | |
| | | public Dt_RobotState? GetByIp(string ipAddress) |
| | | { |
| | | return _db.Queryable<Dt_RobotState>() |
| | | .Where(x => x.IPAddress == ipAddress) |
| | | .First(); |
| | | } |
| | | |
| | | public Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane) |
| | | { |
| | | var existing = GetByIp(ipAddress); |
| | | if (existing != null) |
| | | { |
| | | return existing; |
| | | } |
| | | |
| | | var newState = new Dt_RobotState |
| | | { |
| | | IPAddress = ipAddress, |
| | | RobotCraneJson = JsonConvert.SerializeObject(robotCrane), |
| | | CreateTime = DateTime.Now, |
| | | UpdateTime = DateTime.Now |
| | | }; |
| | | |
| | | _db.Insertable(newState).ExecuteCommand(); |
| | | return newState; |
| | | } |
| | | |
| | | public bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion) |
| | | { |
| | | newState.UpdateTime = DateTime.Now; |
| | | |
| | | var affectedRows = _db.Updateable<Dt_RobotState>(newState) |
| | | .Where(x => x.IPAddress == ipAddress) |
| | | .WhereRowVersion(x => x.RowVersion, expectedRowVersion) |
| | | .ExecuteCommand(); |
| | | |
| | | return affectedRows > 0; |
| | | } |
| | | |
| | | public RobotSocketState ToSocketState(Dt_RobotState entity) |
| | | { |
| | | var state = new RobotSocketState |
| | | { |
| | | IPAddress = entity.IPAddress, |
| | | Version = BitConverter.ToInt64(entity.RowVersion.Length >= 8 ? entity.RowVersion.Take(8).ToArray() : new byte[8], 0), |
| | | IsEventSubscribed = entity.IsEventSubscribed, |
| | | RobotRunMode = entity.RobotRunMode, |
| | | RobotControlMode = entity.RobotControlMode, |
| | | RobotArmObject = entity.RobotArmObject, |
| | | Homed = entity.Homed, |
| | | CurrentAction = entity.CurrentAction, |
| | | OperStatus = entity.OperStatus, |
| | | IsSplitPallet = entity.IsSplitPallet, |
| | | IsGroupPallet = entity.IsGroupPallet, |
| | | RobotTaskTotalNum = entity.RobotTaskTotalNum, |
| | | IsInFakeBatteryMode = entity.IsInFakeBatteryMode, |
| | | CurrentBatchIndex = entity.CurrentBatchIndex, |
| | | ChangePalletPhase = entity.ChangePalletPhase, |
| | | IsScanNG = entity.IsScanNG, |
| | | BatteryArrived = entity.BatteryArrived |
| | | }; |
| | | |
| | | // ååºåå夿 JSON åæ®µ |
| | | if (!string.IsNullOrEmpty(entity.RobotCraneJson)) |
| | | { |
| | | state.RobotCrane = JsonConvert.DeserializeObject<RobotCraneDevice>(entity.RobotCraneJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.CurrentTaskJson)) |
| | | { |
| | | state.CurrentTask = JsonConvert.DeserializeObject<Dt_RobotTask>(entity.CurrentTaskJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.LastPickPositionsJson)) |
| | | { |
| | | state.LastPickPositions = JsonConvert.DeserializeObject<int[]>(entity.LastPickPositionsJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.LastPutPositionsJson)) |
| | | { |
| | | state.LastPutPositions = JsonConvert.DeserializeObject<int[]>(entity.LastPutPositionsJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.CellBarcodeJson)) |
| | | { |
| | | state.CellBarcode = JsonConvert.DeserializeObject<List<string>>(entity.CellBarcodeJson) ?? new List<string>(); |
| | | } |
| | | |
| | | return state; |
| | | } |
| | | |
| | | public Dt_RobotState ToEntity(RobotSocketState state) |
| | | { |
| | | var entity = new Dt_RobotState |
| | | { |
| | | IPAddress = state.IPAddress, |
| | | IsEventSubscribed = state.IsEventSubscribed, |
| | | RobotRunMode = state.RobotRunMode, |
| | | RobotControlMode = state.RobotControlMode, |
| | | RobotArmObject = state.RobotArmObject, |
| | | Homed = state.Homed, |
| | | CurrentAction = state.CurrentAction, |
| | | OperStatus = state.OperStatus, |
| | | IsSplitPallet = state.IsSplitPallet, |
| | | IsGroupPallet = state.IsGroupPallet, |
| | | RobotTaskTotalNum = state.RobotTaskTotalNum, |
| | | IsInFakeBatteryMode = state.IsInFakeBatteryMode, |
| | | CurrentBatchIndex = state.CurrentBatchIndex, |
| | | ChangePalletPhase = state.ChangePalletPhase, |
| | | IsScanNG = state.IsScanNG, |
| | | BatteryArrived = state.BatteryArrived |
| | | }; |
| | | |
| | | // åºååå¤æå¯¹è±¡ä¸º JSON |
| | | if (state.RobotCrane != null) |
| | | { |
| | | entity.RobotCraneJson = JsonConvert.SerializeObject(state.RobotCrane); |
| | | } |
| | | |
| | | if (state.CurrentTask != null) |
| | | { |
| | | entity.CurrentTaskJson = JsonConvert.SerializeObject(state.CurrentTask); |
| | | } |
| | | |
| | | if (state.LastPickPositions != null) |
| | | { |
| | | entity.LastPickPositionsJson = JsonConvert.SerializeObject(state.LastPickPositions); |
| | | } |
| | | |
| | | if (state.LastPutPositions != null) |
| | | { |
| | | entity.LastPutPositionsJson = JsonConvert.SerializeObject(state.LastPutPositions); |
| | | } |
| | | |
| | | if (state.CellBarcode != null && state.CellBarcode.Count > 0) |
| | | { |
| | | entity.CellBarcodeJson = JsonConvert.SerializeObject(state.CellBarcode); |
| | | } |
| | | |
| | | return entity; |
| | | } |
| | | |
| | | public SqlSugarClient GetDbClient() => _db; |
| | | |
| | | public void BeginTran() => _unitOfWork.BeginTran(); |
| | | |
| | | public void CommitTran() => _unitOfWork.CommitTran(); |
| | | |
| | | public void RollbackTran() => _unitOfWork.RollbackTran(); |
| | | } |
| | | } |
| | |
| | | using WIDESEAWCS_Core; |
| | | using WIDESEAWCS_Core.BaseServices; |
| | | using WIDESEAWCS_Core.Helper; |
| | | using WIDESEAWCS_Core.Utilities; |
| | | using WIDESEAWCS_DTO.Stock; |
| | | using WIDESEAWCS_DTO.TaskInfo; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | |
| | | return stock; |
| | | } |
| | | |
| | | public override WebResponseContent AddData(SaveModel saveModel) |
| | | { |
| | | try |
| | | { |
| | | if (saveModel == null || saveModel.MainData == null || saveModel.MainData.Count == 0) |
| | | { |
| | | return WebResponseContent.Instance.Error("ä¼ åé误,åæ°ä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | string validResult = typeof(Dt_RobotTask).ValidateDicInEntity(saveModel.MainData, true, TProperties); |
| | | if (!string.IsNullOrEmpty(validResult)) |
| | | { |
| | | return WebResponseContent.Instance.Error(validResult); |
| | | } |
| | | |
| | | object? taskNumObj = saveModel.MainData[nameof(Dt_RobotTask.RobotTaskNum)]; |
| | | if (taskNumObj != null) |
| | | { |
| | | int taskNum = Convert.ToInt32(taskNumObj); |
| | | if (BaseDal.QueryFirst(x => x.RobotTaskNum == taskNum) != null) |
| | | { |
| | | return WebResponseContent.Instance.Error($"ä»»å¡ç¼å· {taskNum} å·²åå¨"); |
| | | } |
| | | } |
| | | |
| | | Dt_RobotTask entity = saveModel.MainData.DicToModel<Dt_RobotTask>(); |
| | | entity.Creater = "æå¨å建"; |
| | | entity.CreateDate = DateTime.Now; |
| | | |
| | | if (saveModel.DetailData == null || saveModel.DetailData.Count == 0) |
| | | { |
| | | BaseDal.AddData(entity); |
| | | return WebResponseContent.Instance.OK("æ°å¢æå", entity); |
| | | } |
| | | |
| | | return base.AddData(saveModel); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"æ°å¢å¤±è´¥,é误信æ¯:{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ ¹æ®ç®æ å°åæã精确 > åéå¼ãè§£æè§åå¼ã |
| | | /// </summary> |
| | |
| | | using MapsterMapper; |
| | | using Masuit.Tools; |
| | | using Microsoft.Extensions.Configuration; |
| | | using Microsoft.Extensions.Logging; |
| | | using Newtonsoft.Json; |
| | |
| | | /// ä»»å¡æå¡ |
| | | /// </summary> |
| | | private readonly ITaskService _taskService; |
| | | |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººä»»å¡æå¡ |
| | | /// </summary> |
| | | private readonly IRobotTaskService _robotTaskService; |
| | | |
| | | /// <summary> |
| | | /// 任塿§è¡æç»æå¡ |
| | |
| | | /// <param name="mapper">对象æ å°å¨</param> |
| | | /// <param name="httpClientHelper">HTTP 客æ·ç«¯å¸®å©ç±»</param> |
| | | /// <param name="logger">æ¥å¿è®°å½å¨</param> |
| | | public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger<CommonConveyorLineNewJob> logger) |
| | | public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger<CommonConveyorLineNewJob> logger, IRobotTaskService robotTaskService) |
| | | { |
| | | _taskService = taskService; |
| | | _taskExecuteDetailService = taskExecuteDetailService; |
| | |
| | | _mapper = mapper; |
| | | _httpClientHelper = httpClientHelper; |
| | | _logger = logger; |
| | | _robotTaskService = robotTaskService; |
| | | |
| | | // åå§åè°åº¦å¤çå¨ |
| | | _conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper, _logger); |
| | |
| | | ProcessTaskState(conveyorLine, command, task, childDeviceCode); |
| | | return Task.CompletedTask; |
| | | } |
| | | else if (command.TaskNo == 1 && !command.Barcode.IsNullOrEmpty() && childDeviceCode == "11068") |
| | | { |
| | | if (_robotTaskService.Db.Queryable<Dt_RobotTask>().Any(x => x.RobotTargetAddressPalletCode == command.Barcode)) |
| | | { |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | Random rnd = new Random(); |
| | | int num = rnd.StrictNext();//产ççéæºæ° |
| | | // 没æä»»å¡å·ä½ææ¡ç å¹¶ä¸å¨11068ä½ç½®ï¼ç´æ¥æ·»å æºæ¢°æç»çä»»å¡ |
| | | Dt_RobotTask robotTask = new Dt_RobotTask |
| | | { |
| | | RobotTargetAddressPalletCode = command.Barcode, |
| | | RobotSourceAddress = "1", |
| | | RobotTargetAddress = "2", // æºæ¢°æç®æ å°å |
| | | RobotTaskType = (int)RobotTaskTypeEnum.GroupPallet, // ç»çä»»å¡ |
| | | RobotTaskState = (int)TaskRobotStatusEnum.RobotNew, // å¾
æ§è¡ |
| | | RobotTaskTotalNum = 48, |
| | | RobotGrade = 1, |
| | | RobotRoadway = "注液ç»çæºæ¢°æ", |
| | | RobotTargetAddressLineCode = childDeviceCode, |
| | | RobotTaskNum = num, // çæä»»å¡å· |
| | | RobotDispatchertime = DateTime.Now, |
| | | |
| | | }; |
| | | if (_robotTaskService.AddData(robotTask).Status) |
| | | { |
| | | conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, childDeviceCode); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | catch (Exception innerEx) |
| | |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_QuartzJob; |
| | | using WIDESEAWCS_RedisService; |
| | | using WIDESEAWCS_Tasks.SocketServer; |
| | | using WIDESEAWCS_Tasks.Workflow; |
| | | using WIDESEAWCS_Tasks.Workflow.Abstractions; |
| | |
| | | /// <param name="tcpSocket">TCP Socket æå¡å¨å®ä¾</param> |
| | | /// <param name="robotTaskService">æºå¨äººä»»å¡æå¡</param> |
| | | /// <param name="taskService">éç¨ä»»å¡æå¡</param> |
| | | /// <param name="cache">ç¼åæå¡</param> |
| | | /// <param name="robotStateRepository">æºå¨äººç¶æä»å¨</param> |
| | | /// <param name="httpClientHelper">HTTP 客æ·ç«¯å¸®å©ç±»ï¼ç¨äºè°ç¨ WMS æ¥å£</param> |
| | | /// <param name="logger">æ¥å¿è®°å½å¨</param> |
| | | public RobotJob( |
| | | TcpSocketServer tcpSocket, |
| | | IRobotTaskService robotTaskService, |
| | | ITaskService taskService, |
| | | ICacheService cache, |
| | | IRobotStateRepository robotStateRepository, |
| | | HttpClientHelper httpClientHelper, |
| | | ILogger<RobotJob> logger, |
| | | IFakeBatteryPositionService fakeBatteryPositionService) |
| | | { |
| | | // åå§åç¶æç®¡çå¨ï¼ä¼ å
¥ç¼åæå¡ |
| | | _stateManager = new RobotStateManager(cache, _logger); |
| | | // åå§åç¶æç®¡çå¨ï¼ä¼ å
¥ä»å¨æå¡ |
| | | _stateManager = new RobotStateManager(robotStateRepository, _logger); |
| | | _logger = logger; |
| | | |
| | | // å建 Socket ç½å
³ï¼å°è£
TcpSocketServer çè®¿é® |
| | |
| | | // 妿ç¼åä¸ä¸åå¨æç¶æä¸º nullï¼å¿½ç¥æ¤æ¶æ¯ |
| | | if (!_cache.TryGetValue(cacheKey, out RobotSocketState? cachedState) || cachedState == null) |
| | | { |
| | | _logger.LogInformation($"ç¼åä¸ä¸åå¨æç¶æä¸º nullï¼å¿½ç¥æ¤æ¶æ¯"); |
| | | return null; |
| | | } |
| | | |
| | |
| | | { |
| | | // å¤çæååï¼å°åæ¶æ¯ååå°å®¢æ·ç«¯ï¼ä¿æåæè¡ä¸ºï¼ |
| | | await _socketClientGateway.SendMessageAsync(client, message); |
| | | _logger.LogInformation($"åéæ¶æ¯ã{message}ã"); |
| | | QuartzLogger.Info($"åéæ¶æ¯ï¼ã{message}ã", state.RobotCrane.DeviceName); |
| | | |
| | | // å®å
¨æ´æ°ç¶æå° Redis |
| | |
| | | using Microsoft.Extensions.Logging; |
| | | using Newtonsoft.Json; |
| | | using WIDESEAWCS_Common; |
| | | using WIDESEAWCS_Core.Caches; |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_QuartzJob; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | | using WIDESEAWCS_Model.Models; |
| | | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | |
| | | /// æºæ¢°æç¶æç®¡çå¨ - è´è´£ RobotSocketState ç线ç¨å®å
¨æ´æ°åå
é |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// æ ¸å¿åè½æ¯éè¿ç¼åæå¡ï¼ICacheServiceï¼ç®¡ç Redis ä¸çæºæ¢°æç¶æã |
| | | /// æä¾ä¹è§å¹¶åæ§å¶ï¼éè¿çæ¬å·ï¼Versionï¼åæ®µé²æ¢å¹¶åæ´æ°æ¶çæ°æ®è¦çé®é¢ã |
| | | /// æ ¸å¿åè½æ¯éè¿ IRobotStateRepository ç®¡çæ°æ®åºä¸çæºæ¢°æç¶æã |
| | | /// æä¾ä¹è§å¹¶åæ§å¶ï¼éè¿ RowVersion 鲿¢å¹¶åæ´æ°æ¶çæ°æ®è¦çé®é¢ã |
| | | /// </remarks> |
| | | public class RobotStateManager |
| | | { |
| | | /// <summary> |
| | | /// ç¼åæå¡å®ä¾ï¼ç¨äºè¯»å Redis ä¸çç¶ææ°æ® |
| | | /// ä»å¨æå¡å®ä¾ï¼ç¨äºè¯»åæ°æ®åºä¸çç¶ææ°æ® |
| | | /// </summary> |
| | | private readonly ICacheService _cache; |
| | | private readonly IRobotStateRepository _repository; |
| | | |
| | | /// <summary> |
| | | /// æ¥å¿è®°å½å¨ |
| | |
| | | /// <summary> |
| | | /// æé 彿° |
| | | /// </summary> |
| | | /// <param name="cache">ç¼åæå¡å®ä¾ï¼é常为 HybridCacheServiceï¼</param> |
| | | /// <param name="repository">ä»å¨æå¡å®ä¾</param> |
| | | /// <param name="logger">æ¥å¿è®°å½å¨</param> |
| | | public RobotStateManager(ICacheService cache, ILogger logger) |
| | | public RobotStateManager(IRobotStateRepository repository, ILogger logger) |
| | | { |
| | | _cache = cache; |
| | | _repository = repository; |
| | | _logger = logger; |
| | | } |
| | | |
| | |
| | | /// å®å
¨æ´æ° RobotSocketState ç¼åï¼é²æ¢å¹¶åè¦ç |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 使ç¨ä¹è§å¹¶å模å¼ï¼å
读åå½åçæ¬å·ï¼æ§è¡æ´æ°æ¶æ£æ¥çæ¬æ¯å¦ä¸è´ã |
| | | /// å¦æçæ¬ä¸å¹é
ï¼è¯´ææå
¶ä»çº¿ç¨å·²æ´æ°ï¼ï¼åæ´æ°å¤±è´¥è¿å falseã |
| | | /// 使ç¨ä¹è§å¹¶å模å¼ï¼å
读åå½å RowVersionï¼æ§è¡æ´æ°æ¶æ£æ¥çæ¬æ¯å¦ä¸è´ã |
| | | /// 妿 RowVersion ä¸å¹é
ï¼è¯´ææå
¶ä»çº¿ç¨å·²æ´æ°ï¼ï¼åæ´æ°å¤±è´¥è¿å falseã |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°åï¼ç¨äºæå»ºç¼åé®</param> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="updateAction">æ´æ°ç¶æç姿彿°ï¼ä¼ å
¥å½åç¶æå¯æ¬ï¼è¿åä¿®æ¹åçæ°ç¶æ</param> |
| | | /// <returns>æ¯å¦æ´æ°æåï¼false è¡¨ç¤ºçæ¬å²çªæç¶æä¸åå¨</returns> |
| | | public bool TryUpdateStateSafely(string ipAddress, Func<RobotSocketState, RobotSocketState> updateAction) |
| | | { |
| | | // æå»º Redis ç¼åé®ï¼æ ¼å¼ï¼{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress} |
| | | var cacheKey = GetCacheKey(ipAddress); |
| | | // 仿°æ®åºè·åå½ååå¨çç¶æ |
| | | var currentEntity = _repository.GetByIp(ipAddress); |
| | | |
| | | // ä»ç¼åè·åå½ååå¨çç¶æ |
| | | var currentState = _cache.Get<RobotSocketState>(cacheKey); |
| | | |
| | | // 妿ç¼åä¸ä¸åå¨è¯¥è®¾å¤çç¶æï¼ç´æ¥è¿å falseï¼åºç± GetOrCreateState å
åå»ºï¼ |
| | | if (currentState == null) |
| | | if (currentEntity == null) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | // è®°å½å½ååå¨ççæ¬å·ï¼ä½ä¸ºæ´æ°æ¶çææçæ¬ |
| | | var expectedVersion = currentState.Version; |
| | | // è®°å½å½ååå¨ç RowVersionï¼ä½ä¸ºæ´æ°æ¶çææçæ¬ |
| | | var expectedRowVersion = currentEntity.RowVersion; |
| | | |
| | | // åå»ºç¶æçæ·±æ·è´å¯æ¬ï¼é¿å
ç´æ¥ä¿®æ¹å对象å¼ç¨ |
| | | // è¿æ ·å¯ä»¥ç¡®ä¿å¨å¤çº¿ç¨ç¯å¢ä¸ï¼æ¯ä¸ªçº¿ç¨æä½çæ¯ç¬ç«çç¶æå¯æ¬ |
| | | var stateCopy = CloneState(currentState); |
| | | // åå»ºç¶æçæ·±æ·è´å¯æ¬ï¼ä½¿ç¨ JSON åºååå®ç°ï¼ |
| | | var stateCopy = CloneState(_repository.ToSocketState(currentEntity)); |
| | | |
| | | // æ§è¡è°ç¨è
æä¾çæ´æ°é»è¾ï¼ä¼ å
¥å¯æ¬ç¶æï¼è·åæ°çç¶æå¯¹è±¡ |
| | | var newState = updateAction(stateCopy); |
| | | |
| | | // å°æ°ç¶æççæ¬å·æ´æ°ä¸ºææ°çæ¶é´æ³ï¼è¡¨ç¤ºæ°æ®å·²æ´æ° |
| | | newState.Version = DateTime.UtcNow.Ticks; |
| | | // å°æ°ç¶æè½¬æ¢ä¸ºæ°æ®åºå®ä½ |
| | | var newEntity = _repository.ToEntity(newState); |
| | | newEntity.RowVersion = Array.Empty<byte>(); // SqlSugar ä¼èªå¨ç®¡ç |
| | | newEntity.Id = currentEntity.Id; |
| | | |
| | | // è°ç¨ç¼åæå¡çå®å
¨æ´æ°æ¹æ³ï¼ä¼ å
¥ææçæ¬åçæ¬æåå¨ |
| | | // 妿å½åçæ¬ä¸ææçæ¬ä¸ä¸è´ï¼å·²è¢«å
¶ä»çº¿ç¨æ´æ°ï¼ï¼åæ´æ°å¤±è´¥ |
| | | return _cache.TrySafeUpdate( |
| | | cacheKey, |
| | | newState, |
| | | expectedVersion, |
| | | s => s.Version // æå®åªä¸ªå段ä½ä¸ºçæ¬å· |
| | | ); |
| | | // è°ç¨ä»å¨çå®å
¨æ´æ°æ¹æ³ï¼ä¼ å
¥ææ RowVersion |
| | | // 妿 RowVersion ä¸ä¸è´ï¼å·²è¢«å
¶ä»çº¿ç¨æ´æ°ï¼ï¼åæ´æ°å¤±è´¥ |
| | | return _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å®å
¨æ´æ° RobotSocketState ç¼åçéè½½çæ¬ï¼ç´æ¥ä¼ å
¥æ°ç¶æï¼ |
| | | /// å®å
¨æ´æ° RobotSocketState çéè½½çæ¬ï¼ç´æ¥ä¼ å
¥æ°ç¶æï¼ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// ä¸ä¸ä¸ä¸ªéè½½çåºå«ï¼æ¤æ¹æ³ç´æ¥æ¥æ¶å®æ´çæ°ç¶æå¯¹è±¡ï¼è䏿¯æ´æ°å§æã |
| | | /// å¦æè®¾å¤ç¶æä¸åå¨äºç¼åä¸ï¼åç´æ¥æ·»å æ°ç¶æã |
| | | /// å¦ææ°æ®åºä¸ä¸åå¨è¯¥è®¾å¤çç¶æï¼åå建æ°è®°å½ã |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°åï¼ç¨äºæå»ºç¼åé®</param> |
| | | /// <param name="newState">æ°ç¶æå¯¹è±¡ï¼æ¹æ³å
é¨ä¼æ´æ°å
¶ Version åæ®µï¼</param> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="newState">æ°ç¶æå¯¹è±¡</param> |
| | | /// <returns>æ¯å¦æ´æ°æåï¼æ°å»ºè®¾ç½®ä¸º true</returns> |
| | | public bool TryUpdateStateSafely(string ipAddress, RobotSocketState newState) |
| | | { |
| | | // æå»º Redis ç¼åé® |
| | | var cacheKey = GetCacheKey(ipAddress); |
| | | // 仿°æ®åºè·åå½ååå¨çç¶æ |
| | | var currentEntity = _repository.GetByIp(ipAddress); |
| | | |
| | | // ä»ç¼åè·åå½ååå¨çç¶æ |
| | | var currentState = _cache.Get<RobotSocketState>(cacheKey); |
| | | |
| | | // 妿å½åä¸åå¨è¯¥è®¾å¤çç¶æ |
| | | if (currentState == null) |
| | | // 妿å½åä¸åå¨è¯¥è®¾å¤çç¶æï¼å建æ°è®°å½ |
| | | if (currentEntity == null) |
| | | { |
| | | // 为æ°ç¶æè®¾ç½®çæ¬å·ï¼æ¶é´æ³ï¼ |
| | | newState.Version = DateTime.UtcNow.Ticks; |
| | | // ç´æ¥æ·»å å°ç¼å |
| | | _cache.AddObject(cacheKey, newState); |
| | | var entity = _repository.ToEntity(newState); |
| | | entity.CreateTime = DateTime.Now; |
| | | entity.UpdateTime = DateTime.Now; |
| | | _repository.GetOrCreate(newState.IPAddress, newState.RobotCrane ?? new RobotCraneDevice()); |
| | | _logger.LogDebug("TryUpdateStateSafelyï¼å建æ°ç¶æï¼IP: {IpAddress}", ipAddress); |
| | | QuartzLogger.Debug($"å建æ°ç¶æï¼IP: {ipAddress}", ipAddress); |
| | | return true; |
| | | } |
| | | |
| | | // å½ååå¨ç¶æï¼è®°å½ææçæ¬å·ç¨äºä¹è§éæ£æ¥ |
| | | var expectedVersion = currentState.Version; |
| | | // å½ååå¨ç¶æï¼è®°å½ææ RowVersion ç¨äºä¹è§éæ£æ¥ |
| | | var expectedRowVersion = currentEntity.RowVersion; |
| | | |
| | | // æ´æ°æ°ç¶æççæ¬å·ä¸ºææ°æ¶é´æ³ |
| | | newState.Version = DateTime.UtcNow.Ticks; |
| | | // å°æ°ç¶æè½¬æ¢ä¸ºæ°æ®åºå®ä½ |
| | | var newEntity = _repository.ToEntity(newState); |
| | | newEntity.Id = currentEntity.Id; |
| | | newEntity.RowVersion = Array.Empty<byte>(); |
| | | |
| | | // å°è¯å®å
¨æ´æ°ï¼å¦æçæ¬å²çªåè¿å false |
| | | bool success = _cache.TrySafeUpdate( |
| | | cacheKey, |
| | | newState, |
| | | expectedVersion, |
| | | s => s.Version |
| | | ); |
| | | bool success = _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion); |
| | | |
| | | if (!success) |
| | | { |
| | | _logger.LogWarning("TryUpdateStateSafelyï¼çæ¬å²çªï¼æ´æ°å¤±è´¥ï¼IP: {IpAddress}ï¼ææçæ¬: {ExpectedVersion}", ipAddress, expectedVersion); |
| | | _logger.LogWarning("TryUpdateStateSafelyï¼çæ¬å²çªï¼æ´æ°å¤±è´¥ï¼IP: {IpAddress}ï¼ææçæ¬åèé¿åº¦: {ExpectedLength}", ipAddress, expectedRowVersion.Length); |
| | | QuartzLogger.Warn($"çæ¬å²çªï¼æ´æ°å¤±è´¥ï¼IP: {ipAddress}", ipAddress); |
| | | } |
| | | |
| | |
| | | /// <returns>æ°çç¶æå¯¹è±¡ï¼æ¯æºå¯¹è±¡çæ·±æ·è´</returns> |
| | | public RobotSocketState CloneState(RobotSocketState source) |
| | | { |
| | | // å°æºå¯¹è±¡åºåå为 JSON å符串 |
| | | var json = JsonConvert.SerializeObject(source); |
| | | // ååºåå为æ°ç RobotSocketState 对象 |
| | | // 妿ååºåå失败ï¼è¿å nullï¼ï¼å建ä¸ä¸ªæ°å¯¹è±¡å¹¶å¤å¶ IPAddress |
| | | return JsonConvert.DeserializeObject<RobotSocketState>(json) ?? new RobotSocketState { IPAddress = source.IPAddress }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è·å Redis ç¼åé® |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// ç¼å鮿 ¼å¼ï¼{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress} |
| | | /// ä¾å¦ï¼Code:SocketDevices:192.168.1.100 |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <returns>宿´ç Redis ç¼åé®</returns> |
| | | public static string GetCacheKey(string ipAddress) |
| | | { |
| | | return $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// ä»ç¼åè·åæºæ¢°æç¶æ |
| | | /// 仿°æ®åºè·åæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <returns>妿åå¨åè¿åç¶æå¯¹è±¡ï¼å¦åè¿å null</returns> |
| | | public RobotSocketState? GetState(string ipAddress) |
| | | { |
| | | return _cache.Get<RobotSocketState>(GetCacheKey(ipAddress)); |
| | | var entity = _repository.GetByIp(ipAddress); |
| | | return entity != null ? _repository.ToSocketState(entity) : null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è·åæåå»ºæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 妿ç¼åä¸å·²åå¨è¯¥è®¾å¤çç¶æï¼ç´æ¥è¿åã |
| | | /// 妿ä¸åå¨ï¼åå建æ°çç¶æå¯¹è±¡å¹¶åå
¥ç¼åï¼ç¶åè¿åã |
| | | /// å¦ææ°æ®åºä¸å·²åå¨è¯¥è®¾å¤çç¶æï¼ç´æ¥è¿åã |
| | | /// 妿ä¸åå¨ï¼åå建æ°çç¶æè®°å½å¹¶è¿åã |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="robotCrane">æºå¨äººè®¾å¤ä¿¡æ¯ï¼ç¨äºåå§åæ°ç¶æ</param> |
| | | /// <returns>该设å¤çç¶æå¯¹è±¡</returns> |
| | | public RobotSocketState GetOrCreateState(string ipAddress, RobotCraneDevice robotCrane) |
| | | { |
| | | // 使ç¨ç¼åæå¡ç GetOrAdd æ¹æ³ï¼å·¥å彿°å¨ç¼åæªå½ä¸æ¶å建æ°ç¶æ |
| | | return _cache.GetOrAdd(GetCacheKey(ipAddress), _ => new RobotSocketState |
| | | { |
| | | IPAddress = ipAddress, // 设置 IP å°åä½ä¸ºæ è¯ |
| | | RobotCrane = robotCrane // ä¿å设å¤ä¿¡æ¯ |
| | | }); |
| | | var entity = _repository.GetOrCreate(ipAddress, robotCrane); |
| | | return _repository.ToSocketState(entity); |
| | | } |
| | | } |
| | | } |
| | |
| | | // å°ä»»å¡å
³èå°ç¶æå¯¹è±¡ |
| | | state.CurrentTask = task; |
| | | |
| | | if(isScanNG) |
| | | if (isScanNG) |
| | | { |
| | | state.IsScanNG = true; |
| | | } |
| | |
| | | /// <param name="state">æºå¨äººå½åç¶æ</param> |
| | | /// <param name="useSourceAddress">æ¯å¦ä½¿ç¨æºå°åï¼true 表示æç/æ¢çåºæ¯ï¼false 表示ç»ç/æ¢çåºæ¯ï¼</param> |
| | | /// <returns>å¤çæ¯å¦æå</returns> |
| | | public async Task<bool> HandleInboundTaskAsync(RobotSocketState state, bool useSourceAddress) |
| | | public async Task<bool> HandleInboundTaskAsync(RobotSocketState state, bool useSourceAddress, string isRoadway = null) |
| | | { |
| | | // è·åå½åå
³èçä»»å¡ |
| | | var currentTask = state.CurrentTask; |
| | |
| | | } |
| | | |
| | | // è·åå··é代ç |
| | | string roadway = currentTask.RobotSourceAddressLineCode; |
| | | string roadway = string.Empty; |
| | | |
| | | // æ ¹æ®å··éåç§°å¤æä»åº ID |
| | | // ZYRB1 -> 1, HPRB001 -> 2, å
¶ä» -> 3 |
| | | int warehouseId = currentTask.RobotRoadway == "注液ç»çæºæ¢°æ" ? 1 : currentTask.RobotRoadway == "HPRB001" ? 2 : 3; |
| | | |
| | | // ä»»å¡ç±»åï¼0 表示æªå®ä¹ï¼ç¨åæ ¹æ®ä»»å¡ç±»åè®¾ç½®ï¼ |
| | | int taskType = 0; |
| | | int warehouseId = 0; |
| | | |
| | | // æºå°ååç®æ å°åï¼åå§åï¼ |
| | | string SourceAddress = currentTask.RobotTargetAddressLineCode; |
| | | string TargetAddress = currentTask.RobotSourceAddressLineCode; |
| | | |
| | | // ä»»å¡ç±»åï¼0 表示æªå®ä¹ï¼ç¨åæ ¹æ®ä»»å¡ç±»åè®¾ç½®ï¼ |
| | | int taskType = 0; |
| | | |
| | | // æç代ç ï¼åå§åä¸ºç©ºï¼ |
| | | string PalletCode = string.Empty; |
| | |
| | | switch (robotTaskType) |
| | | { |
| | | case RobotTaskTypeEnum.GroupPallet: |
| | | // ç»çä»»å¡ä¸ä½¿ç¨æºå°åï¼ç´æ¥è¿å false |
| | | _logger.LogDebug("HandleInboundTaskAsyncï¼ç»çä»»å¡ä¸ä½¿ç¨æºå°å"); |
| | | QuartzLogger.Debug($"HandleInboundTaskAsyncï¼ç»çä»»å¡ä¸ä½¿ç¨æºå°å", state.RobotCrane?.DeviceName ?? "Unknown"); |
| | | return false; |
| | | warehouseId = 1; |
| | | roadway = "GWSC1"; |
| | | break; |
| | | |
| | | case RobotTaskTypeEnum.ChangePallet: |
| | | // æ¢ç/æçåºæ¯ï¼æçéè¦å
¥åº |
| | | taskType = TaskTypeEnum.InEmpty.GetHashCode(); // 空æçå
¥åº |
| | | PalletCode = currentTask.RobotSourceAddressPalletCode; // ä½¿ç¨æºå°åçæçç |
| | | if (isRoadway == "HWSC1") |
| | | { |
| | | warehouseId = 2; |
| | | roadway = "HWSC1"; |
| | | } |
| | | else if (isRoadway == "GWSC1") |
| | | { |
| | | warehouseId = 1; |
| | | roadway = "GWSC1"; |
| | | } |
| | | |
| | | break; |
| | | case RobotTaskTypeEnum.SplitPallet: |
| | | // æ¢ç/æçåºæ¯ï¼æçéè¦å
¥åº |
| | | taskType = TaskTypeEnum.InEmpty.GetHashCode(); // 空æçå
¥åº |
| | | PalletCode = currentTask.RobotSourceAddressPalletCode; // ä½¿ç¨æºå°åçæçç |
| | | |
| | | warehouseId = 3; |
| | | roadway = "CWSC1"; |
| | | break; |
| | | } |
| | | } |
| | |
| | | switch (robotTaskType) |
| | | { |
| | | case RobotTaskTypeEnum.ChangePallet: |
| | | // æ¢ç/ç»çåºæ¯ï¼è´§ç©éè¦å
¥åº |
| | | taskType = TaskTypeEnum.Inbound.GetHashCode(); // æåå
¥åº |
| | | PalletCode = currentTask.RobotTargetAddressPalletCode; // 使ç¨ç®æ å°åçæçç |
| | | |
| | | if (isRoadway == "HWSC1") |
| | | { |
| | | warehouseId = 2; |
| | | roadway = "HWSC1"; |
| | | } |
| | | else if (isRoadway == "GWSC1") |
| | | { |
| | | warehouseId = 1; |
| | | roadway = "GWSC1"; |
| | | } |
| | | |
| | | break; |
| | | case RobotTaskTypeEnum.GroupPallet: |
| | | // æ¢ç/ç»çåºæ¯ï¼è´§ç©éè¦å
¥åº |
| | | taskType = TaskTypeEnum.Inbound.GetHashCode(); // æåå
¥åº |
| | | PalletCode = currentTask.RobotTargetAddressPalletCode; // 使ç¨ç®æ å°åçæçç |
| | | |
| | | warehouseId = 1; |
| | | roadway = "GWSC1"; |
| | | break; |
| | | |
| | | case RobotTaskTypeEnum.SplitPallet: |
| | | // æçä»»å¡ä¸ä½¿ç¨ç®æ å°å |
| | | _logger.LogDebug("HandleInboundTaskAsyncï¼æçä»»å¡ä¸ä½¿ç¨ç®æ å°å"); |
| | | QuartzLogger.Debug($"HandleInboundTaskAsyncï¼æçä»»å¡ä¸ä½¿ç¨ç®æ å°å", state.RobotCrane?.DeviceName ?? "Unknown"); |
| | | return true; |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | |
| | | { |
| | | PalletCode = PalletCode, // æçæ¡ç |
| | | SourceAddress = SourceAddress ?? string.Empty, // æºå°å |
| | | TargetAddress = TargetAddress ?? string.Empty, // ç®æ å°å |
| | | Roadway = roadway, // å··é |
| | | TargetAddress = roadway ?? string.Empty, // ç®æ å°å |
| | | Roadway = roadway ?? string.Empty, // å··é |
| | | WarehouseId = warehouseId, // ä»åº ID |
| | | PalletType = 1, // æçç±»åï¼é»è®¤ä¸º1ï¼ |
| | | TaskType = taskType // ä»»å¡ç±»åï¼å
¥åº/空æçå
¥åºï¼ |
| | |
| | | using System.Net.Sockets; |
| | | using WIDESEAWCS_Common.HttpEnum; |
| | | using WIDESEAWCS_Common.TaskEnum; |
| | | using WIDESEAWCS_Core.Helper; |
| | | using WIDESEAWCS_ITaskInfoService; |
| | | using WIDESEAWCS_Model.Models; |
| | | using WIDESEAWCS_Tasks.Workflow.Abstractions; |
| | |
| | | if (state.ChangePalletPhase == 5) |
| | | { |
| | | // FlowB æç»é¶æ®µï¼åçµè¯åå®ï¼æºç©ºæçååº HCSC1 |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true, isRoadway: "HCSC1")) |
| | | { |
| | | return false; |
| | | } |
| | |
| | | { |
| | | // FlowA ä¸é´é¶æ®µï¼æ£å¸¸çµè¯åå®ï¼æºç©ºæçååº GWSC1 |
| | | // ä¸å é¤ä»»å¡ï¼ä¸éç½®ç¶æï¼ç»§ç» Phase 3-4 åçµè¯æµç¨ |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true, isRoadway: "GWSC1")) |
| | | { |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | List<string> str = new List<string>() { "11001", "11010" }; |
| | | // Phase == 0: éæ¹æ¬¡æ¨¡å¼ï¼ç®æ æ»æ°==48ï¼ |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true, isRoadway: str.Contains(currentTask.RobotSourceAddressLineCode) ? "GWSC1" : "HCSC1")) |
| | | { |
| | | return false; |
| | | } |
| | |
| | | if (state.ChangePalletPhase == 5) |
| | | { |
| | | // FlowA æç»é¶æ®µï¼åçµè¯æ¾å®ï¼ç®æ æç满48å
¥åº HCSC1 |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false, isRoadway: "HCSC1")) |
| | | { |
| | | return false; |
| | | } |
| | |
| | | { |
| | | // FlowB ä¸é´é¶æ®µï¼æ£å¸¸çµè¯æ¾å®ï¼æè´§æçç»çå
¥åº GWSC1 |
| | | // ä¸å é¤ä»»å¡ï¼ä¸éç½®ç¶æï¼ç»§ç» Phase 3-4 åçµè¯æµç¨ |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false, isRoadway: "GWSC1")) |
| | | { |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | List<string> str = new List<string>() { "11001", "11010" }; |
| | | // Phase == 0: éæ¹æ¬¡æ¨¡å¼ï¼ç®æ æ»æ°==48ï¼ |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) |
| | | if (!await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false, str.Contains(currentTask.RobotTargetAddressLineCode) ? "GWSC1" : "HCSC1")) |
| | | { |
| | | return false; |
| | | } |
| | |
| | | // æ¡ç éå¤ï¼è®°å½é误æ¥å¿å¹¶åæ¢åç»æä½(åç»æ¾è´§æ¶ä¼ç¨å°è¿äºæ¡ç ä¿¡æ¯ï¼ä¾åç»æ¾è´§æ¶ä½¿ç¨ï¼è°è¯åå¯è½ä¼åæ¶æ¤é»è¾) |
| | | |
| | | // åéåè´§æä»¤ æ è®°æ«ç NGï¼æ¾è´§æ¶ä¸ä½¿ç¨è¿äºæ¡ç ï¼å¹¶æ¾å
¥NGå£ |
| | | await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true); |
| | | //await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true); |
| | | return; |
| | | } |
| | | else |
| | |
| | | }; |
| | | } |
| | | |
| | | |
| | | // è®°å½æ¥å¿ï¼è¯»åæçæ¡ç æå |
| | | _logger.LogInformation("HandlePutFinishedStateAsyncï¼è¯»åæçæ¡ç æå: {Barcode1}+{Barcode2}ï¼ä»»å¡å·: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum); |
| | | QuartzLogger.Info($"读åæçæ¡ç æå: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName); |
| | |
| | | else |
| | | { |
| | | // æ¡ç 读å失败ï¼è®°å½é误æ¥å¿ |
| | | _logger.LogError("HandlePutFinishedStateAsyncï¼è¯»åæçæ¡ç 失败ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); |
| | | QuartzLogger.Error($"读åæçæ¡ç 失败", stateForUpdate.RobotCrane.DeviceName); |
| | | _logger.LogError("HandlePutFinishedStateAsyncï¼è¯»åæçæ¡ç 失败ï¼ä»»å¡å·: {TaskNum}ï¼ä¸å·ä½: {trayBarcode1}ï¼äºå·ä½: {trayBarcode2}", task.RobotTaskNum,trayBarcode1,trayBarcode2); |
| | | QuartzLogger.Error($"读åæçæ¡ç 失败,ä¸å·ä½ï¼{trayBarcode1},äºå·ä½ï¼{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName); |
| | | |
| | | |
| | | // åéåè´§æä»¤ æ è®°æ«ç NGï¼æ¾è´§æ¶ä¸ä½¿ç¨è¿äºæ¡ç ï¼å¹¶æ¾å
¥NGå£ |
| | | await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true); |
| | | //await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true); |
| | | return; |
| | | } |
| | | } |
| | |
| | | <!-- é¡¶é¨ï¼æ¬æåºå
¥åºè¶å¿ (å
¨å®½) --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">æ¬æåºå
¥åºè¶å¿</div> |
| | | <div class="card-title">æ¯æåºå
¥åºè¶å¿</div> |
| | | <div id="chart-monthly-trend" class="chart-content"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第äºè¡ï¼ä»æ¥/æ¬å¨åºå
¥åºå¯¹æ¯ --> |
| | | <div class="chart-row"> |
| | | <!-- 第äºè¡ï¼æ¯æ¥åºå
¥åºè¶å¿ (å
¨å®½) --> |
| | | <div class="chart-row full-width"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">仿¥åºå
¥åºå¯¹æ¯</div> |
| | | <div id="chart-today" class="chart-content"></div> |
| | | </div> |
| | | <div class="chart-card"> |
| | | <div class="card-title">æ¬å¨åºå
¥åºå¯¹æ¯</div> |
| | | <div id="chart-week" class="chart-content"></div> |
| | | <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-month" class="chart-content"></div> |
| | | </div> |
| | | <div class="chart-card"> |
| | | <div class="card-title">å½ååºåæ»é</div> |
| | | <div class="stock-total"> |
| | | <div class="total-number">{{ overviewData.TotalStock || 0 }}</div> |
| | | <div class="total-label">æç</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 第åè¡ï¼åºé¾åå¸/ä»åºåå¸ --> |
| | | <div class="chart-row"> |
| | | <div class="chart-card"> |
| | | <div class="card-title">åºååºé¾åå¸</div> |
| | | <div id="chart-stock-age" class="chart-content"></div> |
| | | </div> |
| | | <div class="chart-card"> |
| | | <div class="card-title">åä»åºåºååå¸</div> |
| | | <div id="chart-warehouse" class="chart-content"></div> |
| | |
| | | data() { |
| | | return { |
| | | charts: {}, |
| | | overviewData: { |
| | | TodayInbound: 0, |
| | | TodayOutbound: 0, |
| | | MonthInbound: 0, |
| | | MonthOutbound: 0, |
| | | TotalStock: 0 |
| | | }, |
| | | weeklyData: [], |
| | | dailyData: [], |
| | | monthlyData: [], |
| | | stockAgeData: [], |
| | | warehouseData: [] |
| | | }; |
| | | }, |
| | |
| | | |
| | | initCharts() { |
| | | this.charts.monthlyTrend = echarts.init(document.getElementById("chart-monthly-trend")); |
| | | this.charts.today = echarts.init(document.getElementById("chart-today")); |
| | | this.charts.week = echarts.init(document.getElementById("chart-week")); |
| | | this.charts.month = echarts.init(document.getElementById("chart-month")); |
| | | this.charts.stockAge = echarts.init(document.getElementById("chart-stock-age")); |
| | | this.charts.daily = echarts.init(document.getElementById("chart-daily")); |
| | | this.charts.warehouse = echarts.init(document.getElementById("chart-warehouse")); |
| | | }, |
| | | |
| | | async loadData() { |
| | | await this.loadOverview(); |
| | | await this.loadWeeklyStats(); |
| | | await this.loadMonthlyStats(); |
| | | await this.loadStockAgeDistribution(); |
| | | await this.loadDailyStats(); |
| | | await this.loadStockByWarehouse(); |
| | | }, |
| | | |
| | | async loadOverview() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/Overview"); |
| | | console.log("æ»è§æ°æ®", res.Data); |
| | | if (res.Status && res.Data) { |
| | | this.overviewData = res.Data; |
| | | this.updateTodayChart(); |
| | | this.updateWeekChart(); |
| | | this.updateMonthChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ»è§æ°æ®å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadWeeklyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/WeeklyStats", { weeks: 12 }); |
| | | if (res.Status && res.Data) { |
| | | this.weeklyData = res.Data; |
| | | this.updateWeekChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½æ¯å¨ç»è®¡å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadMonthlyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/MonthlyStats", { months: 12 }); |
| | | if (res.Status && res.Data) { |
| | | this.monthlyData = res.Data; |
| | | if (res.status && res.data) { |
| | | console.log("æ¯æç»è®¡æ°æ®:", res.data); |
| | | this.monthlyData = res.data; |
| | | this.updateMonthlyTrendChart(); |
| | | } |
| | | } catch (e) { |
| | |
| | | } |
| | | }, |
| | | |
| | | async loadStockAgeDistribution() { |
| | | async loadDailyStats() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockAgeDistribution"); |
| | | if (res.Status && res.Data) { |
| | | this.stockAgeData = res.Data; |
| | | this.updateStockAgeChart(); |
| | | 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); |
| | | console.error("å è½½æ¯æ¥ç»è®¡å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | async loadStockByWarehouse() { |
| | | try { |
| | | const res = await this.http.get("/api/Dashboard/StockByWarehouse"); |
| | | if (res.Status && res.Data) { |
| | | this.warehouseData = res.Data; |
| | | if (res.status && res.data) { |
| | | console.log("ä»åºå叿°æ®:", res.data); |
| | | this.warehouseData = res.data.data || res.data; |
| | | this.updateWarehouseChart(); |
| | | } |
| | | } catch (e) { |
| | | console.error("å è½½ä»åºåå¸å¤±è´¥", e); |
| | | } |
| | | }, |
| | | |
| | | updateTodayChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["仿¥"], |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: [this.overviewData.TodayInbound], itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: [this.overviewData.TodayOutbound], itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.today.setOption(option, true); |
| | | }, |
| | | |
| | | updateWeekChart() { |
| | | const thisWeek = this.getThisWeekData(this.weeklyData); |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["æ¬å¨"], |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: [thisWeek.Inbound], itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: [thisWeek.Outbound], itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.week.setOption(option, true); |
| | | }, |
| | | |
| | | getThisWeekData(weeklyData) { |
| | | if (!weeklyData || weeklyData.length === 0) return { Inbound: 0, Outbound: 0 }; |
| | | const thisWeekKey = this.getCurrentWeekKey(); |
| | | const thisWeek = weeklyData.find(w => w.Week === thisWeekKey); |
| | | return thisWeek || { Inbound: 0, Outbound: 0 }; |
| | | }, |
| | | |
| | | getCurrentWeekKey() { |
| | | const now = new Date(); |
| | | const diff = (7 + (now.getDay() - 1)) % 7; |
| | | const monday = new Date(now); |
| | | monday.setDate(now.getDate() - diff); |
| | | const year = monday.getFullYear(); |
| | | const jan1 = new Date(year, 0, 1); |
| | | const weekNum = Math.ceil(((monday - jan1) / 86400000 + jan1.getDay() + 1) / 7); |
| | | return `${year}-W${String(weekNum).padStart(2, "0")}`; |
| | | }, |
| | | |
| | | updateMonthChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: ["æ¬æ"], |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | series: [ |
| | | { name: "å
¥åº", type: "bar", data: [this.overviewData.MonthInbound], itemStyle: { color: "#5470c6" } }, |
| | | { name: "åºåº", type: "bar", data: [this.overviewData.MonthOutbound], itemStyle: { color: "#91cc75" } } |
| | | ] |
| | | }; |
| | | this.charts.month.setOption(option, true); |
| | | }, |
| | | |
| | | updateMonthlyTrendChart() { |
| | |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.monthlyData.map(m => m.Month), |
| | | data: this.monthlyData.map(m => m.month), |
| | | axisLabel: { color: "#fff", rotate: 45 } |
| | | }, |
| | | yAxis: [ |
| | |
| | | } |
| | | ], |
| | | 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" } } |
| | | { 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" } } |
| | | ] |
| | | }; |
| | | this.charts.monthlyTrend.setOption(option, true); |
| | | }, |
| | | |
| | | updateStockAgeChart() { |
| | | updateDailyChart() { |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | legend: { data: ["å
¥åº", "åºåº"], textStyle: { color: "#fff" } }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.stockAgeData.map(s => s.Range), |
| | | axisLabel: { color: "#fff" } |
| | | data: this.dailyData.map(d => d.date), |
| | | axisLabel: { |
| | | color: "#fff", |
| | | interval: 0, |
| | | rotate: 45, |
| | | fontSize: 12, |
| | | margin: 10 |
| | | }, |
| | | axisTick: { |
| | | alignWithLabel: true |
| | | } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | }, |
| | | grid: { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '15%', |
| | | top: '10%', |
| | | containLabel: true |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "bar", |
| | | data: this.stockAgeData.map(s => s.Count), |
| | | itemStyle: { color: "#5470c6" } |
| | | } |
| | | { 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.stockAge.setOption(option, true); |
| | | 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); |
| | | |
| | | const option = { |
| | | tooltip: { trigger: "axis" }, |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | }, |
| | | formatter: function(params) { |
| | | let tip = params[0].name + '<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}`; |
| | | } |
| | | }); |
| | | return tip; |
| | | } |
| | | }, |
| | | legend: { |
| | | data: ['å·²ç¨å®¹é', 'å©ä½å®¹é'], |
| | | textStyle: { color: '#fff' } |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: this.warehouseData.map(w => w.Warehouse), |
| | | axisLabel: { color: "#fff", rotate: 30 } |
| | | type: 'category', |
| | | data: warehouseNames, |
| | | axisLabel: { color: '#fff', rotate: 30 } |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | axisLabel: { color: "#fff" } |
| | | type: 'value', |
| | | axisLabel: { color: '#fff' } |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "bar", |
| | | data: this.warehouseData.map(w => w.Count), |
| | | itemStyle: { color: "#5470c6" } |
| | | 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' } |
| | | }, |
| | | { |
| | | 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' |
| | | } |
| | | } |
| | | } |
| | | })), |
| | | itemStyle: { color: '#fac858' } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | window.homeComponent = this; |
| | | |
| | | this.charts.warehouse.setOption(option, true); |
| | | } |
| | | } |
| | |
| | | <style scoped> |
| | | .dashboard-container { |
| | | padding: 20px; |
| | | background-color: #0e1a2b; |
| | | color: #e0e0e0; |
| | | min-height: calc(100vh - 60px); |
| | | background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); |
| | | background-attachment: fixed; |
| | | } |
| | | |
| | | .chart-row { |
| | |
| | | |
| | | .chart-card { |
| | | flex: 1; |
| | | background: rgba(255, 255, 255, 0.05); |
| | | border: 1px solid rgba(25, 186, 139, 0.17); |
| | | border-radius: 4px; |
| | | background: rgba(10, 16, 35, 0.6); |
| | | backdrop-filter: blur(10px); |
| | | border: 1px solid rgba(64, 224, 208, 0.3); |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | position: relative; |
| | | box-shadow: |
| | | 0 0 15px rgba(0, 255, 255, 0.1), |
| | | inset 0 0 10px rgba(64, 224, 208, 0.1); |
| | | transition: all 0.3s ease; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .chart-card:hover { |
| | | transform: translateY(-5px); |
| | | box-shadow: |
| | | 0 0 25px rgba(0, 255, 255, 0.3), |
| | | inset 0 0 15px rgba(64, 224, 208, 0.2); |
| | | border: 1px solid rgba(64, 224, 208, 0.6); |
| | | } |
| | | |
| | | .chart-card::before { |
| | |
| | | left: 0; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-top: 2px solid #02a6b5; |
| | | border-left: 2px solid #02a6b5; |
| | | border-top: 2px solid #00ffff; |
| | | border-left: 2px solid #00ffff; |
| | | box-shadow: -2px -2px 10px #00ffff, 0 0 10px rgba(0, 255, 255, 0.7); |
| | | } |
| | | |
| | | .chart-card::after { |
| | |
| | | right: 0; |
| | | width: 10px; |
| | | height: 10px; |
| | | border-top: 2px solid #02a6b5; |
| | | border-right: 2px solid #02a6b5; |
| | | border-top: 2px solid #00ffff; |
| | | border-right: 2px solid #00ffff; |
| | | 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: #fff; |
| | | color: #00ffff; |
| | | font-size: 16px; |
| | | text-align: center; |
| | | margin-bottom: 10px; |
| | | text-shadow: 0 0 10px rgba(0, 255, 255, 0.7); |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .chart-content { |
| | | height: 280px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .stock-total { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 280px; |
| | | } |
| | | |
| | | .total-number { |
| | | font-size: 64px; |
| | | font-weight: bold; |
| | | color: #67caca; |
| | | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif; |
| | | } |
| | | |
| | | .total-label { |
| | | font-size: 18px; |
| | | color: #fcf0d8; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | /* å
¨å®½å¾è¡¨ */ |
| | |
| | | .full-width .chart-content { |
| | | height: 350px; |
| | | } |
| | | </style> |
| | | |
| | | /* æ·»å ç½æ ¼çº¿ææ */ |
| | | .dashboard-container::before { |
| | | content: ""; |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-image: |
| | | linear-gradient(rgba(64, 224, 208, 0.05) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(64, 224, 208, 0.05) 1px, transparent 1px); |
| | | background-size: 30px 30px; |
| | | pointer-events: none; |
| | | z-index: -1; |
| | | } |
| | | </style> |
| | |
| | | <div class="vol-aside" :style="{ width: menuWidth + 'px' }"> |
| | | <div class="header" :style="{ width: menuWidth - 1 + 'px' }"> |
| | | <img v-show="!isCollapse" v-bind:src="logo" /> |
| | | <i |
| | | @click="toggleLeft" |
| | | class="collapse-menu" |
| | | :class="isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'" |
| | | /> |
| | | <i @click="toggleLeft" class="collapse-menu" :class="isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'" /> |
| | | </div> |
| | | <div class="vol-menu"> |
| | | <el-scrollbar style="height: 100%"> |
| | | <VolMenu |
| | | :currentMenuId="currentMenuId" |
| | | :on-select="onSelect" |
| | | :enable="true" |
| | | :open-select="false" |
| | | :isCollapse="isCollapse" |
| | | :list="menuOptions" |
| | | ></VolMenu> |
| | | <VolMenu :currentMenuId="currentMenuId" :on-select="onSelect" :enable="true" :open-select="false" |
| | | :isCollapse="isCollapse" :list="menuOptions"></VolMenu> |
| | | </el-scrollbar> |
| | | </div> |
| | | </div> |
| | |
| | | <div class="project-name">WMS</div> |
| | | <div class="header-text"> |
| | | <div class="h-link"> |
| | | <a |
| | | href="javascript:void(0)" |
| | | @click="to(item)" |
| | | v-for="(item, index) in links.filter((c) => { |
| | | return !c.icon; |
| | | })" |
| | | :key="index" |
| | | > |
| | | <a href="javascript:void(0)" @click="to(item)" v-for="(item, index) in links.filter((c) => { |
| | | return !c.icon; |
| | | })" :key="index"> |
| | | <span v-if="!item.icon"> {{ item.text }}</span> |
| | | <i v-else :class="item.icon"></i> |
| | | </a> |
| | |
| | | </div> |
| | | <div class="header-info"> |
| | | <div class="h-link"> |
| | | <a |
| | | href="javascript:void(0)" |
| | | @click="to(item)" |
| | | v-for="(item, index) in links.filter((c) => { |
| | | return c.icon; |
| | | })" |
| | | :key="index" |
| | | > |
| | | <a href="javascript:void(0)" @click="to(item)" v-for="(item, index) in links.filter((c) => { |
| | | return c.icon; |
| | | })" :key="index"> |
| | | <span v-if="!item.icon"> {{ item.text }}</span> |
| | | <i v-else :class="item.icon"></i> |
| | | </a> |
| | |
| | | <!--æ¶æ¯ç®¡ç--> |
| | | |
| | | <div class="h-link" @click="messageModel = true"> |
| | | <a |
| | | ><i class="el-icon-message-solid" |
| | | ><el-badge |
| | | :value="messageList.length" |
| | | :type="messageList.length > 0 ? 'danger' : 'success'" |
| | | class="item" |
| | | style="width: 10px" |
| | | ></el-badge></i |
| | | ></a> |
| | | <a><i class="el-icon-message-solid"><el-badge :value="messageList.length" |
| | | :type="messageList.length > 0 ? 'danger' : 'success'" class="item" |
| | | style="width: 10px"></el-badge></i></a> |
| | | </div> |
| | | <div> |
| | | <img class="user-header" :src="userImg" :onerror="errorImg" /> |
| | |
| | | <span id="index-date"></span> |
| | | </div> |
| | | <div class="settings"> |
| | | <i |
| | | style="font-size: 20px" |
| | | class="el-icon-s-tools" |
| | | @click="drawer_model = true" |
| | | /> |
| | | <i style="font-size: 20px" class="el-icon-s-tools" @click="drawer_model = true" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="vol-path"> |
| | | <el-tabs |
| | | @tab-click="selectNav" |
| | | @tab-remove="removeNav" |
| | | @contextmenu.prevent="bindRightClickMenu(false)" |
| | | type="border-card" |
| | | class="header-navigation" |
| | | v-model="selectId" |
| | | :strtch="false" |
| | | > |
| | | <el-tab-pane |
| | | v-for="(item, navIndex) in navigation" |
| | | type="card" |
| | | :name="navIndex + ''" |
| | | :closable="navIndex > 0" |
| | | :key="navIndex" |
| | | :label="item.name" |
| | | > |
| | | <el-tabs @tab-click="selectNav" @tab-remove="removeNav" @contextmenu.prevent="bindRightClickMenu(false)" |
| | | type="border-card" class="header-navigation" v-model="selectId" :strtch="false"> |
| | | <el-tab-pane v-for="(item, navIndex) in navigation" type="card" :name="navIndex + ''" :closable="navIndex > 0" |
| | | :key="navIndex" :label="item.name"> |
| | | <span style="display: none">{{ navIndex }}</span> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | <!-- å³é®èå --> |
| | | <div v-show="contextMenuVisible"> |
| | | <ul |
| | | :style="{ left: menuLeft + 'px', top: menuTop + 'px' }" |
| | | class="contextMenu" |
| | | > |
| | | <ul :style="{ left: menuLeft + 'px', top: menuTop + 'px' }" class="contextMenu"> |
| | | <li v-show="visibleItem.all"> |
| | | <el-button link @click="closeTabs()"> |
| | | <i class="el-icon-close"></i> |
| | | {{ |
| | | navigation.length == 2 ? "å
³éèå" : "å
³éææ" |
| | | }}</el-button |
| | | > |
| | | }}</el-button> |
| | | </li> |
| | | <li v-show="visibleItem.left"> |
| | | <el-button link @click="closeTabs('left')" |
| | | ><i class="el-icon-back"></i>å
³é左边</el-button |
| | | > |
| | | <el-button link @click="closeTabs('left')"><i class="el-icon-back"></i>å
³é左边</el-button> |
| | | </li> |
| | | <li v-show="visibleItem.right"> |
| | | <el-button link @click="closeTabs('right')"> |
| | | <i class="el-icon-right"></i>å
³éå³è¾¹</el-button |
| | | > |
| | | <i class="el-icon-right"></i>å
³éå³è¾¹</el-button> |
| | | </li> |
| | | <li v-show="visibleItem.other"> |
| | | <el-button link @click="closeTabs('other')" |
| | | ><i class="el-icon-right"></i>å
³éå
¶ä» |
| | | <el-button link @click="closeTabs('other')"><i class="el-icon-right"></i>å
³éå
¶ä» |
| | | </el-button> |
| | | </li> |
| | | </ul> |
| | |
| | | <loading v-show="$store.getters.isLoading()"></loading> |
| | | <router-view v-slot="{ Component }"> |
| | | <keep-alive> |
| | | <component |
| | | :is="Component" |
| | | :key="$route.name" |
| | | v-if=" |
| | | !$route.meta || |
| | | ($route.meta && !$route.meta.hasOwnProperty('keepAlive')) |
| | | " |
| | | /> |
| | | <component :is="Component" :key="$route.name" v-if=" |
| | | !$route.meta || |
| | | ($route.meta && !$route.meta.hasOwnProperty('keepAlive')) |
| | | " /> |
| | | </keep-alive> |
| | | <component |
| | | :is="Component" |
| | | :key="$route.name" |
| | | v-if="$route.meta && $route.meta.hasOwnProperty('keepAlive')" |
| | | /> |
| | | <component :is="Component" :key="$route.name" |
| | | v-if="$route.meta && $route.meta.hasOwnProperty('keepAlive')" /> |
| | | </router-view> |
| | | </el-scrollbar> |
| | | </div> |
| | | </div> |
| | | <el-drawer |
| | | title="鿩䏻é¢" |
| | | v-model="drawer_model" |
| | | direction="rtl" |
| | | destroy-on-close |
| | | > |
| | | <el-drawer title="鿩䏻é¢" v-model="drawer_model" direction="rtl" destroy-on-close> |
| | | <div class="theme-selector"> |
| | | <div |
| | | @click="changeTheme(item.name)" |
| | | class="item" |
| | | v-for="(item, index) in theme_color" |
| | | :key="index" |
| | | :style="{ background: item.color }" |
| | | > |
| | | <div |
| | | v-show="item.leftColor" |
| | | :style="{ background: item.leftColor }" |
| | | style="height: 100%; width: 20px" |
| | | class="t-left" |
| | | ></div> |
| | | <div @click="changeTheme(item.name)" class="item" v-for="(item, index) in theme_color" :key="index" |
| | | :style="{ background: item.color }"> |
| | | <div v-show="item.leftColor" :style="{ background: item.leftColor }" style="height: 100%; width: 20px" |
| | | class="t-left"></div> |
| | | <div class="t-right"></div> |
| | | </div> |
| | | </div> |
| | | </el-drawer> |
| | | |
| | | <el-drawer |
| | | title="æ¶æ¯å表" |
| | | v-model="messageModel" |
| | | direction="rtl" |
| | | destroy-on-close |
| | | size="40%" |
| | | > |
| | | <el-drawer title="æ¶æ¯å表" v-model="messageModel" direction="rtl" destroy-on-close size="40%"> |
| | | <Message :list="messageList"></Message> |
| | | </el-drawer> |
| | | </div> |
| | |
| | | setTimeout(createSocket, 10000); |
| | | }; |
| | | |
| | | client.onerror = function () {}; |
| | | client.onerror = function () { }; |
| | | }; |
| | | |
| | | const changeTheme = (name) => { |
| | |
| | | } |
| | | |
| | | createSocket("ws://127.0.0.1:9296/" + _userInfo.userName); |
| | | |
| | | // createSocket("ws://127.0.0.1:9296"); |
| | | Object.assign(_config.$tabs, { open: open, close: close }); |
| | | |
| | | http.get("api/Sys_Menu/getTreeMenu", {}, true).then((data) => { |
| | |
| | | font-size: 14px; |
| | | color: #333; |
| | | box-shadow: 2px 2px 3px 0 rgb(182 182 182 / 20%); |
| | | |
| | | i, |
| | | button { |
| | | font-size: 14px !important; |
| | |
| | | letter-spacing: 1px; |
| | | } |
| | | |
| | | .el-tabs.el-tabs--top.el-tabs--border-card.header-navigation |
| | | > .el-tabs__header |
| | | .el-tabs__item:last-child, |
| | | .el-tabs--top.el-tabs--border-card.header-navigation |
| | | > .el-tabs__header |
| | | .el-tabs__item:nth-child(2) { |
| | | .el-tabs.el-tabs--top.el-tabs--border-card.header-navigation>.el-tabs__header .el-tabs__item:last-child, |
| | | .el-tabs--top.el-tabs--border-card.header-navigation>.el-tabs__header .el-tabs__item:nth-child(2) { |
| | | padding: 0; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function hasCargo(location) { |
| | | // 妿LocationStatus=100ï¼å³ä½¿æ²¡æåºåä¿¡æ¯ä¹æ¾ç¤ºè´§ä½ |
| | | if (location.locationStatus === 100) { |
| | | return true |
| | | } |
| | | return Number(location.stockQuantity || 0) > 0 || ((location.details && location.details.length > 0) || false) |
| | | } |
| | | |
| | |
| | | { |
| | | public static class WebSocketSetup |
| | | { |
| | | public static void AddWebSocketSetup(this IServiceCollection services) |
| | | { |
| | | if (services == null) throw new ArgumentNullException(nameof(services)); |
| | | //public static void AddWebSocketSetup(this IServiceCollection services) |
| | | //{ |
| | | // if (services == null) throw new ArgumentNullException(nameof(services)); |
| | | |
| | | int port = AppSettings.Get("WebSocketPort").ObjToInt(); |
| | | if (port == 0) |
| | | { |
| | | port = 9296; |
| | | } |
| | | |
| | | services.AddSingleton(x => |
| | | { |
| | | WebSocketServer socketServer = new WebSocketServer(); |
| | | socketServer.ServerStart(port); |
| | | return socketServer; |
| | | }); |
| | | // int port = AppSettings.Get("WebSocketPort").ObjToInt(); |
| | | // if (port == 0) |
| | | // { |
| | | // port = 9296; |
| | | // } |
| | | // services.AddSingleton(x => |
| | | // { |
| | | // WebSocketServer socketServer = new WebSocketServer(); |
| | | // socketServer.ServerStart(port); |
| | | // return socketServer; |
| | | // }); |
| | | //} |
| | | public static void AddWebSocketSetup(this IServiceCollection services)
|
| | | {
|
| | | if (services == null) throw new ArgumentNullException(nameof(services));
|
| | |
|
| | | int port = AppSettings.Get("WebSocketPort").ObjToInt();
|
| | | if (port == 0)
|
| | | {
|
| | | port = 9296;
|
| | | }
|
| | |
|
| | | // ç´æ¥å建并å¯å¨ WebSocket æå¡å¨
|
| | | WebSocketServer socketServer = new WebSocketServer();
|
| | | socketServer.ServerStart(port);
|
| | | services.AddSingleton(socketServer);
|
| | |
|
| | | } |
| | | } |
| | | } |
| | |
| | | using Mapster; |
| | | using System.Text.Json; |
| | | using WIDESEA_Common.Constants; |
| | | using WIDESEA_Common.LocationEnum; |
| | | using WIDESEA_Common.StockEnum; |
| | | using WIDESEA_Common.TaskEnum; |
| | | using WIDESEA_Common.WareHouseEnum; |
| | | using WIDESEA_Core; |
| | | using WIDESEA_Core.Enums; |
| | | using WIDESEA_Core.Helper; |
| | | using WIDESEA_DTO.Task; |
| | | using WIDESEA_IStockService; |
| | | using WIDESEA_Model.Models; |
| | | |
| | | namespace WIDESEA_TaskInfoService |
| | | { |
| | | public partial class TaskService |
| | | { |
| | | |
| | | public string AGV_OutTaskComplete = WIDESEA_Core.Helper.AppSettings.Configuration["AGV_OutTaskComplete"]; |
| | | public string WCS_ReceiveTask = WIDESEA_Core.Helper.AppSettings.Configuration["WCS_ReceiveTask"]; |
| | | |
| | | /// <summary> |
| | | /// æå·åºåºå
¥åºç³è¯· |
| | | /// </summary> |
| | | public async Task<AGVResponse> ApplyInOutAsync(ApplyInOutDto applyInOutDto) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | |
| | | try |
| | | { |
| | | var validationMessage = ValidateApplyInOutRequest(applyInOutDto); |
| | | if (validationMessage != null) |
| | | return response.Error(validationMessage); |
| | | |
| | | var existingTask = await BaseDal.QueryFirstAsync(x => x.PalletCode == applyInOutDto.TrayNumber || x.OrderNo == applyInOutDto.TaskId); |
| | | if (existingTask != null) |
| | | return response.Error($"WMSå·²æå½åä»»å¡ï¼ä¸å¯éå¤ä¸åï¼ä»»å¡å·ï¼{applyInOutDto.TaskId}"); |
| | | |
| | | var task = BuildAgvTask(applyInOutDto); |
| | | |
| | | if (applyInOutDto.InOut == 1) |
| | | { |
| | | var inboundResult = await CreateAgvInboundTaskAsync(task, applyInOutDto); |
| | | if (inboundResult != null) |
| | | return inboundResult; |
| | | } |
| | | else |
| | | { |
| | | var outboundResult = await CreateAgvOutboundTaskAsync(task, applyInOutDto); |
| | | if (outboundResult != null) |
| | | return outboundResult; |
| | | } |
| | | |
| | | return response.OK(BuildAgvDataDto(task, applyInOutDto)); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"WMSä»»å¡å建æ¥å£é误: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æå¨åºåºå®æåé¦ç»AGV |
| | | /// </summary> |
| | | public async Task<WebResponseContent> OutTaskComplete(OutTaskCompleteDto outTaskCompleteDto) |
| | | { |
| | | WebResponseContent response = new WebResponseContent(); |
| | | |
| | | try |
| | | { |
| | | var validationMessage = ValidateOutTaskCompleteRequest(outTaskCompleteDto); |
| | | if (validationMessage != null) |
| | | return response.Error(validationMessage); |
| | | |
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == outTaskCompleteDto.TaskId); |
| | | if (task == null) |
| | | return response.Error("æªæ¾å°ä»»å¡ä¿¡æ¯"); |
| | | |
| | | outTaskCompleteDto.ReqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); |
| | | var httpResponse = _httpClientHelper.Post<AGVResponse>(AGV_OutTaskComplete, outTaskCompleteDto.ToJson()).Data; |
| | | if (httpResponse == null || httpResponse.Data == null) |
| | | return response.Error(httpResponse?.Msg ?? "AGVæ¥å£è°ç¨å¼å¸¸"); |
| | | |
| | | if (!httpResponse.Code) |
| | | return response.Error(string.IsNullOrWhiteSpace(httpResponse.Msg) ? "AGVæ¥å£è°ç¨å¤±è´¥" : httpResponse.Msg); |
| | | |
| | | var syncResult = await CompleteLocalOutboundAfterAgvAckAsync(task); |
| | | return syncResult.Status ? response.OK(httpResponse.Msg) : syncResult; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"WMSä»»å¡å®ææ¥å£é误ï¼{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è¾é线ç³è¯·è¿å
¥ |
| | | /// </summary> |
| | | public async Task<AGVResponse> ApplyEnterAsync(ApplyEnterDto applyEnterDto) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | |
| | | try |
| | | { |
| | | var validationMessage = ValidateApplyEnterRequest(applyEnterDto); |
| | | if (validationMessage != null) |
| | | return response.Error(validationMessage); |
| | | |
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == applyEnterDto.TaskId); |
| | | if (task == null) |
| | | return response.Error($"æªæ¾å°ä»»å¡ä¿¡æ¯ï¼ä»»å¡å·ï¼{applyEnterDto.TaskId}"); |
| | | |
| | | if (CanApplyEnter(task, applyEnterDto)) |
| | | return response.OK(); |
| | | |
| | | return response.Error($"è¾é线{applyEnterDto.DevId}å½åç¹å¿ï¼è¯·ç¨åéè¯"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return response.Error($"WMSè¾é线ç³è¯·æ¥å£é误ï¼{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åæ¾è´§å®æ |
| | | /// </summary> |
| | | public async Task<AGVResponse> TaskCompleteAsync(TaskCompleteDto taskCompleteDto) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | |
| | | try |
| | | { |
| | | var validationMessage = ValidateTaskCompleteRequest(taskCompleteDto); |
| | | if (validationMessage != null) |
| | | return response.Error(validationMessage); |
| | | |
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == taskCompleteDto.TaskId); |
| | | if (task == null) |
| | | return response.Error($"æªæ¾å°ä»»å¡ä¿¡æ¯ï¼ä»»å¡å·ï¼{taskCompleteDto.TaskId}"); |
| | | |
| | | return taskCompleteDto.InOut == 2 |
| | | ? await CompleteAgvOutboundTaskAsync(task) |
| | | : await CompleteAgvInboundTaskAsync(task); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"WMSåæ¾è´§å®ææ¥å£é误ï¼{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// ä»»å¡åæ¶ |
| | | /// </summary> |
| | | public async Task<AGVResponse> TaskCancelAsync(TaskCancelDto taskCancelDto) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | |
| | | try |
| | | { |
| | | var validationMessage = ValidateTaskCancelRequest(taskCancelDto); |
| | | if (validationMessage != null) |
| | | return response.Error(validationMessage); |
| | | |
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == taskCancelDto.TaskId); |
| | | if (task == null) |
| | | return response.OK(); |
| | | |
| | | if (task.TaskStatus == (int)TaskInStatusEnum.InNew) |
| | | return await CancelAgvInboundTask(task); |
| | | |
| | | if (task.TaskStatus == (int)TaskOutStatusEnum.OutNew) |
| | | return await CancelAgvOutboundTaskAsync(task); |
| | | |
| | | return response.Error("ä»»å¡å·²ç»å¨æ§è¡ä¸ï¼ä¸å¯åæ¶"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"WMSä»»å¡åæ¶æ¥å£é误ï¼{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | #region åæ°éªè¯ |
| | | private static string? ValidateApplyInOutRequest(ApplyInOutDto dto) |
| | | { |
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.TrayNumber)) return "æçå·ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.MaterialType)) return "ç©æç±»åä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.MaterialName)) return "ç©ææè¿°ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.ReqTime)) return "è¯·æ±æ¶é´ä¸è½ä¸ºç©º"; |
| | | if (dto.Floor != 1 && dto.Floor != 2) return $"æ¥¼å±æ®µé误ï¼å¿
须为1(æ¨¡åæ®µ)æ2(å·ç»æ®µ)ï¼å½åå¼ï¼{dto.Floor}"; |
| | | if (dto.YinYang != 1 && dto.YinYang != 2) return $"é´é³æé误ï¼å¿
须为1(é´æ)æ2(鳿)ï¼å½åå¼ï¼{dto.YinYang}"; |
| | | if (dto.InOut != 1 && dto.InOut != 2) return $"åºå
¥åºç±»åé误ï¼å¿
须为1(å
¥åº)æ2(åºåº)ï¼å½åå¼ï¼{dto.InOut}"; |
| | | if (dto.InOut == 1 && (dto.Width == null || dto.Width <= 0)) return "å
¥åºæ¶å®½åº¦ä¸è½ä¸ºç©ºä¸å¿
须大äº0"; |
| | | if (dto.InOut == 1 && string.IsNullOrWhiteSpace(dto.Group)) return "å
¥åºæ¶æ´æç»å«ä¸è½ä¸ºç©º"; |
| | | return null; |
| | | } |
| | | |
| | | private static string? ValidateOutTaskCompleteRequest(OutTaskCompleteDto dto) |
| | | { |
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.DevId)) return "åºåºå£ç¼å·ä¸è½ä¸ºç©º"; |
| | | return null; |
| | | } |
| | | |
| | | private static string? ValidateApplyEnterRequest(ApplyEnterDto dto) |
| | | { |
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.DevId)) return "设å¤ç¼å·ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º"; |
| | | if (dto.InOut != 1 && dto.InOut != 2) return $"åºå
¥åºç±»åé误ï¼å¿
须为1(å
¥åº)æ2(åºåº)ï¼å½åå¼ï¼{dto.InOut}"; |
| | | return null; |
| | | } |
| | | |
| | | private static string? ValidateTaskCompleteRequest(TaskCompleteDto dto) |
| | | { |
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.DevId)) return "设å¤ç¼å·ä¸è½ä¸ºç©º"; |
| | | if (dto.InOut != 1 && dto.InOut != 2) return $"åºå
¥åºç±»åé误ï¼å¿
须为1(å
¥åº)æ2(åºåº)ï¼å½åå¼ï¼{dto.InOut}"; |
| | | return null; |
| | | } |
| | | |
| | | private static string? ValidateTaskCancelRequest(TaskCancelDto dto) |
| | | { |
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º"; |
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º"; |
| | | return null; |
| | | } |
| | | #endregion åæ°éªè¯ |
| | | |
| | | #region å
·ä½å®ç° |
| | | // åºå
¥åºå
±ç¨åå»ºä»»å¡ |
| | | private Dt_Task BuildAgvTask(ApplyInOutDto dto) |
| | | { |
| | | var task = new Dt_Task |
| | | { |
| | | OrderNo = dto.TaskId, |
| | | PalletCode = dto.TrayNumber, |
| | | PalletType = dto.Floor, |
| | | Grade = 1, |
| | | Creater = "AGV", |
| | | CreateDate = DateTime.Now, |
| | | Remark = $"ç©æç±»åï¼{dto.MaterialType}ï¼ç©ææè¿°ï¼{dto.MaterialName}" |
| | | }; |
| | | |
| | | if (dto.YinYang == 1) |
| | | { |
| | | task.Roadway = WarehouseEnum.FJ1.ToString(); |
| | | task.WarehouseId = (int)WarehouseEnum.FJ1; |
| | | task.SourceAddress = dto.InOut == 1 ? "D10010" : "D10020"; |
| | | task.NextAddress = "D10080"; |
| | | task.TargetAddress = "é´æå·åº"; |
| | | } |
| | | else |
| | | { |
| | | task.Roadway = WarehouseEnum.ZJ1.ToString(); |
| | | task.WarehouseId = (int)WarehouseEnum.ZJ1; |
| | | task.SourceAddress = dto.InOut == 1 ? "D10100" : "D10090"; |
| | | task.NextAddress = "D10160"; |
| | | task.TargetAddress = "æ£æå·åº"; |
| | | } |
| | | |
| | | return task; |
| | | } |
| | | |
| | | // æå»ºè¿åAGVåºå
¥åºè¯·æ±ä½ |
| | | private AGVDataDto BuildAgvDataDto(Dt_Task task, ApplyInOutDto dto) |
| | | { |
| | | return new AGVDataDto |
| | | { |
| | | DevId = dto.InOut == 1 ? task.SourceAddress : task.TargetAddress, |
| | | TrayNumber = task.PalletCode, |
| | | Group = dto.Group, |
| | | Width = dto.Width ?? 0, |
| | | LabelNumber = dto.LabelNumber, |
| | | ProductNo = dto.ProductNo, |
| | | ProductName = dto.ProductName, |
| | | Quantity = dto.Quantity, |
| | | UomCode = dto.UomCode, |
| | | ProductType = dto.ProductType, |
| | | Equipment = dto.Equipment, |
| | | ProductionDate = dto.ProductionDate, |
| | | LowerLimitTime = dto.LowerLimitTime, |
| | | WarningTime = dto.WarningTime, |
| | | OverdueTime = dto.OverdueTime |
| | | }; |
| | | } |
| | | |
| | | // å
¥åºå建 |
| | | private async Task<AGVResponse?> CreateAgvInboundTaskAsync(Dt_Task task, ApplyInOutDto dto) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(dto.TrayNumber); |
| | | if (stockInfo != null) |
| | | return response.Error($"å½åæç{dto.TrayNumber}å·²ç»å
¥åºäº"); |
| | | |
| | | // å建åºåæç» |
| | | var details = new Dt_StockInfoDetail |
| | | { |
| | | MaterielCode = dto.ProductNo, |
| | | MaterielName = dto.ProductName, |
| | | StockQuantity = int.TryParse(dto.Quantity, out int quantity) ? quantity : 0, |
| | | Unit = dto.UomCode, |
| | | OrderNo = dto.ProductNo, |
| | | ProductionDate =dto.ProductionDate, |
| | | EffectiveDate = dto.LowerLimitTime, |
| | | SerialNumber = dto.TrayNumber, |
| | | Status = (int)StockStatusEmun.å
¥åºç¡®è®¤, |
| | | InboundOrderRowNo = 1, |
| | | Creater = StockConstants.AGV_USER, |
| | | CreateDate = DateTime.Now, |
| | | Remark = $"AGVå
¥åºä»»å¡å建ï¼ä»»å¡å·ï¼{dto.TaskId}" |
| | | }; |
| | | |
| | | // å建åºåä¸»è®°å½ |
| | | var stock = new Dt_StockInfo |
| | | { |
| | | PalletCode = dto.TrayNumber, |
| | | PalletType = dto.Floor, |
| | | WarehouseId = dto.YinYang == 1 ? (int)WarehouseEnum.FJ1 : (int)WarehouseEnum.ZJ1, |
| | | StockStatus = (int)StockStatusEmun.å
¥åºç¡®è®¤, |
| | | Creater = StockConstants.AGV_USER, |
| | | CreateDate = DateTime.Now, |
| | | Remark = $"AGVå
¥åºä»»å¡å建ï¼ä»»å¡å·ï¼{dto.TaskId}", |
| | | Details = new List<Dt_StockInfoDetail> { details } |
| | | }; |
| | | |
| | | task.TaskType = (int)TaskInboundTypeEnum.Inbound; |
| | | task.TaskStatus = (int)TaskInStatusEnum.InNew; |
| | | task.CurrentAddress = task.SourceAddress; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | try |
| | | { |
| | | // å
åå»ºä»»å¡ |
| | | var taskResult = await BaseDal.AddDataAsync(task) > 0; |
| | | if (!taskResult) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("å
¥åºä»»å¡å建失败"); |
| | | using Mapster;
|
| | | using System.Text.Json;
|
| | | using WIDESEA_Common.Constants;
|
| | | using WIDESEA_Common.LocationEnum;
|
| | | using WIDESEA_Common.StockEnum;
|
| | | using WIDESEA_Common.TaskEnum;
|
| | | using WIDESEA_Common.WareHouseEnum;
|
| | | using WIDESEA_Core;
|
| | | using WIDESEA_Core.Enums;
|
| | | using WIDESEA_Core.Helper;
|
| | | using WIDESEA_DTO.Task;
|
| | | using WIDESEA_IStockService;
|
| | | using WIDESEA_Model.Models;
|
| | |
|
| | | namespace WIDESEA_TaskInfoService
|
| | | {
|
| | | public partial class TaskService
|
| | | {
|
| | |
|
| | | public string AGV_OutTaskComplete = WIDESEA_Core.Helper.AppSettings.Configuration["AGV_OutTaskComplete"];
|
| | | public string WCS_ReceiveTask = WIDESEA_Core.Helper.AppSettings.Configuration["WCS_ReceiveTask"];
|
| | |
|
| | | /// <summary>
|
| | | /// æå·åºåºå
¥åºç³è¯·
|
| | | /// </summary>
|
| | | public async Task<AGVResponse> ApplyInOutAsync(ApplyInOutDto applyInOutDto)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | |
|
| | | try
|
| | | {
|
| | | var validationMessage = ValidateApplyInOutRequest(applyInOutDto);
|
| | | if (validationMessage != null)
|
| | | return response.Error(validationMessage);
|
| | |
|
| | | var existingTask = await BaseDal.QueryFirstAsync(x => x.PalletCode == applyInOutDto.TrayNumber || x.OrderNo == applyInOutDto.TaskId);
|
| | | if (existingTask != null)
|
| | | return response.Error($"WMSå·²æå½åä»»å¡ï¼ä¸å¯éå¤ä¸åï¼ä»»å¡å·ï¼{applyInOutDto.TaskId}");
|
| | |
|
| | | var task = BuildAgvTask(applyInOutDto);
|
| | |
|
| | | if (applyInOutDto.InOut == 1)
|
| | | {
|
| | | var inboundResult = await CreateAgvInboundTaskAsync(task, applyInOutDto);
|
| | | if (inboundResult != null)
|
| | | return inboundResult;
|
| | | }
|
| | | else
|
| | | {
|
| | | var outboundResult = await CreateAgvOutboundTaskAsync(task, applyInOutDto);
|
| | | if (outboundResult != null)
|
| | | return outboundResult;
|
| | | }
|
| | |
|
| | | return response.OK(BuildAgvDataDto(task, applyInOutDto));
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"WMSä»»å¡å建æ¥å£é误: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// æå¨åºåºå®æåé¦ç»AGV
|
| | | /// </summary>
|
| | | public async Task<WebResponseContent> OutTaskComplete(OutTaskCompleteDto outTaskCompleteDto)
|
| | | {
|
| | | WebResponseContent response = new WebResponseContent();
|
| | |
|
| | | try
|
| | | {
|
| | | var validationMessage = ValidateOutTaskCompleteRequest(outTaskCompleteDto);
|
| | | if (validationMessage != null)
|
| | | return response.Error(validationMessage);
|
| | |
|
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == outTaskCompleteDto.TaskId);
|
| | | if (task == null)
|
| | | return response.Error("æªæ¾å°ä»»å¡ä¿¡æ¯");
|
| | |
|
| | | outTaskCompleteDto.ReqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
| | | var httpResponse = _httpClientHelper.Post<AGVResponse>(AGV_OutTaskComplete, outTaskCompleteDto.ToJson()).Data;
|
| | | if (httpResponse == null || httpResponse.Data == null)
|
| | | return response.Error(httpResponse?.Msg ?? "AGVæ¥å£è°ç¨å¼å¸¸");
|
| | |
|
| | | if (!httpResponse.Code)
|
| | | return response.Error(string.IsNullOrWhiteSpace(httpResponse.Msg) ? "AGVæ¥å£è°ç¨å¤±è´¥" : httpResponse.Msg);
|
| | |
|
| | | var syncResult = await CompleteLocalOutboundAfterAgvAckAsync(task);
|
| | | return syncResult.Status ? response.OK(httpResponse.Msg) : syncResult;
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"WMSä»»å¡å®ææ¥å£é误ï¼{ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// è¾é线ç³è¯·è¿å
¥
|
| | | /// </summary>
|
| | | public async Task<AGVResponse> ApplyEnterAsync(ApplyEnterDto applyEnterDto)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | |
|
| | | try
|
| | | {
|
| | | var validationMessage = ValidateApplyEnterRequest(applyEnterDto);
|
| | | if (validationMessage != null)
|
| | | return response.Error(validationMessage);
|
| | |
|
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == applyEnterDto.TaskId);
|
| | | if (task == null)
|
| | | return response.Error($"æªæ¾å°ä»»å¡ä¿¡æ¯ï¼ä»»å¡å·ï¼{applyEnterDto.TaskId}");
|
| | |
|
| | | if (CanApplyEnter(task, applyEnterDto))
|
| | | return response.OK();
|
| | |
|
| | | return response.Error($"è¾é线{applyEnterDto.DevId}å½åç¹å¿ï¼è¯·ç¨åéè¯");
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return response.Error($"WMSè¾é线ç³è¯·æ¥å£é误ï¼{ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// åæ¾è´§å®æ
|
| | | /// </summary>
|
| | | public async Task<AGVResponse> TaskCompleteAsync(TaskCompleteDto taskCompleteDto)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | |
|
| | | try
|
| | | {
|
| | | var validationMessage = ValidateTaskCompleteRequest(taskCompleteDto);
|
| | | if (validationMessage != null)
|
| | | return response.Error(validationMessage);
|
| | |
|
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == taskCompleteDto.TaskId);
|
| | | if (task == null)
|
| | | return response.Error($"æªæ¾å°ä»»å¡ä¿¡æ¯ï¼ä»»å¡å·ï¼{taskCompleteDto.TaskId}");
|
| | |
|
| | | return taskCompleteDto.InOut == 2
|
| | | ? await CompleteAgvOutboundTaskAsync(task)
|
| | | : await CompleteAgvInboundTaskAsync(task);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"WMSåæ¾è´§å®ææ¥å£é误ï¼{ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// ä»»å¡åæ¶
|
| | | /// </summary>
|
| | | public async Task<AGVResponse> TaskCancelAsync(TaskCancelDto taskCancelDto)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | |
|
| | | try
|
| | | {
|
| | | var validationMessage = ValidateTaskCancelRequest(taskCancelDto);
|
| | | if (validationMessage != null)
|
| | | return response.Error(validationMessage);
|
| | |
|
| | | var task = await BaseDal.QueryFirstAsync(x => x.OrderNo == taskCancelDto.TaskId);
|
| | | if (task == null)
|
| | | return response.OK();
|
| | |
|
| | | if (task.TaskStatus == (int)TaskInStatusEnum.InNew)
|
| | | return await CancelAgvInboundTask(task);
|
| | |
|
| | | if (task.TaskStatus == (int)TaskOutStatusEnum.OutNew)
|
| | | return await CancelAgvOutboundTaskAsync(task);
|
| | |
|
| | | return response.Error("ä»»å¡å·²ç»å¨æ§è¡ä¸ï¼ä¸å¯åæ¶");
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"WMSä»»å¡åæ¶æ¥å£é误ï¼{ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | #region åæ°éªè¯
|
| | | private static string? ValidateApplyInOutRequest(ApplyInOutDto dto)
|
| | | {
|
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.TrayNumber)) return "æçå·ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.MaterialType)) return "ç©æç±»åä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.MaterialName)) return "ç©ææè¿°ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.ReqTime)) return "è¯·æ±æ¶é´ä¸è½ä¸ºç©º";
|
| | | if (dto.Floor != 1 && dto.Floor != 2) return $"æ¥¼å±æ®µé误ï¼å¿
须为1(æ¨¡åæ®µ)æ2(å·ç»æ®µ)ï¼å½åå¼ï¼{dto.Floor}";
|
| | | if (dto.YinYang != 1 && dto.YinYang != 2) return $"é´é³æé误ï¼å¿
须为1(é´æ)æ2(鳿)ï¼å½åå¼ï¼{dto.YinYang}";
|
| | | if (dto.InOut != 1 && dto.InOut != 2) return $"åºå
¥åºç±»åé误ï¼å¿
须为1(å
¥åº)æ2(åºåº)ï¼å½åå¼ï¼{dto.InOut}";
|
| | | if (dto.InOut == 1 && (dto.Width == null || dto.Width <= 0)) return "å
¥åºæ¶å®½åº¦ä¸è½ä¸ºç©ºä¸å¿
须大äº0";
|
| | | if (dto.InOut == 1 && string.IsNullOrWhiteSpace(dto.Group)) return "å
¥åºæ¶æ´æç»å«ä¸è½ä¸ºç©º";
|
| | | return null;
|
| | | }
|
| | |
|
| | | private static string? ValidateOutTaskCompleteRequest(OutTaskCompleteDto dto)
|
| | | {
|
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.DevId)) return "åºåºå£ç¼å·ä¸è½ä¸ºç©º";
|
| | | return null;
|
| | | }
|
| | |
|
| | | private static string? ValidateApplyEnterRequest(ApplyEnterDto dto)
|
| | | {
|
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.DevId)) return "设å¤ç¼å·ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º";
|
| | | if (dto.InOut != 1 && dto.InOut != 2) return $"åºå
¥åºç±»åé误ï¼å¿
须为1(å
¥åº)æ2(åºåº)ï¼å½åå¼ï¼{dto.InOut}";
|
| | | return null;
|
| | | }
|
| | |
|
| | | private static string? ValidateTaskCompleteRequest(TaskCompleteDto dto)
|
| | | {
|
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.DevId)) return "设å¤ç¼å·ä¸è½ä¸ºç©º";
|
| | | if (dto.InOut != 1 && dto.InOut != 2) return $"åºå
¥åºç±»åé误ï¼å¿
须为1(å
¥åº)æ2(åºåº)ï¼å½åå¼ï¼{dto.InOut}";
|
| | | return null;
|
| | | }
|
| | |
|
| | | private static string? ValidateTaskCancelRequest(TaskCancelDto dto)
|
| | | {
|
| | | if (dto == null) return "请æ±åæ°ä¸è½ä¸ºç©º";
|
| | | if (string.IsNullOrWhiteSpace(dto.TaskId)) return "ä»»å¡å·ä¸è½ä¸ºç©º";
|
| | | return null;
|
| | | }
|
| | | #endregion åæ°éªè¯
|
| | |
|
| | | #region å
·ä½å®ç°
|
| | | // åºå
¥åºå
±ç¨å建任å¡
|
| | | private Dt_Task BuildAgvTask(ApplyInOutDto dto)
|
| | | {
|
| | | var task = new Dt_Task
|
| | | {
|
| | | OrderNo = dto.TaskId,
|
| | | PalletCode = dto.TrayNumber,
|
| | | PalletType = dto.Floor,
|
| | | Grade = 1,
|
| | | Creater = "AGV",
|
| | | CreateDate = DateTime.Now,
|
| | | Remark = $"ç©æç±»åï¼{dto.MaterialType}ï¼ç©ææè¿°ï¼{dto.MaterialName}"
|
| | | };
|
| | |
|
| | | if (dto.YinYang == 1)
|
| | | {
|
| | | task.Roadway = WarehouseEnum.FJ1.ToString();
|
| | | task.WarehouseId = (int)WarehouseEnum.FJ1;
|
| | | task.SourceAddress = dto.InOut == 1 ? "D10010" : "D10020";
|
| | | task.NextAddress = "D10080";
|
| | | task.TargetAddress = "é´æå·åº";
|
| | | }
|
| | | else
|
| | | {
|
| | | task.Roadway = WarehouseEnum.ZJ1.ToString();
|
| | | task.WarehouseId = (int)WarehouseEnum.ZJ1;
|
| | | task.SourceAddress = dto.InOut == 1 ? "D10100" : "D10090";
|
| | | task.NextAddress = "D10160";
|
| | | task.TargetAddress = "æ£æå·åº";
|
| | | }
|
| | |
|
| | | return task;
|
| | | }
|
| | |
|
| | | // æå»ºè¿åAGVåºå
¥åºè¯·æ±ä½
|
| | | private AGVDataDto BuildAgvDataDto(Dt_Task task, ApplyInOutDto dto)
|
| | | {
|
| | | return new AGVDataDto
|
| | | {
|
| | | DevId = dto.InOut == 1 ? task.SourceAddress : task.TargetAddress,
|
| | | TrayNumber = task.PalletCode,
|
| | | Group = dto.Group,
|
| | | Width = dto.Width ?? 0,
|
| | | LabelNumber = dto.LabelNumber,
|
| | | ProductNo = dto.ProductNo,
|
| | | ProductName = dto.ProductName,
|
| | | Quantity = dto.Quantity,
|
| | | UomCode = dto.UomCode,
|
| | | ProductType = dto.ProductType,
|
| | | Equipment = dto.Equipment,
|
| | | ProductionDate = dto.ProductionDate,
|
| | | LowerLimitTime = dto.LowerLimitTime,
|
| | | WarningTime = dto.WarningTime,
|
| | | OverdueTime = dto.OverdueTime
|
| | | };
|
| | | }
|
| | |
|
| | | // å
¥åºå建
|
| | | private async Task<AGVResponse?> CreateAgvInboundTaskAsync(Dt_Task task, ApplyInOutDto dto)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(dto.TrayNumber);
|
| | | if (stockInfo != null)
|
| | | return response.Error($"å½åæç{dto.TrayNumber}å·²ç»å
¥åºäº");
|
| | |
|
| | | // å建åºåæç»
|
| | | var details = new Dt_StockInfoDetail
|
| | | {
|
| | | MaterielCode = dto.ProductNo,
|
| | | MaterielName = dto.ProductName,
|
| | | StockQuantity = int.TryParse(dto.Quantity, out int quantity) ? quantity : 0,
|
| | | Unit = dto.UomCode,
|
| | | OrderNo = dto.ProductNo,
|
| | | ProductionDate =dto.ProductionDate,
|
| | | EffectiveDate = dto.LowerLimitTime,
|
| | | SerialNumber = dto.TrayNumber,
|
| | | Status = (int)StockStatusEmun.å
¥åºç¡®è®¤,
|
| | | InboundOrderRowNo = 1,
|
| | | Creater = StockConstants.AGV_USER,
|
| | | CreateDate = DateTime.Now,
|
| | | Remark = $"AGVå
¥åºä»»å¡å建ï¼ä»»å¡å·ï¼{dto.TaskId}"
|
| | | };
|
| | |
|
| | | // å建åºå主记å½
|
| | | var stock = new Dt_StockInfo
|
| | | {
|
| | | PalletCode = dto.TrayNumber,
|
| | | PalletType = dto.Floor,
|
| | | WarehouseId = dto.YinYang == 1 ? (int)WarehouseEnum.FJ1 : (int)WarehouseEnum.ZJ1,
|
| | | StockStatus = (int)StockStatusEmun.å
¥åºç¡®è®¤,
|
| | | Creater = StockConstants.AGV_USER,
|
| | | CreateDate = DateTime.Now,
|
| | | Remark = $"AGVå
¥åºä»»å¡å建ï¼ä»»å¡å·ï¼{dto.TaskId}",
|
| | | Details = new List<Dt_StockInfoDetail> { details }
|
| | | };
|
| | |
|
| | | task.TaskType = (int)TaskInboundTypeEnum.Inbound;
|
| | | task.TaskStatus = (int)TaskInStatusEnum.InNew;
|
| | | task.CurrentAddress = task.SourceAddress;
|
| | |
|
| | | _unitOfWorkManage.BeginTran();
|
| | | try
|
| | | {
|
| | | // å
å建任å¡
|
| | | var taskResult = await BaseDal.AddDataAsync(task) > 0;
|
| | | if (!taskResult)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("å
¥åºä»»å¡å建失败");
|
| | | }
|
| | | var result = _stockInfoService.Repository.AddData(stock, x => x.Details);
|
| | | if (result)
|
| | | { |
| | | _unitOfWorkManage.CommitTran(); |
| | | return null; |
| | | {
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return null;
|
| | | }
|
| | | else |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("åºåä¿¡æ¯å建失败"); |
| | | else
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("åºåä¿¡æ¯å建失败");
|
| | | }
|
| | | // 使ç¨åºåæå¡æ·»å åºå主记å½åæç»
|
| | | //var stockResult = await _stockInfoService.AddStockWithDetailsUsingTransactionAsync(stock); |
| | | |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"å
¥åºä»»å¡å建å¼å¸¸ï¼{ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | // åºåºå建 |
| | | private async Task<AGVResponse?> CreateAgvOutboundTaskAsync(Dt_Task task, ApplyInOutDto dto) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | |
| | | // æ£æ¥åºåæ¯å¦åå¨ |
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(dto.TrayNumber); |
| | | if (stockInfo == null) |
| | | return response.Error($"æªæ¾å°æç{dto.TrayNumber}çåºåä¿¡æ¯"); |
| | | |
| | | // æ£æ¥åºåæ¯å¦ææç»ï¼å³æ¯å¦ççæåºåï¼ |
| | | if (stockInfo.Details == null || !stockInfo.Details.Any()) |
| | | return response.Error($"æç{dto.TrayNumber}没æåºåæç»ï¼æ æ³åºåº"); |
| | | |
| | | // æ£æ¥åºåæ»æ°éæ¯å¦å¤§äº0 |
| | | var totalQuantity = stockInfo.Details.Sum(d => d.StockQuantity); |
| | | if (totalQuantity <= 0) |
| | | return response.Error($"æç{dto.TrayNumber}åºåæ°éä¸è¶³ï¼æ æ³åºåº"); |
| | | |
| | | // æ ¹æ®dtoåæ°è¿ä¸æ¥éªè¯åºåä¿¡æ¯ |
| | | if (!string.IsNullOrEmpty(dto.ProductNo)) |
| | | { |
| | | // æ£æ¥åºåæç»ä¸æ¯å¦å
嫿å®çç©æç¼ç |
| | | var hasMatchingMaterial = stockInfo.Details.Any(d => d.MaterielCode == dto.ProductNo); |
| | | if (!hasMatchingMaterial) |
| | | return response.Error($"æç{dto.TrayNumber}䏿²¡æç©æç¼ç 为{dto.ProductNo}çåºåï¼æ æ³åºåº"); |
| | | } |
| | | |
| | | // æ£æ¥åºåç¶ææ¯å¦å
许åºåº |
| | | if (stockInfo.StockStatus != (int)StockStatusEmun.å
¥åºå®æ) |
| | | return response.Error($"æç{dto.TrayNumber}æ£å¨ç§»å¨ä¸ï¼è¯·ç¨åï¼"); |
| | | |
| | | // æ£æ¥è´§ä½ä¿¡æ¯ |
| | | var locationInfo = await _locationInfoService.GetLocationInfo(stockInfo.LocationCode); |
| | | if (locationInfo == null) |
| | | return response.Error($"æªæ¾å°æç{stockInfo.LocationCode}çè´§ä½ä¿¡æ¯"); |
| | | |
| | | // æ£æ¥è´§ä½ç¶ææ¯å¦å
许åºåº |
| | | if (locationInfo.LocationStatus != (int)LocationStatusEnum.InStock) |
| | | return response.Error($"å½åè´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误"); |
| | | |
| | | // éªè¯ä»åºIDæ¯å¦å¹é
ï¼æ ¹æ®dtoçé´é³æåæ°ï¼ |
| | | var expectedWarehouseId = dto.YinYang == 1 ? (int)WarehouseEnum.FJ1 : (int)WarehouseEnum.ZJ1; |
| | | if (stockInfo.WarehouseId != expectedWarehouseId) |
| | | return response.Error($"æç{dto.TrayNumber}ä¸å¨é¢æçä»åºä¸ï¼æ æ³åºåº"); |
| | | |
| | | task.TaskType = (int)TaskOutboundTypeEnum.Outbound; |
| | | task.TaskStatus = (int)TaskOutStatusEnum.OutNew; |
| | | task.SourceAddress = stockInfo.LocationCode; |
| | | task.CurrentAddress = stockInfo.LocationCode; |
| | | task.TargetAddress = dto.YinYang == 1 ? "D10020" : "D10090"; |
| | | |
| | | var wmsTaskDto = _mapper.Map<WMSTaskDTO>(task); |
| | | var taskList = new List<WMSTaskDTO> { wmsTaskDto }; |
| | | var requestBody = JsonSerializer.Serialize(taskList); |
| | | |
| | | var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, requestBody); |
| | | if (httpResponse == null || httpResponse.Data == null || !httpResponse.Data.Status) |
| | | return response.Error(httpResponse?.Data?.Message ?? "ä¸åWCS失败"); |
| | | |
| | | stockInfo.StockStatus = (int)StockStatusEmun.åºåºéå®; |
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.InStockLock; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | var addTaskResult = await BaseDal.AddDataAsync(task) > 0; |
| | | var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(locationInfo); |
| | | var updateStockResult = await _stockInfoService.UpdateStockAsync(stockInfo); |
| | | if (!addTaskResult || !updateLocationResult || !updateStockResult) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("åºåºä»»å¡å建失败"); |
| | | } |
| | | _unitOfWorkManage.CommitTran(); |
| | | |
| | | |
| | | return null; |
| | | } |
| | | |
| | | private async Task<WebResponseContent> CompleteLocalOutboundAfterAgvAckAsync(Dt_Task task) |
| | | { |
| | | task.TaskStatus = (int)TaskOutStatusEnum.Line_OutFinish; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | var updateResult = BaseDal.UpdateData(task); |
| | | if (!updateResult) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return WebResponseContent.Instance.Error("AGV宿åä¼ åï¼ä»»å¡æ´æ°å¤±è´¥"); |
| | | } |
| | | |
| | | _unitOfWorkManage.CommitTran(); |
| | | return WebResponseContent.Instance.OK(); |
| | | } |
| | | |
| | | private bool CanApplyEnter(Dt_Task task, ApplyEnterDto dto) |
| | | { |
| | | if (dto.InOut == 1) |
| | | { |
| | | var hasExecutingOutTask = BaseDal.QueryFirst(x => x.TaskType == (int)TaskOutboundTypeEnum.Outbound |
| | | && x.TargetAddress == task.SourceAddress |
| | | && (x.TaskStatus == (int)TaskOutStatusEnum.SC_OutExecuting |
| | | || x.TaskStatus == (int)TaskOutStatusEnum.Line_OutExecuting)); |
| | | |
| | | // å¦ææ²¡ææ£å¨æ§è¡çåºåºä»»å¡ï¼åå
许å
¥åº |
| | | return hasExecutingOutTask == null; |
| | | } |
| | | else |
| | | { |
| | | return task.TaskType == (int)TaskOutboundTypeEnum.Outbound |
| | | && task.TaskStatus == (int)TaskStatusEnum.Line_Finish; |
| | | } |
| | | } |
| | | |
| | | // WCSå
¥åºå®æ |
| | | private async Task<WebResponseContent> CompleteAgvInboundTaskAsync(CreateTaskDto taskDto) |
| | | { |
| | | WebResponseContent response = new WebResponseContent(); |
| | | var task = await BaseDal.QueryFirstAsync(x => x.PalletType == taskDto.PalletType); |
| | | if (task == null) |
| | | return response.Error($"没æå½åæç{taskDto.PalletType}å
¥åºä»»å¡"); |
| | | |
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode); |
| | | if (stockInfo == null) |
| | | return response.Error($"æªæ¾å°æç{task.PalletCode}çåºåä¿¡æ¯"); |
| | | |
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(task.TargetAddress); |
| | | if (locationInfo == null) |
| | | return response.Error($"æªæ¾å°è´§ä½{task.TargetAddress}çä¿¡æ¯"); |
| | | |
| | | if (locationInfo.LocationStatus == (int)LocationStatusEnum.InStock) |
| | | return response.Error($"å½åè´§ä½{locationInfo.LocationStatus}ç¶æä¸æ¯ç©ºé²ç¶æï¼æ æ³å
¥åº"); |
| | | |
| | | // æ´æ°è´§ä½ç¶æä¸ºå ç¨ |
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.InStock; |
| | | task.TaskStatus = (int)TaskInStatusEnum.InFinish; |
| | | stockInfo.StockStatus = (int)StockStatusEmun.å
¥åºå®æ; |
| | | _unitOfWorkManage.BeginTran(); |
| | | var addStockResult = _stockInfoService.UpdateData(stockInfo); |
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo); |
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ); |
| | | |
| | | if (!addStockResult.Status || !updateLocationResult.Status) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("å
¥åºå®æåï¼æ·»å åºåæè´§ä½æ´æ°å¤±è´¥"); |
| | | } |
| | | |
| | | _unitOfWorkManage.CommitTran(); |
| | | return response.OK(); |
| | | } |
| | | |
| | | // AGVåºåºå®æ |
| | | private async Task<AGVResponse> CompleteAgvOutboundTaskAsync(Dt_Task task) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode); |
| | | if (stockInfo == null) |
| | | return response.Error($"æªæ¾å°æç{task.PalletCode}çåºåä¿¡æ¯"); |
| | | |
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(stockInfo.LocationCode); |
| | | if (locationInfo == null) |
| | | return response.Error($"æªæ¾å°æç{stockInfo.LocationCode}çè´§ä½ä¿¡æ¯"); |
| | | |
| | | if (stockInfo.StockStatus != (int)StockStatusEmun.åºåºéå® || locationInfo.LocationStatus != (int)LocationStatusEnum.InStockLock) |
| | | return response.Error($"å½ååºå{stockInfo.StockStatus}æè
è´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误"); |
| | | |
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.Free; |
| | | task.TaskStatus = (int)TaskOutStatusEnum.OutFinish; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | //var deleteStockResult = _stockInfoService.DeleteData(stockInfo) |
| | | var deleteStockResult = await _stockInfoService.DeleteStockWithDetailsAsync(stockInfo.Id); |
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo); |
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ); |
| | | if (!deleteStockResult.Status || !updateLocationResult.Status) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("åºåºå®æåï¼æ¬å°åºåæè´§ä½æ´æ°å¤±è´¥"); |
| | | } |
| | | |
| | | _unitOfWorkManage.CommitTran(); |
| | | return response.OK(); |
| | | } |
| | | |
| | | // AGVå·²æ¾è´§ï¼åå¤è¾é线å
¥åº |
| | | private async Task<AGVResponse> CompleteAgvInboundTaskAsync(Dt_Task task) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | var availableLocation = await _locationInfoService.GetLocationInfo(task.Roadway); |
| | | if (availableLocation == null) |
| | | return response.Error("æ å¯ç¨çå
¥åºè´§ä½"); |
| | | |
| | | task.TargetAddress = availableLocation.LocationCode; |
| | | |
| | | var wmsTaskDto = _mapper.Map<WMSTaskDTO>(task); |
| | | var taskList = new List<WMSTaskDTO> { wmsTaskDto }; |
| | | var requestBody = JsonSerializer.Serialize(taskList); |
| | | |
| | | var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, requestBody); |
| | | if (httpResponse == null || httpResponse.Data == null || !httpResponse.Data.Status) |
| | | return response.Error(httpResponse?.Data?.Message ?? "ä¸åWCS失败"); |
| | | |
| | | task.TaskStatus = (int)TaskInStatusEnum.Line_InExecuting; |
| | | task.Dispatchertime = DateTime.Now; |
| | | |
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(task.TargetAddress); |
| | | if (locationInfo == null) |
| | | return response.Error($"æªæ¾å°æç{task.TargetAddress}çè´§ä½ä¿¡æ¯"); |
| | | |
| | | if (locationInfo.LocationStatus != (int)LocationStatusEnum.Free) |
| | | return response.Error($"å½åè´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误"); |
| | | |
| | | var existingStock = await _stockInfoService.GetStockInfoAsync(task.PalletCode); |
| | | if (existingStock != null) |
| | | return response.Error($"æç{task.PalletCode}çåºåä¿¡æ¯å·²åå¨ï¼è¯·å¿éå¤å
¥åº"); |
| | | |
| | | //Dt_StockInfo stockInfo = new Dt_StockInfo |
| | | //{ |
| | | // PalletCode = task.PalletCode, |
| | | // StockStatus = (int)StockStatusEmun.å
¥åºç¡®è®¤, |
| | | // LocationCode = locationInfo.LocationCode, |
| | | // WarehouseId = task.WarehouseId, |
| | | // Creater = "AGV", |
| | | // CreateDate = DateTime.Now |
| | | //}; |
| | | |
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.FreeLock; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | var updateTaskResult = BaseDal.UpdateData(task); |
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo); |
| | | //var addStockResult = _stockInfoService.AddData(stockInfo); |
| | | if (!updateTaskResult || !updateLocationResult.Status /*|| !addStockResult.Status*/) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("å
¥åºå®æåï¼æ¬å°ä»»å¡ãåºåæè´§ä½æ´æ°å¤±è´¥"); |
| | | } |
| | | |
| | | _unitOfWorkManage.CommitTran(); |
| | | return response.OK(); |
| | | } |
| | | |
| | | // AGVå
¥åºåæ¶ |
| | | private async Task<AGVResponse> CancelAgvInboundTask(Dt_Task task) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | task.TaskStatus = (int)TaskInStatusEnum.InCancel; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | try |
| | | { |
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode); |
| | | if (stockInfo != null) |
| | | { |
| | | var deleteResult = await _stockInfoService.DeleteStockWithDetailsAsync(stockInfo.Id); |
| | | if (!deleteResult.Status) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"å é¤åºå失败: {deleteResult.Message}"); |
| | | } |
| | | } |
| | | |
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ); |
| | | |
| | | _unitOfWorkManage.CommitTran(); |
| | | return response.OK(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error($"åæ¶å
¥åºä»»å¡æ¶åçå¼å¸¸: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | |
| | | // AGVåºåºåæ¶ |
| | | private async Task<AGVResponse> CancelAgvOutboundTaskAsync(Dt_Task task) |
| | | { |
| | | AGVResponse response = new AGVResponse(); |
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode); |
| | | if (stockInfo == null) |
| | | return response.Error($"æªæ¾å°æç{task.PalletCode}çåºåä¿¡æ¯"); |
| | | |
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(stockInfo.LocationCode); |
| | | if (locationInfo == null) |
| | | return response.Error($"æªæ¾å°æç{stockInfo.LocationCode}çè´§ä½ä¿¡æ¯"); |
| | | |
| | | if (stockInfo.StockStatus != (int)StockStatusEmun.åºåºéå® || locationInfo.LocationStatus != (int)LocationStatusEnum.InStockLock) |
| | | return response.Error($"å½ååºå{stockInfo.StockStatus}æè
è´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误"); |
| | | |
| | | stockInfo.StockStatus = (int)StockStatusEmun.å
¥åºå®æ; |
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.InStock; |
| | | task.TaskStatus = (int)TaskOutStatusEnum.OutCancel; |
| | | |
| | | _unitOfWorkManage.BeginTran(); |
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo); |
| | | var updateStockResult = _stockInfoService.UpdateData(stockInfo); |
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ); |
| | | if (!updateLocationResult.Status || !updateStockResult.Status) |
| | | { |
| | | _unitOfWorkManage.RollbackTran(); |
| | | return response.Error("åºåºä»»å¡åæ¶å¤±è´¥"); |
| | | } |
| | | |
| | | _unitOfWorkManage.CommitTran(); |
| | | return response.OK(); |
| | | } |
| | | |
| | | #endregion å
·ä½å®ç° |
| | | } |
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"å
¥åºä»»å¡å建å¼å¸¸ï¼{ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | // åºåºå建
|
| | | private async Task<AGVResponse?> CreateAgvOutboundTaskAsync(Dt_Task task, ApplyInOutDto dto)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | | |
| | | // æ£æ¥åºåæ¯å¦åå¨
|
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(dto.TrayNumber);
|
| | | if (stockInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{dto.TrayNumber}çåºåä¿¡æ¯");
|
| | |
|
| | | // æ£æ¥åºåæ¯å¦ææç»ï¼å³æ¯å¦ççæåºåï¼
|
| | | if (stockInfo.Details == null || !stockInfo.Details.Any())
|
| | | return response.Error($"æç{dto.TrayNumber}没æåºåæç»ï¼æ æ³åºåº");
|
| | |
|
| | | // æ£æ¥åºåæ»æ°éæ¯å¦å¤§äº0
|
| | | var totalQuantity = stockInfo.Details.Sum(d => d.StockQuantity);
|
| | | if (totalQuantity <= 0)
|
| | | return response.Error($"æç{dto.TrayNumber}åºåæ°éä¸è¶³ï¼æ æ³åºåº");
|
| | |
|
| | | // æ ¹æ®dtoåæ°è¿ä¸æ¥éªè¯åºåä¿¡æ¯
|
| | | if (!string.IsNullOrEmpty(dto.ProductNo))
|
| | | {
|
| | | // æ£æ¥åºåæç»ä¸æ¯å¦å
嫿å®çç©æç¼ç
|
| | | var hasMatchingMaterial = stockInfo.Details.Any(d => d.MaterielCode == dto.ProductNo);
|
| | | if (!hasMatchingMaterial)
|
| | | return response.Error($"æç{dto.TrayNumber}䏿²¡æç©æç¼ç 为{dto.ProductNo}çåºåï¼æ æ³åºåº");
|
| | | }
|
| | |
|
| | | // æ£æ¥åºåç¶ææ¯å¦å
许åºåº
|
| | | if (stockInfo.StockStatus != (int)StockStatusEmun.å
¥åºå®æ)
|
| | | return response.Error($"æç{dto.TrayNumber}æ£å¨ç§»å¨ä¸ï¼è¯·ç¨åï¼");
|
| | |
|
| | | // æ£æ¥è´§ä½ä¿¡æ¯
|
| | | var locationInfo = await _locationInfoService.GetLocationInfo(stockInfo.LocationCode);
|
| | | if (locationInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{stockInfo.LocationCode}çè´§ä½ä¿¡æ¯");
|
| | |
|
| | | // æ£æ¥è´§ä½ç¶ææ¯å¦å
许åºåº
|
| | | if (locationInfo.LocationStatus != (int)LocationStatusEnum.InStock)
|
| | | return response.Error($"å½åè´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误");
|
| | |
|
| | | // éªè¯ä»åºIDæ¯å¦å¹é
ï¼æ ¹æ®dtoçé´é³æåæ°ï¼
|
| | | var expectedWarehouseId = dto.YinYang == 1 ? (int)WarehouseEnum.FJ1 : (int)WarehouseEnum.ZJ1;
|
| | | if (stockInfo.WarehouseId != expectedWarehouseId)
|
| | | return response.Error($"æç{dto.TrayNumber}ä¸å¨é¢æçä»åºä¸ï¼æ æ³åºåº");
|
| | |
|
| | | task.TaskType = (int)TaskOutboundTypeEnum.Outbound;
|
| | | task.TaskStatus = (int)TaskOutStatusEnum.OutNew;
|
| | | task.SourceAddress = stockInfo.LocationCode;
|
| | | task.CurrentAddress = stockInfo.LocationCode;
|
| | | task.TargetAddress = dto.YinYang == 1 ? "D10020" : "D10090";
|
| | |
|
| | | var wmsTaskDto = _mapper.Map<WMSTaskDTO>(task);
|
| | | var taskList = new List<WMSTaskDTO> { wmsTaskDto };
|
| | | var requestBody = JsonSerializer.Serialize(taskList);
|
| | |
|
| | | var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, requestBody);
|
| | | if (httpResponse == null || httpResponse.Data == null || !httpResponse.Data.Status)
|
| | | return response.Error(httpResponse?.Data?.Message ?? "ä¸åWCS失败");
|
| | |
|
| | | stockInfo.StockStatus = (int)StockStatusEmun.åºåºéå®;
|
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.InStockLock;
|
| | |
|
| | | _unitOfWorkManage.BeginTran();
|
| | | var addTaskResult = await BaseDal.AddDataAsync(task) > 0;
|
| | | var updateLocationResult = await _locationInfoService.UpdateLocationInfoAsync(locationInfo);
|
| | | var updateStockResult = await _stockInfoService.UpdateStockAsync(stockInfo);
|
| | | if (!addTaskResult || !updateLocationResult || !updateStockResult)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("åºåºä»»å¡å建失败");
|
| | | }
|
| | | _unitOfWorkManage.CommitTran();
|
| | |
|
| | |
|
| | | return null;
|
| | | }
|
| | |
|
| | | private async Task<WebResponseContent> CompleteLocalOutboundAfterAgvAckAsync(Dt_Task task)
|
| | | {
|
| | | task.TaskStatus = (int)TaskOutStatusEnum.Line_OutFinish;
|
| | |
|
| | | _unitOfWorkManage.BeginTran();
|
| | | var updateResult = BaseDal.UpdateData(task);
|
| | | if (!updateResult)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return WebResponseContent.Instance.Error("AGV宿åä¼ åï¼ä»»å¡æ´æ°å¤±è´¥");
|
| | | }
|
| | |
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return WebResponseContent.Instance.OK();
|
| | | }
|
| | |
|
| | | private bool CanApplyEnter(Dt_Task task, ApplyEnterDto dto)
|
| | | {
|
| | | if (dto.InOut == 1)
|
| | | {
|
| | | var hasExecutingOutTask = BaseDal.QueryFirst(x => x.TaskType == (int)TaskOutboundTypeEnum.Outbound
|
| | | && x.TargetAddress == task.SourceAddress
|
| | | && (x.TaskStatus == (int)TaskOutStatusEnum.SC_OutExecuting
|
| | | || x.TaskStatus == (int)TaskOutStatusEnum.Line_OutExecuting));
|
| | |
|
| | | // å¦ææ²¡ææ£å¨æ§è¡çåºåºä»»å¡ï¼åå
许å
¥åº
|
| | | return hasExecutingOutTask == null;
|
| | | }
|
| | | else
|
| | | {
|
| | | return task.TaskType == (int)TaskOutboundTypeEnum.Outbound
|
| | | && task.TaskStatus == (int)TaskStatusEnum.Line_Finish;
|
| | | }
|
| | | }
|
| | |
|
| | | // WCSå
¥åºå®æ
|
| | | private async Task<WebResponseContent> CompleteAgvInboundTaskAsync(CreateTaskDto taskDto)
|
| | | {
|
| | | WebResponseContent response = new WebResponseContent();
|
| | | var task = await BaseDal.QueryFirstAsync(x => x.PalletType == taskDto.PalletType);
|
| | | if (task == null)
|
| | | return response.Error($"没æå½åæç{taskDto.PalletType}å
¥åºä»»å¡");
|
| | |
|
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
|
| | | if (stockInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{task.PalletCode}çåºåä¿¡æ¯");
|
| | |
|
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(task.TargetAddress);
|
| | | if (locationInfo == null)
|
| | | return response.Error($"æªæ¾å°è´§ä½{task.TargetAddress}çä¿¡æ¯");
|
| | |
|
| | | if (locationInfo.LocationStatus == (int)LocationStatusEnum.InStock)
|
| | | return response.Error($"å½åè´§ä½{locationInfo.LocationStatus}ç¶æä¸æ¯ç©ºé²ç¶æï¼æ æ³å
¥åº");
|
| | |
|
| | | // æ´æ°è´§ä½ç¶æä¸ºå ç¨
|
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.InStock;
|
| | | task.TaskStatus = (int)TaskInStatusEnum.InFinish;
|
| | | stockInfo.StockStatus = (int)StockStatusEmun.å
¥åºå®æ;
|
| | | _unitOfWorkManage.BeginTran();
|
| | | var addStockResult = _stockInfoService.UpdateData(stockInfo);
|
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
|
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ);
|
| | |
|
| | | if (!addStockResult.Status || !updateLocationResult.Status)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("å
¥åºå®æåï¼æ·»å åºåæè´§ä½æ´æ°å¤±è´¥");
|
| | | }
|
| | |
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return response.OK();
|
| | | }
|
| | |
|
| | | // AGVåºåºå®æ
|
| | | private async Task<AGVResponse> CompleteAgvOutboundTaskAsync(Dt_Task task)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
|
| | | if (stockInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{task.PalletCode}çåºåä¿¡æ¯");
|
| | |
|
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(stockInfo.LocationCode);
|
| | | if (locationInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{stockInfo.LocationCode}çè´§ä½ä¿¡æ¯");
|
| | |
|
| | | if (stockInfo.StockStatus != (int)StockStatusEmun.åºåºéå® || locationInfo.LocationStatus != (int)LocationStatusEnum.InStockLock)
|
| | | return response.Error($"å½ååºå{stockInfo.StockStatus}æè
è´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误");
|
| | |
|
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.Free;
|
| | | task.TaskStatus = (int)TaskOutStatusEnum.OutFinish;
|
| | |
|
| | | _unitOfWorkManage.BeginTran();
|
| | | //var deleteStockResult = _stockInfoService.DeleteData(stockInfo)
|
| | | var deleteStockResult = await _stockInfoService.DeleteStockWithDetailsAsync(stockInfo.Id);
|
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
|
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ);
|
| | | if (!deleteStockResult.Status || !updateLocationResult.Status)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("åºåºå®æåï¼æ¬å°åºåæè´§ä½æ´æ°å¤±è´¥");
|
| | | }
|
| | |
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return response.OK();
|
| | | }
|
| | |
|
| | | // AGVå·²æ¾è´§ï¼åå¤è¾é线å
¥åº
|
| | | private async Task<AGVResponse> CompleteAgvInboundTaskAsync(Dt_Task task)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | | var availableLocation = await _locationInfoService.GetLocationInfo(task.Roadway);
|
| | | if (availableLocation == null)
|
| | | return response.Error("æ å¯ç¨çå
¥åºè´§ä½");
|
| | |
|
| | | task.TargetAddress = availableLocation.LocationCode;
|
| | |
|
| | | var wmsTaskDto = _mapper.Map<WMSTaskDTO>(task);
|
| | | var taskList = new List<WMSTaskDTO> { wmsTaskDto };
|
| | | var requestBody = JsonSerializer.Serialize(taskList);
|
| | |
|
| | | var httpResponse = _httpClientHelper.Post<WebResponseContent>(WCS_ReceiveTask, requestBody);
|
| | | if (httpResponse == null || httpResponse.Data == null || !httpResponse.Data.Status)
|
| | | return response.Error(httpResponse?.Data?.Message ?? "ä¸åWCS失败");
|
| | |
|
| | | task.TaskStatus = (int)TaskInStatusEnum.Line_InExecuting;
|
| | | task.Dispatchertime = DateTime.Now;
|
| | |
|
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(task.TargetAddress);
|
| | | if (locationInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{task.TargetAddress}çè´§ä½ä¿¡æ¯");
|
| | |
|
| | | if (locationInfo.LocationStatus != (int)LocationStatusEnum.Free)
|
| | | return response.Error($"å½åè´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误");
|
| | |
|
| | | var existingStock = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
|
| | | if (existingStock != null)
|
| | | return response.Error($"æç{task.PalletCode}çåºåä¿¡æ¯å·²åå¨ï¼è¯·å¿éå¤å
¥åº");
|
| | |
|
| | | //Dt_StockInfo stockInfo = new Dt_StockInfo
|
| | | //{
|
| | | // PalletCode = task.PalletCode,
|
| | | // StockStatus = (int)StockStatusEmun.å
¥åºç¡®è®¤,
|
| | | // LocationCode = locationInfo.LocationCode,
|
| | | // WarehouseId = task.WarehouseId,
|
| | | // Creater = "AGV",
|
| | | // CreateDate = DateTime.Now
|
| | | //};
|
| | |
|
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.FreeLock;
|
| | |
|
| | | _unitOfWorkManage.BeginTran();
|
| | | var updateTaskResult = BaseDal.UpdateData(task);
|
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
|
| | | //var addStockResult = _stockInfoService.AddData(stockInfo);
|
| | | if (!updateTaskResult || !updateLocationResult.Status /*|| !addStockResult.Status*/)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("å
¥åºå®æåï¼æ¬å°ä»»å¡ãåºåæè´§ä½æ´æ°å¤±è´¥");
|
| | | }
|
| | |
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return response.OK();
|
| | | }
|
| | |
|
| | | // AGVå
¥åºåæ¶
|
| | | private async Task<AGVResponse> CancelAgvInboundTask(Dt_Task task)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | | task.TaskStatus = (int)TaskInStatusEnum.InCancel;
|
| | | |
| | | _unitOfWorkManage.BeginTran();
|
| | | try
|
| | | {
|
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
|
| | | if (stockInfo != null)
|
| | | {
|
| | | var deleteResult = await _stockInfoService.DeleteStockWithDetailsAsync(stockInfo.Id);
|
| | | if (!deleteResult.Status)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"å é¤åºå失败: {deleteResult.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ);
|
| | |
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return response.OK();
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error($"åæ¶å
¥åºä»»å¡æ¶åçå¼å¸¸: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | |
|
| | | // AGVåºåºåæ¶
|
| | | private async Task<AGVResponse> CancelAgvOutboundTaskAsync(Dt_Task task)
|
| | | {
|
| | | AGVResponse response = new AGVResponse();
|
| | | var stockInfo = await _stockInfoService.GetStockInfoAsync(task.PalletCode);
|
| | | if (stockInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{task.PalletCode}çåºåä¿¡æ¯");
|
| | |
|
| | | var locationInfo = await _locationInfoService.GetLocationInfoAsync(stockInfo.LocationCode);
|
| | | if (locationInfo == null)
|
| | | return response.Error($"æªæ¾å°æç{stockInfo.LocationCode}çè´§ä½ä¿¡æ¯");
|
| | |
|
| | | if (stockInfo.StockStatus != (int)StockStatusEmun.åºåºéå® || locationInfo.LocationStatus != (int)LocationStatusEnum.InStockLock)
|
| | | return response.Error($"å½ååºå{stockInfo.StockStatus}æè
è´§ä½{locationInfo.LocationStatus}ç¶æä¿¡æ¯é误");
|
| | |
|
| | | stockInfo.StockStatus = (int)StockStatusEmun.å
¥åºå®æ;
|
| | | locationInfo.LocationStatus = (int)LocationStatusEnum.InStock;
|
| | | task.TaskStatus = (int)TaskOutStatusEnum.OutCancel;
|
| | |
|
| | | _unitOfWorkManage.BeginTran();
|
| | | var updateLocationResult = _locationInfoService.UpdateData(locationInfo);
|
| | | var updateStockResult = _stockInfoService.UpdateData(stockInfo);
|
| | | BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.èªå¨å®æ : OperateTypeEnum.äººå·¥å®æ);
|
| | | if (!updateLocationResult.Status || !updateStockResult.Status)
|
| | | {
|
| | | _unitOfWorkManage.RollbackTran();
|
| | | return response.Error("åºåºä»»å¡åæ¶å¤±è´¥");
|
| | | }
|
| | |
|
| | | _unitOfWorkManage.CommitTran();
|
| | | return response.OK();
|
| | | }
|
| | |
|
| | | #endregion å
·ä½å®ç°
|
| | | }
|
| | | } |
| | |
| | | using Microsoft.AspNetCore.Authorization; |
| | | using Microsoft.AspNetCore.Mvc; |
| | | using SqlSugar; |
| | | using WIDESEA_Core; |
| | | using WIDESEA_Model.Models; |
| | | |
| | | namespace WIDESEA_WMSServer.Controllers.Dashboard |
| | | { |
| | | /// <summary> |
| | | /// 仪表ç |
| | | /// </summary> |
| | | [Route("api/Dashboard")] |
| | | [ApiController] |
| | | public class DashboardController : ControllerBase |
| | | { |
| | | private readonly ISqlSugarClient _db; |
| | | |
| | | public DashboardController(ISqlSugarClient db) |
| | | { |
| | | _db = db; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ»è§æ°æ® |
| | | /// </summary> |
| | | [HttpGet("Overview"), AllowAnonymous] |
| | | public async Task<WebResponseContent> Overview() |
| | | { |
| | | try |
| | | { |
| | | var today = DateTime.Today; |
| | | var firstDayOfMonth = new DateTime(today.Year, today.Month, 1); |
| | | |
| | | // 仿¥å
¥åºæ° |
| | | var todayInbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= today && t.TaskType >= 500 && t.TaskType < 600) |
| | | .CountAsync(); |
| | | |
| | | // 仿¥åºåºæ° |
| | | var todayOutbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= today && t.TaskType >= 100 && t.TaskType < 200) |
| | | .CountAsync(); |
| | | |
| | | // æ¬æå
¥åºæ° |
| | | var monthInbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 500 && t.TaskType < 600) |
| | | .CountAsync(); |
| | | |
| | | // æ¬æåºåºæ° |
| | | var monthOutbound = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 100 && t.TaskType < 200) |
| | | .CountAsync(); |
| | | |
| | | // å½åæ»åºå |
| | | var totalStock = await _db.Queryable<Dt_StockInfo>().CountAsync(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, new |
| | | { |
| | | TodayInbound = todayInbound, |
| | | TodayOutbound = todayOutbound, |
| | | MonthInbound = monthInbound, |
| | | MonthOutbound = monthOutbound, |
| | | TotalStock = totalStock |
| | | }); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"æ»è§æ°æ®è·å失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¯æ¥ç»è®¡ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 注æï¼æ°æ®å¨ SQL å±è¿æ»¤åï¼å¨åºç¨å±ææ¥æåç»ã |
| | | /// SqlSugar ç GroupBy 䏿¯æå¯¹ .Date è¿æ ·ç计ç®åç´æ¥çæ SQL GROUP BYï¼ |
| | | /// å æ¤éç¨æ¤æ¹å¼ä»¥ç¡®ä¿è·¨æ°æ®åºå
¼å®¹æ§ã |
| | | /// </remarks> |
| | | [HttpGet("DailyStats"), AllowAnonymous] |
| | | public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30) |
| | | { |
| | | try |
| | | { |
| | | if (days <= 0) days = 30; |
| | | if (days > 365) days = 365; |
| | | |
| | | var startDate = DateTime.Today.AddDays(-days + 1); |
| | | |
| | | var query = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= startDate) |
| | | .Select(t => new { t.InsertTime, t.TaskType }) |
| | | .ToListAsync(); |
| | | |
| | | var result = query |
| | | .GroupBy(t => t.InsertTime.Date) |
| | | .Select(g => new |
| | | { |
| | | Date = g.Key.ToString("yyyy-MM-dd"), |
| | | Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600), |
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200) |
| | | }) |
| | | .OrderBy(x => x.Date) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"æ¯æ¥ç»è®¡è·å失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¨ç»è®¡ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 注æï¼æ°æ®å¨ SQL å±è¿æ»¤åï¼å¨åºç¨å±æ ISO 8601 å¨é®åç»ã |
| | | /// å¨é®ä¸º "YYYY-Www" æ ¼å¼ï¼æ æ³ç´æ¥å¨ SQL å±ç¨ GROUP BY å®ç°ã |
| | | /// </remarks> |
| | | [HttpGet("WeeklyStats"), AllowAnonymous] |
| | | public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12) |
| | | { |
| | | try |
| | | { |
| | | if (weeks <= 0) weeks = 12; |
| | | |
| | | var startDate = DateTime.Today.AddDays(-weeks * 7); |
| | | |
| | | var query = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= startDate) |
| | | .Select(t => new { t.InsertTime, t.TaskType }) |
| | | .ToListAsync(); |
| | | |
| | | var result = query |
| | | .GroupBy(t => GetWeekKey(t.InsertTime)) |
| | | .Select(g => new |
| | | { |
| | | Week = g.Key, |
| | | Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600), |
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200) |
| | | }) |
| | | .OrderBy(x => x.Week) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"æ¯å¨ç»è®¡è·å失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private string GetWeekKey(DateTime date) |
| | | { |
| | | // è·åå¨ä¸å¼å§çå¨ (ISO 8601) |
| | | var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7; |
| | | var monday = date.AddDays(-diff); |
| | | var weekNum = System.Globalization.CultureInfo.InvariantCulture |
| | | .Calendar.GetWeekOfYear(monday, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); |
| | | return $"{monday.Year}-W{weekNum:D2}"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æ¯æç»è®¡ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 注æï¼æ°æ®å¨ SQL å±è¿æ»¤åï¼å¨åºç¨å±æå¹´æåç»ã |
| | | /// SqlSugar ç GroupBy 䏿¯æå¿å对象 (Year, Month) ç´æ¥æ å°å° SQL GROUP BYï¼ |
| | | /// å æ¤éç¨æ¤æ¹å¼ä»¥ç¡®ä¿è·¨æ°æ®åºå
¼å®¹æ§ã |
| | | /// </remarks> |
| | | [HttpGet("MonthlyStats"), AllowAnonymous] |
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12) |
| | | { |
| | | try |
| | | { |
| | | if (months <= 0) months = 12; |
| | | |
| | | var startDate = DateTime.Today.AddMonths(-months + 1); |
| | | startDate = new DateTime(startDate.Year, startDate.Month, 1); |
| | | |
| | | var query = await _db.Queryable<Dt_Task_Hty>() |
| | | .Where(t => t.InsertTime >= startDate) |
| | | .Select(t => new { t.InsertTime, t.TaskType }) |
| | | .ToListAsync(); |
| | | |
| | | var result = query |
| | | .GroupBy(t => new { t.InsertTime.Year, t.InsertTime.Month }) |
| | | .Select(g => new |
| | | { |
| | | Month = $"{g.Key.Year}-{g.Key.Month:D2}", |
| | | Inbound = g.Count(t => t.TaskType >= 500 && t.TaskType < 600), |
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200) |
| | | }) |
| | | .OrderBy(x => x.Month) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"æ¯æç»è®¡è·å失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åºååºé¾åå¸ |
| | | /// </summary> |
| | | [HttpGet("StockAgeDistribution"), AllowAnonymous] |
| | | public async Task<WebResponseContent> StockAgeDistribution() |
| | | { |
| | | try |
| | | { |
| | | var today = DateTime.Today; |
| | | |
| | | // ä½¿ç¨ SQL ç´æ¥åç»ç»è®¡ï¼é¿å
å è½½æææ°æ®å°å
å |
| | | var result = new[] |
| | | { |
| | | new { Range = "7天å
", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 7).CountAsync() }, |
| | | new { Range = "7-30天", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 7 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 30).CountAsync() }, |
| | | new { Range = "30-90天", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 30 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 90).CountAsync() }, |
| | | new { Range = "90天以ä¸", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 90).CountAsync() } |
| | | }; |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"åºååºé¾åå¸è·å失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// åä»åºåºååå¸ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// ä½¿ç¨ SQL GROUP BY 卿°æ®åºå±é¢èåï¼é¿å
å è½½å
¨é¨åºåè®°å½å°å
åã |
| | | /// </remarks> |
| | | [HttpGet("StockByWarehouse"), AllowAnonymous] |
| | | public async Task<WebResponseContent> StockByWarehouse() |
| | | { |
| | | try |
| | | { |
| | | // æ¥è¯¢ä»åºåç§° |
| | | var warehouses = await _db.Queryable<Dt_Warehouse>() |
| | | .Select(w => new { w.WarehouseId, w.WarehouseName }) |
| | | .ToListAsync(); |
| | | var warehouseDict = warehouses.ToDictionary(w => w.WarehouseId, w => w.WarehouseName); |
| | | |
| | | // ä½¿ç¨ SQL GROUP BY 卿°æ®åºå±é¢èåï¼ä»
è¿åèåç»æ |
| | | var stockGroups = await _db.Queryable<Dt_StockInfo>() |
| | | .GroupBy(s => s.WarehouseId) |
| | | .Select(s => new { s.WarehouseId, Count = SqlFunc.AggregateCount(s.Id) }) |
| | | .ToListAsync(); |
| | | |
| | | var result = stockGroups |
| | | .Select(g => new |
| | | { |
| | | Warehouse = warehouseDict.TryGetValue(g.WarehouseId, out var name) ? name : $"ä»åº{g.WarehouseId}", |
| | | Count = g.Count |
| | | }) |
| | | .ToList(); |
| | | |
| | | return WebResponseContent.Instance.OK(null, result); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return WebResponseContent.Instance.Error($"åä»åºåºååå¸è·å失败: {ex.Message}"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | using Microsoft.AspNetCore.Authorization;
|
| | | using Microsoft.AspNetCore.Mvc;
|
| | | using SqlSugar;
|
| | | using WIDESEA_Common.LocationEnum;
|
| | | using WIDESEA_Core;
|
| | | using WIDESEA_Model.Models;
|
| | |
|
| | | namespace WIDESEA_WMSServer.Controllers.Dashboard
|
| | | {
|
| | | /// <summary>
|
| | | /// 仪表ç
|
| | | /// </summary>
|
| | | [Route("api/Dashboard")]
|
| | | [ApiController]
|
| | | public class DashboardController : ControllerBase
|
| | | {
|
| | | private readonly ISqlSugarClient _db;
|
| | |
|
| | | public DashboardController(ISqlSugarClient db)
|
| | | {
|
| | | _db = db;
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// æ»è§æ°æ®
|
| | | /// </summary>
|
| | | [HttpGet("Overview"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> Overview()
|
| | | {
|
| | | try
|
| | | {
|
| | | var today = DateTime.Today;
|
| | | var firstDayOfMonth = new DateTime(today.Year, today.Month, 1);
|
| | |
|
| | | // 仿¥å
¥åºæ°
|
| | | var todayInbound = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= today && t.TaskType >= 500 && t.TaskType < 600)
|
| | | .CountAsync();
|
| | |
|
| | | // 仿¥åºåºæ°
|
| | | var todayOutbound = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= today && t.TaskType >= 100 && t.TaskType < 200)
|
| | | .CountAsync();
|
| | |
|
| | | // æ¬æå
¥åºæ°
|
| | | var monthInbound = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 500 && t.TaskType < 600)
|
| | | .CountAsync();
|
| | |
|
| | | // æ¬æåºåºæ°
|
| | | var monthOutbound = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= firstDayOfMonth && t.TaskType >= 100 && t.TaskType < 200)
|
| | | .CountAsync();
|
| | |
|
| | | // å½åæ»åºå
|
| | | var totalStock = await _db.Queryable<Dt_StockInfo>().CountAsync();
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, new
|
| | | {
|
| | | TodayInbound = todayInbound,
|
| | | TodayOutbound = todayOutbound,
|
| | | MonthInbound = monthInbound,
|
| | | MonthOutbound = monthOutbound,
|
| | | TotalStock = totalStock
|
| | | });
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"æ»è§æ°æ®è·å失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// æ¯æ¥ç»è®¡
|
| | | /// </summary>
|
| | | [HttpGet("DailyStats"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> DailyStats([FromQuery] int days = 30)
|
| | | {
|
| | | try
|
| | | {
|
| | | if (days <= 0) days = 30;
|
| | | if (days > 365) days = 365;
|
| | |
|
| | | var startDate = DateTime.Today.AddDays(-days + 1);
|
| | | var endDate = DateTime.Today; // å
å«ä»å¤©
|
| | |
|
| | | var query = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= startDate && t.InsertTime <= endDate)
|
| | | .Select(t => new { t.InsertTime, t.TaskType })
|
| | | .ToListAsync();
|
| | |
|
| | | // çææ¥æèå´
|
| | | var allDates = new List<DateTime>();
|
| | | for (var date = startDate; date <= endDate; date = date.AddDays(1))
|
| | | {
|
| | | allDates.Add(date);
|
| | | }
|
| | |
|
| | | // ææ¥æåç»ç»è®¡
|
| | | var groupedData = query
|
| | | .GroupBy(t => t.InsertTime.Date)
|
| | | .Select(g => new
|
| | | {
|
| | | Date = g.Key,
|
| | | Inbound = g.Count(t => t.TaskType >= 200 && t.TaskType < 300),
|
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
|
| | | })
|
| | | .ToDictionary(x => x.Date, x => x);
|
| | |
|
| | | // è¡¥å
¨ç¼ºå¤±æ¥æ
|
| | | var result = allDates.Select(date =>
|
| | | {
|
| | | if (groupedData.TryGetValue(date, out var data))
|
| | | {
|
| | | return new
|
| | | {
|
| | | Date = date.ToString("MM-dd"),
|
| | | Inbound = data.Inbound,
|
| | | Outbound = data.Outbound
|
| | | };
|
| | | }
|
| | | else
|
| | | {
|
| | | return new
|
| | | {
|
| | | Date = date.ToString("MM-dd"),
|
| | | Inbound = 0,
|
| | | Outbound = 0
|
| | | };
|
| | | }
|
| | | })
|
| | | .OrderBy(x => x.Date)
|
| | | .ToList();
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, result);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"æ¯æ¥ç»è®¡è·å失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | | /// <summary>
|
| | | /// æ¯å¨ç»è®¡
|
| | | /// </summary>
|
| | | /// <remarks>
|
| | | /// 注æï¼æ°æ®å¨ SQL å±è¿æ»¤åï¼å¨åºç¨å±æ ISO 8601 å¨é®åç»ã
|
| | | /// å¨é®ä¸º "YYYY-Www" æ ¼å¼ï¼æ æ³ç´æ¥å¨ SQL å±ç¨ GROUP BY å®ç°ã
|
| | | /// </remarks>
|
| | | [HttpGet("WeeklyStats"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> WeeklyStats([FromQuery] int weeks = 12)
|
| | | {
|
| | | try
|
| | | {
|
| | | if (weeks <= 0) weeks = 12;
|
| | |
|
| | | var startDate = DateTime.Today.AddDays(-weeks * 7);
|
| | |
|
| | | var query = await _db.Queryable<Dt_Task_Hty>()
|
| | | .Where(t => t.InsertTime >= startDate)
|
| | | .Select(t => new { t.InsertTime, t.TaskType })
|
| | | .ToListAsync();
|
| | |
|
| | | var result = query
|
| | | .GroupBy(t => GetWeekKey(t.InsertTime))
|
| | | .Select(g => new
|
| | | {
|
| | | Week = g.Key,
|
| | | Inbound = g.Count(t => t.TaskType >= 200 && t.TaskType < 300),
|
| | | Outbound = g.Count(t => t.TaskType >= 100 && t.TaskType < 200)
|
| | | })
|
| | | .OrderBy(x => x.Week)
|
| | | .ToList();
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, result);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"æ¯å¨ç»è®¡è·å失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | private string GetWeekKey(DateTime date)
|
| | | {
|
| | | // è·åå¨ä¸å¼å§çå¨ (ISO 8601)
|
| | | var diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
|
| | | var monday = date.AddDays(-diff);
|
| | | var weekNum = System.Globalization.CultureInfo.InvariantCulture
|
| | | .Calendar.GetWeekOfYear(monday, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
|
| | | return $"{monday.Year}-W{weekNum:D2}";
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// æ¯æç»è®¡
|
| | | /// </summary>
|
| | | /// <remarks>
|
| | | /// æå¹´æç»è®¡å
¥ç«ååºç«ä»»å¡æ°é
|
| | | /// </remarks>
|
| | | [HttpGet("MonthlyStats"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> MonthlyStats([FromQuery] int months = 12)
|
| | | {
|
| | | try
|
| | | {
|
| | | if (months <= 0) months = 12;
|
| | |
|
| | | 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)
|
| | | .GroupBy(t => new { t.InsertTime.Year, t.InsertTime.Month })
|
| | | .Select(t => new
|
| | | {
|
| | | Year = t.InsertTime.Year,
|
| | | Month = t.InsertTime.Month,
|
| | | Inbound = SqlFunc.AggregateSum(
|
| | | SqlFunc.IIF(t.TaskType >= 200 && t.TaskType < 300, 1, 0)
|
| | | ),
|
| | | Outbound = SqlFunc.AggregateSum(
|
| | | SqlFunc.IIF(t.TaskType >= 100 && t.TaskType < 200, 1, 0)
|
| | | )
|
| | | })
|
| | | .OrderBy(t => t.Year)
|
| | | .OrderBy(t => t.Month)
|
| | | .ToListAsync();
|
| | |
|
| | | // çæææéè¦ç»è®¡çæä»½å表
|
| | | var allMonths = new List<DateTime>();
|
| | | var currentMonth = startDate;
|
| | | var endMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
|
| | |
|
| | | while (currentMonth <= endMonth)
|
| | | {
|
| | | allMonths.Add(currentMonth);
|
| | | currentMonth = currentMonth.AddMonths(1);
|
| | | }
|
| | |
|
| | | // å°æ¥è¯¢ç»æè½¬æ¢ä¸ºåå
¸ï¼æ¹ä¾¿æ¥æ¾
|
| | | var statsDict = monthlyStats.ToDictionary(
|
| | | s => $"{s.Year}-{s.Month:D2}",
|
| | | s => new { s.Inbound, s.Outbound }
|
| | | );
|
| | |
|
| | | // æå»ºå®æ´çç»æå表ï¼å
嫿ææä»½
|
| | | var result = new List<object>();
|
| | | foreach (var month in allMonths)
|
| | | {
|
| | | var monthKey = $"{month.Year}-{month.Month:D2}";
|
| | |
|
| | | if (statsDict.TryGetValue(monthKey, out var stat))
|
| | | {
|
| | | result.Add(new
|
| | | {
|
| | | Month = monthKey,
|
| | | Inbound = stat.Inbound,
|
| | | Outbound = stat.Outbound
|
| | | });
|
| | | }
|
| | | else
|
| | | {
|
| | | result.Add(new
|
| | | {
|
| | | Month = monthKey,
|
| | | Inbound = 0,
|
| | | Outbound = 0
|
| | | });
|
| | | }
|
| | | }
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, result);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | // è®°å½å¼å¸¸æ¥å¿ï¼å®é
项ç®ä¸å»ºè®®ä½¿ç¨æ¥å¿æ¡æ¶ï¼
|
| | | // _logger.LogError(ex, "æ¯æç»è®¡è·å失败");
|
| | |
|
| | | return WebResponseContent.Instance.Error($"æ¯æç»è®¡è·å失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// åºååºé¾åå¸
|
| | | /// </summary>
|
| | | [HttpGet("StockAgeDistribution"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> StockAgeDistribution()
|
| | | {
|
| | | try
|
| | | {
|
| | | var today = DateTime.Today;
|
| | |
|
| | | // ä½¿ç¨ SQL ç´æ¥åç»ç»è®¡ï¼é¿å
å è½½æææ°æ®å°å
å
|
| | | var result = new[]
|
| | | {
|
| | | new { Range = "7天å
", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 7).CountAsync() },
|
| | | new { Range = "7-30天", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 7 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 30).CountAsync() },
|
| | | new { Range = "30-90天", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 30 && SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) <= 90).CountAsync() },
|
| | | new { Range = "90天以ä¸", Count = await _db.Queryable<Dt_StockInfo>().Where(s => SqlFunc.DateDiff(DateType.Day, s.CreateDate, today) > 90).CountAsync() }
|
| | | };
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, result);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"åºååºé¾åå¸è·å失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// åä»åºåºååå¸
|
| | | /// </summary>
|
| | | /// <remarks>
|
| | | /// ä½¿ç¨ SQL GROUP BY 卿°æ®åºå±é¢èåï¼é¿å
å è½½å
¨é¨åºåè®°å½å°å
åã
|
| | | /// </remarks>
|
| | | [HttpGet("StockByWarehouse"), AllowAnonymous]
|
| | | public async Task<WebResponseContent> StockByWarehouse()
|
| | | {
|
| | | try
|
| | | {
|
| | | // æ¥è¯¢ææä»åºä¿¡æ¯
|
| | | var warehouses = await _db.Queryable<Dt_Warehouse>()
|
| | | .Select(w => new { w.WarehouseId, w.WarehouseName })
|
| | | .ToListAsync();
|
| | |
|
| | | // æ¥è¯¢ææè´§ä½ä¿¡æ¯ï¼æä»åºåç»ç»è®¡æ»æ°
|
| | | var locationGroups = await _db.Queryable<Dt_LocationInfo>()
|
| | | .GroupBy(l => l.WarehouseId)
|
| | | .Select(l => new
|
| | | {
|
| | | WarehouseId = l.WarehouseId,
|
| | | TotalLocations = SqlFunc.AggregateCount(l.Id)
|
| | | })
|
| | | .ToListAsync();
|
| | |
|
| | | // æ¥è¯¢ç¶æä¸ä¸ºFreeçè´§ä½ä¿¡æ¯ï¼æè´§è´§ä½ï¼ï¼æä»åºåç»ç»è®¡
|
| | | var occupiedLocationGroups = await _db.Queryable<Dt_LocationInfo>()
|
| | | .Where(l => l.LocationStatus != (int)LocationStatusEnum.Free)
|
| | | .GroupBy(l => l.WarehouseId)
|
| | | .Select(l => new
|
| | | {
|
| | | WarehouseId = l.WarehouseId,
|
| | | OccupiedLocations = SqlFunc.AggregateCount(l.Id)
|
| | | })
|
| | | .ToListAsync();
|
| | |
|
| | | // å°ä»åºä¿¡æ¯ä¸è´§ä½ç»è®¡ä¿¡æ¯åå¹¶
|
| | | var result = warehouses.Select(w =>
|
| | | {
|
| | | var totalLocations = locationGroups.FirstOrDefault(lg => lg.WarehouseId == w.WarehouseId)?.TotalLocations ?? 0;
|
| | | var occupiedLocations = occupiedLocationGroups.FirstOrDefault(og => og.WarehouseId == w.WarehouseId)?.OccupiedLocations ?? 0;
|
| | | var emptyLocations = totalLocations - occupiedLocations;
|
| | |
|
| | | var occupiedPercentage = totalLocations > 0 ? Math.Round((double)occupiedLocations / totalLocations * 100, 2) : 0.0;
|
| | | var emptyPercentage = totalLocations > 0 ? Math.Round((double)emptyLocations / totalLocations * 100, 2) : 0.0;
|
| | |
|
| | | return new
|
| | | {
|
| | | Warehouse = w.WarehouseName,
|
| | | Total = totalLocations,
|
| | | HasStock = occupiedLocations,
|
| | | NoStock = emptyLocations,
|
| | | HasStockPercentage = $"{occupiedPercentage}%",
|
| | | NoStockPercentage = $"{emptyPercentage}%"
|
| | | };
|
| | | }).ToList();
|
| | |
|
| | | return WebResponseContent.Instance.OK(null, result);
|
| | | }
|
| | | catch (Exception ex)
|
| | | {
|
| | | return WebResponseContent.Instance.Error($"åä»åºåºååå¸è·å失败: {ex.Message}");
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | <WebPublishMethod>FileSystem</WebPublishMethod> |
| | | <_TargetId>Folder</_TargetId> |
| | | <SiteUrlToLaunchAfterPublish /> |
| | | <TargetFramework>net6.0</TargetFramework> |
| | | <TargetFramework>net8.0</TargetFramework> |
| | | <ProjectGuid>d81a65b5-47d1-40c1-8fde-7d24ff003f51</ProjectGuid> |
| | | <SelfContained>false</SelfContained> |
| | | </PropertyGroup> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # RobotState Redis â æ°æ®åºè¿ç§»å®æ½è®¡å |
| | | |
| | | > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| | | |
| | | **Goal:** å° `RobotSocketState` çåå¨ä» Redis åæ¢å° SQL Server æ°æ®åºï¼ä½¿ç¨ SqlSugar RowVersion å®ç°ä¹è§å¹¶åæ§å¶ã |
| | | |
| | | **Architecture:** æ°å¢ `Dt_RobotState` æ°æ®åºå®ä½ + `IRobotStateRepository` ä»å¨æ¥å£ + `RobotStateRepository` å®ç°ã`RobotStateManager` æ¹ä¸ºä¾èµ `IRobotStateRepository`ï¼ä¸å¡å±ï¼`RobotJob` çï¼éè¿ DI è·åä»å¨å®ä¾ã |
| | | |
| | | **Tech Stack:** C# / .NET 6, SqlSugar ORM, SQL Server, Newtonsoft.Json |
| | | |
| | | --- |
| | | |
| | | ## æä»¶åæ´æ»è§ |
| | | |
| | | | æä½ | æä»¶è·¯å¾ | |
| | | |------|---------| |
| | | | æ°å¢ | `WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` | |
| | | | æ°å¢ | `WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs` | |
| | | | æ°å¢ | `WIDESEAWCS_TaskInfoRepository/RobotStateRepository.cs` | |
| | | | æ¹é | `WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` | |
| | | | æ¹é | `WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` | |
| | | | ä¿ç | `WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs`ï¼ä½ä¸ºå
å DTOï¼ | |
| | | |
| | | --- |
| | | |
| | | ## Task 1: æ°å»º Dt_RobotState æ°æ®åºå®ä½ |
| | | |
| | | **æä»¶ï¼** |
| | | - æ°å¢: `WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` |
| | | |
| | | - [ ] **Step 1: å建å®ä½ç±»æä»¶** |
| | | |
| | | ```csharp |
| | | using Newtonsoft.Json; |
| | | using SqlSugar; |
| | | using WIDESEAWCS_Core.DB.Models; |
| | | |
| | | namespace WIDESEAWCS_Model.Models |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶ææ°æ®åºå®ä½ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// å¯¹åºæ°æ®åºè¡¨ Dt_RobotStateï¼ä½¿ç¨ RowVersion å®ç°ä¹è§å¹¶åæ§å¶ã |
| | | /// å¤æå¯¹è±¡ï¼RobotCraneãCurrentTaskãæ°ç»çï¼ä»¥ JSON å符串åå¨ã |
| | | /// </remarks> |
| | | [SugarTable(nameof(Dt_RobotState), "æºæ¢°æç¶æè¡¨")] |
| | | public class Dt_RobotState : BaseEntity |
| | | { |
| | | /// <summary> |
| | | /// ä¸»é® ID |
| | | /// </summary> |
| | | [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主é®ID")] |
| | | public int Id { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æ IP å°åï¼å¯ä¸ç´¢å¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 50, ColumnDescription = "æºæ¢°æIPå°å", IsJsonKey = true)] |
| | | public string IPAddress { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// è¡çæ¬ï¼ç¨äºä¹è§å¹¶åæ§å¶ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// SqlSugar ä¼èªå¨ç®¡çæ¤åæ®µï¼æ¯æ¬¡æ´æ°æ¶æ°æ®åºèªå¨éå¢ã |
| | | /// æ´æ°æ¶ WHERE RowVersion = @expectedRowVersionï¼æ£æ¥å½±åè¡æ°å¤ææ¯å¦å²çªã |
| | | /// </remarks> |
| | | [SugarColumn(ColumnDescription = "è¡çæ¬ï¼ä¹è§éï¼", IsJsonKey = true)] |
| | | public byte[] RowVersion { get; set; } = Array.Empty<byte>(); |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦å·²è®¢é
æ¶æ¯äºä»¶ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦å·²è®¢é
æ¶æ¯äºä»¶", IsJsonKey = true)] |
| | | public bool IsEventSubscribed { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æè¿è¡æ¨¡å¼ |
| | | /// </summary> |
| | | /// <remarks>1: æå¨æ¨¡å¼, 2: èªå¨æ¨¡å¼</remarks> |
| | | [SugarColumn(ColumnDescription = "è¿è¡æ¨¡å¼", IsNullable = true, IsJsonKey = true)] |
| | | public int? RobotRunMode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°ææ§å¶æ¨¡å¼ |
| | | /// </summary> |
| | | /// <remarks>1: 客æ·ç«¯æ§å¶, 2: å
¶ä»</remarks> |
| | | [SugarColumn(ColumnDescription = "æ§å¶æ¨¡å¼", IsNullable = true, IsJsonKey = true)] |
| | | public int? RobotControlMode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°ææèæåå¯¹è±¡ç¶æ |
| | | /// </summary> |
| | | /// <remarks>0: æ ç©æï¼æè空é²ï¼, 1: æç©æï¼å·²æåè´§ç©ï¼</remarks> |
| | | [SugarColumn(ColumnDescription = "æèæåç¶æ", IsNullable = true, IsJsonKey = true)] |
| | | public int? RobotArmObject { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æè®¾å¤åºç¡ä¿¡æ¯ï¼JSON åºååï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "设å¤ä¿¡æ¯JSON", IsJsonKey = true)] |
| | | public string RobotCraneJson { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æåå§å宿åå°å¾
æºä½ç¶æ |
| | | /// </summary> |
| | | /// <remarks>Possible values: "Homed", "Homing"</remarks> |
| | | [SugarColumn(Length = 50, ColumnDescription = "åé¶ç¶æ", IsNullable = true, IsJsonKey = true)] |
| | | public string? Homed { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åæ£å¨æ§è¡çå¨ä½ |
| | | /// </summary> |
| | | [SugarColumn(Length = 50, ColumnDescription = "å½åå¨ä½", IsNullable = true, IsJsonKey = true)] |
| | | public string? CurrentAction { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åè¿è¡ç¶æ |
| | | /// </summary> |
| | | [SugarColumn(Length = 50, ColumnDescription = "è¿è¡ç¶æ", IsNullable = true, IsJsonKey = true)] |
| | | public string? OperStatus { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æè¿ä¸æ¬¡åè´§å®æçä½ç½®æ°ç»ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 500, ColumnDescription = "åè´§ä½ç½®æ°ç»JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? LastPickPositionsJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æè¿ä¸æ¬¡æ¾è´§å®æçä½ç½®æ°ç»ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 500, ColumnDescription = "æ¾è´§ä½ç½®æ°ç»JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? LastPutPositionsJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// çµæ± /è´§ä½æ¡ç å表ï¼JSONï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "çµè¯æ¡ç å表JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? CellBarcodeJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºæ¢°æå½åæ£å¨æ§è¡çä»»å¡ï¼JSON åºååï¼ |
| | | /// </summary> |
| | | [SugarColumn(Length = 2000, ColumnDescription = "å½åä»»å¡JSON", IsNullable = true, IsJsonKey = true)] |
| | | public string? CurrentTaskJson { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦éè¦æ§è¡æçä»»å¡ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦æçä»»å¡", IsJsonKey = true)] |
| | | public bool IsSplitPallet { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦éè¦æ§è¡ç»çä»»å¡ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦ç»çä»»å¡", IsJsonKey = true)] |
| | | public bool IsGroupPallet { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æºå¨äººå·²å¤çç任塿»æ° |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "å·²å¤ç任塿»æ°", IsJsonKey = true)] |
| | | public int RobotTaskTotalNum { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦å¤äºåçµè¯è¡¥å
æ¨¡å¼ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦åçµè¯æ¨¡å¼", IsJsonKey = true)] |
| | | public bool IsInFakeBatteryMode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// å½åæ¹æ¬¡èµ·å§ç¼å· |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "å½åæ¹æ¬¡ç¼å·", IsJsonKey = true)] |
| | | public int CurrentBatchIndex { get; set; } = 1; |
| | | |
| | | /// <summary> |
| | | /// æ¢çä»»å¡å½åé¶æ®µ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¢çé¶æ®µ", IsJsonKey = true)] |
| | | public int ChangePalletPhase { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦æ«ç NG |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "æ¯å¦æ«ç NG", IsJsonKey = true)] |
| | | public bool IsScanNG { get; set; } |
| | | |
| | | /// <summary> |
| | | /// æ¯å¦çµè¯å°ä½ |
| | | /// </summary> |
| | | [SugarColumn(ColumnDescription = "çµè¯æ¯å¦å°ä½", IsJsonKey = true)] |
| | | public bool BatteryArrived { get; set; } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: éªè¯æä»¶å建æå** |
| | | |
| | | Run: `dir "D:\Git\ShanMeiXinNengYuan\Code\WCS\WIDESEAWCS_Server\WIDESEAWCS_Model\Models\RobotState\"` |
| | | Expected: `Dt_RobotState.cs` æä»¶åå¨ |
| | | |
| | | - [ ] **Step 3: æäº¤** |
| | | |
| | | ```bash |
| | | git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs" |
| | | git commit -m "feat(RobotState): æ°å¢ Dt_RobotState æ°æ®åºå®ä½ï¼ä½¿ç¨ RowVersion ä¹è§é" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 2: æ°å»º IRobotStateRepository æ¥å£ |
| | | |
| | | **æä»¶ï¼** |
| | | - æ°å¢: `WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs` |
| | | |
| | | - [ ] **Step 1: å建æ¥å£æä»¶** |
| | | |
| | | ```csharp |
| | | using WIDESEAWCS_Model.Models; |
| | | |
| | | namespace WIDESEAWCS_ITaskInfoRepository |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶æä»å¨æ¥å£ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// å®ä¹æºæ¢°æç¶æçæ°æ®åºè®¿é®æä½ã |
| | | /// å¤æå¯¹è±¡ï¼RobotCraneãCurrentTaskãæ°ç»çï¼å¨è°ç¨æ¹ä½¿ç¨å¼ºç±»åï¼ |
| | | /// 卿¤æ¥å£å±é¢ä»¥ Dt_RobotState å®ä½ä¸ºæä½åä½ã |
| | | /// </remarks> |
| | | public interface IRobotStateRepository |
| | | { |
| | | /// <summary> |
| | | /// æ ¹æ® IP å°åè·åæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <returns>ç¶æå®ä½ï¼ä¸åå¨åè¿å null</returns> |
| | | Dt_RobotState? GetByIp(string ipAddress); |
| | | |
| | | /// <summary> |
| | | /// è·åæåå»ºæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="robotCrane">æºå¨äººè®¾å¤ä¿¡æ¯ï¼ç¨äºåå§åæ°ç¶æ</param> |
| | | /// <returns>ç¶æå®ä½</returns> |
| | | Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane); |
| | | |
| | | /// <summary> |
| | | /// å®å
¨æ´æ°æºæ¢°æç¶æï¼ä¹è§éï¼ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="newState">æ°ç¶æå®ä½ï¼RowVersion ä¼è¢«æ´æ°ï¼</param> |
| | | /// <param name="expectedRowVersion">ææçè¡çæ¬å·ï¼æ´æ°åççæ¬ï¼</param> |
| | | /// <returns>æ¯å¦æ´æ°æåï¼false è¡¨ç¤ºçæ¬å²çªæè®°å½ä¸åå¨</returns> |
| | | bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion); |
| | | |
| | | /// <summary> |
| | | /// å° Dt_RobotState å®ä½è½¬æ¢ä¸º RobotSocketState å
å对象 |
| | | /// </summary> |
| | | /// <param name="entity">æ°æ®åºå®ä½</param> |
| | | /// <returns>å
åç¶æå¯¹è±¡</returns> |
| | | RobotSocketState ToSocketState(Dt_RobotState entity); |
| | | |
| | | /// <summary> |
| | | /// å° RobotSocketState å
å对象转æ¢ä¸º Dt_RobotState å®ä½ |
| | | /// </summary> |
| | | /// <param name="state">å
åç¶æå¯¹è±¡</param> |
| | | /// <returns>æ°æ®åºå®ä½</returns> |
| | | Dt_RobotState ToEntity(RobotSocketState state); |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: æäº¤** |
| | | |
| | | ```bash |
| | | git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs" |
| | | git commit -m "feat(RobotState): æ°å¢ IRobotStateRepository æ¥å£" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 3: æ°å»º RobotStateRepository å®ç° |
| | | |
| | | **æä»¶ï¼** |
| | | - æ°å¢: `WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/RobotStateRepository.cs` |
| | | |
| | | - [ ] **Step 1: å建 RobotStateRepository å®ç°æä»¶** |
| | | |
| | | ```csharp |
| | | using Newtonsoft.Json; |
| | | using SqlSugar; |
| | | using WIDESEAWCS_Core.BaseRepository; |
| | | using WIDESEAWCS_Core.UnitOfWork; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | | using WIDESEAWCS_Model.Models; |
| | | |
| | | namespace WIDESEAWCS_TaskInfoRepository |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶æ SqlSugar ä»å¨å®ç° |
| | | /// </summary> |
| | | public class RobotStateRepository : IUnitOfWork, IRobotStateRepository |
| | | { |
| | | private readonly IUnitOfWorkManage _unitOfWork; |
| | | private readonly SqlSugarClient _db; |
| | | |
| | | public RobotStateRepository(IUnitOfWorkManage unitOfWork) |
| | | { |
| | | _unitOfWork = unitOfWork; |
| | | _db = unitOfWork.GetDbClient(); |
| | | } |
| | | |
| | | public Dt_RobotState? GetByIp(string ipAddress) |
| | | { |
| | | return _db.Queryable<Dt_RobotState>() |
| | | .Where(x => x.IPAddress == ipAddress) |
| | | .First(); |
| | | } |
| | | |
| | | public Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane) |
| | | { |
| | | var existing = GetByIp(ipAddress); |
| | | if (existing != null) |
| | | { |
| | | return existing; |
| | | } |
| | | |
| | | var newState = new Dt_RobotState |
| | | { |
| | | IPAddress = ipAddress, |
| | | RobotCraneJson = JsonConvert.SerializeObject(robotCrane), |
| | | CreateTime = DateTime.Now, |
| | | UpdateTime = DateTime.Now |
| | | }; |
| | | |
| | | _db.Insertable(newState).ExecuteCommand(); |
| | | return newState; |
| | | } |
| | | |
| | | public bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion) |
| | | { |
| | | newState.UpdateTime = DateTime.Now; |
| | | |
| | | var affectedRows = _db.Updateable<Dt_RobotState>(newState) |
| | | .Where(x => x.IPAddress == ipAddress) |
| | | .WhereRowVersion(x => x.RowVersion, expectedRowVersion) |
| | | .ExecuteCommand(); |
| | | |
| | | return affectedRows > 0; |
| | | } |
| | | |
| | | public RobotSocketState ToSocketState(Dt_RobotState entity) |
| | | { |
| | | var state = new RobotSocketState |
| | | { |
| | | IPAddress = entity.IPAddress, |
| | | Version = BitConverter.ToInt64(entity.RowVersion.Length >= 8 ? entity.RowVersion.Take(8).ToArray() : new byte[8], 0), |
| | | IsEventSubscribed = entity.IsEventSubscribed, |
| | | RobotRunMode = entity.RobotRunMode, |
| | | RobotControlMode = entity.RobotControlMode, |
| | | RobotArmObject = entity.RobotArmObject, |
| | | Homed = entity.Homed, |
| | | CurrentAction = entity.CurrentAction, |
| | | OperStatus = entity.OperStatus, |
| | | IsSplitPallet = entity.IsSplitPallet, |
| | | IsGroupPallet = entity.IsGroupPallet, |
| | | RobotTaskTotalNum = entity.RobotTaskTotalNum, |
| | | IsInFakeBatteryMode = entity.IsInFakeBatteryMode, |
| | | CurrentBatchIndex = entity.CurrentBatchIndex, |
| | | ChangePalletPhase = entity.ChangePalletPhase, |
| | | IsScanNG = entity.IsScanNG, |
| | | BatteryArrived = entity.BatteryArrived |
| | | }; |
| | | |
| | | // ååºåå夿 JSON åæ®µ |
| | | if (!string.IsNullOrEmpty(entity.RobotCraneJson)) |
| | | { |
| | | state.RobotCrane = JsonConvert.DeserializeObject<RobotCraneDevice>(entity.RobotCraneJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.CurrentTaskJson)) |
| | | { |
| | | state.CurrentTask = JsonConvert.DeserializeObject<Dt_RobotTask>(entity.CurrentTaskJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.LastPickPositionsJson)) |
| | | { |
| | | state.LastPickPositions = JsonConvert.DeserializeObject<int[]>(entity.LastPickPositionsJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.LastPutPositionsJson)) |
| | | { |
| | | state.LastPutPositions = JsonConvert.DeserializeObject<int[]>(entity.LastPutPositionsJson); |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(entity.CellBarcodeJson)) |
| | | { |
| | | state.CellBarcode = JsonConvert.DeserializeObject<List<string>>(entity.CellBarcodeJson) ?? new List<string>(); |
| | | } |
| | | |
| | | return state; |
| | | } |
| | | |
| | | public Dt_RobotState ToEntity(RobotSocketState state) |
| | | { |
| | | var entity = new Dt_RobotState |
| | | { |
| | | IPAddress = state.IPAddress, |
| | | IsEventSubscribed = state.IsEventSubscribed, |
| | | RobotRunMode = state.RobotRunMode, |
| | | RobotControlMode = state.RobotControlMode, |
| | | RobotArmObject = state.RobotArmObject, |
| | | Homed = state.Homed, |
| | | CurrentAction = state.CurrentAction, |
| | | OperStatus = state.OperStatus, |
| | | IsSplitPallet = state.IsSplitPallet, |
| | | IsGroupPallet = state.IsGroupPallet, |
| | | RobotTaskTotalNum = state.RobotTaskTotalNum, |
| | | IsInFakeBatteryMode = state.IsInFakeBatteryMode, |
| | | CurrentBatchIndex = state.CurrentBatchIndex, |
| | | ChangePalletPhase = state.ChangePalletPhase, |
| | | IsScanNG = state.IsScanNG, |
| | | BatteryArrived = state.BatteryArrived |
| | | }; |
| | | |
| | | // åºååå¤æå¯¹è±¡ä¸º JSON |
| | | if (state.RobotCrane != null) |
| | | { |
| | | entity.RobotCraneJson = JsonConvert.SerializeObject(state.RobotCrane); |
| | | } |
| | | |
| | | if (state.CurrentTask != null) |
| | | { |
| | | entity.CurrentTaskJson = JsonConvert.SerializeObject(state.CurrentTask); |
| | | } |
| | | |
| | | if (state.LastPickPositions != null) |
| | | { |
| | | entity.LastPickPositionsJson = JsonConvert.SerializeObject(state.LastPickPositions); |
| | | } |
| | | |
| | | if (state.LastPutPositions != null) |
| | | { |
| | | entity.LastPutPositionsJson = JsonConvert.SerializeObject(state.LastPutPositions); |
| | | } |
| | | |
| | | if (state.CellBarcode != null && state.CellBarcode.Count > 0) |
| | | { |
| | | entity.CellBarcodeJson = JsonConvert.SerializeObject(state.CellBarcode); |
| | | } |
| | | |
| | | return entity; |
| | | } |
| | | |
| | | public SqlSugarClient GetDbClient() => _db; |
| | | |
| | | public void BeginTran() => _unitOfWork.BeginTran(); |
| | | |
| | | public void CommitTran() => _unitOfWork.CommitTran(); |
| | | |
| | | public void RollbackTran() => _unitOfWork.RollbackTran(); |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 2: æäº¤** |
| | | |
| | | ```bash |
| | | git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoRepository/RobotStateRepository.cs" |
| | | git commit -m "feat(RobotState): å®ç° RobotStateRepositoryï¼å°è£
RowVersion ä¹è§é" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 4: æ¹é RobotStateManager |
| | | |
| | | **æä»¶ï¼** |
| | | - æ¹é : `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` |
| | | |
| | | **åæ´å
容ï¼** |
| | | - å° `ICacheService _cache` æ¿æ¢ä¸º `IRobotStateRepository _repository` |
| | | - å°ææ Redis æä½ï¼`_cache.Get`, `_cache.GetOrAdd`, `_cache.TrySafeUpdate`ï¼æ¿æ¢ä¸ºæ°æ®åºæä½ |
| | | - `TryUpdateStateSafely` æ¹ä¸ºä¸¤æ¥ï¼å
éè¿ `_repository.GetByIp` è·åå½åå®ä½ç `RowVersion`ï¼åè°ç¨ `_repository.TryUpdate` |
| | | - `CloneState` ä¿çï¼ç¨äºå
åä¸çæ·±æ·è´ï¼ä¸æ¶åæ°æ®åºï¼ |
| | | - ç§»é¤ `GetCacheKey` æ¹æ³ï¼ä¸åéè¦ Redis Keyï¼ |
| | | - ä¿çåæç `RobotSocketState` 对象ä½ä¸ºå
å DTOï¼Repository å±è´è´£ä¸ `Dt_RobotState` äºè½¬ |
| | | |
| | | - [ ] **Step 1: 读åå½å RobotStateManager.cs å
¨æï¼å·²å¨ä¸ä¸æï¼** |
| | | |
| | | å½å代ç ä¸å
³é®åæ´ç¹ï¼ |
| | | |
| | | | å代ç | æ¿æ¢ä¸º | |
| | | |--------|--------| |
| | | | `ICacheService _cache` | `IRobotStateRepository _repository` | |
| | | | `RobotStateManager(ICacheService cache, ILogger logger)` | `RobotStateManager(IRobotStateRepository repository, ILogger logger)` | |
| | | | `_cache.Get<RobotSocketState>(cacheKey)` | `_repository.GetByIp(ipAddress)` ç¶å `_repository.ToSocketState(entity)` | |
| | | | `_cache.GetOrAdd(cacheKey, _ => new RobotSocketState{...})` | `_repository.GetOrCreate(ipAddress, robotCrane)` ç¶å `_repository.ToSocketState(entity)` | |
| | | | `_cache.AddObject(cacheKey, newState)` | å¨ `TryUpdate` ç"ä¸åå¨"忝ä¸è°ç¨ INSERT | |
| | | | `_cache.TrySafeUpdate(cacheKey, newState, expectedVersion, s => s.Version)` | `_repository.TryUpdate(ipAddress, entity, expectedRowVersion)` | |
| | | | `GetCacheKey(ipAddress)` | ç§»é¤ | |
| | | |
| | | - [ ] **Step 2: åå
¥æ¹é åç宿´æä»¶** |
| | | |
| | | æ¹é åç宿´ `RobotStateManager.cs`ï¼ |
| | | |
| | | ```csharp |
| | | using Microsoft.Extensions.Logging; |
| | | using Newtonsoft.Json; |
| | | using WIDESEAWCS_Common; |
| | | using WIDESEAWCS_Core.LogHelper; |
| | | using WIDESEAWCS_ITaskInfoRepository; |
| | | using WIDESEAWCS_Model.Models; |
| | | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | | /// <summary> |
| | | /// æºæ¢°æç¶æç®¡çå¨ - è´è´£ RobotSocketState ç线ç¨å®å
¨æ´æ°åå
é |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// æ ¸å¿åè½æ¯éè¿ IRobotStateRepository ç®¡çæ°æ®åºä¸çæºæ¢°æç¶æã |
| | | /// æä¾ä¹è§å¹¶åæ§å¶ï¼éè¿ RowVersion 鲿¢å¹¶åæ´æ°æ¶çæ°æ®è¦çé®é¢ã |
| | | /// </remarks> |
| | | public class RobotStateManager |
| | | { |
| | | /// <summary> |
| | | /// ä»å¨æå¡å®ä¾ï¼ç¨äºè¯»åæ°æ®åºä¸çç¶ææ°æ® |
| | | /// </summary> |
| | | private readonly IRobotStateRepository _repository; |
| | | |
| | | /// <summary> |
| | | /// æ¥å¿è®°å½å¨ |
| | | /// </summary> |
| | | private readonly ILogger _logger; |
| | | |
| | | /// <summary> |
| | | /// æé 彿° |
| | | /// </summary> |
| | | /// <param name="repository">ä»å¨æå¡å®ä¾</param> |
| | | /// <param name="logger">æ¥å¿è®°å½å¨</param> |
| | | public RobotStateManager(IRobotStateRepository repository, ILogger logger) |
| | | { |
| | | _repository = repository; |
| | | _logger = logger; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å®å
¨æ´æ° RobotSocketState ç¼åï¼é²æ¢å¹¶åè¦ç |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 使ç¨ä¹è§å¹¶å模å¼ï¼å
读åå½å RowVersionï¼æ§è¡æ´æ°æ¶æ£æ¥çæ¬æ¯å¦ä¸è´ã |
| | | /// 妿 RowVersion ä¸å¹é
ï¼è¯´ææå
¶ä»çº¿ç¨å·²æ´æ°ï¼ï¼åæ´æ°å¤±è´¥è¿å falseã |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="updateAction">æ´æ°ç¶æç姿彿°ï¼ä¼ å
¥å½åç¶æå¯æ¬ï¼è¿åä¿®æ¹åçæ°ç¶æ</param> |
| | | /// <returns>æ¯å¦æ´æ°æåï¼false è¡¨ç¤ºçæ¬å²çªæç¶æä¸åå¨</returns> |
| | | public bool TryUpdateStateSafely(string ipAddress, Func<RobotSocketState, RobotSocketState> updateAction) |
| | | { |
| | | // 仿°æ®åºè·åå½ååå¨çç¶æ |
| | | var currentEntity = _repository.GetByIp(ipAddress); |
| | | |
| | | if (currentEntity == null) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | // è®°å½å½ååå¨ç RowVersionï¼ä½ä¸ºæ´æ°æ¶çææçæ¬ |
| | | var expectedRowVersion = currentEntity.RowVersion; |
| | | |
| | | // åå»ºç¶æçæ·±æ·è´å¯æ¬ï¼ä½¿ç¨ JSON åºååå®ç°ï¼ |
| | | var stateCopy = CloneState(_repository.ToSocketState(currentEntity)); |
| | | |
| | | // æ§è¡è°ç¨è
æä¾çæ´æ°é»è¾ï¼ä¼ å
¥å¯æ¬ç¶æï¼è·åæ°çç¶æå¯¹è±¡ |
| | | var newState = updateAction(stateCopy); |
| | | |
| | | // å°æ°ç¶æè½¬æ¢ä¸ºæ°æ®åºå®ä½ |
| | | var newEntity = _repository.ToEntity(newState); |
| | | newEntity.RowVersion = Array.Empty<byte>(); // SqlSugar ä¼èªå¨ç®¡ç |
| | | newEntity.Id = currentEntity.Id; |
| | | |
| | | // è°ç¨ä»å¨çå®å
¨æ´æ°æ¹æ³ï¼ä¼ å
¥ææ RowVersion |
| | | // 妿 RowVersion ä¸ä¸è´ï¼å·²è¢«å
¶ä»çº¿ç¨æ´æ°ï¼ï¼åæ´æ°å¤±è´¥ |
| | | return _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å®å
¨æ´æ° RobotSocketState çéè½½çæ¬ï¼ç´æ¥ä¼ å
¥æ°ç¶æï¼ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// ä¸ä¸ä¸ä¸ªéè½½çåºå«ï¼æ¤æ¹æ³ç´æ¥æ¥æ¶å®æ´çæ°ç¶æå¯¹è±¡ï¼è䏿¯æ´æ°å§æã |
| | | /// å¦ææ°æ®åºä¸ä¸åå¨è¯¥è®¾å¤çç¶æï¼åå建æ°è®°å½ã |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="newState">æ°ç¶æå¯¹è±¡</param> |
| | | /// <returns>æ¯å¦æ´æ°æåï¼æ°å»ºè®¾ç½®ä¸º true</returns> |
| | | public bool TryUpdateStateSafely(string ipAddress, RobotSocketState newState) |
| | | { |
| | | // 仿°æ®åºè·åå½ååå¨çç¶æ |
| | | var currentEntity = _repository.GetByIp(ipAddress); |
| | | |
| | | // 妿å½åä¸åå¨è¯¥è®¾å¤çç¶æï¼å建æ°è®°å½ |
| | | if (currentEntity == null) |
| | | { |
| | | var entity = _repository.ToEntity(newState); |
| | | entity.CreateTime = DateTime.Now; |
| | | entity.UpdateTime = DateTime.Now; |
| | | _repository.GetOrCreate(newState.IPAddress, newState.RobotCrane ?? new RobotCraneDevice()); |
| | | _logger.LogDebug("TryUpdateStateSafelyï¼å建æ°ç¶æï¼IP: {IpAddress}", ipAddress); |
| | | QuartzLogger.Debug($"å建æ°ç¶æï¼IP: {ipAddress}", ipAddress); |
| | | return true; |
| | | } |
| | | |
| | | // å½ååå¨ç¶æï¼è®°å½ææ RowVersion ç¨äºä¹è§éæ£æ¥ |
| | | var expectedRowVersion = currentEntity.RowVersion; |
| | | |
| | | // å°æ°ç¶æè½¬æ¢ä¸ºæ°æ®åºå®ä½ |
| | | var newEntity = _repository.ToEntity(newState); |
| | | newEntity.Id = currentEntity.Id; |
| | | newEntity.RowVersion = Array.Empty<byte>(); |
| | | |
| | | // å°è¯å®å
¨æ´æ°ï¼å¦æçæ¬å²çªåè¿å false |
| | | bool success = _repository.TryUpdate(ipAddress, newEntity, expectedRowVersion); |
| | | |
| | | if (!success) |
| | | { |
| | | _logger.LogWarning("TryUpdateStateSafelyï¼çæ¬å²çªï¼æ´æ°å¤±è´¥ï¼IP: {IpAddress}ï¼ææçæ¬åèé¿åº¦: {ExpectedLength}", ipAddress, expectedRowVersion.Length); |
| | | QuartzLogger.Warn($"çæ¬å²çªï¼æ´æ°å¤±è´¥ï¼IP: {ipAddress}", ipAddress); |
| | | } |
| | | |
| | | return success; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// å
é RobotSocketState å¯¹è±¡ï¼æ·±æ·è´ï¼ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// ä½¿ç¨ JSON åºåå/ååºååå®ç°æ·±æ·è´ã |
| | | /// è¿æ ·å¯ä»¥ç¡®ä¿æ°å¯¹è±¡ä¸å对象å®å
¨ç¬ç«ï¼ä¿®æ¹æ°å¯¹è±¡ä¸ä¼å½±åå对象ã |
| | | /// </remarks> |
| | | /// <param name="source">æºç¶æå¯¹è±¡</param> |
| | | /// <returns>æ°çç¶æå¯¹è±¡ï¼æ¯æºå¯¹è±¡çæ·±æ·è´</returns> |
| | | public RobotSocketState CloneState(RobotSocketState source) |
| | | { |
| | | var json = JsonConvert.SerializeObject(source); |
| | | return JsonConvert.DeserializeObject<RobotSocketState>(json) ?? new RobotSocketState { IPAddress = source.IPAddress }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 仿°æ®åºè·åæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <returns>妿åå¨åè¿åç¶æå¯¹è±¡ï¼å¦åè¿å null</returns> |
| | | public RobotSocketState? GetState(string ipAddress) |
| | | { |
| | | var entity = _repository.GetByIp(ipAddress); |
| | | return entity != null ? _repository.ToSocketState(entity) : null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è·åæåå»ºæºæ¢°æç¶æ |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// å¦ææ°æ®åºä¸å·²åå¨è¯¥è®¾å¤çç¶æï¼ç´æ¥è¿åã |
| | | /// 妿ä¸åå¨ï¼åå建æ°çç¶æè®°å½å¹¶è¿åã |
| | | /// </remarks> |
| | | /// <param name="ipAddress">è®¾å¤ IP å°å</param> |
| | | /// <param name="robotCrane">æºå¨äººè®¾å¤ä¿¡æ¯ï¼ç¨äºåå§åæ°ç¶æ</param> |
| | | /// <returns>该设å¤çç¶æå¯¹è±¡</returns> |
| | | public RobotSocketState GetOrCreateState(string ipAddress, RobotCraneDevice robotCrane) |
| | | { |
| | | var entity = _repository.GetOrCreate(ipAddress, robotCrane); |
| | | return _repository.ToSocketState(entity); |
| | | } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | - [ ] **Step 3: æäº¤** |
| | | |
| | | ```bash |
| | | git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs" |
| | | git commit -m "refactor(RobotState): å° RobotStateManager ä» Redis æ¹ä¸ºä¾èµæ°æ®åºä»å¨" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 5: æ¹é RobotJob |
| | | |
| | | **æä»¶ï¼** |
| | | - æ¹é : `WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` |
| | | |
| | | **åæ´å
容ï¼** |
| | | - æé 彿°å¢å `IRobotStateRepository` åæ° |
| | | - å° `new RobotStateManager(cache, _logger)` æ¹ä¸º `new RobotStateManager(repository, _logger)` |
| | | - ç§»é¤æªä½¿ç¨ç `ICacheService cache` åæ°ï¼å¦æåæ¥æï¼ |
| | | |
| | | 注æï¼æ ¹æ® `JobFactory.cs`ï¼`RobotJob` éè¿ `IServiceProvider.GetService(bundle.JobDetail.JobType)` è§£æï¼ |
| | | æææé 彿°åæ°å¦æé½å®ç°äº `IDependency` æ¥å£æè¢« Autofac æ«æå°ï¼å°±ä¼èªå¨æ³¨å
¥ã |
| | | |
| | | - [ ] **Step 1: æ¹é RobotJob æé 彿°** |
| | | |
| | | å¨ `RobotJob` æé 彿°ä¸ï¼ |
| | | |
| | | **忥çæé ï¼**ï¼åèä¸æå·²è¯»åç代ç ï¼ |
| | | ```csharp |
| | | public RobotJob( |
| | | TcpSocketServer tcpSocket, |
| | | IRobotTaskService robotTaskService, |
| | | ITaskService taskService, |
| | | ICacheService cache, |
| | | HttpClientHelper httpClientHelper, |
| | | ILogger<RobotJob> logger, |
| | | IFakeBatteryPositionService fakeBatteryPositionService) |
| | | { |
| | | _stateManager = new RobotStateManager(cache, _logger); |
| | | // ... |
| | | } |
| | | ``` |
| | | |
| | | **æ¹é åï¼** |
| | | ```csharp |
| | | public RobotJob( |
| | | TcpSocketServer tcpSocket, |
| | | IRobotTaskService robotTaskService, |
| | | ITaskService taskService, |
| | | IRobotStateRepository robotStateRepository, |
| | | HttpClientHelper httpClientHelper, |
| | | ILogger<RobotJob> logger, |
| | | IFakeBatteryPositionService fakeBatteryPositionService) |
| | | { |
| | | _stateManager = new RobotStateManager(robotStateRepository, _logger); |
| | | // ... |
| | | } |
| | | ``` |
| | | |
| | | åæ¶å¨ç±»æå声æå¤ï¼ç¡®ä¿ `_stateManager` ç±»åæ£ç¡®ï¼å·²å¨ä¸ä¸æ¥ç RobotStateManager æ¹é ä¸å®æï¼ã |
| | | |
| | | - [ ] **Step 2: æäº¤** |
| | | |
| | | ```bash |
| | | git add "WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs" |
| | | git commit -m "refactor(RobotJob): æé 彿°ä» ICacheService æ¹ä¸º IRobotStateRepository" |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## Task 6: DI 注åéªè¯ |
| | | |
| | | **æä»¶ï¼** |
| | | - æ éæ°å»ºæä»¶ï¼ä½éè¦éªè¯ AutofacModuleRegister çèªå¨æ«æè½è¦çæ°å¢ç `RobotStateRepository` |
| | | |
| | | **说æï¼** |
| | | `AutofacModuleRegister.Load()` 䏿以ä¸èªå¨æ³¨åé»è¾ï¼ |
| | | ```csharp |
| | | builder.RegisterAssemblyTypes(assemblyList.ToArray()) |
| | | .Where(x => !x.IsInterface && !x.IsAbstract && baseType.IsAssignableFrom(x)) |
| | | .AsImplementedInterfaces() |
| | | .PropertiesAutowired() |
| | | .InstancePerDependency() |
| | | .EnableInterfaceInterceptors() |
| | | .InterceptedBy(cacheType.ToArray()); |
| | | ``` |
| | | |
| | | è¿æå³çåªè¦ `RobotStateRepository` å®ç°äº `IRobotStateRepository` æ¥å£å¹¶ä¸ï¼ |
| | | - æå¨ç¨åºé被 `assemblyList` å
å«ï¼`WIDESEAWCS_TaskInfoRepository` æ¯é¡¹ç®å¼ç¨ï¼ |
| | | - å®ç°äº `IDependency`ï¼`IUnitOfWorkManage` ç»§æ¿é¾ä¸æ `IDependency`ï¼ |
| | | |
| | | **ä¸éè¦é¢å¤æ³¨åã** 妿 Autofac éè¦æ¾å¼æ³¨åï¼å¨ `AutofacModuleRegister.cs` 䏿·»å ï¼ |
| | | ```csharp |
| | | builder.RegisterType<RobotStateRepository>().As<IRobotStateRepository>().InstancePerDependency(); |
| | | ``` |
| | | |
| | | - [ ] **Step 1: éªè¯æå»º** |
| | | |
| | | Run: `dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln --no-restore` |
| | | Expected: ç¼è¯æåï¼æ é误 |
| | | |
| | | - [ ] **Step 2: 妿éè¯¯ï¼æ ¹æ®é误信æ¯è°æ´ DI 注å** |
| | | |
| | | --- |
| | | |
| | | ## Task 7: æ´ä½éªè¯ |
| | | |
| | | - [ ] **Step 1: 宿´æå»º** |
| | | |
| | | Run: `dotnet build WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln` |
| | | Expected: BUILD SUCCEEDED |
| | | |
| | | - [ ] **Step 2: æ£æ¥æ¯å¦æéæ¼ç Redis å¼ç¨** |
| | | |
| | | å¨ `RobotJob/` æä»¶å¤¹å
æç´¢æ¯å¦è¿æ `_cache` æ `Redis` ç¸å
³å¼ç¨ï¼ |
| | | ```bash |
| | | grep -r "ICacheService\|_cache\." "WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/" --include="*.cs" |
| | | ``` |
| | | Expected: ä»
RobotStateManager.cs 䏿 `_cache` ç¸å
³å·²æ¿æ¢ï¼æ éæ¼ |
| | | |
| | | - [ ] **Step 3: æäº¤å
¨é¨åæ´** |
| | | |
| | | ```bash |
| | | git add -A |
| | | git commit -m "feat(RobotState): 宿 Redisâæ°æ®åºè¿ç§»ï¼IRobotStateRepository æ¿æ¢ ICacheService" |
| | | ``` |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # RobotState Redis â æ°æ®åºè¿ç§»è®¾è®¡ |
| | | |
| | | **æ¥æ**ï¼2026-04-19 |
| | | **ç¶æ**ï¼å·²æ¹å |
| | | |
| | | ## 1. èæ¯ä¸ç®æ |
| | | |
| | | å° `RobotSocketState` çåå¨ä» Redis åæ¢å° SQL Server æ°æ®åºï¼å©ç¨ SqlSugar ç `RowVersion` å®ç°ä¹è§å¹¶åæ§å¶ã |
| | | |
| | | ### æ¶åèå´ |
| | | |
| | | - `RobotJob/` æä»¶å¤¹ä¸ææä½¿ç¨ Redis 读å `RobotSocketState` ç代ç |
| | | - æ°å»º `Dt_RobotState` æ°æ®åºå®ä½å `IRobotStateRepository` ä»å¨å± |
| | | |
| | | ### 䏿¶ååæ´ |
| | | |
| | | - `RobotWorkflowOrchestrator`ã`RobotTaskProcessor`ã`RobotSimpleCommandHandler` çä¸å¡é»è¾ä¸å |
| | | - ä¾èµ `RobotStateManager` çè°ç¨æ¹ä»£ç ä¸åï¼åªæ¹åå¨å端 |
| | | |
| | | --- |
| | | |
| | | ## 2. æ°æ®åºå®ä½è®¾è®¡ |
| | | |
| | | **表å**ï¼`Dt_RobotState` |
| | | |
| | | **å®ä½è·¯å¾**ï¼`WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` |
| | | |
| | | | åæ®µå | ç±»å | 说æ | |
| | | |--------|------|------| |
| | | | `Id` | int | 主é®ï¼èªå¢ | |
| | | | `IPAddress` | string(50) | 设å¤IPï¼å¯ä¸ç´¢å¼ | |
| | | | `RowVersion` | byte[] | SqlSugar è¡çæ¬ï¼å¹¶åæ§å¶ | |
| | | | `IsEventSubscribed` | bool | æ¯å¦å·²è®¢é
æ¶æ¯ | |
| | | | `RobotRunMode` | int? | è¿è¡æ¨¡å¼ï¼1æå¨ 2èªå¨ï¼ | |
| | | | `RobotControlMode` | int? | æ§å¶æ¨¡å¼ï¼1客æ·ç«¯æ§å¶ 2å
¶ä»ï¼ | |
| | | | `RobotArmObject` | int? | æèæåç¶æï¼0æ ç©æ 1æç©æï¼ | |
| | | | `RobotCraneJson` | string(max) | 设å¤ä¿¡æ¯åºåå JSON | |
| | | | `Homed` | string(50) | åé¶ç¶æï¼Homed/Homingï¼ | |
| | | | `CurrentAction` | string(50) | å½åå¨ä½ï¼Picking/Putting/PickFinished çï¼ | |
| | | | `OperStatus` | string(50) | è¿è¡ç¶æï¼Running/Pausing/Emstoping çï¼ | |
| | | | `LastPickPositionsJson` | string(max) | åè´§ä½ç½®æ°ç» JSON | |
| | | | `LastPutPositionsJson` | string(max) | æ¾è´§ä½ç½®æ°ç» JSON | |
| | | | `CellBarcodeJson` | string(max) | çµè¯æ¡ç å表 JSON | |
| | | | `CurrentTaskJson` | string(max) | å½åä»»å¡ Dt_RobotTask åºåå JSON | |
| | | | `IsSplitPallet` | bool | æ¯å¦æçä»»å¡ | |
| | | | `IsGroupPallet` | bool | æ¯å¦ç»ç/æ¢çä»»å¡ | |
| | | | `RobotTaskTotalNum` | int | å·²å¤ç任塿»æ° | |
| | | | `IsInFakeBatteryMode` | bool | æ¯å¦åçµè¯è¡¥å
æ¨¡å¼ | |
| | | | `CurrentBatchIndex` | int | å½åæ¹æ¬¡èµ·å§ç¼å· | |
| | | | `ChangePalletPhase` | int | æ¢çä»»å¡é¶æ®µï¼0-5ï¼ | |
| | | | `IsScanNG` | bool | æ¯å¦æ«ç NG | |
| | | | `BatteryArrived` | bool | çµè¯æ¯å¦å°ä½ | |
| | | | `CreateTime` | datetime | å建æ¶é´ | |
| | | | `UpdateTime` | datetime | æåæ´æ°æ¶é´ | |
| | | |
| | | **ç´¢å¼**ï¼`IPAddress` å¯ä¸ç´¢å¼ï¼ç¨äºå¿«éå®ä½è®¾å¤ç¶æ |
| | | |
| | | **å¹¶åæ§å¶**ï¼SqlSugar `RowVersion`ï¼æ°æ®åºèªå¨éå¢ï¼æ´æ°æ¶ `WHERE RowVersion = @expected` |
| | | |
| | | --- |
| | | |
| | | ## 3. JSON åºåååæ®µ |
| | | |
| | | 以ä¸å¤æå¯¹è±¡ä»¥ JSON å符串åå¨ï¼ååºååæ¶ä¿æä¸å `RobotSocketState` 屿§å®å
¨å
¼å®¹ï¼ |
| | | |
| | | | JSON åæ®µ | 对åºå屿§ | ååºååç±»å | |
| | | |-----------|-----------|-------------| |
| | | | `RobotCraneJson` | `RobotCrane` | `RobotCraneDevice` | |
| | | | `CurrentTaskJson` | `CurrentTask` | `Dt_RobotTask` | |
| | | | `LastPickPositionsJson` | `LastPickPositions` | `int[]` | |
| | | | `LastPutPositionsJson` | `LastPutPositions` | `int[]` | |
| | | | `CellBarcodeJson` | `CellBarcode` | `List<string>` | |
| | | |
| | | åºååå·¥å
·ï¼`Newtonsoft.Json`ï¼ä¸é¡¹ç®ç°æä¿æä¸è´ï¼ |
| | | |
| | | --- |
| | | |
| | | ## 4. æ¶æåå± |
| | | |
| | | ``` |
| | | è°ç¨æ¹ï¼RobotJob / Workflow / Processorï¼ |
| | | â ä¾èµ |
| | | RobotStateManager |
| | | â ä¾èµ |
| | | IRobotStateRepositoryï¼æ¥å£ï¼ |
| | | â å®ç° |
| | | RobotStateRepositoryï¼SqlSugar å®ç°ï¼ |
| | | â æä½ |
| | | SQL Server (Dt_RobotState 表) |
| | | ``` |
| | | |
| | | ### 4.1 IRobotStateRepository æ¥å£ |
| | | |
| | | ```csharp |
| | | public interface IRobotStateRepository |
| | | { |
| | | /// <summary>æ ¹æ® IP è·åç¶æï¼ä¸åå¨è¿å null</summary> |
| | | Dt_RobotState? GetByIp(string ipAddress); |
| | | |
| | | /// <summary>è·åæåå»ºç¶æï¼æ°æ®åºæ è®°å½æ¶å建ï¼</summary> |
| | | Dt_RobotState GetOrCreate(string ipAddress, RobotCraneDevice robotCrane); |
| | | |
| | | /// <summary>å®å
¨æ´æ°ï¼ä¹è§éï¼ï¼è¿åæ¯å¦æå</summary> |
| | | bool TryUpdate(string ipAddress, Dt_RobotState newState, byte[] expectedRowVersion); |
| | | } |
| | | ``` |
| | | |
| | | ### 4.2 RobotStateRepository å®ç°è¦ç¹ |
| | | |
| | | - 注å
¥ `ISqlSugarClient` |
| | | - `GetOrCreate`ï¼å
æ¥ï¼æ è®°å½å INSERT |
| | | - `TryUpdate`ï¼æ§è¡ `UPDATE ... WHERE RowVersion = @expected`ï¼æ£æ¥å½±åè¡æ° |
| | | - æ°ç»/å¤æå¯¹è±¡ï¼å¨ Repository å±åºåå/ååºååï¼å¯¹å¤æ´é²å¼ºç±»å屿§ |
| | | |
| | | --- |
| | | |
| | | ## 5. RobotStateManager æ¹é |
| | | |
| | | **æä»¶**ï¼`WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` |
| | | |
| | | ### æ¹é å
容 |
| | | |
| | | | 忥ï¼Redisï¼ | æ¹é åï¼DBï¼ | |
| | | |-------------|-------------| |
| | | | `ICacheService _cache` | `IRobotStateRepository _repository` | |
| | | | `GetState(ipAddress)` | `_repository.GetByIp(ipAddress)` | |
| | | | `GetOrCreateState(ipAddress, robotCrane)` | `_repository.GetOrCreate(ipAddress, robotCrane)` | |
| | | | `TryUpdateStateSafely(ipAddress, func)` | å
é¨è°ç¨ `_repository.TryUpdate`ï¼ä½¿ç¨ `RowVersion` ä½ä¸ºææçæ¬ | |
| | | | `CloneState` | ä¿çï¼JSON åºååæ·±æ·è´ï¼ | |
| | | | `GetCacheKey(ipAddress)` | ç§»é¤ï¼ä¸åéè¦ Redis Keyï¼ | |
| | | |
| | | ### æé 彿°åæ´ |
| | | |
| | | ```csharp |
| | | // 忥 |
| | | public RobotStateManager(ICacheService cache, ILogger logger) |
| | | |
| | | // æ¹é å |
| | | public RobotStateManager(IRobotStateRepository repository, ILogger logger) |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 6. RobotJob æé 彿°æ¹é |
| | | |
| | | **æä»¶**ï¼`WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` |
| | | |
| | | ```csharp |
| | | // 忥 |
| | | _stateManager = new RobotStateManager(cache, _logger); |
| | | |
| | | // æ¹é åï¼éè¦éè¿ DI 注å
¥ IRobotStateRepository |
| | | _stateManager = new RobotStateManager( |
| | | ResolvedInstances.FirstOrDefault(typeof(IRobotStateRepository)) as IRobotStateRepository, |
| | | _logger); |
| | | ``` |
| | | |
| | | **å¤éæ¹æ¡**ï¼å¦æ DI 容å¨å¨ Job æé æ¶ä¸ä¾¿è§£æï¼å¯éè¿æ¹æ³åæ°æ³¨å
¥ `IRobotStateRepository`ã |
| | | |
| | | --- |
| | | |
| | | ## 7. æä»¶åæ´æ¸
å |
| | | |
| | | | æä½ | æä»¶è·¯å¾ | |
| | | |------|---------| |
| | | | æ°å¢ | `WIDESEAWCS_Model/Models/RobotState/Dt_RobotState.cs` | |
| | | | æ°å¢ | `WIDESEAWCS_ITaskInfoRepository/IRobotStateRepository.cs` | |
| | | | æ°å¢ | `WIDESEAWCS_ITaskInfoRepository/RobotStateRepository.cs` | |
| | | | æ¹é | `WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs` | |
| | | | æ¹é | `WIDESEAWCS_Tasks/RobotJob/RobotJob.cs` | |
| | | | æ¹é | `WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs`ï¼ç§»é¤æä¿ç为 DTOï¼ï¼ | |
| | | |
| | | **说æ**ï¼`RobotSocketState.cs` 建议ä¿çä½ä¸ºå
åä¸çç¶æå¯¹è±¡ï¼DTOï¼ï¼å¨ Repository å±åå®ä½è½¬æ¢ãä¸å¡å±ç»§ç»ä½¿ç¨ `RobotSocketState`ï¼Repository å±è´è´£ä¸ `Dt_RobotState` äºè½¬ã |
| | | |
| | | --- |
| | | |
| | | ## 8. ä¾èµæ³¨å
¥æ³¨å |
| | | |
| | | å¨ `AutofacModuleRegister` æå¯¹åº DI é
ç½®ä¸æ³¨åï¼ |
| | | |
| | | ```csharp |
| | | builder.RegisterType<RobotStateRepository>().As<IRobotStateRepository>().InstancePerDependency(); |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 9. è¿ç§»æ¥éª¤ï¼å®æ½è®¡åï¼ |
| | | |
| | | 1. **æ°å»º `Dt_RobotState` å®ä½ç±»** |
| | | 2. **æ°å»º `IRobotStateRepository` æ¥å£å `RobotStateRepository` å®ç°** |
| | | 3. **æ¹é `RobotStateManager`**ï¼ä¾èµ Repositoryï¼æ¿æ¢ Redis è°ç¨ |
| | | 4. **æ¹é `RobotJob`**ï¼æ³¨å
¥ Repository å° StateManager |
| | | 5. **æ´æ° `RobotSocketState`**ï¼ä½ä¸ºå
å DTO ä¿çï¼æä¸å®ä½åå¹¶ï¼å¾
å®ï¼ |
| | | 6. **é
ç½® DI 注å** |
| | | 7. **æµè¯éªè¯**ï¼ç¡®ä¿å¹¶åæ´æ°ãç¶ææµè½¬é»è¾ä¸åæ¥ä¸è´ |