You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

489 lines
19 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Haoliang.Models.Production;
using Haoliang.Models.Device;
using Haoliang.Models.DataCollection;
using Haoliang.Data.Repositories;
using Haoliang.Core.Services;
namespace Haoliang.Core.Services
{
public interface IProductionCalculator
{
Task<int> CalculateProductionAsync(DeviceCurrentStatus current, DeviceCurrentStatus last);
Task<int> CalculateProgramSwitchProductionAsync(DeviceCurrentStatus current, DeviceCurrentStatus last);
Task<bool> IsNewProgramAsync(string currentProgram, string lastProgram);
Task HandleCrossDayResetAsync(DeviceCurrentStatus current, DeviceCurrentStatus last);
Task<int> ValidateProductionValueAsync(int quantity, DeviceCurrentStatus current);
Task<ProductionRecord> CreateProductionRecordAsync(int deviceId, DeviceCurrentStatus current, DeviceCurrentStatus last, int quantity);
}
public interface IProductionService
{
Task CalculateProductionAsync(int deviceId);
Task CalculateAllProductionAsync();
Task<ProductionRecord> GetTodayProductionAsync(int deviceId);
Task<IEnumerable<ProductionRecord>> GetProductionByDateAsync(int deviceId, DateTime date);
Task<ProductionStatistics> GetProductionStatisticsAsync(int deviceId, DateTime date);
Task<ProductionSummary> GetProductionSummaryAsync(DateTime date);
Task<decimal> GetQualityRateAsync(int deviceId, DateTime date);
Task<bool> HasProductionDataAsync(int deviceId, DateTime date);
Task ArchiveProductionDataAsync(int daysToKeep = 90);
}
public interface IProductionScheduler
{
Task StartProductionCalculationAsync();
Task StopProductionCalculationAsync();
Task ScheduleProductionCalculationAsync(int deviceId);
Task<DateTime> GetNextCalculationTimeAsync();
Task<bool> IsCalculationRunningAsync();
}
public class ProductionCalculator : IProductionCalculator
{
private readonly IProductionRepository _productionRepository;
private readonly IProgramProductionSummaryRepository _summaryRepository;
public ProductionCalculator(
IProductionRepository productionRepository,
IProgramProductionSummaryRepository summaryRepository)
{
_productionRepository = productionRepository;
_summaryRepository = summaryRepository;
}
public async Task<int> CalculateProductionAsync(DeviceCurrentStatus current, DeviceCurrentStatus last)
{
// 同一程序连续加工
if (current.NCProgram == last.NCProgram)
{
int diff = current.CumulativeCount - last.CumulativeCount;
return Math.Max(0, await ValidateProductionValueAsync(diff, current)); // 异常值保护,避免负数
}
// 程序切换逻辑
return await CalculateProgramSwitchProductionAsync(current, last);
}
public async Task<int> CalculateProgramSwitchProductionAsync(DeviceCurrentStatus current, DeviceCurrentStatus last)
{
// 检查是否切换到新程序
if (await IsNewProgramAsync(current.NCProgram, last.NCProgram))
{
// 新程序以当前累计数为起点
return current.CumulativeCount;
}
else
{
// 切回历史程序,视为重新开始
return 0;
}
}
public async Task<bool> IsNewProgramAsync(string currentProgram, string lastProgram)
{
// 实现程序切换判断逻辑
return currentProgram != lastProgram && !string.IsNullOrEmpty(currentProgram);
}
public async Task HandleCrossDayResetAsync(DeviceCurrentStatus current, DeviceCurrentStatus last)
{
// 跨天处理0点自动重置
if (current.RecordTime.Date != last.RecordTime.Date)
{
// 新日期以首次采集累计值为起点
// 这个逻辑需要在业务服务中实现
var todayProduction = await _productionRepository.GetByDeviceAndDateAsync(
current.DeviceId, current.RecordTime.Date);
if (!todayProduction.Any())
{
// 如果当天没有生产记录,创建一条记录记录当前累计数
var firstRecord = new ProductionRecord
{
DeviceId = current.DeviceId,
NCProgram = current.NCProgram,
ProductionDate = current.RecordTime.Date,
Quantity = 0,
QualityRate = 100,
CreatedAt = DateTime.Now
};
await _productionRepository.AddAsync(firstRecord);
await _productionRepository.SaveAsync();
}
}
}
public async Task<int> ValidateProductionValueAsync(int quantity, DeviceCurrentStatus current)
{
// 跳变检测:产量变化超过阈值时跳过
const int maxJumpThreshold = 1000; // 单次最大产量变化
if (quantity > maxJumpThreshold)
{
await LogWarningAsync($"Production jump detected: {quantity} exceeds threshold {maxJumpThreshold}");
return 0;
}
// 负数保护:产量为负数时归零
if (quantity < 0)
{
await LogWarningAsync("Negative production quantity detected, setting to 0");
return 0;
}
return quantity;
}
public async Task<ProductionRecord> CreateProductionRecordAsync(int deviceId, DeviceCurrentStatus current, DeviceCurrentStatus last, int quantity)
{
var productionRecord = new ProductionRecord
{
DeviceId = deviceId,
NCProgram = current.NCProgram,
ProductionDate = current.RecordTime.Date,
Quantity = quantity,
QualityRate = 100, // 默认合格率,可以根据实际情况调整
StartTime = last != null ? last.RecordTime : (DateTime?)null,
EndTime = current.RecordTime,
CreatedAt = DateTime.Now
};
await _productionRepository.AddAsync(productionRecord);
await _productionRepository.SaveAsync();
// 更新程序产量汇总
await _summaryRepository.UpdateSummaryAsync(
deviceId,
current.NCProgram,
current.RecordTime.Date,
quantity,
productionRecord.QualityRate);
return productionRecord;
}
private async Task LogWarningAsync(string message)
{
// 这里可以实现日志记录逻辑
Console.WriteLine($"Warning: {message}");
}
}
public class ProductionService : IProductionService
{
private readonly IProductionRepository _productionRepository;
private readonly IProgramProductionSummaryRepository _summaryRepository;
private readonly IDeviceRepository _deviceRepository;
private readonly IProductionCalculator _calculator;
private readonly IProductionScheduler _scheduler;
private readonly ICollectionResultRepository _collectionRepository;
private readonly ILoggerService _logger;
public ProductionService(
IProductionRepository productionRepository,
IProgramProductionSummaryRepository summaryRepository,
IDeviceRepository deviceRepository,
IProductionCalculator calculator,
IProductionScheduler scheduler,
ICollectionResultRepository collectionRepository,
ILoggerService logger)
{
_productionRepository = productionRepository;
_summaryRepository = summaryRepository;
_deviceRepository = deviceRepository;
_calculator = calculator;
_scheduler = scheduler;
_collectionRepository = collectionRepository;
_logger = logger;
}
public async Task CalculateProductionAsync(int deviceId)
{
try
{
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (device == null || !device.IsAvailable)
return;
var collectionResults = await _collectionRepository.GetResultsByDeviceIdAsync(deviceId)
.OrderBy(cr => cr.CollectionTime)
.ToListAsync();
for (int i = 1; i < collectionResults.Count; i++)
{
var current = collectionResults[i];
var last = collectionResults[i - 1];
if (current.IsSuccess && last.IsSuccess)
{
var currentStatus = ParseCollectionResult(current);
var lastStatus = ParseCollectionResult(last);
if (currentStatus != null && lastStatus != null)
{
// 处理跨天重置
await _calculator.HandleCrossDayResetAsync(currentStatus, lastStatus);
// 计算产量
var quantity = await _calculator.CalculateProductionAsync(currentStatus, lastStatus);
if (quantity > 0)
{
// 创建生产记录
var productionRecord = await _calculator.CreateProductionRecordAsync(
deviceId, currentStatus, lastStatus, quantity);
await _logger.LogInformationAsync(
$"Production calculated: Device {device.DeviceCode}, " +
$"Program {currentStatus.NCProgram}, Quantity {quantity}");
}
}
}
}
}
catch (Exception ex)
{
await _logger.LogErrorAsync(
$"Failed to calculate production for device {deviceId}: {ex.Message}");
throw;
}
}
public async Task CalculateAllProductionAsync()
{
var devices = await _deviceRepository.GetAvailableDevicesAsync();
foreach (var device in devices)
{
try
{
await CalculateProductionAsync(device.Id);
}
catch (Exception ex)
{
await _logger.LogErrorAsync(
$"Failed to calculate production for device {device.DeviceCode}: {ex.Message}");
}
}
}
public async Task<ProductionRecord> GetTodayProductionAsync(int deviceId)
{
var today = DateTime.Today;
var productions = await _productionRepository.GetByDeviceAndDateAsync(deviceId, today);
return productions.OrderByDescending(p => p.CreatedAt).FirstOrDefault();
}
public async Task<IEnumerable<ProductionRecord>> GetProductionByDateAsync(int deviceId, DateTime date)
{
return await _productionRepository.GetByDeviceAndDateAsync(deviceId, date);
}
public async Task<ProductionStatistics> GetProductionStatisticsAsync(int deviceId, DateTime date)
{
var productions = await _productionRepository.GetByDeviceAndDateAsync(deviceId, date);
var device = await _deviceRepository.GetByIdAsync(deviceId);
if (!productions.Any())
{
return new ProductionStatistics
{
Date = date,
DeviceId = deviceId,
DeviceName = device?.DeviceName ?? "",
TotalQuantity = 0,
ValidQuantity = 0,
InvalidQuantity = 0,
QualityRate = 100
};
}
var totalQuantity = productions.Sum(p => p.Quantity);
var qualityRate = productions.Any() ?
productions.Average(p => p.QualityRate) : 100;
return new ProductionStatistics
{
Date = date,
DeviceId = deviceId,
DeviceName = device?.DeviceName ?? "",
TotalQuantity = totalQuantity,
ValidQuantity = (int)(totalQuantity * qualityRate / 100),
InvalidQuantity = totalQuantity - (int)(totalQuantity * qualityRate / 100),
QualityRate = qualityRate
};
}
public async Task<ProductionSummary> GetProductionSummaryAsync(DateTime date)
{
var devices = await _deviceRepository.GetAllAsync();
var summaries = new List<ProductionStatistics>();
foreach (var device in devices)
{
var stats = await GetProductionStatisticsAsync(device.Id, date);
summaries.Add(stats);
}
var totalQuantity = summaries.Sum(s => s.TotalQuantity);
var totalValidQuantity = summaries.Sum(s => s.ValidQuantity);
var overallQualityRate = totalQuantity > 0 ?
(decimal)totalValidQuantity / totalQuantity * 100 : 100;
return new ProductionSummary
{
Date = date,
DeviceCount = summaries.Count,
TotalQuantity = totalQuantity,
TotalValidQuantity = totalValidQuantity,
TotalInvalidQuantity = totalQuantity - totalValidQuantity,
OverallQualityRate = overallQualityRate,
DeviceStatistics = summaries
};
}
public async Task<decimal> GetQualityRateAsync(int deviceId, DateTime date)
{
return await _productionRepository.GetQualityRateAsync(deviceId, date);
}
public async Task<bool> HasProductionDataAsync(int deviceId, DateTime date)
{
return await _productionRepository.HasProductionDataAsync(deviceId, date);
}
public async Task ArchiveProductionDataAsync(int daysToKeep = 90)
{
var cutoffDate = DateTime.Now.AddDays(-daysToKeep);
var oldProductions = await _productionRepository.FindAsync(
p => p.CreatedAt < cutoffDate);
if (oldProductions.Any())
{
await _productionRepository.RemoveRange(oldProductions);
await _productionRepository.SaveAsync();
await _logger.LogInformationAsync(
$"Archived {oldProductions.Count()} production records older than {daysToKeep} days");
}
}
private DeviceCurrentStatus ParseCollectionResult(CollectionResult result)
{
// 这里需要根据实际的CollectionResult结构来解析
// 暂时返回一个空的实现
return new DeviceCurrentStatus
{
DeviceId = result.DeviceId,
RecordTime = result.CollectionTime,
NCProgram = "",
CumulativeCount = 0
};
}
}
public class ProductionScheduler : IProductionScheduler
{
private readonly IProductionService _productionService;
private readonly ILoggerService _logger;
private Timer _calculationTimer;
private bool _isRunning;
public ProductionScheduler(
IProductionService productionService,
ILoggerService logger)
{
_productionService = productionService;
_logger = logger;
}
public async Task StartProductionCalculationAsync()
{
if (_isRunning)
return;
_isRunning = true;
// 立即执行一次计算
await _productionService.CalculateAllProductionAsync();
// 设置定时器每5分钟执行一次
_calculationTimer = new Timer(async _ =>
{
try
{
await _productionService.CalculateAllProductionAsync();
await _logger.LogInformationAsync("Scheduled production calculation completed");
}
catch (Exception ex)
{
await _logger.LogErrorAsync($"Scheduled production calculation failed: {ex.Message}");
}
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
await _logger.LogInformationAsync("Production calculation scheduler started");
}
public async Task StopProductionCalculationAsync()
{
if (!_isRunning)
return;
_isRunning = false;
_calculationTimer?.Dispose();
_calculationTimer = null;
await _logger.LogInformationAsync("Production calculation scheduler stopped");
}
public async Task ScheduleProductionCalculationAsync(int deviceId)
{
if (!_isRunning)
{
await _logger.LogWarningAsync("Production calculation scheduler is not running");
return;
}
await _productionService.CalculateProductionAsync(deviceId);
}
public async Task<DateTime> GetNextCalculationTimeAsync()
{
if (!_isRunning || _calculationTimer == null)
return DateTime.MinValue;
// 这里可以根据定时器的配置返回下一个执行时间
return DateTime.Now.AddMinutes(5);
}
public async Task<bool> IsCalculationRunningAsync()
{
return _isRunning;
}
}
public class ProductionSummary
{
public DateTime Date { get; set; }
public int DeviceCount { get; set; }
public int TotalQuantity { get; set; }
public int TotalValidQuantity { get; set; }
public int TotalInvalidQuantity { get; set; }
public decimal OverallQualityRate { get; set; }
public List<ProductionStatistics> DeviceStatistics { get; set; }
}
public interface ILoggerService
{
Task LogInformationAsync(string message);
Task LogWarningAsync(string message);
Task LogErrorAsync(string message);
Task LogDebugAsync(string message);
}
}