新增质量模块(工艺执行书 / 样茧试缫)与报表中心
质量模块: - 11 张工艺执行书表:封面、试产、选剥茧(含混茧明细)、试缫与大样、煮茧(含分车间配温、副煮车间)、自动缫(含大样)、后缫 - 10 张样茧试缫表:原料小样/综合样主表 + 选茧、解舒、茧幅、切剖、清洁洁净、茧丝百回纤度 - 服务与接口、主控指针回写(列表 √ 标记)、封面编号自增、报表数据包 - 前端 8 张报表打印版式、7 类工艺单据与 6 类调查录入弹窗、字段驱动表单/表格组件 - 菜单种子新增「质量」目录,样茧子表增加 YjssType 判别列,避免原料小样与综合样自增 Id 重号串数据 报表中心: - 报表模板管理、锐浪 grhtml5 加载与预览、全屏设计面板、数据源 Provider(落丝记录、职工花名册) 其他:同步工作进度 1-11 文档、开发数据库快照、.gitignore 忽略运行日志目录
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.QC;
|
||||
using F9WebAiTest.Application.Services.QC;
|
||||
using F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.QC;
|
||||
|
||||
/// <summary>
|
||||
/// 质量模块 —— 工艺执行书(对应 F9 生产\质量\工艺执行书)。
|
||||
/// 主列表按「庄口 + 年度」展示,每张子单据一个入口;保存子单据会联动主控表。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/qc/gyzxs")]
|
||||
[Authorize]
|
||||
public class GyzxsController : ControllerBase
|
||||
{
|
||||
private readonly IGyzxsService _svc;
|
||||
public GyzxsController(IGyzxsService svc) => _svc = svc;
|
||||
|
||||
// ---------- 主列表 ----------
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] int year = 0, [FromQuery] string keyword = "")
|
||||
=> Ok(ApiResult<PageResult<GyzxsDocVersionDto>>.Ok(await _svc.GetDocVersionPagedAsync(pageIndex, pageSize, year, keyword)));
|
||||
|
||||
[HttpPost("version")]
|
||||
public async Task<IActionResult> CreateVersion([FromQuery] long gyzkId)
|
||||
{
|
||||
try { return Ok(ApiResult<GyzxsDocVersionDto>.Ok(await _svc.CreateDocVersionAsync(gyzkId), "创建成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("version/{id}")]
|
||||
public async Task<IActionResult> DeleteVersion(long id)
|
||||
{
|
||||
try { await _svc.DeleteDocVersionAsync(id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 封面 ----------
|
||||
[HttpGet("fengmian")]
|
||||
public async Task<IActionResult> GetFengMian([FromQuery] long gyzkId)
|
||||
=> Ok(ApiResult<GyzxsFengMian>.Ok(await _svc.GetFengMianAsync(gyzkId)));
|
||||
|
||||
[HttpPost("fengmian")]
|
||||
public async Task<IActionResult> SaveFengMian([FromBody] GyzxsFengMian dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveFengMianAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 试产(试缫工艺单) ----------
|
||||
[HttpGet("shichan")]
|
||||
public async Task<IActionResult> GetShichan([FromQuery] long gyzkId, [FromQuery] string guige = "")
|
||||
=> Ok(ApiResult<GyzxsShichan>.Ok(await _svc.GetShichanAsync(gyzkId, guige)));
|
||||
|
||||
[HttpPost("shichan")]
|
||||
public async Task<IActionResult> SaveShichan([FromBody] GyzxsShichan dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveShichanAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 混剥选茧 ----------
|
||||
[HttpGet("xbj")]
|
||||
public async Task<IActionResult> GetXbj([FromQuery] long gyzkId)
|
||||
=> Ok(ApiResult<GyzxsXbjDto>.Ok(await _svc.GetXbjAsync(gyzkId)));
|
||||
|
||||
[HttpPost("xbj")]
|
||||
public async Task<IActionResult> SaveXbj([FromBody] GyzxsXbjDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveXbjAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 试缫与大样 ----------
|
||||
[HttpGet("ssdy")]
|
||||
public async Task<IActionResult> GetXbjSsDy([FromQuery] long gyzkId, [FromQuery] string guige = "")
|
||||
=> Ok(ApiResult<GyzxsXbjSsDy>.Ok(await _svc.GetXbjSsDyAsync(gyzkId, guige)));
|
||||
|
||||
[HttpPost("ssdy")]
|
||||
public async Task<IActionResult> SaveXbjSsDy([FromBody] GyzxsXbjSsDy dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveXbjSsDyAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 煮茧 ----------
|
||||
[HttpGet("zhujian")]
|
||||
public async Task<IActionResult> GetZhuJian([FromQuery] long gyzkId)
|
||||
=> Ok(ApiResult<GyzxsZhuJianDto>.Ok(await _svc.GetZhuJianAsync(gyzkId)));
|
||||
|
||||
[HttpPost("zhujian")]
|
||||
public async Task<IActionResult> SaveZhuJian([FromBody] GyzxsZhuJianDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveZhuJianAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 自动缫 ----------
|
||||
[HttpGet("zds")]
|
||||
public async Task<IActionResult> GetZds([FromQuery] long gyzkId, [FromQuery] string guige = "")
|
||||
=> Ok(ApiResult<GyzxsZdsDto>.Ok(await _svc.GetZdsAsync(gyzkId, guige)));
|
||||
|
||||
[HttpPost("zds")]
|
||||
public async Task<IActionResult> SaveZds([FromBody] GyzxsZdsDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveZdsAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 后缫(工艺执行书) ----------
|
||||
[HttpGet("hs")]
|
||||
public async Task<IActionResult> GetHs([FromQuery] long gyzkId, [FromQuery] string guige = "")
|
||||
=> Ok(ApiResult<GyzxsHs>.Ok(await _svc.GetHsAsync(gyzkId, guige)));
|
||||
|
||||
[HttpPost("hs")]
|
||||
public async Task<IActionResult> SaveHs([FromBody] GyzxsHs dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveHsAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 报表数据 ----------
|
||||
/// <summary>报表数据(type: fengmian / gyzxs / xbj / shichan / ssdy / zhujian / zds / zdsdy)</summary>
|
||||
[HttpGet("print/{type}")]
|
||||
public async Task<IActionResult> GetPrintData(string type, [FromQuery] long gyzkId, [FromQuery] string guige = "")
|
||||
{
|
||||
try { return Ok(ApiResult<GyzxsPrintDto>.Ok(await _svc.GetPrintDataAsync(type, gyzkId, guige))); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.QC;
|
||||
using F9WebAiTest.Application.Services.QC;
|
||||
using F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers.QC;
|
||||
|
||||
/// <summary>
|
||||
/// 质量模块 —— 样茧试缫(对应 F9 生产\质量\样茧试缫)。
|
||||
/// type: 1=原料小样 2=综合样。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/qc/yjss")]
|
||||
[Authorize]
|
||||
public class YjssController : ControllerBase
|
||||
{
|
||||
private readonly IYjssService _svc;
|
||||
public YjssController(IYjssService svc) => _svc = svc;
|
||||
|
||||
// ---------- 主列表 / 主表 ----------
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] int type = 1, [FromQuery] int year = 0, [FromQuery] string keyword = "")
|
||||
=> Ok(ApiResult<PageResult<YjssListItemDto>>.Ok(await _svc.GetPagedAsync(pageIndex, pageSize, type, year, keyword)));
|
||||
|
||||
[HttpPost("ylxy")]
|
||||
public async Task<IActionResult> SaveYlxy([FromBody] YjssMainYlxy dto)
|
||||
{
|
||||
try { return Ok(ApiResult<YjssListItemDto>.Ok(await _svc.SaveYlxyAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpPost("zhy")]
|
||||
public async Task<IActionResult> SaveZhy([FromBody] YjssMainZhy dto)
|
||||
{
|
||||
try { return Ok(ApiResult<YjssListItemDto>.Ok(await _svc.SaveZhyAsync(dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{type}/{id}")]
|
||||
public async Task<IActionResult> Delete(int type, long id)
|
||||
{
|
||||
try { await _svc.DeleteAsync(type, id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 选茧调查 ----------
|
||||
[HttpGet("xjdc/{yjssId}")]
|
||||
public async Task<IActionResult> GetXjdc(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<YjssDcXjdc>.Ok(await _svc.GetXjdcAsync(type, yjssId)));
|
||||
|
||||
[HttpPost("xjdc")]
|
||||
public async Task<IActionResult> SaveXjdc([FromQuery] int type, [FromBody] YjssDcXjdc dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveXjdcAsync(type, dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 解舒调查 ----------
|
||||
[HttpGet("jsdc/{yjssId}")]
|
||||
public async Task<IActionResult> GetJsdc(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<List<YjssDcJsdc>>.Ok(await _svc.GetJsdcAsync(type, yjssId)));
|
||||
|
||||
[HttpPost("jsdc")]
|
||||
public async Task<IActionResult> SaveJsdc([FromQuery] int type, [FromBody] YjssDcJsdc dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveJsdcAsync(type, dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpDelete("jsdc/{type}/{id}")]
|
||||
public async Task<IActionResult> DeleteJsdc(int type, long id)
|
||||
{
|
||||
try { await _svc.DeleteJsdcAsync(type, id); return Ok(ApiResult<object>.Ok(null, "删除成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 茧幅调查 ----------
|
||||
[HttpGet("jfdc/{yjssId}")]
|
||||
public async Task<IActionResult> GetJfdc(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<YjssJfdcDto>.Ok(await _svc.GetJfdcAsync(type, yjssId)));
|
||||
|
||||
[HttpPost("jfdc")]
|
||||
public async Task<IActionResult> SaveJfdc([FromQuery] int type, [FromBody] YjssJfdcDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveJfdcAsync(type, dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 切剖调查 ----------
|
||||
[HttpGet("qpdc/{yjssId}")]
|
||||
public async Task<IActionResult> GetQpdc(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<YjssDcQpdc>.Ok(await _svc.GetQpdcAsync(type, yjssId)));
|
||||
|
||||
[HttpPost("qpdc")]
|
||||
public async Task<IActionResult> SaveQpdc([FromQuery] int type, [FromBody] YjssDcQpdc dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveQpdcAsync(type, dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 清洁洁净 ----------
|
||||
[HttpGet("qjjj/{yjssId}")]
|
||||
public async Task<IActionResult> GetQjjj(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<YjssDcQjjj>.Ok(await _svc.GetQjjjAsync(type, yjssId)));
|
||||
|
||||
[HttpPost("qjjj")]
|
||||
public async Task<IActionResult> SaveQjjj([FromQuery] int type, [FromBody] YjssDcQjjj dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveQjjjAsync(type, dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 茧丝百回纤度 ----------
|
||||
[HttpGet("jsbhxd/{yjssId}")]
|
||||
public async Task<IActionResult> GetJsbhxd(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<YjssJsbhxdDto>.Ok(await _svc.GetJsbhxdAsync(type, yjssId)));
|
||||
|
||||
[HttpPost("jsbhxd")]
|
||||
public async Task<IActionResult> SaveJsbhxd([FromQuery] int type, [FromBody] YjssJsbhxdDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<bool>.Ok(await _svc.SaveJsbhxdAsync(type, dto), "保存成功")); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
// ---------- 整页 / 报表 ----------
|
||||
[HttpGet("full/{type}/{id}")]
|
||||
public async Task<IActionResult> GetFull(int type, long id)
|
||||
{
|
||||
try { return Ok(ApiResult<YjssFullDto>.Ok(await _svc.GetFullAsync(type, id))); }
|
||||
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
|
||||
}
|
||||
|
||||
[HttpGet("jsdc-summary/{yjssId}")]
|
||||
public async Task<IActionResult> GetJsdcSummary(long yjssId, [FromQuery] int type = 1)
|
||||
=> Ok(ApiResult<Dictionary<string, object?>>.Ok(await _svc.GetJsdcSummaryAsync(type, yjssId)));
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
using System.Security.Claims;
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Application.Services.Auth;
|
||||
using F9WebAiTest.Application.Services.Reports;
|
||||
using F9WebAiTest.Domain.Entities.SYS.RBAC;
|
||||
using F9WebAiTest.Domain.Enums.SYS.RBAC;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers;
|
||||
|
||||
/// <summary>报表中心:报表定义维护 + 模板下载 + 数据查询(Grid++Report 7 Web 嵌入)</summary>
|
||||
[ApiController]
|
||||
[Route("api/sys/report")]
|
||||
[Authorize]
|
||||
public class SysReportController : ControllerBase
|
||||
{
|
||||
private readonly IReportService _reportService;
|
||||
private readonly IFreeSql _fsql;
|
||||
private readonly IJwtService _jwt;
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public SysReportController(IReportService reportService, IFreeSql fsql, IJwtService jwt, IConfiguration config)
|
||||
{
|
||||
_reportService = reportService;
|
||||
_fsql = fsql;
|
||||
_jwt = jwt;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
// ==================== 报表管理 ====================
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged(
|
||||
[FromQuery] string like = "",
|
||||
[FromQuery] string category = "",
|
||||
[FromQuery] bool? isActive = null,
|
||||
[FromQuery] int pageIndex = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
{
|
||||
var data = await _reportService.GetPagedAsync(like ?? "", category ?? "", isActive, pageIndex, pageSize);
|
||||
return Ok(ApiResult<object>.Ok(data));
|
||||
}
|
||||
|
||||
[HttpGet("all")]
|
||||
public async Task<IActionResult> GetAllActive()
|
||||
=> Ok(ApiResult<object>.Ok(await _reportService.GetAllActiveAsync()));
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _reportService.GetByIdAsync(id))); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] ReportCreateDto dto)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(new { id = await _reportService.CreateAsync(dto) }, "新增成功")); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] ReportUpdateDto dto)
|
||||
{
|
||||
try { await _reportService.UpdateAsync(id, dto); return Ok(ApiResult<object>.Ok(new { id }, "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { await _reportService.DeleteAsync(id); return Ok(ApiResult<object>.Ok(new { id }, "删除成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
}
|
||||
|
||||
/// <summary>上传 .grf 模板文件。
|
||||
/// 常规编辑流程已改为「模板随报表一起保存」(POST/PUT 携带 templateJson),
|
||||
/// 本接口保留给 f9designer:// 本机设计器协议回传使用。</summary>
|
||||
[HttpPost("{id}/template")]
|
||||
[RequestSizeLimit(50 * 1024 * 1024)]
|
||||
public async Task<IActionResult> UploadTemplate(long id, IFormFile file)
|
||||
{
|
||||
if (file == null || file.Length == 0) return BadRequest(ApiResult<object>.Fail("请选择要上传的 .grf 模板文件", 400));
|
||||
if (!file.FileName.EndsWith(".grf", StringComparison.OrdinalIgnoreCase))
|
||||
return BadRequest(ApiResult<object>.Fail("仅支持上传 .grf 模板文件", 400));
|
||||
try
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await file.CopyToAsync(ms);
|
||||
await _reportService.UploadTemplateAsync(id, ms.ToArray(), file.FileName);
|
||||
return Ok(ApiResult<object>.Ok(new { size = file.Length }, "上传成功"));
|
||||
}
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接保存模板文本(前端在线编辑「模板源码」后保存,免下载 / 上传 .grf)。
|
||||
/// 与上传接口的区别:这里提交的是文本(JSON 或 "_WR" 压缩文本),不是文件流。
|
||||
/// </summary>
|
||||
[HttpPut("{id}/template/content")]
|
||||
[RequestSizeLimit(50 * 1024 * 1024)]
|
||||
public async Task<IActionResult> SaveTemplateContent(long id, [FromBody] ReportTemplateContentDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var size = await _reportService.SaveTemplateContentAsync(id, dto?.Content ?? "");
|
||||
return Ok(ApiResult<object>.Ok(new { id, size, hasTemplate = true }, "模板已保存"));
|
||||
}
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>下载 .grf 模板文件。保留给 f9designer:// 本机设计器协议(下载 → 编辑 → 自动回传)使用。</summary>
|
||||
[HttpGet("{id}/template/download")]
|
||||
public async Task<IActionResult> DownloadTemplate(long id)
|
||||
{
|
||||
var r = await _fsql.Select<Domain.Entities.SYS.Reports.Report>().Where(x => x.Id == id && !x.IsDeleted).FirstAsync();
|
||||
var content = r == null ? null : await _reportService.GetTemplateContentAsync(r);
|
||||
if (string.IsNullOrEmpty(content))
|
||||
return NotFound(ApiResult<object>.Fail("模板尚未上传", 404));
|
||||
return File(ToGrfBytes(content), "application/octet-stream", $"{r!.Code}.grf");
|
||||
}
|
||||
|
||||
// ==================== 新增报表辅助:数据源 + 结构探测 ====================
|
||||
|
||||
/// <summary>数据源清单(IReportDataProvider 注册表 + 各数据源声明的查询参数)</summary>
|
||||
[HttpGet("datasources")]
|
||||
public async Task<IActionResult> GetDataSources()
|
||||
=> Ok(ApiResult<object>.Ok(await _reportService.GetDataSourcesAsync()));
|
||||
|
||||
/// <summary>
|
||||
/// 探测数据源结构:按 DsName(或 DataSql)执行一次查询,
|
||||
/// 返回可自动配置的查询参数 + 明细字段(列)+ 少量样本行。
|
||||
/// </summary>
|
||||
[HttpPost("scaffold")]
|
||||
public async Task<IActionResult> Scaffold([FromBody] ReportProbeRequestDto dto)
|
||||
{
|
||||
var result = await _reportService.ProbeAsync(dto ?? new ReportProbeRequestDto());
|
||||
return Ok(ApiResult<object>.Ok(result));
|
||||
}
|
||||
|
||||
// ==================== 模板与数据公开端点(供 HTML5 渲染 / 客户端调用) ====================
|
||||
// grhtml5.insertReportViewer 的模板参数是普通 GET,浏览器无法附加 Authorization header,
|
||||
// 故模板/数据接口接受 ?access_token=xxx 方式鉴权(仅内部网络使用,与 /hubs 相同的处理思路)。
|
||||
|
||||
/// <summary>下载报表 .grf 模板(带权限校验,支持 query token 鉴权)。
|
||||
/// 额外暴露 template.json / template.grf 别名:Grid++ 的 JS 库与客户端对 URL 中不含
|
||||
/// ".grf/.txt/.xml/.json" 的请求会用 POST(取模板会误入上传接口)。</summary>
|
||||
[HttpGet("{id}/template")]
|
||||
[HttpGet("{id}/template.json")]
|
||||
[HttpGet("{id}/template.grf")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetTemplate(long id, [FromQuery(Name = "access_token")] string? accessToken = null)
|
||||
{
|
||||
var (userId, isAdmin) = await ResolveCallerAsync(accessToken);
|
||||
if (userId <= 0) return Unauthorized(new { code = 401, message = "未授权" });
|
||||
|
||||
var r = await _fsql.Select<Domain.Entities.SYS.Reports.Report>().Where(x => x.Id == id && !x.IsDeleted).FirstAsync();
|
||||
var content = r == null ? null : await _reportService.GetTemplateContentAsync(r);
|
||||
if (string.IsNullOrEmpty(content))
|
||||
return NotFound(new { code = 404, message = "模板尚未上传" });
|
||||
if (!await CanAccessReportAsync(userId, isAdmin, r!)) return ForbidReport();
|
||||
|
||||
// 不输出 BOM:HTML5 渲染库按 { 或 _WR 前缀判断模板格式,BOM 会破坏该判断
|
||||
return File(System.Text.Encoding.UTF8.GetBytes(content!), "application/json; charset=utf-8", $"{r!.Code}.grf");
|
||||
}
|
||||
|
||||
/// <summary>执行报表数据查询(GET/POST 均可)。data.json 为 GET 别名(客户端 URL 方法判定约定)</summary>
|
||||
[HttpGet("{id}/data")]
|
||||
[HttpGet("{id}/data.json")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetData(long id, [FromQuery] string? access_token = null, [FromQuery] string? params_ = null)
|
||||
=> await RunDataAsync(id, access_token, params_);
|
||||
|
||||
[HttpPost("{id}/data")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> PostData(long id, [FromQuery(Name = "access_token")] string? accessToken = null)
|
||||
{
|
||||
string? paramsJson = null;
|
||||
try
|
||||
{
|
||||
Request.EnableBuffering();
|
||||
Request.Body.Position = 0;
|
||||
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
||||
var body = await reader.ReadToEndAsync();
|
||||
if (!string.IsNullOrEmpty(body)) paramsJson = body;
|
||||
}
|
||||
catch { /* 忽略读取错误 */ }
|
||||
return await RunDataAsync(id, accessToken, paramsJson);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> RunDataAsync(long id, string? accessToken, string? paramsJson)
|
||||
{
|
||||
var (userId, isAdmin) = await ResolveCallerAsync(accessToken);
|
||||
if (userId <= 0) return Unauthorized(new { code = 401, message = "未授权" });
|
||||
|
||||
var r = await _fsql.Select<Domain.Entities.SYS.Reports.Report>().Where(x => x.Id == id && !x.IsDeleted).FirstAsync();
|
||||
if (r == null) return NotFound(new { code = 404, message = "报表不存在" });
|
||||
if (!await CanAccessReportAsync(userId, isAdmin, r)) return ForbidReport();
|
||||
|
||||
var p = ParseParams(paramsJson);
|
||||
try
|
||||
{
|
||||
var data = await _reportService.ExecuteDataAsync(r, p);
|
||||
return Ok(data);
|
||||
}
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
private static System.Collections.Generic.Dictionary<string, object?> ParseParams(string? json)
|
||||
{
|
||||
var result = new System.Collections.Generic.Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
if (string.IsNullOrWhiteSpace(json)) return result;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(json);
|
||||
if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object)
|
||||
{
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
result[prop.Name] = System.Text.Json.JsonSerializer.Deserialize<object?>(prop.Value.GetRawText());
|
||||
}
|
||||
else if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Array)
|
||||
{
|
||||
// 支持 [{name,value}] 形式(与 Grid++ WEB 客户端参数一致)
|
||||
foreach (var el in doc.RootElement.EnumerateArray())
|
||||
{
|
||||
if (el.TryGetProperty("name", out var n) && el.TryGetProperty("value", out var v))
|
||||
result[n.GetString() ?? ""] = System.Text.Json.JsonSerializer.Deserialize<object?>(v.GetRawText());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* 忽略解析错误,返回空 */ }
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 报表中心(用户视图) ====================
|
||||
|
||||
/// <summary>当前用户可见的报表(按权限过滤)</summary>
|
||||
[HttpGet("visible")]
|
||||
public async Task<IActionResult> GetVisible()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var isAdmin = await IsAdminAsync(userId);
|
||||
var perms = await GetPermissionsAsync(userId, isAdmin);
|
||||
return Ok(ApiResult<object>.Ok(await _reportService.GetVisibleReportsAsync(userId, isAdmin, perms)));
|
||||
}
|
||||
|
||||
/// <summary>当前用户预览某报表(带数据)</summary>
|
||||
[HttpGet("{id}/preview")]
|
||||
public async Task<IActionResult> Preview(long id, [FromQuery] string? params_ = null)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var isAdmin = await IsAdminAsync(userId);
|
||||
var perms = await GetPermissionsAsync(userId, isAdmin);
|
||||
try
|
||||
{
|
||||
await _reportService.EnsureReportAccessAsync(userId, isAdmin, perms, id);
|
||||
var r = await _fsql.Select<Domain.Entities.SYS.Reports.Report>().Where(x => x.Id == id && !x.IsDeleted).FirstAsync();
|
||||
if (r == null) return NotFound(ApiResult<object>.Fail("报表不存在", 404));
|
||||
if (r.ReportFileId == 0)
|
||||
return BadRequest(ApiResult<object>.Fail("该报表尚未配置模板", 400));
|
||||
var p = ParseParams(params_);
|
||||
var data = await _reportService.ExecuteDataAsync(r, p);
|
||||
return Ok(ApiResult<object>.Ok(new
|
||||
{
|
||||
report = new
|
||||
{
|
||||
r.Id,
|
||||
r.Code,
|
||||
r.Name,
|
||||
r.Category,
|
||||
r.Description,
|
||||
config = r.Config,
|
||||
hasTemplate = true
|
||||
},
|
||||
templateUrl = $"/api/sys/report/{r.Id}/template.json",
|
||||
data
|
||||
}));
|
||||
}
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
// ==================== 内部辅助 ====================
|
||||
|
||||
private long GetUserId() => long.Parse(User.FindFirst("id")!.Value);
|
||||
|
||||
private async Task<bool> IsAdminAsync(long userId)
|
||||
{
|
||||
var roleIds = await _fsql.Select<UserRole>().Where(ur => ur.UserId == userId).ToListAsync(x => x.RoleId);
|
||||
if (roleIds.Count == 0) return false;
|
||||
return await _fsql.Select<Role>().Where(r => roleIds.Contains(r.Id) && r.Code == "admin").AnyAsync();
|
||||
}
|
||||
|
||||
private async Task<System.Collections.Generic.HashSet<string>> GetPermissionsAsync(long userId, bool isAdmin)
|
||||
{
|
||||
if (isAdmin)
|
||||
return new System.Collections.Generic.HashSet<string>(
|
||||
await _fsql.Select<Permission>().Where(p => !p.IsDeleted && !string.IsNullOrEmpty(p.Code)).ToListAsync(p => p.Code));
|
||||
var roleIds = await _fsql.Select<UserRole>().Where(ur => ur.UserId == userId).ToListAsync(x => x.RoleId);
|
||||
if (roleIds.Count == 0) return new();
|
||||
var permIds = await _fsql.Select<RolePermission>().Where(rp => roleIds.Contains(rp.RoleId) && !rp.IsDeleted).ToListAsync(x => x.PermissionId);
|
||||
if (permIds.Count == 0) return new();
|
||||
var codes = await _fsql.Select<Permission>().Where(p => permIds.Contains(p.Id) && !p.IsDeleted && !string.IsNullOrEmpty(p.Code)).ToListAsync(p => p.Code);
|
||||
return new System.Collections.Generic.HashSet<string>(codes);
|
||||
}
|
||||
|
||||
/// <summary>用 query 中的 access_token 解析用户;优先尝试从 Authorization 头解析;解析失败返回 0。</summary>
|
||||
private async Task<(long UserId, bool IsAdmin)> ResolveCallerAsync(string? accessToken)
|
||||
{
|
||||
// 优先用当前 HTTP 请求上下文(JWT 中间件已解析)
|
||||
if (User?.Identity?.IsAuthenticated == true)
|
||||
return (GetUserId(), await IsAdminAsync(GetUserId()));
|
||||
|
||||
if (string.IsNullOrEmpty(accessToken)) return (0, false);
|
||||
try
|
||||
{
|
||||
var (uid, _) = _jwt.ValidateAccessToken(accessToken);
|
||||
return (uid, await IsAdminAsync(uid));
|
||||
}
|
||||
catch { return (0, false); }
|
||||
}
|
||||
|
||||
private async Task<bool> CanAccessReportAsync(long userId, bool isAdmin, Domain.Entities.SYS.Reports.Report r)
|
||||
{
|
||||
if (isAdmin) return true;
|
||||
if (!r.IsActive) return false;
|
||||
var perms = await GetPermissionsAsync(userId, isAdmin);
|
||||
return perms.Contains($"report:{r.Code}:visble");
|
||||
}
|
||||
|
||||
private IActionResult ForbidReport() => StatusCode(403, new { code = 403, message = "无权访问该报表" });
|
||||
|
||||
/// <summary>模板文本 → .grf 字节(带 UTF-8 BOM,与锐浪设计器保存格式一致)</summary>
|
||||
private static byte[] ToGrfBytes(string content)
|
||||
{
|
||||
var text = content.Length > 0 && content[0] == '\uFEFF' ? content.Substring(1) : content;
|
||||
var enc = new System.Text.UTF8Encoding(true);
|
||||
var pre = enc.GetPreamble();
|
||||
var body = enc.GetBytes(text);
|
||||
var data = new byte[pre.Length + body.Length];
|
||||
Buffer.BlockCopy(pre, 0, data, 0, pre.Length);
|
||||
Buffer.BlockCopy(body, 0, data, pre.Length, body.Length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,16 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
|
||||
};
|
||||
// SignalR 通过 query string 传递 token,需从 access_token 读取
|
||||
// SignalR 与报表模板/数据下载通过 query string 传递 token
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var accessToken = context.Request.Query["access_token"];
|
||||
if (!string.IsNullOrEmpty(accessToken) && context.HttpContext.Request.Path.StartsWithSegments("/hubs"))
|
||||
if (string.IsNullOrEmpty(accessToken)) return Task.CompletedTask;
|
||||
var path = context.HttpContext.Request.Path;
|
||||
if (path.StartsWithSegments("/hubs")
|
||||
|| path.StartsWithSegments("/api/sys/report"))
|
||||
{
|
||||
context.Token = accessToken;
|
||||
}
|
||||
|
||||
Binary file not shown.
+11
@@ -5,6 +5,7 @@ using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.SYS.RBAC;
|
||||
using F9WebAiTest.Domain.Enums.SYS.RBAC;
|
||||
using NotificationTypeEntity = F9WebAiTest.Domain.Entities.Notice.NotificationType;
|
||||
using MenuEntity = F9WebAiTest.Domain.Entities.SYS.RBAC.Menu;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.RBAC.Permissions;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.RBAC.Roles;
|
||||
using MediatR;
|
||||
@@ -21,6 +22,7 @@ public class AssignRolePermissionCommandHandler(
|
||||
IRolePermissionWriteRepository rolePermissionRep,
|
||||
IPermissionWriteRepository permissionWriteRepository,
|
||||
IRepository<NotificationTypeEntity> notificationTypeRepository,
|
||||
IRepository<MenuEntity> menuRepository,
|
||||
ISnowflakeIdGenerator snowflakeIdGenerator,
|
||||
IPublisher eventBus) : IRequestHandler<RolePermission_AssignCommand, long>
|
||||
{
|
||||
@@ -46,6 +48,15 @@ public class AssignRolePermissionCommandHandler(
|
||||
command.ResourceIdentity,
|
||||
command.ResourceId);
|
||||
|
||||
// 菜单/操作权限:Code 对齐菜单上声明的权限标识(如 sys:user:add),
|
||||
// 否则 v-permission 之类按业务权限码判断的地方永远匹配不上
|
||||
if (command.ResourceType is Resource_Type_Enum.菜单 or Resource_Type_Enum.操作)
|
||||
{
|
||||
var menu = await menuRepository.GetByIdAsync(command.ResourceId);
|
||||
if (menu != null && !string.IsNullOrEmpty(menu.Permission))
|
||||
dbPermission.SetCode(menu.Permission);
|
||||
}
|
||||
|
||||
// 消息订阅权限:Code 对齐消息类型的 RequiredPermission(业务权限码),保证订阅校验一致
|
||||
if (command.ResourceType == Resource_Type_Enum.消息订阅)
|
||||
{
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using MediatR;
|
||||
|
||||
namespace F9WebAiTest.Application.CQRS.SYS.Reports.Queries;
|
||||
|
||||
/// <summary>职工花名册报表数据查询(示例,对应 DsName = RepDt_Sys_Users)</summary>
|
||||
public record GetEmployeeDirectoryRepQuery(long DepId, string Like) : IRequest<ReportDto_DataSource>;
|
||||
|
||||
public class GetEmployeeDirectoryRepQueryHandler : IRequestHandler<GetEmployeeDirectoryRepQuery, ReportDto_DataSource>
|
||||
{
|
||||
private readonly IEmployeeReadRepository _readRepository;
|
||||
|
||||
public GetEmployeeDirectoryRepQueryHandler(IEmployeeReadRepository readRepository)
|
||||
{
|
||||
_readRepository = readRepository;
|
||||
}
|
||||
|
||||
public async Task<ReportDto_DataSource> Handle(GetEmployeeDirectoryRepQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var dto = new ReportDto_DataSource
|
||||
{
|
||||
Report_Data = new Dictionary<string, ReportDataDto_Data>()
|
||||
};
|
||||
|
||||
// 1. 通过仓储查询职工数据(部门含子部门)
|
||||
var page = await _readRepository.GetPagedAsync(query.DepId, 0, null, null,
|
||||
string.IsNullOrWhiteSpace(query.Like) ? "" : query.Like, 1, 1000);
|
||||
|
||||
// 2. 部门名称(查询条件展示用)
|
||||
var depName = query.DepId > 0 ? await _readRepository.GetDepNameAsync(query.DepId) : "";
|
||||
|
||||
// 3. 明细数据:属性名须与 .grf 模板 DetailGrid.Recordset.Field 一致
|
||||
var rows = new List<object>();
|
||||
foreach (var e in page.Items)
|
||||
{
|
||||
rows.Add(new
|
||||
{
|
||||
EmployeeCode = e.Code ?? "",
|
||||
EmployeeName = e.Name ?? "",
|
||||
Gender = e.Gender ?? "",
|
||||
DepName = e.DepName ?? "",
|
||||
WorktypeName = e.WorktypeName ?? "",
|
||||
Phone = e.Phone ?? "",
|
||||
RuzhiDate = e.RuzhiDate?.ToString("yyyy-MM-dd") ?? ""
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 报表参数(模板以 StaticBox 的 Parameter 属性引用)
|
||||
var mainData = new ReportDataDto_Data { Field_Data = rows };
|
||||
mainData.Parameter_Data = new
|
||||
{
|
||||
部门 = string.IsNullOrWhiteSpace(depName) ? "(全部)" : depName,
|
||||
关键字 = string.IsNullOrWhiteSpace(query.Like) ? "(全部)" : query.Like,
|
||||
记录数 = page.Total,
|
||||
查询时间 = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||
};
|
||||
|
||||
dto.Report_Data.Add("main", mainData);
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
using MediatR;
|
||||
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
|
||||
namespace F9WebAiTest.Application.CQRS.SYS.Reports.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 落丝记录报表查询(对应 DsName = RepDt_Prod_Lsjl)。
|
||||
/// 参数:日期范围 / 车间(含子车间)/ 机组 / 庄口 / 关键字(庄口、规格、颜色)。
|
||||
/// 输出字段名与 F9 落丝记录报表模板一致(中文列名)。
|
||||
/// </summary>
|
||||
public record GetLsjlRepQuery(
|
||||
DateTime? DateStart,
|
||||
DateTime? DateEnd,
|
||||
long DepId,
|
||||
int Jizu,
|
||||
long GyzkId,
|
||||
string Like) : IRequest<ReportDto_DataSource>;
|
||||
|
||||
public class GetLsjlRepQueryHandler : IRequestHandler<GetLsjlRepQuery, ReportDto_DataSource>
|
||||
{
|
||||
private readonly IRepository<LsjlRecord> _repo;
|
||||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _zkRepo;
|
||||
|
||||
public GetLsjlRepQueryHandler(
|
||||
IRepository<LsjlRecord> repo,
|
||||
IRepository<SysDept.Department> deptRepo,
|
||||
IRepository<CraftZhuangKou> zkRepo)
|
||||
{
|
||||
_repo = repo;
|
||||
_deptRepo = deptRepo;
|
||||
_zkRepo = zkRepo;
|
||||
}
|
||||
|
||||
public async Task<ReportDto_DataSource> Handle(GetLsjlRepQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var all = await _repo.GetAllAsync();
|
||||
var depts = await _deptRepo.GetAllAsync();
|
||||
var zks = await _zkRepo.GetAllAsync();
|
||||
|
||||
var deptDict = depts.GroupBy(d => d.Id).ToDictionary(g => g.Key, g => g.First().Name);
|
||||
var zkDict = zks.GroupBy(z => z.Id).ToDictionary(g => g.Key, g => g.First().ZhuangKouName);
|
||||
|
||||
var depIds = ResolveDepIds(depts, query.DepId);
|
||||
|
||||
IEnumerable<LsjlRecord> rows = all;
|
||||
if (query.DateStart.HasValue)
|
||||
{
|
||||
var start = query.DateStart.Value.Date;
|
||||
rows = rows.Where(x => x.WorkDate.Date >= start);
|
||||
}
|
||||
if (query.DateEnd.HasValue)
|
||||
{
|
||||
var end = query.DateEnd.Value.Date;
|
||||
rows = rows.Where(x => x.WorkDate.Date <= end);
|
||||
}
|
||||
if (depIds != null)
|
||||
rows = rows.Where(x => depIds.Contains(x.DeptId));
|
||||
if (query.Jizu > 0)
|
||||
rows = rows.Where(x => x.Jizu == query.Jizu);
|
||||
if (query.GyzkId > 0)
|
||||
rows = rows.Where(x => x.GyzkId == query.GyzkId);
|
||||
if (!string.IsNullOrWhiteSpace(query.Like))
|
||||
{
|
||||
var kw = query.Like.Trim();
|
||||
rows = rows.Where(x =>
|
||||
deptDict.GetValueOrDefault(x.DeptId, "").Contains(kw) ||
|
||||
zkDict.GetValueOrDefault(x.GyzkId, "").Contains(kw) ||
|
||||
(x.Guige ?? "").Contains(kw) ||
|
||||
(x.Yanse ?? "").Contains(kw));
|
||||
}
|
||||
|
||||
var list = rows
|
||||
.OrderBy(x => x.WorkDate)
|
||||
.ThenBy(x => deptDict.GetValueOrDefault(x.DeptId, ""))
|
||||
.ThenBy(x => x.Jizu)
|
||||
.ThenBy(x => x.LuosiTime)
|
||||
.ToList();
|
||||
|
||||
// 明细行:键名与 .grf 模板 DetailGrid.Recordset.Field 完全一致(中文列名)
|
||||
var fieldRows = new List<object>(list.Count);
|
||||
foreach (var x in list)
|
||||
{
|
||||
var depName = deptDict.GetValueOrDefault(x.DeptId, "");
|
||||
var zkName = zkDict.GetValueOrDefault(x.GyzkId, "");
|
||||
fieldRows.Add(new Dictionary<string, object>(StringComparer.Ordinal)
|
||||
{
|
||||
["日期"] = x.WorkDate.ToString("yyyy-MM-dd"),
|
||||
["车间"] = depName,
|
||||
["机组"] = x.Jizu,
|
||||
["庄口"] = zkName,
|
||||
["回数"] = x.Huishu,
|
||||
["颜色"] = x.Yanse ?? "",
|
||||
["半车"] = x.Isbanche ? "半车" : "整车",
|
||||
["落丝时间"] = x.LuosiTime ?? "",
|
||||
["缫丝桶数"] = x.Chetaishu,
|
||||
// 以下是备选列(模板未使用时不显示,可用于自定义模板)
|
||||
["规格"] = x.Guige ?? "",
|
||||
["车台范围"] = x.ChetaiStart > 0 ? $"{x.ChetaiStart}-{x.ChetaiEnd}" : "",
|
||||
["缫丝时长"] = Math.Round(x.Hours, 2),
|
||||
["车速"] = Math.Round(x.Chesu, 2),
|
||||
});
|
||||
}
|
||||
|
||||
var depText = query.DepId > 0 ? deptDict.GetValueOrDefault(query.DepId, "") : "(全部)";
|
||||
var zkText = query.GyzkId > 0 ? zkDict.GetValueOrDefault(query.GyzkId, "") : "(全部)";
|
||||
var paramData = new ReportDataDto_Data { Field_Data = fieldRows };
|
||||
paramData.Parameter_Data = new
|
||||
{
|
||||
开始日期 = query.DateStart?.ToString("yyyy-MM-dd") ?? "",
|
||||
结束日期 = query.DateEnd?.ToString("yyyy-MM-dd") ?? "",
|
||||
车间 = string.IsNullOrWhiteSpace(depText) ? "(全部)" : depText,
|
||||
机组 = query.Jizu > 0 ? query.Jizu.ToString() : "(全部)",
|
||||
庄口 = string.IsNullOrWhiteSpace(zkText) ? "(全部)" : zkText,
|
||||
记录数 = fieldRows.Count,
|
||||
查询时间 = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
};
|
||||
|
||||
var dto = new ReportDto_DataSource();
|
||||
dto.Report_Data.Add("main", paramData);
|
||||
return dto;
|
||||
}
|
||||
|
||||
/// <summary>车间 Id → 自身 + 全部子孙车间 Id(null 表示不限)</summary>
|
||||
private static HashSet<long>? ResolveDepIds(List<SysDept.Department> depts, long depId)
|
||||
{
|
||||
if (depId <= 0) return null;
|
||||
|
||||
var children = depts
|
||||
.GroupBy(d => d.ParentId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(d => d.Id).ToList());
|
||||
|
||||
var result = new HashSet<long> { depId };
|
||||
var stack = new Stack<long>();
|
||||
stack.Push(depId);
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var cur = stack.Pop();
|
||||
if (!children.TryGetValue(cur, out var kids)) continue;
|
||||
foreach (var k in kids)
|
||||
if (result.Add(k)) stack.Push(k);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ using F9WebAiTest.Application.Services.Auth;
|
||||
using F9WebAiTest.Application.Services.Base;
|
||||
using F9WebAiTest.Application.Services.Notice;
|
||||
using F9WebAiTest.Application.Services.Prod;
|
||||
using F9WebAiTest.Application.Services.QC;
|
||||
using F9WebAiTest.Application.Services.Reports;
|
||||
using F9WebAiTest.Application.Interfaces.Reports;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -25,6 +28,18 @@ public static class DependencyInjection
|
||||
services.AddScoped<IHbjyService, HbjyService>();
|
||||
services.AddScoped<IChejianBanciService, ChejianBanciService>();
|
||||
services.AddScoped<INotificationService, NotificationService>();
|
||||
services.AddScoped<IReportService, ReportService>();
|
||||
|
||||
// 质量模块:工艺执行书 / 样茧试缫
|
||||
services.AddScoped<IGyzxsService, GyzxsService>();
|
||||
services.AddScoped<IYjssService, YjssService>();
|
||||
|
||||
// 报表数据提供者(按 Config.DsName 解析,自动注册所有实现)
|
||||
foreach (var t in typeof(DependencyInjection).Assembly.GetTypes()
|
||||
.Where(t => !t.IsAbstract && typeof(IReportDataProvider).IsAssignableFrom(t)))
|
||||
{
|
||||
services.AddScoped(typeof(IReportDataProvider), t);
|
||||
}
|
||||
|
||||
// MediatR:注册命令/查询 Handler
|
||||
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(NotificationService).Assembly));
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
namespace F9WebAiTest.Application.Dtos.QC;
|
||||
|
||||
/// <summary>工艺执行书主列表行(庄口 + 各子单据完成标记)</summary>
|
||||
public class GyzxsDocVersionDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long GyzkId { get; set; }
|
||||
public string ZhuangKouName { get; set; } = "";
|
||||
public string ZhuangKouCode { get; set; } = "";
|
||||
public int Year { get; set; }
|
||||
public int Sort { get; set; }
|
||||
|
||||
public long ShichanId { get; set; }
|
||||
public long FengMianId { get; set; }
|
||||
public long XbjId { get; set; }
|
||||
public long XbjSsdyId { get; set; }
|
||||
public long ZhuJianId { get; set; }
|
||||
public long ZdsId { get; set; }
|
||||
public long ZdsDyId { get; set; }
|
||||
public long HsId { get; set; }
|
||||
|
||||
/// <summary>该庄口已录入的规格(自动缫 / 后缫 / 试产 / 试缫大样 均按 庄口+规格 唯一)</summary>
|
||||
public List<string> Guiges { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>选剥茧(含混茧比例明细)</summary>
|
||||
public class GyzxsXbjDto
|
||||
{
|
||||
public GyzxsXbj Xbj { get; set; } = new();
|
||||
public List<GyzxsXbjHunJian> HunJian { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>煮茧(含分车间指标、副煮车间结果)</summary>
|
||||
public class GyzxsZhuJianDto
|
||||
{
|
||||
public GyzxsZhuJian ZhuJian { get; set; } = new();
|
||||
public List<GyzxsZhuJianCheJian> CheJian { get; set; } = new();
|
||||
public List<GyzxsZhuJianFuZhu> FuZhu { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>自动缫(含大样资料)</summary>
|
||||
public class GyzxsZdsDto
|
||||
{
|
||||
public GyzxsZds Zds { get; set; } = new();
|
||||
public GyzxsZdsDyzl? Dyzl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报表数据包:Parameters=报表参数区,Main/Sub1/Sub2=结果集。
|
||||
/// 结构与 Grid++Report 的 Report_Data 对齐,便于日后直接对接报表中心。
|
||||
/// </summary>
|
||||
public class GyzxsPrintDto
|
||||
{
|
||||
public string ReportName { get; set; } = "";
|
||||
public string Title { get; set; } = "";
|
||||
public Dictionary<string, object?> Parameters { get; set; } = new();
|
||||
public List<Dictionary<string, object?>> Main { get; set; } = new();
|
||||
public List<Dictionary<string, object?>> Sub1 { get; set; } = new();
|
||||
public List<Dictionary<string, object?>> Sub2 { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
namespace F9WebAiTest.Application.Dtos.QC;
|
||||
|
||||
/// <summary>样茧试缫主列表行(原料小样 / 综合样共用)</summary>
|
||||
public class YjssListItemDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public int Type { get; set; } // 1=原料小样 2=综合样
|
||||
public long ZhuangKouId { get; set; } // 原料庄口 / 工艺庄口
|
||||
public string ZhuangKouName { get; set; } = "";
|
||||
public DateTime Date { get; set; }
|
||||
public int SampleType { get; set; } // 原料小样抽样类型:1定价 2入库 3鲜茧
|
||||
public string SampleTypeName { get; set; } = "";
|
||||
public decimal ZhongLiang { get; set; }
|
||||
public decimal HuiChaoLv { get; set; }
|
||||
public long XjdcId { get; set; }
|
||||
public int JsdcCt { get; set; }
|
||||
public long JfdcId { get; set; }
|
||||
public long QpdcId { get; set; }
|
||||
public long QjjjId { get; set; }
|
||||
public int JsbhxdCt { get; set; }
|
||||
public string Memo { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>茧幅调查(主表 + 明细)</summary>
|
||||
public class YjssJfdcDto
|
||||
{
|
||||
public YjssDcJfdcMain Main { get; set; } = new();
|
||||
public List<YjssDcJfdcItem> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>茧丝百回纤度(汇总 + 明细)</summary>
|
||||
public class YjssJsbhxdDto
|
||||
{
|
||||
public YjssDcJsbhxdMain Main { get; set; } = new();
|
||||
public List<YjssDcJsbhxd> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>样茧试缫整页数据(用于报表 / 打印)</summary>
|
||||
public class YjssFullDto
|
||||
{
|
||||
public int Type { get; set; }
|
||||
public string ZhuangKouName { get; set; } = "";
|
||||
public DateTime Date { get; set; }
|
||||
public decimal ZhongLiang { get; set; }
|
||||
public decimal HuiChaoLv { get; set; }
|
||||
public string SampleTypeName { get; set; } = "";
|
||||
public string Memo { get; set; } = "";
|
||||
|
||||
public YjssDcXjdc? Xjdc { get; set; }
|
||||
public List<YjssDcJsdc> Jsdc { get; set; } = new();
|
||||
public YjssJfdcDto? Jfdc { get; set; }
|
||||
public YjssDcQpdc? Qpdc { get; set; }
|
||||
public YjssDcQjjj? Qjjj { get; set; }
|
||||
public YjssJsbhxdDto? Jsbhxd { get; set; }
|
||||
|
||||
/// <summary>报表参数区(中文键,与 F9 RepDt_Qc_Yjss_* 对齐)</summary>
|
||||
public Dictionary<string, object?> Parameters { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
|
||||
/// <summary>
|
||||
/// 报表数据集(对应 F9 的 ReportDataDto_Data):
|
||||
/// Field_Data 为记录集(每行一个匿名对象,属性名须与 .grf 模板 DetailGrid.Recordset.Field 一致);
|
||||
/// Parameter_Data 为报表参数(账期 / 车间 / 查询时间等,供模板以 Parameter 方式引用)。
|
||||
/// </summary>
|
||||
public class ReportDataDto_Data
|
||||
{
|
||||
public List<object> Field_Data { get; set; } = new();
|
||||
public object? Parameter_Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报表数据源(对应 F9 的 ReportDto_DataSource):
|
||||
/// Report_Data 为「数据集名 → 数据集」的字典,如 "main" / "detail"。
|
||||
/// </summary>
|
||||
public class ReportDto_DataSource
|
||||
{
|
||||
public Dictionary<string, ReportDataDto_Data> Report_Data { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
namespace F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
|
||||
public record ReportListDto(
|
||||
long Id,
|
||||
string Code,
|
||||
string Name,
|
||||
string Category,
|
||||
string Description,
|
||||
int SortOrder,
|
||||
bool IsActive,
|
||||
long ReportFileId,
|
||||
bool HasTemplate,
|
||||
DateTime CreatedAt,
|
||||
DateTime? UpdatedAt);
|
||||
|
||||
public record ReportDetailDto(
|
||||
long Id,
|
||||
string Code,
|
||||
string Name,
|
||||
string Category,
|
||||
string Description,
|
||||
string DataSql,
|
||||
string DataParams,
|
||||
string Config,
|
||||
int SortOrder,
|
||||
bool IsActive,
|
||||
long ReportFileId,
|
||||
bool HasTemplate,
|
||||
DateTime CreatedAt,
|
||||
DateTime? UpdatedAt);
|
||||
|
||||
public record ReportViewDto(
|
||||
long Id,
|
||||
string Code,
|
||||
string Name,
|
||||
string Category,
|
||||
string Description,
|
||||
int SortOrder,
|
||||
bool HasTemplate);
|
||||
|
||||
public class ReportCreateDto
|
||||
{
|
||||
public string Code { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public string? Category { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? DataSql { get; set; }
|
||||
public string? DataParams { get; set; }
|
||||
public string? Config { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报表模板(.grf 的 JSON 文本)。与报表配置一起保存,无需再单独上传模板文件。
|
||||
/// 为空表示本次不改动模板。
|
||||
/// </summary>
|
||||
public string? TemplateJson { get; set; }
|
||||
}
|
||||
|
||||
public class ReportUpdateDto
|
||||
{
|
||||
public string Code { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public string? Category { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? DataSql { get; set; }
|
||||
public string? DataParams { get; set; }
|
||||
public string? Config { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报表模板(.grf 的 JSON 文本)。与报表配置一起保存,无需再单独上传模板文件。
|
||||
/// 为空表示本次不改动模板(保留原模板)。
|
||||
/// </summary>
|
||||
public string? TemplateJson { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>在线编辑模板源码后直接保存的内容(免下载 / 上传 .grf)</summary>
|
||||
public class ReportTemplateContentDto
|
||||
{
|
||||
/// <summary>模板全文:GR6 时代的 UTF-8 JSON 文本,或 GR7 设计器的 "_WR" 压缩文本</summary>
|
||||
public string? Content { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
|
||||
/// <summary>已注册的报表数据源(IReportDataProvider.ReportName)</summary>
|
||||
public class ReportDataSourceInfoDto
|
||||
{
|
||||
public string DsName { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>明细字段(.grf 模板 DetailGrid.Recordset.Field 的映射来源),全部由查询结果推断</summary>
|
||||
public class ReportFieldDefDto
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Label { get; set; } = "";
|
||||
public string DataType { get; set; } = "String";
|
||||
}
|
||||
|
||||
/// <summary>探测请求:按 DsName(或 DataSql)执行一次查询,识别返回的列</summary>
|
||||
public class ReportProbeRequestDto
|
||||
{
|
||||
/// <summary>数据源名称(IReportDataProvider.ReportName),优先于 DataSql</summary>
|
||||
public string? DsName { get; set; }
|
||||
|
||||
/// <summary>数据 SQL(无 DsName 时用于探测列)</summary>
|
||||
public string? DataSql { get; set; }
|
||||
|
||||
/// <summary>执行探测时传入的参数值(键 = 参数名;报表上已配置的参数由前端带入)</summary>
|
||||
public Dictionary<string, object?> Params { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报表参数(后端 DataSource.Parameter_Data 的键值)。
|
||||
/// 注意:这是「报表参数」——由查询执行后返回、供模板头展示的统计口径;
|
||||
/// 与 Config.Parms 里的「查询条件」(q_*,用于查询面板传参)不是一回事。
|
||||
/// </summary>
|
||||
public class ReportParamValueDto
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>探测结果:查询返回的明细字段 + 报表参数 + 少量样本行</summary>
|
||||
public class ReportProbeResultDto
|
||||
{
|
||||
public string DsName { get; set; } = "";
|
||||
public List<ReportFieldDefDto> Fields { get; set; } = new();
|
||||
|
||||
/// <summary>报表参数(Parameter_Data 的键值),模板头/合计区直接引用</summary>
|
||||
public List<ReportParamValueDto> Parameters { get; set; } = new();
|
||||
|
||||
public List<Dictionary<string, object?>> Rows { get; set; } = new();
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>探测失败原因</summary>
|
||||
public string? Error { get; set; }
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using MediatR;
|
||||
|
||||
namespace F9WebAiTest.Application.Interfaces.Reports;
|
||||
|
||||
/// <summary>
|
||||
/// 报表数据提供者(对应 F9 的 IReportService):
|
||||
/// 报表配置 Config.DsName 指定查询名称,后端按名称解析到对应实现执行查询。
|
||||
/// 实现类注册到 DI 后自动被发现(静态 ReportName 属性作为解析键)。
|
||||
/// 查询实现应通过 MediatR 发送 CQRS Query,由 QueryHandler 组装 ReportDto_DataSource。
|
||||
/// </summary>
|
||||
public interface IReportDataProvider
|
||||
{
|
||||
/// <summary>查询名称(对应报表配置中的 DsName,如 "RepDt_Sys_Users")</summary>
|
||||
string ReportName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行查询。pairs 为前端查询控件收集的键值对(键 = Config.Parms[].Name);
|
||||
/// 返回含记录集(Field_Data)与报表参数(Parameter_Data)的数据源。
|
||||
/// </summary>
|
||||
Task<ReportDto_DataSource> GetData(Dictionary<string, object> pairs, IMediator mediator);
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.QC;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.QC;
|
||||
|
||||
public interface IGyzxsService
|
||||
{
|
||||
Task<PageResult<GyzxsDocVersionDto>> GetDocVersionPagedAsync(int pageIndex, int pageSize, int year, string keyword);
|
||||
Task<GyzxsDocVersionDto> CreateDocVersionAsync(long gyzkId);
|
||||
Task DeleteDocVersionAsync(long id);
|
||||
|
||||
Task<GyzxsFengMian> GetFengMianAsync(long gyzkId);
|
||||
Task<bool> SaveFengMianAsync(GyzxsFengMian dto);
|
||||
|
||||
Task<GyzxsShichan> GetShichanAsync(long gyzkId, string guige);
|
||||
Task<bool> SaveShichanAsync(GyzxsShichan dto);
|
||||
|
||||
Task<GyzxsXbjDto> GetXbjAsync(long gyzkId);
|
||||
Task<bool> SaveXbjAsync(GyzxsXbjDto dto);
|
||||
|
||||
Task<GyzxsXbjSsDy> GetXbjSsDyAsync(long gyzkId, string guige);
|
||||
Task<bool> SaveXbjSsDyAsync(GyzxsXbjSsDy dto);
|
||||
|
||||
Task<GyzxsZhuJianDto> GetZhuJianAsync(long gyzkId);
|
||||
Task<bool> SaveZhuJianAsync(GyzxsZhuJianDto dto);
|
||||
|
||||
Task<GyzxsZdsDto> GetZdsAsync(long gyzkId, string guige);
|
||||
Task<bool> SaveZdsAsync(GyzxsZdsDto dto);
|
||||
|
||||
Task<GyzxsHs> GetHsAsync(long gyzkId, string guige);
|
||||
Task<bool> SaveHsAsync(GyzxsHs dto);
|
||||
|
||||
/// <summary>报表数据(type: fengmian / gyzxs / xbj / shichan / ssdy / zhujian / zds / zdsdy)</summary>
|
||||
Task<GyzxsPrintDto> GetPrintDataAsync(string type, long gyzkId, string guige);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 质量模块 —— 工艺执行书(对应 F9 生产\质量\工艺执行书)。
|
||||
/// 设计要点:以工艺庄口为主体,7 张子单据各自按「庄口(+规格)」唯一,
|
||||
/// 任一子单据保存都会 upsert 主控表 qc_gyzxs_doc_version 的对应指针列。
|
||||
/// </summary>
|
||||
public class GyzxsService : IGyzxsService
|
||||
{
|
||||
private readonly IRepository<CraftZhuangKou> _zkRepo;
|
||||
private readonly IRepository<GyzxsDocVersion> _versionRepo;
|
||||
private readonly IRepository<GyzxsFengMian> _fengMianRepo;
|
||||
private readonly IRepository<GyzxsShichan> _shichanRepo;
|
||||
private readonly IRepository<GyzxsXbj> _xbjRepo;
|
||||
private readonly IRepository<GyzxsXbjHunJian> _hunjianRepo;
|
||||
private readonly IRepository<GyzxsXbjSsDy> _ssdyRepo;
|
||||
private readonly IRepository<GyzxsZhuJian> _zhuJianRepo;
|
||||
private readonly IRepository<GyzxsZhuJianCheJian> _zjCheJianRepo;
|
||||
private readonly IRepository<GyzxsZhuJianFuZhu> _zjFuZhuRepo;
|
||||
private readonly IRepository<GyzxsZds> _zdsRepo;
|
||||
private readonly IRepository<GyzxsZdsDyzl> _zdsDyRepo;
|
||||
private readonly IRepository<GyzxsHs> _hsRepo;
|
||||
|
||||
public GyzxsService(
|
||||
IRepository<CraftZhuangKou> zkRepo,
|
||||
IRepository<GyzxsDocVersion> versionRepo,
|
||||
IRepository<GyzxsFengMian> fengMianRepo,
|
||||
IRepository<GyzxsShichan> shichanRepo,
|
||||
IRepository<GyzxsXbj> xbjRepo,
|
||||
IRepository<GyzxsXbjHunJian> hunjianRepo,
|
||||
IRepository<GyzxsXbjSsDy> ssdyRepo,
|
||||
IRepository<GyzxsZhuJian> zhuJianRepo,
|
||||
IRepository<GyzxsZhuJianCheJian> zjCheJianRepo,
|
||||
IRepository<GyzxsZhuJianFuZhu> zjFuZhuRepo,
|
||||
IRepository<GyzxsZds> zdsRepo,
|
||||
IRepository<GyzxsZdsDyzl> zdsDyRepo,
|
||||
IRepository<GyzxsHs> hsRepo)
|
||||
{
|
||||
_zkRepo = zkRepo;
|
||||
_versionRepo = versionRepo;
|
||||
_fengMianRepo = fengMianRepo;
|
||||
_shichanRepo = shichanRepo;
|
||||
_xbjRepo = xbjRepo;
|
||||
_hunjianRepo = hunjianRepo;
|
||||
_ssdyRepo = ssdyRepo;
|
||||
_zhuJianRepo = zhuJianRepo;
|
||||
_zjCheJianRepo = zjCheJianRepo;
|
||||
_zjFuZhuRepo = zjFuZhuRepo;
|
||||
_zdsRepo = zdsRepo;
|
||||
_zdsDyRepo = zdsDyRepo;
|
||||
_hsRepo = hsRepo;
|
||||
}
|
||||
|
||||
// ==================== 主控(庄口年度列表) ====================
|
||||
|
||||
public async Task<PageResult<GyzxsDocVersionDto>> GetDocVersionPagedAsync(int pageIndex, int pageSize, int year, string keyword)
|
||||
{
|
||||
if (year <= 0) year = DateTime.Today.Year;
|
||||
|
||||
var zks = (await _zkRepo.FindAsync(x => !x.IsDeleted && x.BelongingYear == year))
|
||||
.OrderBy(x => x.Sort).ThenBy(x => x.ZhuangKouCode).ToList();
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
zks = zks.Where(x => x.ZhuangKouName.Contains(keyword) || x.ZhuangKouCode.Contains(keyword)
|
||||
|| x.ProductionDisplayName.Contains(keyword)).ToList();
|
||||
|
||||
var versions = (await _versionRepo.FindAsync(x => !x.IsDeleted))
|
||||
.GroupBy(x => x.GyzkId).ToDictionary(g => g.Key, g => g.OrderByDescending(v => v.Id).First());
|
||||
|
||||
// 规格集合:来自试产 / 自动缫 / 后缫 / 试缫大样
|
||||
var guigeMap = new Dictionary<long, HashSet<string>>();
|
||||
void Collect(IEnumerable<(long GyzkId, string Guige)> rows)
|
||||
{
|
||||
foreach (var (gk, gg) in rows)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(gg)) continue;
|
||||
if (!guigeMap.TryGetValue(gk, out var set)) guigeMap[gk] = set = new HashSet<string>();
|
||||
set.Add(gg);
|
||||
}
|
||||
}
|
||||
Collect((await _shichanRepo.FindAsync(x => !x.IsDeleted)).Select(x => (x.GyzkId, x.Guige)));
|
||||
Collect((await _zdsRepo.FindAsync(x => !x.IsDeleted)).Select(x => (x.GyzkId, x.Guige)));
|
||||
Collect((await _hsRepo.FindAsync(x => !x.IsDeleted)).Select(x => (x.GyzkId, x.Guige)));
|
||||
Collect((await _ssdyRepo.FindAsync(x => !x.IsDeleted)).Select(x => (x.GyzkId, x.Guige)));
|
||||
|
||||
var total = zks.Count;
|
||||
var page = zks.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<GyzxsDocVersionDto>
|
||||
{
|
||||
Total = total,
|
||||
PageIndex = pageIndex,
|
||||
PageSize = pageSize,
|
||||
Items = page.Select(zk =>
|
||||
{
|
||||
versions.TryGetValue(zk.Id, out var v);
|
||||
guigeMap.TryGetValue(zk.Id, out var gs);
|
||||
return new GyzxsDocVersionDto
|
||||
{
|
||||
Id = v?.Id ?? 0,
|
||||
GyzkId = zk.Id,
|
||||
ZhuangKouName = zk.ZhuangKouName,
|
||||
ZhuangKouCode = zk.ZhuangKouCode,
|
||||
Year = zk.BelongingYear,
|
||||
Sort = zk.Sort,
|
||||
ShichanId = v?.ShichanId ?? 0,
|
||||
FengMianId = v?.FengMianId ?? 0,
|
||||
XbjId = v?.XbjId ?? 0,
|
||||
XbjSsdyId = v?.XbjSsdyId ?? 0,
|
||||
ZhuJianId = v?.ZhuJianId ?? 0,
|
||||
ZdsId = v?.ZdsId ?? 0,
|
||||
ZdsDyId = v?.ZdsDyId ?? 0,
|
||||
HsId = v?.HsId ?? 0,
|
||||
Guiges = gs?.OrderBy(x => x).ToList() ?? new List<string>()
|
||||
};
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<GyzxsDocVersionDto> CreateDocVersionAsync(long gyzkId)
|
||||
{
|
||||
var zk = await _zkRepo.GetByIdAsync(gyzkId) ?? throw new ServiceException("工艺庄口不存在", 404);
|
||||
var exist = await _versionRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId);
|
||||
if (exist == null)
|
||||
{
|
||||
exist = new GyzxsDocVersion { GyzkId = gyzkId };
|
||||
await _versionRepo.AddAsync(exist);
|
||||
}
|
||||
return new GyzxsDocVersionDto
|
||||
{
|
||||
Id = exist.Id, GyzkId = gyzkId, ZhuangKouName = zk.ZhuangKouName,
|
||||
ZhuangKouCode = zk.ZhuangKouCode, Year = zk.BelongingYear, Sort = zk.Sort
|
||||
};
|
||||
}
|
||||
|
||||
public async Task DeleteDocVersionAsync(long id)
|
||||
{
|
||||
var v = await _versionRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
|
||||
await _versionRepo.DeleteAsync(v);
|
||||
}
|
||||
|
||||
/// <summary>主控表 upsert:任一子单据保存后回写指针列</summary>
|
||||
private async Task TouchVersionAsync(long gyzkId, Action<GyzxsDocVersion> apply)
|
||||
{
|
||||
var v = await _versionRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId);
|
||||
if (v == null)
|
||||
{
|
||||
v = new GyzxsDocVersion { GyzkId = gyzkId };
|
||||
apply(v);
|
||||
await _versionRepo.AddAsync(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
apply(v);
|
||||
await _versionRepo.UpdateAsync(v);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 封面 ====================
|
||||
|
||||
public async Task<GyzxsFengMian> GetFengMianAsync(long gyzkId)
|
||||
{
|
||||
var exist = await _fengMianRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId);
|
||||
if (exist != null) return exist;
|
||||
|
||||
// 新封面:按 F9 规则预生成编号 SJZ-{年度}-工艺-{序号}
|
||||
var zk = await _zkRepo.GetByIdAsync(gyzkId);
|
||||
var year = zk?.BelongingYear ?? DateTime.Today.Year;
|
||||
var prefix = $"SJZ-{year}-工艺-";
|
||||
var used = (await _fengMianRepo.FindAsync(x => !x.IsDeleted))
|
||||
.Where(x => x.BianHao != null && x.BianHao.StartsWith(prefix));
|
||||
var max = 0;
|
||||
foreach (var f in used)
|
||||
{
|
||||
if (int.TryParse(f.BianHao.Substring(prefix.Length), out var n) && n > max) max = n;
|
||||
}
|
||||
return new GyzxsFengMian { GyzkId = gyzkId, BianHao = prefix + (max + 1), ShengChanCheJian = "" };
|
||||
}
|
||||
|
||||
public async Task<bool> SaveFengMianAsync(GyzxsFengMian dto)
|
||||
{
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _fengMianRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.GyzkId);
|
||||
var id = await UpsertAsync(_fengMianRepo, dto, exist);
|
||||
await TouchVersionAsync(dto.GyzkId, v => v.FengMianId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 试产(试缫工艺单) ====================
|
||||
|
||||
public async Task<GyzxsShichan> GetShichanAsync(long gyzkId, string guige)
|
||||
=> await _shichanRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId && x.Guige == (guige ?? ""))
|
||||
?? new GyzxsShichan { GyzkId = gyzkId, Guige = guige ?? "" };
|
||||
|
||||
public async Task<bool> SaveShichanAsync(GyzxsShichan dto)
|
||||
{
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _shichanRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.GyzkId && x.Guige == (dto.Guige ?? ""));
|
||||
var id = await UpsertAsync(_shichanRepo, dto, exist);
|
||||
await TouchVersionAsync(dto.GyzkId, v => v.ShichanId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 混剥选茧 ====================
|
||||
|
||||
public async Task<GyzxsXbjDto> GetXbjAsync(long gyzkId)
|
||||
{
|
||||
var e = await _xbjRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId);
|
||||
var dto = new GyzxsXbjDto { Xbj = e ?? new GyzxsXbj { GyzkId = gyzkId } };
|
||||
if (e != null)
|
||||
dto.HunJian = (await _hunjianRepo.FindAsync(x => !x.IsDeleted && x.XbjId == e.Id))
|
||||
.OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList();
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveXbjAsync(GyzxsXbjDto dto)
|
||||
{
|
||||
if (dto.Xbj.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _xbjRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.Xbj.GyzkId);
|
||||
var id = await UpsertAsync(_xbjRepo, dto.Xbj, exist);
|
||||
|
||||
var olds = await _hunjianRepo.FindAsync(x => x.XbjId == id);
|
||||
if (olds.Count > 0) await _hunjianRepo.HardDeleteRangeAsync(olds);
|
||||
var rows = dto.HunJian ?? new List<GyzxsXbjHunJian>();
|
||||
for (var i = 0; i < rows.Count; i++)
|
||||
{
|
||||
rows[i].Id = 0;
|
||||
rows[i].XbjId = id;
|
||||
rows[i].Sort = i + 1;
|
||||
await _hunjianRepo.AddAsync(rows[i]);
|
||||
}
|
||||
await TouchVersionAsync(dto.Xbj.GyzkId, v => v.XbjId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 试缫与大样 ====================
|
||||
|
||||
public async Task<GyzxsXbjSsDy> GetXbjSsDyAsync(long gyzkId, string guige)
|
||||
=> await _ssdyRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId && x.Guige == (guige ?? ""))
|
||||
?? new GyzxsXbjSsDy { GyzkId = gyzkId, Guige = guige ?? "" };
|
||||
|
||||
public async Task<bool> SaveXbjSsDyAsync(GyzxsXbjSsDy dto)
|
||||
{
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _ssdyRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.GyzkId && x.Guige == (dto.Guige ?? ""));
|
||||
var id = await UpsertAsync(_ssdyRepo, dto, exist);
|
||||
await TouchVersionAsync(dto.GyzkId, v => v.XbjSsdyId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 煮茧 ====================
|
||||
|
||||
public async Task<GyzxsZhuJianDto> GetZhuJianAsync(long gyzkId)
|
||||
{
|
||||
var e = await _zhuJianRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId);
|
||||
var dto = new GyzxsZhuJianDto { ZhuJian = e ?? new GyzxsZhuJian { GyzkId = gyzkId } };
|
||||
if (e != null)
|
||||
{
|
||||
dto.CheJian = (await _zjCheJianRepo.FindAsync(x => !x.IsDeleted && x.ZhuJianId == e.Id))
|
||||
.OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList();
|
||||
dto.FuZhu = (await _zjFuZhuRepo.FindAsync(x => !x.IsDeleted && x.ZhuJianId == e.Id))
|
||||
.OrderBy(x => x.Sort).ThenBy(x => x.Id).ToList();
|
||||
}
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveZhuJianAsync(GyzxsZhuJianDto dto)
|
||||
{
|
||||
if (dto.ZhuJian.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _zhuJianRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.ZhuJian.GyzkId);
|
||||
var id = await UpsertAsync(_zhuJianRepo, dto.ZhuJian, exist);
|
||||
|
||||
var oldChe = await _zjCheJianRepo.FindAsync(x => x.ZhuJianId == id);
|
||||
if (oldChe.Count > 0) await _zjCheJianRepo.HardDeleteRangeAsync(oldChe);
|
||||
var che = dto.CheJian ?? new List<GyzxsZhuJianCheJian>();
|
||||
for (var i = 0; i < che.Count; i++)
|
||||
{
|
||||
che[i].Id = 0; che[i].ZhuJianId = id; che[i].Sort = i + 1;
|
||||
await _zjCheJianRepo.AddAsync(che[i]);
|
||||
}
|
||||
|
||||
var oldFz = await _zjFuZhuRepo.FindAsync(x => x.ZhuJianId == id);
|
||||
if (oldFz.Count > 0) await _zjFuZhuRepo.HardDeleteRangeAsync(oldFz);
|
||||
var fz = dto.FuZhu ?? new List<GyzxsZhuJianFuZhu>();
|
||||
for (var i = 0; i < fz.Count; i++)
|
||||
{
|
||||
fz[i].Id = 0; fz[i].ZhuJianId = id; fz[i].Sort = i + 1;
|
||||
await _zjFuZhuRepo.AddAsync(fz[i]);
|
||||
}
|
||||
|
||||
await TouchVersionAsync(dto.ZhuJian.GyzkId, v => v.ZhuJianId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 自动缫(含大样) ====================
|
||||
|
||||
public async Task<GyzxsZdsDto> GetZdsAsync(long gyzkId, string guige)
|
||||
{
|
||||
var e = await _zdsRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId && x.Guige == (guige ?? ""));
|
||||
var dto = new GyzxsZdsDto { Zds = e ?? new GyzxsZds { GyzkId = gyzkId, Guige = guige ?? "" } };
|
||||
if (e != null)
|
||||
dto.Dyzl = await _zdsDyRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.ZdsId == e.Id);
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveZdsAsync(GyzxsZdsDto dto)
|
||||
{
|
||||
if (dto.Zds.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _zdsRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.Zds.GyzkId && x.Guige == (dto.Zds.Guige ?? ""));
|
||||
var id = await UpsertAsync(_zdsRepo, dto.Zds, exist);
|
||||
|
||||
var dy = dto.Dyzl;
|
||||
if (dy != null)
|
||||
{
|
||||
var oldDy = await _zdsDyRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.ZdsId == id);
|
||||
dy.ZdsId = id;
|
||||
dy.GyzkId = dto.Zds.GyzkId;
|
||||
if (string.IsNullOrWhiteSpace(dy.Guige)) dy.Guige = dto.Zds.Guige;
|
||||
var dyId = await UpsertAsync(_zdsDyRepo, dy, oldDy);
|
||||
await TouchVersionAsync(dto.Zds.GyzkId, v => v.ZdsDyId = dyId);
|
||||
}
|
||||
await TouchVersionAsync(dto.Zds.GyzkId, v => v.ZdsId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 后缫(工艺执行书) ====================
|
||||
|
||||
public async Task<GyzxsHs> GetHsAsync(long gyzkId, string guige)
|
||||
=> await _hsRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == gyzkId && x.Guige == (guige ?? ""))
|
||||
?? new GyzxsHs { GyzkId = gyzkId, Guige = guige ?? "", ZhiXingDate = DateTime.Today };
|
||||
|
||||
public async Task<bool> SaveHsAsync(GyzxsHs dto)
|
||||
{
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
var exist = await _hsRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.GyzkId == dto.GyzkId && x.Guige == (dto.Guige ?? ""));
|
||||
var id = await UpsertAsync(_hsRepo, dto, exist);
|
||||
await TouchVersionAsync(dto.GyzkId, v => v.HsId = id);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 报表数据 ====================
|
||||
|
||||
public async Task<GyzxsPrintDto> GetPrintDataAsync(string type, long gyzkId, string guige)
|
||||
{
|
||||
var zk = await _zkRepo.GetByIdAsync(gyzkId);
|
||||
var zkName = zk?.ZhuangKouName ?? "";
|
||||
var dto = new GyzxsPrintDto { ReportName = type };
|
||||
|
||||
switch ((type ?? "").ToLowerInvariant())
|
||||
{
|
||||
case "fengmian":
|
||||
{
|
||||
var e = await GetFengMianAsync(gyzkId);
|
||||
dto.Title = "桑蚕丝工艺设计书";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["编号"] = string.IsNullOrWhiteSpace(e.BianHao) ? $"SJZ-{zk?.BelongingYear ?? DateTime.Today.Year}-工艺" : e.BianHao;
|
||||
dto.Parameters["缫制规格"] = e.Guige;
|
||||
dto.Parameters["原料总量"] = $"{e.YuanLiaoZhongLiang:0.##}吨";
|
||||
dto.Parameters["生产丝量"] = $"{e.ShengSiZhongLiang:0.##}吨";
|
||||
dto.Parameters["缫制车间"] = e.ShengChanCheJian;
|
||||
dto.Parameters["生产时间"] = FormatRange(e.ShengChanStartDate, e.ShengChanEndDate);
|
||||
dto.Parameters["发送部门"] = e.FaSongBuMen;
|
||||
dto.Parameters["日期"] = DateTime.Today.ToString("yyyy年MM月dd日");
|
||||
break;
|
||||
}
|
||||
case "gyzxs": // 后缫 = 工艺执行书
|
||||
{
|
||||
var e = await GetHsAsync(gyzkId, guige);
|
||||
dto.Title = "工艺执行书";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["执行时间"] = e.ZhiXingDate?.ToString("yyyy/M/d") ?? "";
|
||||
dto.Parameters["生丝规格"] = e.Guige;
|
||||
dto.Parameters["前缫生产车间"] = e.QianSaoCheJian;
|
||||
dto.Parameters["预缫天数"] = e.YuSaoTianShu;
|
||||
dto.Parameters["整理规格"] = e.ZhengLiGuiGe;
|
||||
// 生产指标
|
||||
dto.Parameters["单产"] = e.SczbDanChan;
|
||||
dto.Parameters["日产"] = e.SczbRiChan;
|
||||
dto.Parameters["编丝量"] = e.SczbBianSiLiang;
|
||||
dto.Parameters["整检量"] = e.SczbZhengJianLiang;
|
||||
dto.Parameters["生产正品率"] = e.SczbShengChanZhengPinLv;
|
||||
dto.Parameters["商检正品率"] = e.SczbShangJianZhengPinLv;
|
||||
dto.Parameters["切断"] = e.SczbQieDuan;
|
||||
dto.Parameters["效率"] = e.SczbXiaoLv;
|
||||
dto.Parameters["编检品质疵点"] = e.SczbPinZhiCiDian;
|
||||
// 工艺条件及控制指标
|
||||
dto.Parameters["车速"] = e.GyzbCheSu;
|
||||
dto.Parameters["小䈅待返时间"] = e.GyzbXiaoYuDaiFanShiJian;
|
||||
dto.Parameters["车间温度"] = e.GyzbCheJianWenDu;
|
||||
dto.Parameters["车间相对湿度"] = e.GyzbCheJianXiangDuiShiDu;
|
||||
dto.Parameters["车厢温度"] = e.GyzbCheXiangWenDu;
|
||||
dto.Parameters["车厢湿度"] = e.GyzbCheXiangShiDu;
|
||||
dto.Parameters["平衡室温度"] = e.GyzbPingHengShiWenDu;
|
||||
dto.Parameters["平衡室相对湿度"] = e.GyzbPingHengShiXiangDuiShiDu;
|
||||
dto.Parameters["小䈅给湿温度"] = e.GyzbXiaoYuGeiShiWenDu;
|
||||
dto.Parameters["小䈅丝片平衡时间"] = e.GyzbXiaoYuSiPianPingHengShiJian;
|
||||
dto.Parameters["真空度"] = e.GyzbZhenKongDu;
|
||||
dto.Parameters["抽气次数"] = e.GyzbChouQiCiShu;
|
||||
dto.Parameters["大䈅丝片平衡时间"] = e.GyzbDaYuSiPianPingHengShiJian;
|
||||
dto.Parameters["大䈅丝片回潮率"] = e.GyzbDaYuSiPianHuiChaoLv;
|
||||
dto.Parameters["生丝回潮率"] = e.GyzbShengSiHuiChaoLv;
|
||||
dto.Parameters["大䈅丝片宽度"] = e.GyzbDaYuSiPianKuanDu;
|
||||
dto.Parameters["屑丝对丝率"] = e.GyzbXieSiDuiSiLv;
|
||||
dto.Parameters["大䈅丝片疵点"] = e.GyzbDaYuSiPianCiDian;
|
||||
dto.Parameters["主要技术措施"] = e.JiShuCuoShi;
|
||||
dto.Parameters["设计"] = e.SheJiRen;
|
||||
dto.Parameters["审核"] = e.ShenHeRen;
|
||||
dto.Parameters["审批"] = e.PiFuRen;
|
||||
break;
|
||||
}
|
||||
case "xbj":
|
||||
{
|
||||
var e = await GetXbjAsync(gyzkId);
|
||||
var x = e.Xbj;
|
||||
dto.Title = "混剥选茧工艺执行书";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["换庄时间"] = x.HuanZhuangDate?.ToString("yyyy/M/d") ?? "";
|
||||
dto.Parameters["缫制规格"] = x.Guige;
|
||||
dto.Parameters["缫制车间"] = x.CheJian;
|
||||
dto.Parameters["日产量"] = x.ScRiChanLiang;
|
||||
dto.Parameters["班产量"] = x.ScBanChanLiang;
|
||||
dto.Parameters["上车率"] = x.ScShangCheLv;
|
||||
dto.Parameters["联统茧率"] = x.ScLianTongLv;
|
||||
dto.Parameters["五级茧率"] = x.ScWuJiLv;
|
||||
dto.Parameters["下茧茧衣率"] = x.ScXiaJiaoLv;
|
||||
dto.Parameters["茧衣率"] = x.ScJianYiLv;
|
||||
dto.Parameters["技术措施要求"] = x.ScJiShuCuoShi;
|
||||
dto.Parameters["统号茧五级茧率"] = x.QcTongHaoWuJiLv;
|
||||
dto.Parameters["统号茧下茧率"] = x.QcTongHaoXiaJianLv;
|
||||
dto.Parameters["五级茧联统茧率"] = x.QcWuJiLianTongLv;
|
||||
dto.Parameters["五级茧下脚率"] = x.QcWuJiXiaJiaoLv;
|
||||
dto.Parameters["下脚茧联统率"] = x.QcXiaJiaoLianTongLv;
|
||||
dto.Parameters["下脚茧次茧率"] = x.QcXiaJiaoCiJianLv;
|
||||
dto.Parameters["好五茧组产量"] = x.QcZbHaoWuZuChanLiang;
|
||||
dto.Parameters["六级组产量"] = x.QcZbLiuJiZuChanLiang;
|
||||
dto.Parameters["棉茧组产量"] = x.QcZbMianJianZuChanLiang;
|
||||
dto.Parameters["下脚组产量"] = x.QcZbXiaJiaoZuChanLiang;
|
||||
dto.Parameters["双宫组产量"] = x.QcZbShuangGongZuChanLiang;
|
||||
dto.Parameters["公斤粒数"] = x.QcZbGongJinLiShu;
|
||||
dto.Parameters["剥光率"] = x.QcZbBaoGuangLv;
|
||||
dto.Parameters["质量控制茧衣率"] = x.QcZbJianYiLv;
|
||||
dto.Parameters["合计包数"] = e.HunJian.Sum(i => i.BaoShu);
|
||||
dto.Parameters["合计比例"] = e.HunJian.Sum(i => i.BiLi);
|
||||
dto.Parameters["合计混茧包数"] = e.HunJian.Sum(i => i.HunJianBaoShu);
|
||||
dto.Sub1 = e.HunJian.Select((i, idx) => new Dictionary<string, object?>
|
||||
{
|
||||
["序号"] = idx + 1,
|
||||
["庄别"] = i.ZhuangBie,
|
||||
["总包数"] = i.BaoShu,
|
||||
["比例"] = i.BiLi,
|
||||
["混茧包数"] = i.HunJianBaoShu
|
||||
}).ToList();
|
||||
break;
|
||||
}
|
||||
case "shichan":
|
||||
{
|
||||
var e = await GetShichanAsync(gyzkId, guige);
|
||||
dto.Title = "自动缫试缫工艺单";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["试缫日期"] = e.ScDate?.ToString("yyyy/M/d") ?? "";
|
||||
dto.Parameters["换庄方式"] = e.HuanZhuangFangShi;
|
||||
dto.Parameters["规格"] = e.Guige;
|
||||
dto.Parameters["生产车间"] = e.ScCheJian;
|
||||
dto.Parameters["一茧丝长"] = e.YiJianSiChang;
|
||||
dto.Parameters["解舒率"] = e.JieShuLv;
|
||||
dto.Parameters["解舒丝长"] = e.JieShuSiChang;
|
||||
dto.Parameters["茧层率"] = e.JianCengLv;
|
||||
dto.Parameters["茧丝纤度"] = e.JianSiXianDu;
|
||||
dto.Parameters["解舒光折"] = e.JieShuGuangZhe;
|
||||
dto.Parameters["万吊"] = e.WanDiao;
|
||||
dto.Parameters["车速"] = e.ZdsCheSu;
|
||||
dto.Parameters["允许粒数"] = e.ZdsYunXuLiShu;
|
||||
dto.Parameters["缫折"] = e.ZdsSaoZhe;
|
||||
dto.Parameters["中心纤度"] = e.ZdsZhongXinXianDu;
|
||||
dto.Parameters["中心粒符率"] = e.ZdsZhongXinLiFuLv;
|
||||
dto.Parameters["缫丝温度"] = e.ZdsSaoSiWenDu;
|
||||
dto.Parameters["计算纤度"] = e.ZdsJiSuanXianDu;
|
||||
dto.Parameters["允许粒符率"] = e.ZdsYunXuLiFuLv;
|
||||
dto.Parameters["索绪温度"] = e.ZdsSuoXuWenDu;
|
||||
dto.Parameters["平均粒数"] = e.ZdsPingJunLiShu;
|
||||
dto.Parameters["白茧引绪"] = e.ZdsBaiJianYinXu;
|
||||
dto.Parameters["理绪温度"] = e.ZdsLiXuWenDu;
|
||||
dto.Parameters["中心粒数"] = e.ZdsZhongXinLiShu;
|
||||
dto.Parameters["给茧口宽度"] = e.ZdsGeiJianKouKuanDu;
|
||||
dto.Parameters["落丝笼数"] = e.ZdsLuoSiLongShu;
|
||||
dto.Parameters["笼茧粒数"] = e.ZdsLongJianLiShu;
|
||||
dto.Parameters["煮茧车速"] = e.ZjCheSu;
|
||||
dto.Parameters["蒸煮室温度"] = $"{e.ZjZhengZhuShi1}、{e.ZjZhengZhuShi2}、{e.ZjZhengZhuShi3}";
|
||||
dto.Parameters["外层中层内层掉绪"] = $"{e.ZjLuoXuWai} {e.ZjLuoXuZhong} {e.ZjLuoXuNei}";
|
||||
dto.Parameters["调整部温度"] = e.ZjTiaoZhengBuWenDu;
|
||||
dto.Parameters["出口温度"] = e.ZjChuKouWenDu;
|
||||
dto.Parameters["附煮车间"] = e.ZjFuZhuCheJian;
|
||||
dto.Parameters["附煮庄口"] = e.ZjFuZhuGyzkName;
|
||||
dto.Parameters["设计"] = e.SheJiRen;
|
||||
dto.Parameters["审核"] = e.ShenHeRen;
|
||||
dto.Parameters["审批"] = e.PiFuRen;
|
||||
break;
|
||||
}
|
||||
case "ssdy":
|
||||
{
|
||||
var e = await GetXbjSsDyAsync(gyzkId, guige);
|
||||
dto.Title = "(三)试缫与大样资料";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["规格"] = e.Guige;
|
||||
dto.Parameters["主要技术措施"] = e.JiShuCuoShi;
|
||||
dto.Parameters["设计"] = e.SheJiRen;
|
||||
dto.Parameters["审核"] = e.ShenHeRen;
|
||||
dto.Parameters["审批"] = e.PiFuRen;
|
||||
dto.Main = new List<Dictionary<string, object?>>
|
||||
{
|
||||
new()
|
||||
{
|
||||
["项目"] = "试缫",
|
||||
["上车率"] = e.SsShangCheLv, ["联统率"] = e.SsLianTongLv, ["次茧率"] = e.SsCiJianLv,
|
||||
["次六级"] = e.SsCiLiuJiLv, ["棉茧率"] = e.SsMianJianLv, ["下脚率"] = e.SsXiaJiaoLv,
|
||||
["双宫率"] = e.SsShuangGongLv, ["茧衣率"] = e.SsJianYiLv,
|
||||
["400粒重"] = e.SsLiJianZhong, ["公斤粒数"] = e.SsGongJinLiShu
|
||||
},
|
||||
new()
|
||||
{
|
||||
["项目"] = "大样",
|
||||
["上车率"] = e.DyShangCheLv, ["联统率"] = e.DyLianTongLv, ["次茧率"] = e.DyCiJianLv,
|
||||
["次六级"] = e.DyCiLiuJiLv, ["棉茧率"] = e.DyMianJianLv, ["下脚率"] = e.DyXiaJiaoLv,
|
||||
["双宫率"] = e.DyShuangGongLv, ["茧衣率"] = e.DyJianYiLv,
|
||||
["400粒重"] = e.DyLiJianZhong, ["公斤粒数"] = e.DyGongJinLiShu
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "zhujian":
|
||||
{
|
||||
var e = await GetZhuJianAsync(gyzkId);
|
||||
var z = e.ZhuJian;
|
||||
dto.Title = "煮茧工艺执行书";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["缫制规格"] = z.Guige;
|
||||
dto.Parameters["换庄时间"] = z.HuanZhuangDate?.ToString("yyyy/M/d") ?? "";
|
||||
dto.Parameters["接缫方式"] = z.HuanZhuangFangShi;
|
||||
dto.Parameters["生产车间"] = z.CheJian;
|
||||
dto.Parameters["煮茧机号"] = z.ZhuJianJi;
|
||||
// 庄口原料性能
|
||||
dto.Parameters["公斤粒数"] = z.YlGongJinLiShu;
|
||||
dto.Parameters["缩绉"] = z.YlSuoZhou;
|
||||
dto.Parameters["茧层率"] = z.YlJianCengLv;
|
||||
dto.Parameters["清洁"] = z.YlQingJie;
|
||||
dto.Parameters["净度"] = z.YlJingDu;
|
||||
dto.Parameters["粒茧丝长"] = z.YlLiJianSiChang;
|
||||
dto.Parameters["解舒率"] = z.YlJieShuLv;
|
||||
dto.Parameters["解舒丝长"] = z.YlJieShuSiChang;
|
||||
dto.Parameters["茧丝纤度"] = z.YlJianSiXianDu;
|
||||
dto.Parameters["万米吊糙"] = z.YlWanMiDiaoCao;
|
||||
dto.Parameters["解舒光折"] = z.YlJieShuGuangZhe;
|
||||
// 工艺技术指标
|
||||
dto.Parameters["实缫解舒"] = z.GyzbJieShuLv;
|
||||
dto.Parameters["实缫万吊"] = z.GyzbWanDiao;
|
||||
dto.Parameters["工艺清洁"] = z.GyzbQingJie;
|
||||
dto.Parameters["工艺净度"] = z.GyzbJingDu;
|
||||
dto.Parameters["丝胶溶失率"] = z.GyzbSiJiaoRongShiLv;
|
||||
dto.Parameters["组落丝笼数"] = z.GyzbZuLuoSiLongShu;
|
||||
dto.Parameters["缫折"] = z.GyzbSaoZhe;
|
||||
dto.Parameters["笼茧粒数"] = z.GyzbLongJianLiShu;
|
||||
dto.Parameters["每桶茧量"] = z.GyzbMeiTongJianLiang;
|
||||
dto.Parameters["每包桶数"] = z.GyzbMeiBaoTongShu;
|
||||
dto.Parameters["每小时用茧笼数"] = z.GyzbMeiXiaoShiYongJianLong;
|
||||
dto.Parameters["条吐率"] = z.GyzbTiaoTuLv;
|
||||
// 真空渗透
|
||||
dto.Parameters["真空度"] = z.ZkstZhenKongDu;
|
||||
dto.Parameters["吸水温度"] = z.ZkstXiShuiWenDu;
|
||||
dto.Parameters["达到最高真空度时长"] = z.ZkstDaZhenKongShiChang;
|
||||
dto.Parameters["液面到最高位时长"] = z.ZkstZuiGaoWeiShiChang;
|
||||
dto.Parameters["关水后继续真空时长"] = z.ZkstGuanShuiZhenKongShiChang;
|
||||
dto.Parameters["出茧温度"] = z.ZkstChuJianWenDu;
|
||||
dto.Parameters["全机回转时间"] = z.ZkstQuanJiHuiZhuanShiJian;
|
||||
dto.Parameters["主要技术措施"] = z.JiShuCuoShi;
|
||||
dto.Parameters["设计"] = z.SheJiRen;
|
||||
dto.Parameters["审核"] = z.ShenHeRen;
|
||||
dto.Parameters["审批"] = z.PiFuRen;
|
||||
dto.Sub1 = e.CheJian.Select(c => new Dictionary<string, object?>
|
||||
{
|
||||
["车间"] = c.CheJian, ["低温渗透"] = c.DiWenShenTou,
|
||||
["蒸煮室1"] = c.ZhengZhuShi1, ["蒸煮室2"] = c.ZhengZhuShi2, ["蒸煮室3"] = c.ZhengZhuShi3,
|
||||
["调整部1"] = c.TiaoZhengBu1, ["调整部2"] = c.TiaoZhengBu2, ["调整部3"] = c.TiaoZhengBu3,
|
||||
["调整部4"] = c.TiaoZhengBu4, ["调整部5"] = c.TiaoZhengBu5,
|
||||
["出茧温度"] = c.ChuJianWenDu, ["全机回转时间"] = c.HuiZhuanShiJian
|
||||
}).ToList();
|
||||
dto.Sub2 = e.FuZhu.Select(c => new Dictionary<string, object?>
|
||||
{
|
||||
["附煮车间"] = c.CheJian, ["解舒"] = c.JieShuLv, ["万吊"] = c.WanDiao, ["缫折"] = c.SaoZhe,
|
||||
["温度1"] = c.WenDu1, ["温度2"] = c.WenDu2, ["温度3"] = c.WenDu3,
|
||||
["温度4"] = c.WenDu4, ["温度5"] = c.WenDu5
|
||||
}).ToList();
|
||||
break;
|
||||
}
|
||||
case "zds":
|
||||
{
|
||||
var e = await GetZdsAsync(gyzkId, guige);
|
||||
var z = e.Zds;
|
||||
dto.Title = "自动缫车间生产工艺执行书(表一)";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["接缫日期"] = z.HuanZhuangDate?.ToString("yyyy/M/d") ?? "";
|
||||
dto.Parameters["换庄方式"] = z.HuanZhuangFangShi;
|
||||
dto.Parameters["规格"] = z.Guige;
|
||||
dto.Parameters["生产车间"] = z.CheJian;
|
||||
dto.Parameters["预缫天数"] = z.YuSaoTianShu;
|
||||
dto.Parameters["预缫丝量"] = z.YuSaoSiLiang;
|
||||
// 茧质概况
|
||||
dto.Parameters["一茧丝长"] = z.ZgYiJianSiChang;
|
||||
dto.Parameters["解舒丝长"] = z.ZgJieShuSiChang;
|
||||
dto.Parameters["解舒率"] = z.ZgJieShuLv;
|
||||
dto.Parameters["茧丝纤度"] = z.ZgJianSiXianDu;
|
||||
dto.Parameters["解舒光折"] = z.ZgJieShuGuangZhe;
|
||||
dto.Parameters["万米吊糙"] = z.ZgWanMiDiaoCao;
|
||||
dto.Parameters["清洁分"] = z.ZgQingJie;
|
||||
dto.Parameters["洁净分"] = z.ZgJingDu;
|
||||
dto.Parameters["茧层率"] = z.ZgJianCengLv;
|
||||
dto.Parameters["茧丝纤度均方差"] = z.ZgXianDuJunFangCha;
|
||||
dto.Parameters["外三百回"] = z.ZgWaiSanBaiHui;
|
||||
dto.Parameters["中三百回"] = z.ZgZhongSanBaiHui;
|
||||
dto.Parameters["内三百回"] = z.ZgNeiSanBaiHui;
|
||||
dto.Parameters["最大开差"] = z.ZgZuiDaKaiCha;
|
||||
dto.Parameters["内外开差"] = z.ZgNeiWaiKaiCha;
|
||||
dto.Parameters["白色茧单纤"] = z.ZgBaiSeJianDanXian;
|
||||
dto.Parameters["灰色茧单纤"] = z.ZgHuiSeJianDanXian;
|
||||
dto.Parameters["红色茧单纤"] = z.ZgHongSeJianDanXian;
|
||||
dto.Parameters["落绪外"] = z.ZgLuoXuWai;
|
||||
dto.Parameters["落绪中"] = z.ZgLuoXuZhong;
|
||||
dto.Parameters["落绪内"] = z.ZgLuoXuNei;
|
||||
dto.Parameters["平均茧幅"] = z.ZgPingJunJianFu;
|
||||
dto.Parameters["茧幅整齐率"] = z.ZgJianFuZhengQiLv;
|
||||
dto.Parameters["茧丝百回纤度"] = z.ZgJianSiBaiHuiXianDu;
|
||||
// 生产指标
|
||||
dto.Parameters["品味"] = z.SczbPinWei;
|
||||
dto.Parameters["缫折"] = z.SczbSaoZhe;
|
||||
dto.Parameters["台产"] = z.SczbTaiChan;
|
||||
dto.Parameters["偏差"] = z.SczbPianCha;
|
||||
dto.Parameters["总差"] = z.SczbZongCha;
|
||||
dto.Parameters["清洁"] = z.SczbQingJie;
|
||||
dto.Parameters["洁净"] = z.SczbJingJie;
|
||||
dto.Parameters["匀二变化"] = z.SczbYunEr;
|
||||
dto.Parameters["生产正品率"] = z.SczbZhengPinLv;
|
||||
// 工艺技术指标
|
||||
dto.Parameters["车速"] = z.GyzbCheSu;
|
||||
dto.Parameters["添绪次数"] = z.GyzbTianXuCiShu;
|
||||
dto.Parameters["运转率"] = z.GyzbYunZhuanLv;
|
||||
dto.Parameters["中心纤度"] = z.GyzbZhongXinXianDu;
|
||||
dto.Parameters["设计单纤度"] = z.GyzbSheJiDanXianDu;
|
||||
dto.Parameters["平均粒数"] = z.GyzbPingJunLiShu;
|
||||
dto.Parameters["中心粒数"] = z.GyzbZhongXinLiShu;
|
||||
dto.Parameters["允许粒数"] = z.GyzbYunXuLiShu;
|
||||
dto.Parameters["引绪"] = z.GyzbYinXu;
|
||||
dto.Parameters["中心粒符率"] = z.GyzbZhongXinLiFuLv;
|
||||
dto.Parameters["允许粒符率"] = z.GyzbYunXuLiFuLv;
|
||||
dto.Parameters["给茧口宽度"] = z.GyzbGeiJianKouKuanDu;
|
||||
dto.Parameters["条吐率"] = z.GyzbTiaoTuLv;
|
||||
dto.Parameters["丝鞘长度"] = z.GyzbSiQiaoChangDu;
|
||||
dto.Parameters["车头汽压"] = z.GyzbCheTouQiYa;
|
||||
dto.Parameters["索绪温度"] = z.GyzbSuoXuWenDu;
|
||||
dto.Parameters["理绪温度"] = z.GyzbLiXuWenDu;
|
||||
dto.Parameters["偏心盘茧量"] = z.GyzbPianXinPanJianLiang;
|
||||
dto.Parameters["缫汤温度"] = z.GyzbSaoTangWenDu;
|
||||
dto.Parameters["加茧口茧量"] = z.GyzbJiaJianKouJianLiang;
|
||||
dto.Parameters["无绪率"] = z.GyzbWuXuLv;
|
||||
dto.Parameters["蓬糙茧率"] = z.GyzbPengCaoJianLv;
|
||||
dto.Parameters["加茧均匀率"] = z.GyzbJiaJianJunYunLv;
|
||||
dto.Parameters["正确添绪率"] = z.GyzbZhengQueTianXuLv;
|
||||
dto.Parameters["桶茧量"] = z.GyzbTongJianLiang;
|
||||
dto.Parameters["落丝笼数"] = z.GyzbLuoSiLongShu;
|
||||
dto.Parameters["设计"] = z.SheJiRen;
|
||||
dto.Parameters["审核"] = z.ShenHeRen;
|
||||
dto.Parameters["审批"] = z.PiFuRen;
|
||||
break;
|
||||
}
|
||||
case "zdsdy":
|
||||
{
|
||||
var e = await GetZdsAsync(gyzkId, guige);
|
||||
var d = e.Dyzl ?? new GyzxsZdsDyzl { GyzkId = gyzkId, Guige = guige ?? "" };
|
||||
dto.Title = "自动缫大样(表二)";
|
||||
dto.Parameters["庄口名称"] = zkName;
|
||||
dto.Parameters["规格"] = d.Guige;
|
||||
dto.Parameters["偏差"] = d.PianCha;
|
||||
dto.Parameters["清洁"] = d.QingJie;
|
||||
dto.Parameters["净度"] = d.JingDu;
|
||||
dto.Parameters["解舒率"] = d.JieShuLv;
|
||||
dto.Parameters["万米吊糙"] = d.WanMiDiaoCao;
|
||||
dto.Parameters["缫折"] = d.SaoZhe;
|
||||
dto.Parameters["技术措施及要求"] = d.JiShuCuoShi;
|
||||
dto.Parameters["设计"] = d.SheJiRen;
|
||||
dto.Parameters["审核"] = d.ShenHeRen;
|
||||
dto.Parameters["审批"] = d.PiFuRen;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ServiceException($"未知的报表类型:{type}");
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
// ==================== 内部工具 ====================
|
||||
|
||||
private static async Task<long> UpsertAsync<T>(IRepository<T> repo, T dto, T? exist) where T : class, IEntity
|
||||
{
|
||||
if (exist == null)
|
||||
{
|
||||
dto.Id = 0;
|
||||
dto.IsDeleted = false;
|
||||
await repo.AddAsync(dto);
|
||||
return dto.Id;
|
||||
}
|
||||
dto.Id = exist.Id;
|
||||
dto.CreatedAt = exist.CreatedAt;
|
||||
dto.IsDeleted = false;
|
||||
await repo.UpdateAsync(dto);
|
||||
return dto.Id;
|
||||
}
|
||||
|
||||
private static string FormatRange(DateTime? start, DateTime? end)
|
||||
{
|
||||
if (start == null && end == null) return "";
|
||||
var s = start?.ToString("yy年M月d日") ?? "";
|
||||
var e2 = end?.ToString("yy年M月d日") ?? "";
|
||||
return string.IsNullOrEmpty(e2) ? s : $"{s}-{e2}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.QC;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
using F9WebAiTest.Domain.Entities.SYS.YuanliaoZhuangkous;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.QC;
|
||||
|
||||
public interface IYjssService
|
||||
{
|
||||
Task<PageResult<YjssListItemDto>> GetPagedAsync(int pageIndex, int pageSize, int type, int year, string keyword);
|
||||
Task<YjssListItemDto> SaveYlxyAsync(YjssMainYlxy dto);
|
||||
Task<YjssListItemDto> SaveZhyAsync(YjssMainZhy dto);
|
||||
Task DeleteAsync(int type, long id);
|
||||
|
||||
Task<YjssDcXjdc> GetXjdcAsync(int type, long yjssId);
|
||||
Task<bool> SaveXjdcAsync(int type, YjssDcXjdc dto);
|
||||
|
||||
Task<List<YjssDcJsdc>> GetJsdcAsync(int type, long yjssId);
|
||||
Task<bool> SaveJsdcAsync(int type, YjssDcJsdc dto);
|
||||
Task DeleteJsdcAsync(int type, long id);
|
||||
|
||||
Task<YjssJfdcDto> GetJfdcAsync(int type, long yjssId);
|
||||
Task<bool> SaveJfdcAsync(int type, YjssJfdcDto dto);
|
||||
|
||||
Task<YjssDcQpdc> GetQpdcAsync(int type, long yjssId);
|
||||
Task<bool> SaveQpdcAsync(int type, YjssDcQpdc dto);
|
||||
|
||||
Task<YjssDcQjjj> GetQjjjAsync(int type, long yjssId);
|
||||
Task<bool> SaveQjjjAsync(int type, YjssDcQjjj dto);
|
||||
|
||||
Task<YjssJsbhxdDto> GetJsbhxdAsync(int type, long yjssId);
|
||||
Task<bool> SaveJsbhxdAsync(int type, YjssJsbhxdDto dto);
|
||||
|
||||
/// <summary>整页数据(含报表参数区):用于「综合样报表 / 原料小样报表 / 解舒调查报表」</summary>
|
||||
Task<YjssFullDto> GetFullAsync(int type, long yjssId);
|
||||
/// <summary>解舒调查平均值汇总(跨多条记录,口径对齐 F9 GetDataByYjssCommand)</summary>
|
||||
Task<Dictionary<string, object?>> GetJsdcSummaryAsync(int type, long yjssId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 质量模块 —— 样茧试缫(对应 F9 生产\质量\样茧试缫)。
|
||||
/// 两类样品:原料小样(选茧 / 解舒 / 清洁洁净)、综合样(加 茧幅 / 切剖 / 茧丝百回纤度)。
|
||||
/// 子表统一外键 yjss_id;一对一子表保存后回写主表指针,解舒/百回纤度回写记录数。
|
||||
/// </summary>
|
||||
public class YjssService : IYjssService
|
||||
{
|
||||
private readonly IRepository<YjssMainYlxy> _ylxyRepo;
|
||||
private readonly IRepository<YjssMainZhy> _zhyRepo;
|
||||
private readonly IRepository<YjssDcXjdc> _xjdcRepo;
|
||||
private readonly IRepository<YjssDcJsdc> _jsdcRepo;
|
||||
private readonly IRepository<YjssDcJfdcMain> _jfdcRepo;
|
||||
private readonly IRepository<YjssDcJfdcItem> _jfdcItemRepo;
|
||||
private readonly IRepository<YjssDcQpdc> _qpdcRepo;
|
||||
private readonly IRepository<YjssDcQjjj> _qjjjRepo;
|
||||
private readonly IRepository<YjssDcJsbhxdMain> _jsbhxdMainRepo;
|
||||
private readonly IRepository<YjssDcJsbhxd> _jsbhxdRepo;
|
||||
private readonly IRepository<YuanliaoZhuangkou> _ylzkRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _gyzkRepo;
|
||||
|
||||
public YjssService(
|
||||
IRepository<YjssMainYlxy> ylxyRepo,
|
||||
IRepository<YjssMainZhy> zhyRepo,
|
||||
IRepository<YjssDcXjdc> xjdcRepo,
|
||||
IRepository<YjssDcJsdc> jsdcRepo,
|
||||
IRepository<YjssDcJfdcMain> jfdcRepo,
|
||||
IRepository<YjssDcJfdcItem> jfdcItemRepo,
|
||||
IRepository<YjssDcQpdc> qpdcRepo,
|
||||
IRepository<YjssDcQjjj> qjjjRepo,
|
||||
IRepository<YjssDcJsbhxdMain> jsbhxdMainRepo,
|
||||
IRepository<YjssDcJsbhxd> jsbhxdRepo,
|
||||
IRepository<YuanliaoZhuangkou> ylzkRepo,
|
||||
IRepository<CraftZhuangKou> gyzkRepo)
|
||||
{
|
||||
_ylxyRepo = ylxyRepo;
|
||||
_zhyRepo = zhyRepo;
|
||||
_xjdcRepo = xjdcRepo;
|
||||
_jsdcRepo = jsdcRepo;
|
||||
_jfdcRepo = jfdcRepo;
|
||||
_jfdcItemRepo = jfdcItemRepo;
|
||||
_qpdcRepo = qpdcRepo;
|
||||
_qjjjRepo = qjjjRepo;
|
||||
_jsbhxdMainRepo = jsbhxdMainRepo;
|
||||
_jsbhxdRepo = jsbhxdRepo;
|
||||
_ylzkRepo = ylzkRepo;
|
||||
_gyzkRepo = gyzkRepo;
|
||||
}
|
||||
|
||||
private static string SampleTypeName(int t) => t switch
|
||||
{
|
||||
1 => "定价抽样",
|
||||
2 => "入库抽样",
|
||||
3 => "鲜茧抽样",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
// ==================== 主表 ====================
|
||||
|
||||
public async Task<PageResult<YjssListItemDto>> GetPagedAsync(int pageIndex, int pageSize, int type, int year, string keyword)
|
||||
{
|
||||
if (year <= 0) year = DateTime.Today.Year;
|
||||
var items = new List<YjssListItemDto>();
|
||||
|
||||
var ylzks = (await _ylzkRepo.FindAsync(x => !x.IsDeleted)).ToDictionary(x => x.Id, x => x.Name);
|
||||
var gyzks = (await _gyzkRepo.FindAsync(x => !x.IsDeleted)).ToDictionary(x => x.Id, x => x.ZhuangKouName);
|
||||
|
||||
if (type == 1)
|
||||
{
|
||||
var rows = await _ylxyRepo.FindAsync(x => !x.IsDeleted && x.Date.Year == year);
|
||||
items = rows.Select(x => new YjssListItemDto
|
||||
{
|
||||
Id = x.Id, Type = 1, ZhuangKouId = x.YlzkId,
|
||||
ZhuangKouName = ylzks.GetValueOrDefault(x.YlzkId, ""),
|
||||
Date = x.Date, SampleType = x.Type, SampleTypeName = SampleTypeName(x.Type),
|
||||
ZhongLiang = x.ZhongLiang, HuiChaoLv = x.HuiChaoLv,
|
||||
XjdcId = x.XjdcId, JsdcCt = x.JsdcCt, QjjjId = x.QjjjId, Memo = x.Memo
|
||||
}).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var rows = await _zhyRepo.FindAsync(x => !x.IsDeleted && x.Date.Year == year);
|
||||
items = rows.Select(x => new YjssListItemDto
|
||||
{
|
||||
Id = x.Id, Type = 2, ZhuangKouId = x.GyzkId,
|
||||
ZhuangKouName = gyzks.GetValueOrDefault(x.GyzkId, ""),
|
||||
Date = x.Date, SampleType = 2, SampleTypeName = "综合样",
|
||||
ZhongLiang = x.ZhongLiang, XjdcId = x.XjdcId, JsdcCt = x.JsdcCt,
|
||||
JfdcId = x.JfdcId, QpdcId = x.QpdcId, QjjjId = x.QjjjId,
|
||||
JsbhxdCt = x.JsbhxdCt, Memo = x.Memo
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
items = items.Where(x => x.ZhuangKouName.Contains(keyword)).ToList();
|
||||
|
||||
items = items.OrderByDescending(x => x.Date).ThenByDescending(x => x.Id).ToList();
|
||||
var total = items.Count;
|
||||
return new PageResult<YjssListItemDto>
|
||||
{
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize,
|
||||
Items = items.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<YjssListItemDto> SaveYlxyAsync(YjssMainYlxy dto)
|
||||
{
|
||||
if (dto.YlzkId <= 0) throw new ServiceException("请选择原料庄口");
|
||||
if (dto.Date == default) dto.Date = DateTime.Today;
|
||||
|
||||
var exist = dto.Id > 0 ? await _ylxyRepo.GetByIdAsync(dto.Id) : null;
|
||||
if (exist == null)
|
||||
{
|
||||
dto.Id = 0; dto.IsDeleted = false;
|
||||
await _ylxyRepo.AddAsync(dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.CreatedAt = exist.CreatedAt; dto.IsDeleted = false;
|
||||
await _ylxyRepo.UpdateAsync(dto);
|
||||
}
|
||||
var name = (await _ylzkRepo.GetByIdAsync(dto.YlzkId))?.Name ?? "";
|
||||
return new YjssListItemDto
|
||||
{
|
||||
Id = dto.Id, Type = 1, ZhuangKouId = dto.YlzkId, ZhuangKouName = name,
|
||||
Date = dto.Date, SampleType = dto.Type, SampleTypeName = SampleTypeName(dto.Type),
|
||||
ZhongLiang = dto.ZhongLiang, HuiChaoLv = dto.HuiChaoLv,
|
||||
XjdcId = dto.XjdcId, JsdcCt = dto.JsdcCt, QjjjId = dto.QjjjId, Memo = dto.Memo
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<YjssListItemDto> SaveZhyAsync(YjssMainZhy dto)
|
||||
{
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
if (dto.Date == default) dto.Date = DateTime.Today;
|
||||
|
||||
var exist = dto.Id > 0 ? await _zhyRepo.GetByIdAsync(dto.Id) : null;
|
||||
if (exist == null)
|
||||
{
|
||||
dto.Id = 0; dto.IsDeleted = false;
|
||||
await _zhyRepo.AddAsync(dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.CreatedAt = exist.CreatedAt; dto.IsDeleted = false;
|
||||
await _zhyRepo.UpdateAsync(dto);
|
||||
}
|
||||
var name = (await _gyzkRepo.GetByIdAsync(dto.GyzkId))?.ZhuangKouName ?? "";
|
||||
return new YjssListItemDto
|
||||
{
|
||||
Id = dto.Id, Type = 2, ZhuangKouId = dto.GyzkId, ZhuangKouName = name,
|
||||
Date = dto.Date, SampleType = 2, SampleTypeName = "综合样",
|
||||
ZhongLiang = dto.ZhongLiang, XjdcId = dto.XjdcId, JsdcCt = dto.JsdcCt,
|
||||
JfdcId = dto.JfdcId, QpdcId = dto.QpdcId, QjjjId = dto.QjjjId,
|
||||
JsbhxdCt = dto.JsbhxdCt, Memo = dto.Memo
|
||||
};
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int type, long id)
|
||||
{
|
||||
if (type == 1)
|
||||
{
|
||||
var e = await _ylxyRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
|
||||
await _ylxyRepo.DeleteAsync(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
var e = await _zhyRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
|
||||
await _zhyRepo.DeleteAsync(e);
|
||||
}
|
||||
// 连带软删除子单据(按 主表 Id + 样品类型 精确定位)
|
||||
foreach (var x in await _xjdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _xjdcRepo.DeleteAsync(x);
|
||||
foreach (var x in await _jsdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _jsdcRepo.DeleteAsync(x);
|
||||
foreach (var x in await _jfdcItemRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _jfdcItemRepo.DeleteAsync(x);
|
||||
foreach (var x in await _jfdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _jfdcRepo.DeleteAsync(x);
|
||||
foreach (var x in await _qpdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _qpdcRepo.DeleteAsync(x);
|
||||
foreach (var x in await _qjjjRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _qjjjRepo.DeleteAsync(x);
|
||||
foreach (var x in await _jsbhxdRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _jsbhxdRepo.DeleteAsync(x);
|
||||
foreach (var x in await _jsbhxdMainRepo.FindAsync(x => !x.IsDeleted && x.YjssId == id && x.YjssType == type)) await _jsbhxdMainRepo.DeleteAsync(x);
|
||||
}
|
||||
|
||||
// ==================== 选茧调查 ====================
|
||||
|
||||
public async Task<YjssDcXjdc> GetXjdcAsync(int type, long yjssId)
|
||||
=> await _xjdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type)
|
||||
?? new YjssDcXjdc { YjssId = yjssId, YjssType = type };
|
||||
|
||||
public async Task<bool> SaveXjdcAsync(int type, YjssDcXjdc dto)
|
||||
{
|
||||
dto.YjssType = type;
|
||||
var exist = await _xjdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == dto.YjssId && x.YjssType == type);
|
||||
var id = await UpsertAsync(_xjdcRepo, dto, exist);
|
||||
await SetParentPointerAsync(type, dto.YjssId, xjdcId: id, qjjjId: null, jfdcId: null, qpdcId: null, jsdcCt: null, jsbhxdCt: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 解舒调查(多条) ====================
|
||||
|
||||
public async Task<List<YjssDcJsdc>> GetJsdcAsync(int type, long yjssId)
|
||||
=> (await _jsdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type))
|
||||
.OrderBy(x => x.XuHao).ThenBy(x => x.Id).ToList();
|
||||
|
||||
public async Task<bool> SaveJsdcAsync(int type, YjssDcJsdc dto)
|
||||
{
|
||||
dto.YjssType = type;
|
||||
var exist = dto.Id > 0 ? await _jsdcRepo.GetByIdAsync(dto.Id) : null;
|
||||
if (exist == null)
|
||||
{
|
||||
dto.Id = 0; dto.IsDeleted = false;
|
||||
if (dto.XuHao <= 0) dto.XuHao = (await GetJsdcAsync(type, dto.YjssId)).Count + 1;
|
||||
await _jsdcRepo.AddAsync(dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
dto.CreatedAt = exist.CreatedAt; dto.IsDeleted = false;
|
||||
await _jsdcRepo.UpdateAsync(dto);
|
||||
}
|
||||
var ct = (await _jsdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == dto.YjssId && x.YjssType == type)).Count;
|
||||
await SetParentPointerAsync(type, dto.YjssId, null, null, null, null, ct, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task DeleteJsdcAsync(int type, long id)
|
||||
{
|
||||
var e = await _jsdcRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
|
||||
await _jsdcRepo.DeleteAsync(e);
|
||||
var ct = (await _jsdcRepo.FindAsync(x => !x.IsDeleted && x.YjssId == e.YjssId && x.YjssType == e.YjssType)).Count;
|
||||
await SetParentPointerAsync(type, e.YjssId, null, null, null, null, ct, null);
|
||||
}
|
||||
|
||||
// ==================== 茧幅调查 ====================
|
||||
|
||||
public async Task<YjssJfdcDto> GetJfdcAsync(int type, long yjssId)
|
||||
{
|
||||
var main = await _jfdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
var dto = new YjssJfdcDto { Main = main ?? new YjssDcJfdcMain { YjssId = yjssId, YjssType = type } };
|
||||
if (main != null)
|
||||
dto.Items = (await _jfdcItemRepo.FindAsync(x => !x.IsDeleted && x.JfdcId == main.Id))
|
||||
.OrderBy(x => x.Sort).ThenBy(x => x.JianFu).ToList();
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveJfdcAsync(int type, YjssJfdcDto dto)
|
||||
{
|
||||
dto.Main.YjssType = type;
|
||||
var exist = await _jfdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == dto.Main.YjssId && x.YjssType == type);
|
||||
var id = await UpsertAsync(_jfdcRepo, dto.Main, exist);
|
||||
|
||||
var olds = await _jfdcItemRepo.FindAsync(x => x.JfdcId == id);
|
||||
if (olds.Count > 0) await _jfdcItemRepo.HardDeleteRangeAsync(olds);
|
||||
var rows = dto.Items ?? new List<YjssDcJfdcItem>();
|
||||
for (var i = 0; i < rows.Count; i++)
|
||||
{
|
||||
rows[i].Id = 0; rows[i].JfdcId = id; rows[i].YjssId = dto.Main.YjssId;
|
||||
rows[i].YjssType = type; rows[i].Sort = i + 1;
|
||||
await _jfdcItemRepo.AddAsync(rows[i]);
|
||||
}
|
||||
await SetParentPointerAsync(type, dto.Main.YjssId, null, null, id, null, null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 切剖调查 ====================
|
||||
|
||||
public async Task<YjssDcQpdc> GetQpdcAsync(int type, long yjssId)
|
||||
=> await _qpdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type)
|
||||
?? new YjssDcQpdc { YjssId = yjssId, YjssType = type };
|
||||
|
||||
public async Task<bool> SaveQpdcAsync(int type, YjssDcQpdc dto)
|
||||
{
|
||||
dto.YjssType = type;
|
||||
var exist = await _qpdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == dto.YjssId && x.YjssType == type);
|
||||
var id = await UpsertAsync(_qpdcRepo, dto, exist);
|
||||
await SetParentPointerAsync(type, dto.YjssId, null, null, null, id, null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 清洁洁净 ====================
|
||||
|
||||
public async Task<YjssDcQjjj> GetQjjjAsync(int type, long yjssId)
|
||||
=> await _qjjjRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type)
|
||||
?? new YjssDcQjjj { YjssId = yjssId, YjssType = type };
|
||||
|
||||
public async Task<bool> SaveQjjjAsync(int type, YjssDcQjjj dto)
|
||||
{
|
||||
dto.YjssType = type;
|
||||
var exist = await _qjjjRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == dto.YjssId && x.YjssType == type);
|
||||
var id = await UpsertAsync(_qjjjRepo, dto, exist);
|
||||
await SetParentPointerAsync(type, dto.YjssId, null, id, null, null, null, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 茧丝百回纤度 ====================
|
||||
|
||||
public async Task<YjssJsbhxdDto> GetJsbhxdAsync(int type, long yjssId)
|
||||
{
|
||||
var main = await _jsbhxdMainRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
var dto = new YjssJsbhxdDto { Main = main ?? new YjssDcJsbhxdMain { YjssId = yjssId, YjssType = type } };
|
||||
if (main != null)
|
||||
dto.Items = (await _jsbhxdRepo.FindAsync(x => !x.IsDeleted && x.JsbhxdMainId == main.Id))
|
||||
.OrderBy(x => x.HuiShu).ToList();
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveJsbhxdAsync(int type, YjssJsbhxdDto dto)
|
||||
{
|
||||
dto.Main.YjssType = type;
|
||||
var exist = await _jsbhxdMainRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == dto.Main.YjssId && x.YjssType == type);
|
||||
var id = await UpsertAsync(_jsbhxdMainRepo, dto.Main, exist);
|
||||
|
||||
var olds = await _jsbhxdRepo.FindAsync(x => x.JsbhxdMainId == id);
|
||||
if (olds.Count > 0) await _jsbhxdRepo.HardDeleteRangeAsync(olds);
|
||||
var rows = dto.Items ?? new List<YjssDcJsbhxd>();
|
||||
for (var i = 0; i < rows.Count; i++)
|
||||
{
|
||||
rows[i].Id = 0; rows[i].JsbhxdMainId = id; rows[i].YjssId = dto.Main.YjssId;
|
||||
rows[i].YjssType = type; rows[i].Sort = i + 1;
|
||||
await _jsbhxdRepo.AddAsync(rows[i]);
|
||||
}
|
||||
await SetParentPointerAsync(type, dto.Main.YjssId, null, null, null, null, null, rows.Count);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== 整页 / 报表 ====================
|
||||
|
||||
public async Task<YjssFullDto> GetFullAsync(int type, long yjssId)
|
||||
{
|
||||
var dto = new YjssFullDto { Type = type };
|
||||
if (type == 1)
|
||||
{
|
||||
var m = await _ylxyRepo.GetByIdAsync(yjssId) ?? throw new ServiceException("原料小样不存在", 404);
|
||||
dto.ZhuangKouName = (await _ylzkRepo.GetByIdAsync(m.YlzkId))?.Name ?? "";
|
||||
dto.Date = m.Date; dto.ZhongLiang = m.ZhongLiang; dto.HuiChaoLv = m.HuiChaoLv;
|
||||
dto.SampleTypeName = SampleTypeName(m.Type); dto.Memo = m.Memo;
|
||||
}
|
||||
else
|
||||
{
|
||||
var m = await _zhyRepo.GetByIdAsync(yjssId) ?? throw new ServiceException("综合样不存在", 404);
|
||||
dto.ZhuangKouName = (await _gyzkRepo.GetByIdAsync(m.GyzkId))?.ZhuangKouName ?? "";
|
||||
dto.Date = m.Date; dto.ZhongLiang = m.ZhongLiang; dto.SampleTypeName = "综合样"; dto.Memo = m.Memo;
|
||||
}
|
||||
|
||||
var xjdc = await _xjdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
if (xjdc != null) dto.Xjdc = xjdc;
|
||||
|
||||
dto.Jsdc = await GetJsdcAsync(type, yjssId);
|
||||
|
||||
var jfdcMain = await _jfdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
if (jfdcMain != null)
|
||||
dto.Jfdc = new YjssJfdcDto
|
||||
{
|
||||
Main = jfdcMain,
|
||||
Items = (await _jfdcItemRepo.FindAsync(x => !x.IsDeleted && x.JfdcId == jfdcMain.Id)).OrderBy(x => x.JianFu).ToList()
|
||||
};
|
||||
|
||||
dto.Qpdc = await _qpdcRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
dto.Qjjj = await _qjjjRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
|
||||
var bhMain = await _jsbhxdMainRepo.FirstOrDefaultAsync(x => !x.IsDeleted && x.YjssId == yjssId && x.YjssType == type);
|
||||
if (bhMain != null)
|
||||
dto.Jsbhxd = new YjssJsbhxdDto
|
||||
{
|
||||
Main = bhMain,
|
||||
Items = (await _jsbhxdRepo.FindAsync(x => !x.IsDeleted && x.JsbhxdMainId == bhMain.Id)).OrderBy(x => x.HuiShu).ToList()
|
||||
};
|
||||
|
||||
BuildParameters(dto);
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, object?>> GetJsdcSummaryAsync(int type, long yjssId)
|
||||
{
|
||||
var list = await GetJsdcAsync(type, yjssId);
|
||||
var valid = list.Where(x => x.YiJianSiChang > 0).ToList();
|
||||
decimal Avg(Func<YjssDcJsdc, decimal> f)
|
||||
{
|
||||
var v = list.Where(x => f(x) > 0).Select(f).ToList();
|
||||
return v.Count == 0 ? 0 : Math.Round(v.Average(), 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
return new Dictionary<string, object?>
|
||||
{
|
||||
["解舒样茧粒数"] = valid.Select(x => x.JieShuLiShu).DefaultIfEmpty(0).Max(),
|
||||
["解舒样茧重量"] = valid.Select(x => x.JieShuZhongLiang).DefaultIfEmpty(0).Max(),
|
||||
["一茧丝长"] = Avg(x => x.YiJianSiChang),
|
||||
["解舒率"] = Avg(x => x.JieShuLv),
|
||||
["解舒丝长"] = Avg(x => x.JieShuSiChang),
|
||||
["茧丝纤度"] = Avg(x => x.JianSiXianDu),
|
||||
["解舒光折"] = Avg(x => x.JieShuGuangZhe),
|
||||
["万米吊糙"] = Avg(x => x.XwWanMiDiaoCao),
|
||||
["落绪外"] = Math.Round(list.Where(x => x.LdfbWai > 0).Select(x => (decimal)x.LdfbWai).DefaultIfEmpty(0).Average(), 0, MidpointRounding.AwayFromZero),
|
||||
["落绪中"] = Math.Round(list.Where(x => x.LdfbZhong > 0).Select(x => (decimal)x.LdfbZhong).DefaultIfEmpty(0).Average(), 0, MidpointRounding.AwayFromZero),
|
||||
["落绪内"] = Math.Round(list.Where(x => x.LdfbNei > 0).Select(x => (decimal)x.LdfbNei).DefaultIfEmpty(0).Average(), 0, MidpointRounding.AwayFromZero),
|
||||
["记录数"] = list.Count
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>按 F9 RepDt_Qc_Yjss_* 的中文键组装报表参数区</summary>
|
||||
private static void BuildParameters(YjssFullDto dto)
|
||||
{
|
||||
var p = dto.Parameters;
|
||||
p["查询时间"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
|
||||
p["庄口名称"] = dto.ZhuangKouName;
|
||||
p["日期"] = dto.Date.ToString("yyyy/M/d");
|
||||
p["样茧重量"] = dto.ZhongLiang;
|
||||
p["类型"] = dto.SampleTypeName;
|
||||
p["回潮率"] = dto.HuiChaoLv;
|
||||
p["备注"] = dto.Memo;
|
||||
|
||||
var x = dto.Xjdc;
|
||||
if (x != null)
|
||||
{
|
||||
p["公斤茧粒"] = x.GongJinLiShu;
|
||||
p["解舒样茧粒数"] = x.JieShuLiShu;
|
||||
p["解舒样茧重量"] = x.JieShuZhongLiang;
|
||||
p["茧重联统茧"] = x.ZlLtj; p["茧重好五级茧"] = x.ZlHwj; p["茧重五级茧"] = x.ZlWj;
|
||||
p["茧重棉茧"] = x.ZlMianJian; p["茧重黄斑"] = x.ZlHuangBan; p["茧重蛆茧"] = x.ZlQuJian;
|
||||
p["茧重双宫"] = x.ZlShuangGong; p["茧重血茧"] = x.ZlXueJian; p["茧重薄皮"] = x.ZlBaoPi;
|
||||
p["茧重茧衣"] = x.ZlJianYi; p["茧重霉茧"] = x.ZlMeiJian;
|
||||
p["比例联统茧"] = x.BlLtj; p["比例好五级茧"] = x.BlHwj; p["比例五级茧"] = x.BlWj;
|
||||
p["比例棉茧"] = x.BlMianJian; p["比例黄斑"] = x.BlHuangBan; p["比例蛆茧"] = x.BlQuJian;
|
||||
p["比例双宫"] = x.BlShuangGong; p["比例血茧"] = x.BlXueJian; p["比例薄皮"] = x.BlBaoPi;
|
||||
p["比例茧衣"] = x.BlJianYi; p["比例霉茧"] = x.BlMeiJian;
|
||||
}
|
||||
|
||||
var q = dto.Qpdc;
|
||||
if (q != null)
|
||||
{
|
||||
p["茧层率"] = q.JianCengLv; p["公量茧层率"] = q.JianCengLvGongLiang; p["脱胶率"] = q.TuoJiaoLv;
|
||||
p["原量全茧"] = q.YlQuanJian; p["原量茧层"] = q.YlJianCeng; p["原量蛹体"] = q.YlYongTi;
|
||||
p["干量煮茧后"] = q.GlZhuJian; p["干量茧层"] = q.GlJianCeng; p["干量蛹体"] = q.GlYongTi;
|
||||
p["粒数僵蚕"] = q.LsJiangCan; p["粒数内染"] = q.LsNeiRan; p["粒数霉蛹"] = q.LsMeiYong; p["粒数毛脚"] = q.LsMaoJiao;
|
||||
p["比例僵蚕"] = q.BlJiangCan; p["比例内染"] = q.BlNeiRan; p["比例霉蛹"] = q.BlMeiYong; p["比例毛脚"] = q.BlMaoJiao;
|
||||
}
|
||||
|
||||
var jj = dto.Qjjj;
|
||||
if (jj != null)
|
||||
{
|
||||
p["清洁"] = jj.QingJie; p["洁净"] = jj.JingJie; p["片数"] = jj.PianShu;
|
||||
p["一度"] = jj.YdYiDu; p["二度"] = jj.YdErDu; p["三度"] = jj.YdSanDu;
|
||||
p["洁净100"] = jj.Jj100; p["洁净95"] = jj.Jj95; p["洁净90"] = jj.Jj90; p["洁净85"] = jj.Jj85;
|
||||
p["洁净80"] = jj.Jj80; p["洁净75"] = jj.Jj75; p["洁净70"] = jj.Jj70; p["洁净65"] = jj.Jj65; p["洁净60"] = jj.Jj60;
|
||||
p["特大糙"] = jj.QjTdc; p["废丝"] = jj.QjFs; p["大糙"] = jj.QjDc; p["黏附糙"] = jj.QjNfc;
|
||||
p["大长结"] = jj.QjDcj; p["重螺旋"] = jj.QjZlx;
|
||||
p["小糙"] = jj.QjXc; p["长结"] = jj.QjCj; p["螺旋"] = jj.QjLx; p["环结及裂丝"] = jj.QjHjls;
|
||||
}
|
||||
|
||||
if (dto.Jfdc != null)
|
||||
{
|
||||
p["平均茧幅"] = dto.Jfdc.Main.PingJunJianFu;
|
||||
p["最大茧幅"] = dto.Jfdc.Main.ZuiDaJianFu;
|
||||
p["茧幅整齐率"] = dto.Jfdc.Main.ZhengQiLv;
|
||||
p["茧幅均方差"] = dto.Jfdc.Main.JunFangCha;
|
||||
}
|
||||
|
||||
if (dto.Jsbhxd != null)
|
||||
{
|
||||
p["茧丝纤度均方差"] = dto.Jsbhxd.Main.XianDuJunFangCha;
|
||||
p["最大开差"] = dto.Jsbhxd.Main.ZuiDaKaiCha;
|
||||
p["内外开差"] = dto.Jsbhxd.Main.NeiWaiKaiCha;
|
||||
p["外三百回"] = dto.Jsbhxd.Main.WaiSanBaiHui;
|
||||
p["中三百回"] = dto.Jsbhxd.Main.ZhongSanBaiHui;
|
||||
p["内三百回"] = dto.Jsbhxd.Main.NeiSanBaiHui;
|
||||
p["白色茧单纤"] = dto.Jsbhxd.Main.BaiSeJianDanXian;
|
||||
p["灰色茧单纤"] = dto.Jsbhxd.Main.HuiSeJianDanXian;
|
||||
p["红色茧单纤"] = dto.Jsbhxd.Main.HongSeJianDanXian;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内部工具 ====================
|
||||
|
||||
private async Task SetParentPointerAsync(int type, long yjssId,
|
||||
long? xjdcId, long? qjjjId, long? jfdcId, long? qpdcId, int? jsdcCt, int? jsbhxdCt)
|
||||
{
|
||||
if (type == 1)
|
||||
{
|
||||
var m = await _ylxyRepo.GetByIdAsync(yjssId);
|
||||
if (m == null) return;
|
||||
if (xjdcId.HasValue) m.XjdcId = xjdcId.Value;
|
||||
if (qjjjId.HasValue) m.QjjjId = qjjjId.Value;
|
||||
if (jsdcCt.HasValue) m.JsdcCt = jsdcCt.Value;
|
||||
await _ylxyRepo.UpdateAsync(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
var m = await _zhyRepo.GetByIdAsync(yjssId);
|
||||
if (m == null) return;
|
||||
if (xjdcId.HasValue) m.XjdcId = xjdcId.Value;
|
||||
if (qjjjId.HasValue) m.QjjjId = qjjjId.Value;
|
||||
if (jfdcId.HasValue) m.JfdcId = jfdcId.Value;
|
||||
if (qpdcId.HasValue) m.QpdcId = qpdcId.Value;
|
||||
if (jsdcCt.HasValue) m.JsdcCt = jsdcCt.Value;
|
||||
if (jsbhxdCt.HasValue) m.JsbhxdCt = jsbhxdCt.Value;
|
||||
await _zhyRepo.UpdateAsync(m);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<long> UpsertAsync<T>(IRepository<T> repo, T dto, T? exist) where T : class, IEntity
|
||||
{
|
||||
if (exist == null)
|
||||
{
|
||||
dto.Id = 0;
|
||||
dto.IsDeleted = false;
|
||||
await repo.AddAsync(dto);
|
||||
return dto.Id;
|
||||
}
|
||||
dto.Id = exist.Id;
|
||||
dto.CreatedAt = exist.CreatedAt;
|
||||
dto.IsDeleted = false;
|
||||
await repo.UpdateAsync(dto);
|
||||
return dto.Id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Reports;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Reports;
|
||||
|
||||
public interface IReportService
|
||||
{
|
||||
Task<PageResult<ReportListDto>> GetPagedAsync(string like, string category, bool? isActive, int pageIndex, int pageSize);
|
||||
Task<ReportDetailDto> GetByIdAsync(long id);
|
||||
Task<long> CreateAsync(ReportCreateDto dto);
|
||||
Task UpdateAsync(long id, ReportUpdateDto dto);
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>上传 .grf 模板文件(f9designer:// 本机设计器协议回传用);常规保存走 Create/Update 的 templateJson</summary>
|
||||
Task UploadTemplateAsync(long id, byte[] data, string fileName);
|
||||
|
||||
/// <summary>直接保存模板文本(前端在线编辑模板源码用,免下载 / 上传),返回保存后的字符数</summary>
|
||||
Task<int> SaveTemplateContentAsync(long id, string content);
|
||||
|
||||
/// <summary>读取报表模板内容(ReportFile.DesignContent,.grf 的 JSON 文本);无模板返回 null</summary>
|
||||
Task<string?> GetTemplateContentAsync(Report report);
|
||||
|
||||
/// <summary>用户可见报表(按权限码过滤,admin 返回全部启用报表)</summary>
|
||||
Task<List<ReportViewDto>> GetVisibleReportsAsync(long userId, bool isAdmin, IReadOnlyCollection<string> permissions);
|
||||
|
||||
/// <summary>校验用户是否拥有某报表的查看权限(admin 视为通过)</summary>
|
||||
Task EnsureReportAccessAsync(long userId, bool isAdmin, IReadOnlyCollection<string> permissions, long reportId);
|
||||
|
||||
/// <summary>执行报表数据查询 SQL,返回 Grid++Report 约定的 JSON 形态</summary>
|
||||
Task<Dictionary<string, object?>> ExecuteDataAsync(Report report, IReadOnlyDictionary<string, object?> parameters);
|
||||
|
||||
/// <summary>列出所有启用的报表(角色管理-报表权限分配时使用)</summary>
|
||||
Task<List<ReportListDto>> GetAllActiveAsync();
|
||||
|
||||
/// <summary>数据源清单(IReportDataProvider 注册表 + 各自声明的查询参数),新增报表自动配置用</summary>
|
||||
Task<List<ReportDataSourceInfoDto>> GetDataSourcesAsync();
|
||||
|
||||
/// <summary>探测数据源结构:按 DsName / DataSql 执行一次查询,识别明细列与参数</summary>
|
||||
Task<ReportProbeResultDto> ProbeAsync(ReportProbeRequestDto request);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using F9WebAiTest.Application.CQRS.SYS.Reports.Queries;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Application.Interfaces.Reports;
|
||||
using MediatR;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Reports.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// 落丝记录报表数据提供者(生产-前缫)。
|
||||
/// DsName = "RepDt_Prod_Lsjl"
|
||||
/// 参数:q_date_start / q_date_end(日期范围)、q_dep(车间,含子车间)、q_jizu(机组)、
|
||||
/// q_zk(庄口 Id,选择页取值)、q_like(关键字:庄口 / 规格 / 颜色)。
|
||||
/// 参数与列完全由查询结果/报表配置决定,Provider 只负责取数。
|
||||
/// </summary>
|
||||
public class RepDt_Prod_Lsjl : IReportDataProvider
|
||||
{
|
||||
public string ReportName => "RepDt_Prod_Lsjl";
|
||||
|
||||
public async Task<ReportDto_DataSource> GetData(Dictionary<string, object> pairs, IMediator mediator)
|
||||
{
|
||||
DateTime? dateStart = null;
|
||||
if (pairs.TryGetValue("q_date_start", out var ds) && ds != null && ds.ToString() is { Length: > 0 } s1
|
||||
&& DateTime.TryParse(s1, out var d1))
|
||||
dateStart = d1;
|
||||
|
||||
DateTime? dateEnd = null;
|
||||
if (pairs.TryGetValue("q_date_end", out var de) && de != null && de.ToString() is { Length: > 0 } s2
|
||||
&& DateTime.TryParse(s2, out var d2))
|
||||
dateEnd = d2;
|
||||
|
||||
long depId = 0;
|
||||
if (pairs.TryGetValue("q_dep", out var dep) && dep != null && long.TryParse(dep.ToString(), out var d))
|
||||
depId = d;
|
||||
|
||||
int jizu = 0;
|
||||
if (pairs.TryGetValue("q_jizu", out var jz) && jz != null && int.TryParse(jz.ToString(), out var j))
|
||||
jizu = j;
|
||||
|
||||
long gyzkId = 0;
|
||||
if (pairs.TryGetValue("q_zk", out var zk) && zk != null && long.TryParse(zk.ToString(), out var z))
|
||||
gyzkId = z;
|
||||
|
||||
var like = pairs.TryGetValue("q_like", out var lk) && lk != null ? lk.ToString() ?? "" : "";
|
||||
|
||||
return await mediator.Send(new GetLsjlRepQuery(dateStart, dateEnd, depId, jizu, gyzkId, like));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using F9WebAiTest.Application.CQRS.SYS.Reports.Queries;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Application.Interfaces.Reports;
|
||||
using MediatR;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Reports.Providers;
|
||||
|
||||
/// <summary>
|
||||
/// 职工花名册数据提供者(对应 F9 的 IReportService 实现):
|
||||
/// 解析查询参数 → 通过 MediatR 发送 CQRS 查询 → 返回含记录集与报表参数的数据源。
|
||||
/// DsName = "RepDt_Sys_Users"
|
||||
/// </summary>
|
||||
public class RepDt_Sys_Users : IReportDataProvider
|
||||
{
|
||||
public string ReportName => "RepDt_Sys_Users";
|
||||
|
||||
public async Task<ReportDto_DataSource> GetData(Dictionary<string, object> pairs, IMediator mediator)
|
||||
{
|
||||
// 1. 解析输入参数(部门 q_dep、关键字 q_like)
|
||||
long depId = 0;
|
||||
if (pairs.TryGetValue("q_dep", out var depObj) && depObj != null && long.TryParse(depObj.ToString(), out var d))
|
||||
depId = d;
|
||||
|
||||
string like = "";
|
||||
if (pairs.TryGetValue("q_like", out var likeObj) && likeObj != null)
|
||||
like = likeObj.ToString() ?? "";
|
||||
|
||||
// 2. 发送 CQRS 查询获取报表数据
|
||||
return await mediator.Send(new GetEmployeeDirectoryRepQuery(depId, like));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Reports;
|
||||
using F9WebAiTest.Application.Interfaces.Reports;
|
||||
using F9WebAiTest.Application.Interfaces.SnowflakeID;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.SYS.RBAC;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Reports;
|
||||
using F9WebAiTest.Domain.Enums.SYS.RBAC;
|
||||
using MediatR;
|
||||
using System.Data.SQLite;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Reports;
|
||||
|
||||
public class ReportService : IReportService
|
||||
{
|
||||
private readonly IRepository<Report> _reportRepo;
|
||||
private readonly IRepository<ReportFile> _reportFileRepo;
|
||||
private readonly IRepository<Permission> _permissionRepo;
|
||||
private readonly IRepository<RolePermission> _rolePermissionRepo;
|
||||
private readonly IFreeSql _fsql;
|
||||
private readonly ISnowflakeIdGenerator _snowflake;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly Dictionary<string, IReportDataProvider> _dataProviders;
|
||||
|
||||
public ReportService(
|
||||
IRepository<Report> reportRepo,
|
||||
IRepository<ReportFile> reportFileRepo,
|
||||
IRepository<Permission> permissionRepo,
|
||||
IRepository<RolePermission> rolePermissionRepo,
|
||||
IFreeSql fsql,
|
||||
ISnowflakeIdGenerator snowflake,
|
||||
IMediator mediator,
|
||||
IEnumerable<IReportDataProvider> dataProviders)
|
||||
{
|
||||
_reportRepo = reportRepo;
|
||||
_reportFileRepo = reportFileRepo;
|
||||
_permissionRepo = permissionRepo;
|
||||
_rolePermissionRepo = rolePermissionRepo;
|
||||
_fsql = fsql;
|
||||
_snowflake = snowflake;
|
||||
_mediator = mediator;
|
||||
_dataProviders = dataProviders.ToDictionary(p => p.ReportName, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// ===== 报表管理 =====
|
||||
|
||||
public async Task<PageResult<ReportListDto>> GetPagedAsync(string like, string category, bool? isActive, int pageIndex, int pageSize)
|
||||
{
|
||||
var query = _fsql.Select<Report>().Where(r => !r.IsDeleted);
|
||||
if (!string.IsNullOrWhiteSpace(like))
|
||||
query = query.Where(r => r.Name.Contains(like) || r.Code.Contains(like));
|
||||
if (!string.IsNullOrWhiteSpace(category))
|
||||
query = query.Where(r => r.Category == category);
|
||||
if (isActive.HasValue)
|
||||
query = query.Where(r => r.IsActive == isActive.Value);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var rows = await query
|
||||
.OrderBy(r => r.SortOrder)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Page(pageIndex, pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var items = rows.Select(r => new ReportListDto(
|
||||
r.Id, r.Code, r.Name, r.Category, r.Description, r.SortOrder, r.IsActive,
|
||||
r.ReportFileId, HasTemplate(r), r.CreatedAt, r.UpdatedAt)).ToList();
|
||||
return new PageResult<ReportListDto> { Items = items, Total = total, PageIndex = pageIndex, PageSize = pageSize };
|
||||
}
|
||||
|
||||
public async Task<ReportDetailDto> GetByIdAsync(long id)
|
||||
{
|
||||
var r = await _reportRepo.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted)
|
||||
?? throw new F9NotFoundException("报表不存在");
|
||||
return ToDetail(r);
|
||||
}
|
||||
|
||||
public async Task<long> CreateAsync(ReportCreateDto dto)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dto.Code)) throw new F9OperationException("报表编码不能为空");
|
||||
if (string.IsNullOrWhiteSpace(dto.Name)) throw new F9OperationException("报表名称不能为空");
|
||||
ValidateCode(dto.Code);
|
||||
ValidateConfig(dto.Config);
|
||||
// 有 DsName 时允许不填 SQL;否则必须为合法 SELECT
|
||||
if (GetConfigDsName(dto.Config) == null) ValidateSql(dto.DataSql);
|
||||
|
||||
if (await _reportRepo.AnyAsync(r => r.Code == dto.Code && !r.IsDeleted))
|
||||
throw new F9OperationException($"报表编码已存在:{dto.Code}");
|
||||
|
||||
var entity = new Report(_snowflake.CreateId(), dto.Code.Trim(), dto.Name.Trim(),
|
||||
dto.Category ?? "通用", dto.Description ?? "", dto.DataSql ?? "",
|
||||
dto.DataParams ?? "[]", dto.SortOrder, dto.Config ?? "");
|
||||
|
||||
// 模板与配置一起保存(模板实例写入 ReportFile,Report 持有 ReportFileId)
|
||||
if (!string.IsNullOrWhiteSpace(dto.TemplateJson))
|
||||
await SaveTemplateAsync(entity, dto.TemplateJson);
|
||||
|
||||
await _reportRepo.AddAsync(entity);
|
||||
await _reportRepo.SaveChangesAsync();
|
||||
|
||||
// 同步创建权限单元:report:{code}:visble
|
||||
var perm = new Permission(_snowflake.CreateId(), Permission_Type_Enum.Visble,
|
||||
Resource_Type_Enum.报表, entity.Code, entity.Id);
|
||||
perm.SetCode($"report:{entity.Code}:visble");
|
||||
await _permissionRepo.AddAsync(perm);
|
||||
await _permissionRepo.SaveChangesAsync();
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(long id, ReportUpdateDto dto)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dto.Code)) throw new F9OperationException("报表编码不能为空");
|
||||
if (string.IsNullOrWhiteSpace(dto.Name)) throw new F9OperationException("报表名称不能为空");
|
||||
ValidateCode(dto.Code);
|
||||
ValidateConfig(dto.Config);
|
||||
if (GetConfigDsName(dto.Config) == null) ValidateSql(dto.DataSql);
|
||||
|
||||
var entity = await _reportRepo.FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted)
|
||||
?? throw new F9NotFoundException("报表不存在");
|
||||
|
||||
if (await _reportRepo.AnyAsync(r => r.Code == dto.Code && r.Id != id && !r.IsDeleted))
|
||||
throw new F9OperationException($"报表编码已存在:{dto.Code}");
|
||||
|
||||
var oldCode = entity.Code;
|
||||
entity.UpdateBasic(dto.Code.Trim(), dto.Name.Trim(), dto.Category ?? "通用",
|
||||
dto.Description ?? "", dto.DataSql ?? "", dto.DataParams ?? "[]", dto.SortOrder, dto.Config ?? "");
|
||||
entity.Activate();
|
||||
|
||||
// 模板与配置一起保存;不传模板时保留原模板
|
||||
if (!string.IsNullOrWhiteSpace(dto.TemplateJson))
|
||||
await SaveTemplateAsync(entity, dto.TemplateJson);
|
||||
|
||||
await _reportRepo.UpdateAsync(entity);
|
||||
await _reportRepo.SaveChangesAsync();
|
||||
|
||||
// 同步更新权限 Code(编码变更时)
|
||||
if (!string.Equals(oldCode, entity.Code, StringComparison.Ordinal))
|
||||
{
|
||||
var perm = await _permissionRepo.FirstOrDefaultAsync(p =>
|
||||
p.ResourceType == Resource_Type_Enum.报表 && p.ResourceId == entity.Id && !p.IsDeleted);
|
||||
if (perm != null)
|
||||
{
|
||||
perm.SetCode($"report:{entity.Code}:visble");
|
||||
await _permissionRepo.UpdateAsync(perm);
|
||||
await _permissionRepo.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
var entity = await _reportRepo.FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted)
|
||||
?? throw new F9NotFoundException("报表不存在");
|
||||
await _reportRepo.DeleteAsync(entity);
|
||||
await _reportRepo.SaveChangesAsync();
|
||||
|
||||
// 级联删除模板文件实例
|
||||
var files = await _reportFileRepo.FindAsync(f => f.ReportId == id && !f.IsDeleted);
|
||||
if (files.Count > 0)
|
||||
{
|
||||
await _reportFileRepo.DeleteRangeAsync(files);
|
||||
await _reportFileRepo.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// 同步删除报表关联的权限单元与角色授权
|
||||
var perms = await _permissionRepo.FindAsync(p => p.ResourceType == Resource_Type_Enum.报表 && p.ResourceId == id && !p.IsDeleted);
|
||||
if (perms.Count > 0)
|
||||
{
|
||||
var permIds = perms.Select(p => p.Id).ToList();
|
||||
await _rolePermissionRepo.HardDeleteRangeAsync(
|
||||
(await _rolePermissionRepo.FindAsync(rp => permIds.Contains(rp.PermissionId) && !rp.IsDeleted)));
|
||||
await _permissionRepo.HardDeleteRangeAsync(perms);
|
||||
await _permissionRepo.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UploadTemplateAsync(long id, byte[] data, string fileName)
|
||||
{
|
||||
if (data == null || data.Length == 0) throw new F9OperationException("模板内容为空");
|
||||
var entity = await _reportRepo.FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted)
|
||||
?? throw new F9NotFoundException("报表不存在");
|
||||
|
||||
// .grf 可能是 GR6 时代的 UTF-8 JSON,也可能是 GR7 的 "_WR" 压缩文本,按 BOM 自动识别编码
|
||||
var text = DecodeTemplateBytes(data);
|
||||
await SaveTemplateAsync(entity, text);
|
||||
await _reportRepo.UpdateAsync(entity);
|
||||
await _reportRepo.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接保存模板文本(前端「模板源码」在线编辑后保存)。
|
||||
/// JSON 格式模板做一次语法校验,避免保存后在设计器 / 渲染端打不开;
|
||||
/// "_WR" 压缩模板无法在服务端校验,原样保存。
|
||||
/// </summary>
|
||||
public async Task<int> SaveTemplateContentAsync(long id, string content)
|
||||
{
|
||||
var entity = await _reportRepo.FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted)
|
||||
?? throw new F9NotFoundException("报表不存在");
|
||||
|
||||
var text = NormalizeTemplateContent(content);
|
||||
var head = text.TrimStart();
|
||||
if (head.StartsWith('{') || head.StartsWith('['))
|
||||
{
|
||||
try { using var _ = JsonDocument.Parse(text); }
|
||||
catch (JsonException ex) { throw new F9OperationException($"模板 JSON 不合法:{ex.Message}"); }
|
||||
}
|
||||
|
||||
await SaveTemplateAsync(entity, text);
|
||||
await _reportRepo.UpdateAsync(entity);
|
||||
await _reportRepo.SaveChangesAsync();
|
||||
return text.Length;
|
||||
}
|
||||
|
||||
public async Task<string?> GetTemplateContentAsync(Report report)
|
||||
{
|
||||
var file = await FindTemplateFileAsync(report);
|
||||
return file?.DesignContent;
|
||||
}
|
||||
|
||||
/// <summary>查找报表的模板文件实例(优先 ReportFileId,兼容按 ReportId 查找)</summary>
|
||||
private async Task<ReportFile?> FindTemplateFileAsync(Report report)
|
||||
{
|
||||
if (report.ReportFileId != 0)
|
||||
{
|
||||
var byId = await _reportFileRepo.FirstOrDefaultAsync(f => f.Id == report.ReportFileId && !f.IsDeleted);
|
||||
if (byId != null) return byId;
|
||||
}
|
||||
return await _reportFileRepo.FirstOrDefaultAsync(f => f.ReportId == report.Id && !f.IsDeleted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存模板:新建或更新 ReportFile.DesignContent,并把 Report.ReportFileId 绑定到该实例。
|
||||
/// 模板与报表配置一起保存,前端无需再单独上传 .grf 文件。
|
||||
/// 注意:只修改内存中的 report,不落库(由调用方统一保存)。
|
||||
/// </summary>
|
||||
private async Task SaveTemplateAsync(Report report, string templateContent)
|
||||
{
|
||||
var content = NormalizeTemplateContent(templateContent);
|
||||
var file = await FindTemplateFileAsync(report);
|
||||
if (file != null)
|
||||
{
|
||||
file.UpdateContent(content);
|
||||
await _reportFileRepo.UpdateAsync(file);
|
||||
await _reportFileRepo.SaveChangesAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
file = new ReportFile(_snowflake.CreateId(), report.Id, content);
|
||||
await _reportFileRepo.AddAsync(file);
|
||||
await _reportFileRepo.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (report.ReportFileId != file.Id) report.BindReportFile(file.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规范化模板内容:仅去 BOM / 去掉首尾空白,不做格式校验。
|
||||
/// .grf 有两种格式:GR6 的 UTF-8 JSON 文本,以及 GR7 的 "_WR" 前缀压缩文本;
|
||||
/// 两者都由 Grid++Report 的 HTML5 渲染库自行识别(内部 decodeWR),服务端原样保存即可。
|
||||
/// </summary>
|
||||
private static string NormalizeTemplateContent(string? content)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content)) throw new F9OperationException("报表模板内容为空");
|
||||
|
||||
var text = content.Trim();
|
||||
if (text.Length > 0 && text[0] == '\uFEFF') text = text.Substring(1);
|
||||
if (string.IsNullOrWhiteSpace(text)) throw new F9OperationException("报表模板内容为空");
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>按 BOM / 内容特征解码 .grf 字节:UTF-8(BOM) / UTF-16LE(BOM) / UTF-16BE(BOM) / 无 BOM 按 UTF-8</summary>
|
||||
private static string DecodeTemplateBytes(byte[] data)
|
||||
{
|
||||
if (data.Length >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF)
|
||||
return Encoding.UTF8.GetString(data, 3, data.Length - 3);
|
||||
if (data.Length >= 2 && data[0] == 0xFF && data[1] == 0xFE)
|
||||
return Encoding.Unicode.GetString(data, 2, data.Length - 2);
|
||||
if (data.Length >= 2 && data[0] == 0xFE && data[1] == 0xFF)
|
||||
return Encoding.BigEndianUnicode.GetString(data, 2, data.Length - 2);
|
||||
|
||||
// 无 BOM:先按 UTF-8 严格解码,失败则按 UTF-16LE 尝试
|
||||
try
|
||||
{
|
||||
var strict = new UTF8Encoding(false, true);
|
||||
return strict.GetString(data);
|
||||
}
|
||||
catch (DecoderFallbackException)
|
||||
{
|
||||
try { return Encoding.Unicode.GetString(data); }
|
||||
catch { return Encoding.UTF8.GetString(data); }
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 用户可见报表 =====
|
||||
|
||||
public async Task<List<ReportViewDto>> GetVisibleReportsAsync(long userId, bool isAdmin, IReadOnlyCollection<string> permissions)
|
||||
{
|
||||
var all = await _reportRepo.FindAsync(r => r.IsActive && !r.IsDeleted);
|
||||
IEnumerable<Report> visible;
|
||||
if (isAdmin)
|
||||
{
|
||||
visible = all;
|
||||
}
|
||||
else
|
||||
{
|
||||
visible = all.Where(r => permissions.Contains($"report:{r.Code}:visble"));
|
||||
}
|
||||
return visible
|
||||
.OrderBy(r => r.SortOrder).ThenBy(r => r.Name)
|
||||
.Select(r => new ReportViewDto(r.Id, r.Code, r.Name, r.Category, r.Description, r.SortOrder, HasTemplate(r)))
|
||||
.ToList();
|
||||
|
||||
}
|
||||
|
||||
public async Task EnsureReportAccessAsync(long userId, bool isAdmin, IReadOnlyCollection<string> permissions, long reportId)
|
||||
{
|
||||
if (isAdmin) return;
|
||||
var report = await _reportRepo.FirstOrDefaultAsync(r => r.Id == reportId && !r.IsDeleted)
|
||||
?? throw new F9NotFoundException("报表不存在", 404);
|
||||
if (!report.IsActive) throw new F9NotFoundException("报表已停用", 404);
|
||||
if (!permissions.Contains($"report:{report.Code}:visble"))
|
||||
throw new F9OperationException("无权访问该报表");
|
||||
}
|
||||
|
||||
// ===== 数据查询 =====
|
||||
|
||||
public async Task<Dictionary<string, object?>> ExecuteDataAsync(Report report, IReadOnlyDictionary<string, object?> parameters)
|
||||
{
|
||||
// 1. 优先按 Config.DsName 解析到数据提供者(F9 IReportService 模式)
|
||||
var dsName = GetConfigDsName(report.Config);
|
||||
if (!string.IsNullOrWhiteSpace(dsName))
|
||||
{
|
||||
if (!_dataProviders.TryGetValue(dsName, out var provider))
|
||||
throw new F9OperationException($"未找到数据源查询实现:{dsName}");
|
||||
|
||||
var pairs = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
foreach (var kv in parameters) pairs[kv.Key] = kv.Value!;
|
||||
|
||||
var dataSource = await provider.GetData(pairs, _mediator);
|
||||
return ToGridReportJson(dataSource);
|
||||
}
|
||||
|
||||
// 2. 回退:DataSql + @参数名(仅绑定 SQL 中实际出现的参数,防注入)
|
||||
if (string.IsNullOrWhiteSpace(report.DataSql))
|
||||
throw new F9OperationException("报表未配置数据源(Config.DsName 或 DataSql)");
|
||||
|
||||
try
|
||||
{
|
||||
var sqlTokens = Regex.Matches(report.DataSql, @"@([A-Za-z_][A-Za-z0-9_]*)")
|
||||
.Select(m => m.Groups[1].Value).Distinct().ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
var dbParams = new List<DbParameter>();
|
||||
foreach (var kv in parameters)
|
||||
{
|
||||
if (kv.Key.Length > 0 && sqlTokens.Contains(kv.Key) && kv.Value != null)
|
||||
dbParams.Add(new SQLiteParameter("@" + kv.Key, kv.Value));
|
||||
}
|
||||
|
||||
var dt = dbParams.Count > 0
|
||||
? _fsql.Ado.ExecuteDataTable(report.DataSql, dbParams.ToArray())
|
||||
: _fsql.Ado.ExecuteDataTable(report.DataSql);
|
||||
|
||||
var recordset = new List<Dictionary<string, object?>>();
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
var d = new Dictionary<string, object?>(dt.Columns.Count, StringComparer.Ordinal);
|
||||
foreach (DataColumn col in dt.Columns)
|
||||
{
|
||||
var v = row[col];
|
||||
d[col.ColumnName] = v == DBNull.Value ? null : v;
|
||||
}
|
||||
recordset.Add(d);
|
||||
}
|
||||
return new Dictionary<string, object?> { ["recordset"] = recordset };
|
||||
}
|
||||
catch (F9OperationException) { throw; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new F9OperationException("报表数据查询失败:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 F9 的 ReportDto_DataSource 转换为 Grid++Report 约定的 JSON 数据:
|
||||
/// 单数据集 → { "recordset": [ {...} ] }(兼容无名记录集的模板)
|
||||
/// 多数据集 → { "main": [ {...} ], "detail": [ {...} ] }(顶层键为数据集名)
|
||||
/// 报表参数 → 附加 { "parameter": [ { 账期: "...", 车间: "..." } ] }(模板以 Parameter 方式引用)
|
||||
/// </summary>
|
||||
private static Dictionary<string, object?> ToGridReportJson(ReportDto_DataSource dataSource)
|
||||
{
|
||||
var result = new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
|
||||
var datasets = dataSource.Report_Data
|
||||
.Where(kv => (kv.Value.Field_Data?.Count ?? 0) > 0)
|
||||
.ToList();
|
||||
|
||||
if (datasets.Count <= 1)
|
||||
{
|
||||
// 0 行时也必须输出空 recordset:否则渲染库找不到主记录集,
|
||||
// 报表参数(Parameter)会被错位渲染到明细区(实测 0 行报表参数值错乱)
|
||||
result["recordset"] = datasets.Count == 0
|
||||
? new List<object>()
|
||||
: datasets[0].Value.Field_Data.Select(ToNameValueDict).Cast<object>().ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in datasets)
|
||||
result[kv.Key] = kv.Value.Field_Data.Select(ToNameValueDict).Cast<object>().ToList();
|
||||
}
|
||||
|
||||
// 报表参数:以 Parameter 数据集输出(键名须与 .grf 模板的 Parameter 节点一致,HTML5 渲染器取数组第一项)
|
||||
var paramData = dataSource.Report_Data.Values
|
||||
.FirstOrDefault(d => d.Parameter_Data != null)?.Parameter_Data;
|
||||
if (paramData != null)
|
||||
result["Parameter"] = new List<object> { ToNameValueDict(paramData) };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对象 → 字段名/值的字典。
|
||||
/// 用字典承载是为了保证属性名原样输出(不受 ASP.NET Core 默认 camelCase 序列化策略影响),
|
||||
/// 从而与 .grf 模板 DetailGrid.Recordset.Field 定义的字段名严格一致。
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> _rowPropsCache = new();
|
||||
|
||||
private static Dictionary<string, object?> ToNameValueDict(object row)
|
||||
{
|
||||
if (row is IDictionary<string, object> exists) return exists.ToDictionary(k => k.Key, v => (object?)v.Value);
|
||||
|
||||
var props = _rowPropsCache.GetOrAdd(row.GetType(), t => t.GetProperties());
|
||||
var dict = new Dictionary<string, object?>(props.Length, StringComparer.Ordinal);
|
||||
foreach (var p in props)
|
||||
{
|
||||
if (!p.CanRead) continue;
|
||||
dict[p.Name] = p.GetValue(row);
|
||||
}
|
||||
return dict!;
|
||||
}
|
||||
|
||||
private static string? GetConfigDsName(string configJson)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configJson)) return null;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(configJson);
|
||||
if (doc.RootElement.TryGetProperty("DsName", out var n) && n.ValueKind == JsonValueKind.String)
|
||||
return n.GetString();
|
||||
}
|
||||
catch { /* 配置 JSON 非法时忽略 */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<ReportListDto>> GetAllActiveAsync()
|
||||
{
|
||||
var list = await _reportRepo.FindAsync(r => r.IsActive && !r.IsDeleted);
|
||||
return list
|
||||
.OrderBy(r => r.SortOrder).ThenBy(r => r.Name)
|
||||
.Select(r => new ReportListDto(
|
||||
r.Id, r.Code, r.Name, r.Category, r.Description, r.SortOrder, r.IsActive,
|
||||
r.ReportFileId, HasTemplate(r), r.CreatedAt, r.UpdatedAt))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// ===== 数据源清单 / 结构探测(新增报表时按查询结果自动识别明细列) =====
|
||||
|
||||
public Task<List<ReportDataSourceInfoDto>> GetDataSourcesAsync()
|
||||
{
|
||||
var list = _dataProviders.Values
|
||||
.OrderBy(p => p.ReportName, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(p => new ReportDataSourceInfoDto { DsName = p.ReportName })
|
||||
.ToList();
|
||||
return Task.FromResult(list);
|
||||
}
|
||||
|
||||
public async Task<ReportProbeResultDto> ProbeAsync(ReportProbeRequestDto request)
|
||||
{
|
||||
var dsName = (request.DsName ?? "").Trim();
|
||||
var result = new ReportProbeResultDto { DsName = dsName };
|
||||
|
||||
_dataProviders.TryGetValue(dsName, out var provider);
|
||||
|
||||
// 探测参数只用前端带入的取值(参数本身由用户在报表配置里维护,不在这里生成控件)
|
||||
var pairs = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
foreach (var kv in request.Params)
|
||||
if (kv.Value != null) pairs[kv.Key] = kv.Value;
|
||||
|
||||
try
|
||||
{
|
||||
if (provider != null)
|
||||
await FillFromProviderAsync(result, provider, pairs);
|
||||
else if (!string.IsNullOrWhiteSpace(request.DataSql))
|
||||
FillFromSql(result, request.DataSql!, pairs);
|
||||
else if (!string.IsNullOrWhiteSpace(dsName))
|
||||
result.Error = $"未找到数据源实现:{dsName}";
|
||||
else
|
||||
result.Error = "请先选择数据源(DsName)或填写数据 SQL";
|
||||
}
|
||||
catch (F9OperationException ex) { result.Error = ex.Message; }
|
||||
catch (Exception ex) { result.Error = "数据源探测失败:" + ex.Message; }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>通过 IReportDataProvider 执行一次查询,取返回记录集的字段名作为明细列</summary>
|
||||
private async Task FillFromProviderAsync(ReportProbeResultDto result, IReportDataProvider provider, Dictionary<string, object> pairs)
|
||||
{
|
||||
var dataSource = await provider.GetData(pairs, _mediator);
|
||||
|
||||
// 报表参数:来自 Provider 返回的 Parameter_Data(不是查询条件,也不是硬编码声明)
|
||||
var paramDatum = dataSource.Report_Data.FirstOrDefault(kv => kv.Value?.Parameter_Data != null);
|
||||
if (paramDatum.Value?.Parameter_Data != null)
|
||||
{
|
||||
result.Parameters = ToNameValueDict(paramDatum.Value.Parameter_Data)
|
||||
.Select(kv => new ReportParamValueDto
|
||||
{
|
||||
Name = kv.Key,
|
||||
Value = kv.Value?.ToString() ?? "",
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var datums = dataSource.Report_Data
|
||||
.Where(kv => kv.Value?.Field_Data is { Count: > 0 })
|
||||
.ToList();
|
||||
result.Total = datums.Sum(kv => kv.Value.Field_Data.Count);
|
||||
|
||||
if (datums.Count == 0)
|
||||
{
|
||||
result.Error = "查询未返回数据,无法识别明细列(可手工添加列)";
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = datums[0].Value.Field_Data.Take(3).Select(ToNameValueDict).ToList();
|
||||
result.Rows = rows;
|
||||
result.Fields = rows[0].Select(kv => new ReportFieldDefDto
|
||||
{
|
||||
Name = kv.Key,
|
||||
Label = kv.Key,
|
||||
DataType = InferDataType(kv.Value),
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>直接用 DataSql 执行一次查询(带 @参数),取 DataTable 列作为明细列</summary>
|
||||
private void FillFromSql(ReportProbeResultDto result, string sql, Dictionary<string, object> pairs)
|
||||
{
|
||||
ValidateSql(sql);
|
||||
|
||||
var sqlTokens = Regex.Matches(sql, @"@([A-Za-z_][A-Za-z0-9_]*)")
|
||||
.Select(m => m.Groups[1].Value).Distinct().ToHashSet(StringComparer.Ordinal);
|
||||
var dbParams = new List<DbParameter>();
|
||||
foreach (var kv in pairs)
|
||||
if (sqlTokens.Contains(kv.Key) && kv.Value != null)
|
||||
dbParams.Add(new SQLiteParameter("@" + kv.Key, kv.Value));
|
||||
|
||||
var dt = dbParams.Count > 0
|
||||
? _fsql.Ado.ExecuteDataTable(sql, dbParams.ToArray())
|
||||
: _fsql.Ado.ExecuteDataTable(sql);
|
||||
|
||||
result.Fields = dt.Columns.Cast<DataColumn>().Select(c => new ReportFieldDefDto
|
||||
{
|
||||
Name = c.ColumnName,
|
||||
Label = c.ColumnName,
|
||||
DataType = c.DataType.Name,
|
||||
}).ToList();
|
||||
|
||||
result.Total = dt.Rows.Count;
|
||||
result.Rows = dt.Rows.Cast<DataRow>().Take(3).Select(row =>
|
||||
{
|
||||
var d = new Dictionary<string, object?>(dt.Columns.Count, StringComparer.Ordinal);
|
||||
foreach (DataColumn col in dt.Columns)
|
||||
{
|
||||
var v = row[col];
|
||||
d[col.ColumnName] = v == DBNull.Value ? null : v;
|
||||
}
|
||||
return d;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private static string InferDataType(object? value)
|
||||
{
|
||||
if (value == null) return "String";
|
||||
if (value is JsonElement je)
|
||||
{
|
||||
return je.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Number => "Number",
|
||||
JsonValueKind.True or JsonValueKind.False => "Boolean",
|
||||
_ => "String",
|
||||
};
|
||||
}
|
||||
return value switch
|
||||
{
|
||||
int or long or short or byte or uint or ulong or ushort or sbyte => "Integer",
|
||||
decimal or double or float => "Number",
|
||||
DateTime or DateTimeOffset => "DateTime",
|
||||
bool => "Boolean",
|
||||
_ => "String",
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 工具 =====
|
||||
|
||||
private static bool HasTemplate(Report r) => r.ReportFileId != 0;
|
||||
|
||||
private static ReportDetailDto ToDetail(Report r) => new(
|
||||
r.Id, r.Code, r.Name, r.Category, r.Description, r.DataSql, r.DataParams, r.Config,
|
||||
r.SortOrder, r.IsActive, r.ReportFileId, HasTemplate(r),
|
||||
r.CreatedAt, r.UpdatedAt);
|
||||
|
||||
private static void ValidateCode(string code)
|
||||
{
|
||||
if (!Regex.IsMatch(code ?? "", "^[a-zA-Z0-9_]{2,40}$"))
|
||||
throw new F9OperationException("报表编码仅允许字母/数字/下划线,长度 2-40");
|
||||
}
|
||||
|
||||
private static void ValidateSql(string sql)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) throw new F9OperationException("数据集 SQL 不能为空");
|
||||
var trimmed = sql.Trim();
|
||||
if (!trimmed.StartsWith("select", StringComparison.OrdinalIgnoreCase))
|
||||
throw new F9OperationException("数据集 SQL 必须以 SELECT 开头(仅允许查询语句)");
|
||||
// 禁止分号后跟其他语句
|
||||
if (trimmed.IndexOf(';') >= 0 && trimmed.IndexOf(';') < trimmed.Length - 1)
|
||||
throw new F9OperationException("数据集 SQL 不允许包含多条语句");
|
||||
}
|
||||
|
||||
private static void ValidateConfig(string? configJson)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configJson)) return;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(configJson);
|
||||
if (doc.RootElement.ValueKind != JsonValueKind.Object)
|
||||
throw new F9OperationException("查询配置(Config)必须是 JSON 对象");
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
throw new F9OperationException("查询配置(Config)不是合法 JSON");
|
||||
}
|
||||
catch (F9OperationException) { throw; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 工艺执行书主控(对应 F9 biz_prod_qc_gyzxs_version):
|
||||
/// 以「工艺庄口」为主键的一条汇总指针记录,记录该庄口下每张子单据是否已录入。
|
||||
/// 列表上的 √ 标记全部来自本表各 *Id > 0。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_doc_version")]
|
||||
public class GyzxsDocVersion : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口 Id
|
||||
public long ZongheyangId { get; set; } // 综合样茧质 Id(本系统暂未建综合样茧质,保留指针)
|
||||
public long ShichanId { get; set; } // 试产(试缫工艺单)Id
|
||||
public long FengMianId { get; set; } // 封面 Id
|
||||
public long XbjSsdyId { get; set; } // 试缫与大样 Id
|
||||
public long XbjId { get; set; } // 混剥选茧 Id
|
||||
public long ZhuJianId { get; set; } // 煮茧 Id
|
||||
public long ZdsId { get; set; } // 自动缫 Id
|
||||
public long ZdsDyId { get; set; } // 自动缫大样 Id
|
||||
public long HsId { get; set; } // 后缫(工艺执行书)Id
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 封面(对应 F9 biz_prod_qc_gyzxs_fengmian):
|
||||
/// 报表「封面」= 桑蚕丝工艺设计书,含编号、庄口、规格、原料总量、生产丝量、缫制车间、生产时间。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_fengmian")]
|
||||
public class GyzxsFengMian : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口 Id
|
||||
public string BianHao { get; set; } = ""; // 编号(如 SJZ-2025-武工艺-58)
|
||||
public string Guige { get; set; } = ""; // 缫制规格
|
||||
public decimal YuanLiaoZhongLiang { get; set; } // 原料总量(吨)
|
||||
public decimal ShengSiZhongLiang { get; set; } // 生产丝量(吨)
|
||||
public string ShengChanCheJian { get; set; } = "";// 缫制车间
|
||||
public DateTime? ShengChanStartDate { get; set; } // 开始生产日期
|
||||
public DateTime? ShengChanEndDate { get; set; } // 结束生产日期
|
||||
public string FaSongBuMen { get; set; } = ""; // 发送部门
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 后缫(编丝整理)工艺执行书(对应 F9 biz_prod_qc_gyzxs_hs)。
|
||||
/// 报表「工艺执行书」即本单据:生产指标 9 项 + 工艺条件及控制指标 18 项 + 主要技术措施 + 设计/审核/审批。
|
||||
/// 指标按 F9 约定全部为字符串,可存 "3100"、"99.8"、"20~38"、"185±5" 这类文本。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_hs")]
|
||||
public class GyzxsHs : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public string Guige { get; set; } = ""; // 生丝规格
|
||||
public DateTime? ZhiXingDate { get; set; } // 执行时间
|
||||
public string QianSaoCheJian { get; set; } = ""; // 前缫生产车间
|
||||
public int YuSaoTianShu { get; set; } // 预缫天数
|
||||
public string ZhengLiGuiGe { get; set; } = "GB1798-2008 白厂丝"; // 整理规格
|
||||
|
||||
// ---- 生产指标 ----
|
||||
public string SczbDanChan { get; set; } = ""; // 单产(片/人、班)
|
||||
public string SczbRiChan { get; set; } = ""; // 日产(片/班)
|
||||
public string SczbBianSiLiang { get; set; } = ""; // 编丝量(片/人、班)
|
||||
public string SczbZhengJianLiang { get; set; } = ""; // 整检量(片/日)
|
||||
public string SczbShengChanZhengPinLv { get; set; } = ""; // 生产正品率(%)
|
||||
public string SczbShangJianZhengPinLv { get; set; } = ""; // 商检正品率(%)
|
||||
public string SczbQieDuan { get; set; } = ""; // 切断(次/批)
|
||||
public string SczbXiaoLv { get; set; } = ""; // 效率(%)
|
||||
public string SczbPinZhiCiDian { get; set; } = ""; // 编检品质疵点(%)
|
||||
|
||||
// ---- 工艺条件及控制指标 ----
|
||||
public string GyzbCheSu { get; set; } = ""; // 车速(转/分)
|
||||
public string GyzbXiaoYuDaiFanShiJian { get; set; } = ""; // 小䈅待返时间(分)
|
||||
public string GyzbCheJianWenDu { get; set; } = ""; // 车间温度(℃)
|
||||
public string GyzbCheJianXiangDuiShiDu { get; set; } = ""; // 车间相对湿度(%)
|
||||
public string GyzbCheXiangWenDu { get; set; } = ""; // 车厢温度(℃)
|
||||
public string GyzbCheXiangShiDu { get; set; } = ""; // 车厢湿度(%)
|
||||
public string GyzbPingHengShiWenDu { get; set; } = ""; // 平衡室温度(℃)
|
||||
public string GyzbPingHengShiXiangDuiShiDu { get; set; } = ""; // 平衡室相对湿度(%)
|
||||
public string GyzbXiaoYuGeiShiWenDu { get; set; } = ""; // 小䈅给湿温度(℃)
|
||||
public string GyzbXiaoYuSiPianPingHengShiJian { get; set; } = ""; // 小䈅丝片平衡时间(分)
|
||||
public string GyzbZhenKongDu { get; set; } = ""; // 真空度(mp)
|
||||
public string GyzbChouQiCiShu { get; set; } = ""; // 抽气次数(次)
|
||||
public string GyzbDaYuSiPianPingHengShiJian { get; set; } = ""; // 大䈅丝片平衡时间(分)
|
||||
public string GyzbDaYuSiPianHuiChaoLv { get; set; } = ""; // 大䈅丝片回潮率(%)
|
||||
public string GyzbShengSiHuiChaoLv { get; set; } = ""; // 生丝回潮率(%)
|
||||
public string GyzbDaYuSiPianKuanDu { get; set; } = ""; // 大䈅丝片宽度(cm)
|
||||
public string GyzbXieSiDuiSiLv { get; set; } = ""; // 屑丝对丝率(%)
|
||||
public string GyzbDaYuSiPianCiDian { get; set; } = ""; // 大䈅丝片疵点(%)
|
||||
|
||||
public string JiShuCuoShi { get; set; } = ""; // 主要技术措施
|
||||
public string SheJiRen { get; set; } = ""; // 设计
|
||||
public string ShenHeRen { get; set; } = ""; // 审核
|
||||
public string PiFuRen { get; set; } = ""; // 审批
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 试产(试缫工艺单,对应 F9 biz_prod_qc_gyzxs_shichan):
|
||||
/// 两个分组——「自动缫试缫工艺」与「试缫煮茧工艺」。
|
||||
/// 工艺指标大量是区间文本(如 190±5、9、10、11、≥99),因此车速/温度类用 string。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_shichan")]
|
||||
public class GyzxsShichan : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public string Guige { get; set; } = ""; // 规格
|
||||
public string HuanZhuangFangShi { get; set; } = ""; // 换庄方式
|
||||
public DateTime? ScDate { get; set; } // 试缫日期
|
||||
public string ScCheJian { get; set; } = ""; // 生产车间
|
||||
|
||||
// ---- 茧质调查(来源:综合样茧质 / 手工填写)----
|
||||
public decimal YiJianSiChang { get; set; } // 一茧丝长(m)
|
||||
public decimal JieShuLv { get; set; } // 解舒率(%)
|
||||
public decimal JieShuSiChang { get; set; } // 解舒丝长(m)
|
||||
public decimal JianCengLv { get; set; } // 茧层率(%)
|
||||
public decimal JianSiXianDu { get; set; } // 茧丝纤度(D)
|
||||
public decimal JieShuGuangZhe { get; set; } // 解舒光折(kg)
|
||||
public decimal WanDiao { get; set; } // 万吊(次)
|
||||
|
||||
// ---- 自动缫试缫工艺指标 ----
|
||||
public string ZdsCheSu { get; set; } = ""; // 车速(转)
|
||||
public string ZdsYunXuLiShu { get; set; } = ""; // 允许粒数(粒)
|
||||
public decimal ZdsSaoZhe { get; set; } // 缫折(kg)
|
||||
public decimal ZdsZhongXinXianDu { get; set; } // 中心纤度(D)
|
||||
public string ZdsZhongXinLiFuLv { get; set; } = ""; // 中心粒符率(%)
|
||||
public string ZdsSaoSiWenDu { get; set; } = ""; // 缫丝温度(℃)
|
||||
public decimal ZdsJiSuanXianDu { get; set; } // 计算纤度(D)
|
||||
public string ZdsYunXuLiFuLv { get; set; } = ""; // 允许粒符率(%)
|
||||
public string ZdsSuoXuWenDu { get; set; } = ""; // 索绪温度(℃)
|
||||
public decimal ZdsPingJunLiShu { get; set; } // 平均粒数(粒)
|
||||
public decimal ZdsBaiJianYinXu { get; set; } // 白茧引绪(粒)
|
||||
public string ZdsLiXuWenDu { get; set; } = ""; // 理绪温度(℃)
|
||||
public decimal ZdsZhongXinLiShu { get; set; } // 中心粒数(粒)
|
||||
public string ZdsGeiJianKouKuanDu { get; set; } = ""; // 给茧口宽度(mm)
|
||||
public decimal ZdsLuoSiLongShu { get; set; } // 落丝笼数(笼)
|
||||
public decimal ZdsLongJianLiShu { get; set; } // 笼茧粒数(粒)
|
||||
|
||||
// ---- 试缫煮茧工艺 ----
|
||||
public string ZjCheSu { get; set; } = ""; // 煮茧车速(转)
|
||||
public string ZjZhengZhuShi1 { get; set; } = ""; // 蒸煮室温度1
|
||||
public string ZjZhengZhuShi2 { get; set; } = ""; // 蒸煮室温度2
|
||||
public string ZjZhengZhuShi3 { get; set; } = ""; // 蒸煮室温度3
|
||||
public decimal ZjLuoXuWai { get; set; } // 外层掉绪
|
||||
public decimal ZjLuoXuZhong { get; set; } // 中层掉绪
|
||||
public decimal ZjLuoXuNei { get; set; } // 内层掉绪
|
||||
public string ZjTiaoZhengBuWenDu { get; set; } = ""; // 调整部温度
|
||||
public string ZjChuKouWenDu { get; set; } = ""; // 出口温度
|
||||
public string ZjFuZhuCheJian { get; set; } = ""; // 附煮车间
|
||||
public string ZjFuZhuGyzkName { get; set; } = ""; // 附煮庄口
|
||||
|
||||
// ---- 签署 ----
|
||||
public string SheJiRen { get; set; } = ""; // 设计人
|
||||
public string ShenHeRen { get; set; } = ""; // 审核
|
||||
public string PiFuRen { get; set; } = ""; // 审批
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 混剥选茧工艺执行书(对应 F9 biz_prod_qc_gyzxs_xbj)。
|
||||
/// 含生产指标 / 质量指标 / 质量控制指标 三组,指标多为百分数或区间文本。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_xbj")]
|
||||
public class GyzxsXbj : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public string Guige { get; set; } = ""; // 缫制规格
|
||||
public string CheJian { get; set; } = ""; // 缫制车间
|
||||
public DateTime? HuanZhuangDate { get; set; } // 换庄时间
|
||||
|
||||
// ---- 生产指标 ----
|
||||
public decimal ScRiChanLiang { get; set; } // 日产量(包/人)
|
||||
public decimal ScBanChanLiang { get; set; } // 班产量(包)
|
||||
public string ScShangCheLv { get; set; } = ""; // 上车率
|
||||
public string ScLianTongLv { get; set; } = ""; // 联统茧率
|
||||
public string ScWuJiLv { get; set; } = ""; // 五级茧率
|
||||
public string ScXiaJiaoLv { get; set; } = ""; // 下茧/茧衣率
|
||||
public string ScJianYiLv { get; set; } = ""; // 茧衣率
|
||||
public string ScJiShuCuoShi { get; set; } = ""; // 技术措施要求
|
||||
|
||||
// ---- 质量指标(统号茧 / 五级茧 / 下脚茧 交叉率)----
|
||||
public string QcTongHaoWuJiLv { get; set; } = ""; // 统号茧-五级茧率
|
||||
public string QcTongHaoXiaJianLv { get; set; } = ""; // 统号茧-下茧率
|
||||
public string QcWuJiLianTongLv { get; set; } = ""; // 五级茧-联统茧率
|
||||
public string QcWuJiXiaJiaoLv { get; set; } = ""; // 五级茧-下脚率
|
||||
public string QcXiaJiaoLianTongLv { get; set; } = ""; // 下脚茧-联统率
|
||||
public string QcXiaJiaoCiJianLv { get; set; } = ""; // 下脚茧-次茧率
|
||||
|
||||
// ---- 质量控制指标(其它级茧日产量 + 共性指标)----
|
||||
public string QcZbHaoWuZuChanLiang { get; set; } = ""; // 好五茧(kg/组/3人)
|
||||
public string QcZbLiuJiZuChanLiang { get; set; } = ""; // 六级茧(kg/组/3人)
|
||||
public string QcZbMianJianZuChanLiang { get; set; } = ""; // 棉茧(kg/组/3人)
|
||||
public string QcZbXiaJiaoZuChanLiang { get; set; } = ""; // 下脚茧(kg/组/3人)
|
||||
public string QcZbShuangGongZuChanLiang { get; set; } = ""; // 双宫茧(kg/组/3人)
|
||||
public string QcZbGongJinLiShu { get; set; } = ""; // 公斤粒数(粒)
|
||||
public string QcZbBaoGuangLv { get; set; } = ""; // 剥光率(%)
|
||||
public string QcZbJianYiLv { get; set; } = ""; // 茧衣率(%)
|
||||
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
|
||||
/// <summary>混茧比例明细(对应 F9 biz_prod_qc_gyzxs_xbj_hunjian)</summary>
|
||||
[Table(Name = "qc_gyzxs_xbj_hunjian")]
|
||||
public class GyzxsXbjHunJian : BaseEntity
|
||||
{
|
||||
public long XbjId { get; set; } // 选剥茧主表 Id
|
||||
public long YlzkId { get; set; } // 原料庄口 Id
|
||||
public string ZhuangBie { get; set; } = ""; // 庄别(冗余名称,便于打印)
|
||||
public decimal ZhongLiang { get; set; } // 重量(kg)
|
||||
public int BaoShu { get; set; } // 总包数
|
||||
public decimal BiLi { get; set; } // 比例(%)
|
||||
public int HunJianBaoShu { get; set; } // 混茧包数
|
||||
public int Sort { get; set; } // 排序
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 试缫与大样资料(对应 F9 biz_prod_qc_gyzxs_xbj_ssdy):
|
||||
/// 「试缫」与「大样」两组同构指标,报表为两行对比表。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_xbj_ssdy")]
|
||||
public class GyzxsXbjSsDy : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public string Guige { get; set; } = ""; // 缫制规格
|
||||
|
||||
// ---- 试缫 ----
|
||||
public string SsShangCheLv { get; set; } = ""; // 上车率(%)
|
||||
public string SsLianTongLv { get; set; } = ""; // 联统率(%)
|
||||
public string SsCiJianLv { get; set; } = ""; // 次茧率(%)
|
||||
public string SsCiLiuJiLv { get; set; } = ""; // 次六级(%)
|
||||
public string SsMianJianLv { get; set; } = ""; // 棉茧率(%)
|
||||
public string SsXiaJiaoLv { get; set; } = ""; // 下脚率(%)
|
||||
public string SsShuangGongLv { get; set; } = ""; // 双宫率(%)
|
||||
public string SsJianYiLv { get; set; } = ""; // 茧衣率(%)
|
||||
public decimal SsLiJianZhong { get; set; } // 400粒重(克)
|
||||
public decimal SsGongJinLiShu { get; set; } // 公斤粒数
|
||||
|
||||
// ---- 大样 ----
|
||||
public string DyShangCheLv { get; set; } = ""; // 上车率(%)
|
||||
public string DyLianTongLv { get; set; } = ""; // 联统率(%)
|
||||
public string DyCiJianLv { get; set; } = ""; // 次茧率(%)
|
||||
public string DyCiLiuJiLv { get; set; } = ""; // 次六级(%)
|
||||
public string DyMianJianLv { get; set; } = ""; // 棉茧率(%)
|
||||
public string DyXiaJiaoLv { get; set; } = ""; // 下脚率(%)
|
||||
public string DyShuangGongLv { get; set; } = ""; // 双宫率(%)
|
||||
public string DyJianYiLv { get; set; } = ""; // 茧衣率(%)
|
||||
public decimal DyLiJianZhong { get; set; } // 400粒重(克)
|
||||
public decimal DyGongJinLiShu { get; set; } // 公斤粒数
|
||||
|
||||
public string JiShuCuoShi { get; set; } = ""; // 主要技术措施及要求
|
||||
public string SheJiRen { get; set; } = ""; // 设计
|
||||
public string ShenHeRen { get; set; } = ""; // 审核
|
||||
public string PiFuRen { get; set; } = ""; // 审批
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 自动缫车间生产工艺执行书(表一,对应 F9 biz_prod_qc_gyzxs_zds)。
|
||||
/// 含「茧质概况」(冗余自综合样茧质)、「生产指标」、「工艺技术指标」三块。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_zds")]
|
||||
public class GyzxsZds : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public string Guige { get; set; } = ""; // 生丝规格
|
||||
public DateTime? HuanZhuangDate { get; set; } // 接缫日期
|
||||
public string HuanZhuangFangShi { get; set; } = ""; // 换庄方式
|
||||
public string CheJian { get; set; } = ""; // 生产车间
|
||||
public int YuSaoTianShu { get; set; } // 预缫天数(天)
|
||||
public decimal YuSaoSiLiang { get; set; } // 预缫丝量(吨)
|
||||
|
||||
// ---- 茧质概况 ----
|
||||
public decimal ZgYiJianSiChang { get; set; } // 一茧丝长(米)
|
||||
public decimal ZgJieShuSiChang { get; set; } // 解舒丝长
|
||||
public decimal ZgJieShuLv { get; set; } // 解舒率(%)
|
||||
public decimal ZgJianSiXianDu { get; set; } // 茧丝纤度(D)
|
||||
public decimal ZgJieShuGuangZhe { get; set; } // 解舒光折(kg)
|
||||
public decimal ZgWanMiDiaoCao { get; set; } // 万米吊糙(次)
|
||||
public decimal ZgQingJie { get; set; } // 清洁(分)
|
||||
public decimal ZgJingDu { get; set; } // 洁净(分)
|
||||
public decimal ZgJianCengLv { get; set; } // 茧层率(%)
|
||||
public decimal ZgXianDuJunFangCha { get; set; } // 茧丝纤度均方差
|
||||
public decimal ZgWaiSanBaiHui { get; set; } // 外三百回纤度
|
||||
public decimal ZgZhongSanBaiHui { get; set; } // 中三百回纤度
|
||||
public decimal ZgNeiSanBaiHui { get; set; } // 内三百回纤度
|
||||
public decimal ZgZuiDaKaiCha { get; set; } // 最大开差
|
||||
public decimal ZgNeiWaiKaiCha { get; set; } // 内外开差
|
||||
public decimal ZgBaiSeJianDanXian { get; set; } // 白色茧单纤
|
||||
public decimal ZgHuiSeJianDanXian { get; set; } // 灰色茧单纤
|
||||
public decimal ZgHongSeJianDanXian { get; set; } // 红色茧单纤
|
||||
public decimal ZgLuoXuWai { get; set; } // 落绪分布-外
|
||||
public decimal ZgLuoXuZhong { get; set; } // 落绪分布-中
|
||||
public decimal ZgLuoXuNei { get; set; } // 落绪分布-内
|
||||
public decimal ZgPingJunJianFu { get; set; } // 平均茧幅(mm)
|
||||
public decimal ZgJianFuZhengQiLv { get; set; } // 茧幅整齐率(%)
|
||||
public string ZgJianSiBaiHuiXianDu { get; set; } = ""; // 茧丝百回纤度(明细串)
|
||||
|
||||
// ---- 生产指标 ----
|
||||
public string SczbPinWei { get; set; } = ""; // 品味
|
||||
public string SczbSaoZhe { get; set; } = ""; // 缫折(kg)
|
||||
public string SczbTaiChan { get; set; } = ""; // 台产
|
||||
public string SczbPianCha { get; set; } = ""; // 偏差
|
||||
public string SczbZongCha { get; set; } = ""; // 总差
|
||||
public string SczbQingJie { get; set; } = ""; // 清洁
|
||||
public string SczbJingJie { get; set; } = ""; // 洁净
|
||||
public string SczbYunEr { get; set; } = ""; // 匀Ⅱ
|
||||
public string SczbZhengPinLv { get; set; } = ""; // 生产正品率
|
||||
|
||||
// ---- 工艺技术指标(第一行)----
|
||||
public string GyzbCheSu { get; set; } = ""; // 车速(转/分)
|
||||
public string GyzbTianXuCiShu { get; set; } = ""; // 添绪次数(次/分)
|
||||
public string GyzbYunZhuanLv { get; set; } = ""; // 运转率(%)
|
||||
public string GyzbZhongXinXianDu { get; set; } = ""; // 中心纤度(D)
|
||||
public string GyzbSheJiDanXianDu { get; set; } = ""; // 设计单纤度(D)
|
||||
public string GyzbPingJunLiShu { get; set; } = ""; // 平均粒数(粒)
|
||||
public string GyzbZhongXinLiShu { get; set; } = ""; // 中心粒数(粒)
|
||||
public string GyzbYunXuLiShu { get; set; } = ""; // 允许粒数(粒)
|
||||
public string GyzbYinXu { get; set; } = ""; // 引绪(粒)
|
||||
public string GyzbZhongXinLiFuLv { get; set; } = ""; // 中心粒符率
|
||||
public string GyzbYunXuLiFuLv { get; set; } = ""; // 允许粒符率
|
||||
public string GyzbGeiJianKouKuanDu { get; set; } = ""; // 给茧口宽度
|
||||
public string GyzbTiaoTuLv { get; set; } = ""; // 条吐率(%)
|
||||
public string GyzbSiQiaoChangDu { get; set; } = ""; // 丝鞘长度(cm)
|
||||
|
||||
// ---- 工艺技术指标(第二行)----
|
||||
public string GyzbCheTouQiYa { get; set; } = ""; // 车头汽压(MP)
|
||||
public string GyzbSuoXuWenDu { get; set; } = ""; // 索绪温度(℃)
|
||||
public string GyzbLiXuWenDu { get; set; } = ""; // 理绪温度(℃)
|
||||
public string GyzbPianXinPanJianLiang { get; set; } = ""; // 偏心盘茧量
|
||||
public string GyzbSaoTangWenDu { get; set; } = ""; // 缫汤温度(℃)
|
||||
public string GyzbJiaJianKouJianLiang { get; set; } = ""; // 加茧口茧量(粒)
|
||||
public string GyzbWuXuLv { get; set; } = ""; // 无绪率(%)
|
||||
public string GyzbPengCaoJianLv { get; set; } = ""; // 蓬糙茧率(%)
|
||||
public string GyzbJiaJianJunYunLv { get; set; } = ""; // 加茧均匀率(%)
|
||||
public string GyzbZhengQueTianXuLv { get; set; } = ""; // 正确添绪率(%)
|
||||
public string GyzbTongJianLiang { get; set; } = ""; // 桶茧量(g)
|
||||
public string GyzbLuoSiLongShu { get; set; } = ""; // 落丝笼数
|
||||
|
||||
public string SheJiRen { get; set; } = ""; // 设计
|
||||
public string ShenHeRen { get; set; } = ""; // 审核
|
||||
public string PiFuRen { get; set; } = ""; // 审批
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
|
||||
/// <summary>自动缫大样资料(表二,对应 F9 biz_prod_qc_gyzxs_zds_dyzl)</summary>
|
||||
[Table(Name = "qc_gyzxs_zds_dyzl")]
|
||||
public class GyzxsZdsDyzl : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public long ZdsId { get; set; } // 自动缫主表 Id
|
||||
public string Guige { get; set; } = ""; // 生丝规格
|
||||
public string PianCha { get; set; } = ""; // 偏差
|
||||
public string QingJie { get; set; } = ""; // 清洁
|
||||
public string JingDu { get; set; } = ""; // 净度
|
||||
public string JieShuLv { get; set; } = ""; // 解舒率
|
||||
public string WanMiDiaoCao { get; set; } = ""; // 万米吊糙
|
||||
public string SaoZhe { get; set; } = ""; // 缫折
|
||||
public string JiShuCuoShi { get; set; } = ""; // 技术措施及要求
|
||||
public string SheJiRen { get; set; } = ""; // 设计
|
||||
public string ShenHeRen { get; set; } = ""; // 审核
|
||||
public string PiFuRen { get; set; } = ""; // 审批
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Gyzxs;
|
||||
|
||||
/// <summary>
|
||||
/// 煮茧工艺执行书主表(对应 F9 biz_prod_qc_gyzxs_zhujian)。
|
||||
/// 「庄口原料性能」区在本系统内冗余存储在表上,避免强依赖综合样茧质。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_gyzxs_zhujian")]
|
||||
public class GyzxsZhuJian : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口
|
||||
public string Guige { get; set; } = ""; // 缫制规格
|
||||
public DateTime? HuanZhuangDate { get; set; } // 换庄时间
|
||||
public string HuanZhuangFangShi { get; set; } = ""; // 接缫方式
|
||||
public string CheJian { get; set; } = ""; // 生产车间
|
||||
public string ZhuJianJi { get; set; } = ""; // 煮茧机号
|
||||
|
||||
// ---- 庄口原料性能 ----
|
||||
public decimal YlGongJinLiShu { get; set; } // 公斤粒数(粒)
|
||||
public string YlSuoZhou { get; set; } = ""; // 缩绉
|
||||
public decimal YlJianCengLv { get; set; } // 茧层率
|
||||
public decimal YlQingJie { get; set; } // 清洁
|
||||
public decimal YlJingDu { get; set; } // 净度
|
||||
public decimal YlLiJianSiChang { get; set; } // 粒茧丝长
|
||||
public decimal YlJieShuLv { get; set; } // 解舒率
|
||||
public decimal YlJieShuSiChang { get; set; } // 解舒丝长
|
||||
public decimal YlJianSiXianDu { get; set; } // 茧丝纤度
|
||||
public decimal YlWanMiDiaoCao { get; set; } // 万米吊糙
|
||||
public decimal YlJieShuGuangZhe { get; set; } // 解舒光折
|
||||
|
||||
// ---- 工艺技术指标 ----
|
||||
public string GyzbJieShuLv { get; set; } = ""; // 实缫解舒
|
||||
public string GyzbWanDiao { get; set; } = ""; // 实缫万吊
|
||||
public string GyzbQingJie { get; set; } = ""; // 清洁
|
||||
public string GyzbJingDu { get; set; } = ""; // 净度
|
||||
public string GyzbSiJiaoRongShiLv { get; set; } = ""; // 丝胶溶失率
|
||||
public string GyzbZuLuoSiLongShu { get; set; } = ""; // 组落丝笼数
|
||||
public string GyzbSaoZhe { get; set; } = ""; // 缫折(kg)
|
||||
public string GyzbLongJianLiShu { get; set; } = ""; // 笼茧粒数
|
||||
public string GyzbMeiTongJianLiang { get; set; } = ""; // 每桶茧量(g)
|
||||
public string GyzbMeiBaoTongShu { get; set; } = ""; // 每包桶数(笼)
|
||||
public string GyzbMeiXiaoShiYongJianLong { get; set; } = ""; // 每小时用茧笼数
|
||||
public string GyzbTiaoTuLv { get; set; } = ""; // 条吐率(%)
|
||||
|
||||
// ---- 真空渗透 ----
|
||||
public string ZkstZhenKongDu { get; set; } = ""; // 真空度
|
||||
public string ZkstXiShuiWenDu { get; set; } = ""; // 吸水温度
|
||||
public string ZkstDaZhenKongShiChang { get; set; } = ""; // 没进水时达到最高真空度时长
|
||||
public string ZkstZuiGaoWeiShiChang { get; set; } = ""; // 液面到最高位时长
|
||||
public string ZkstGuanShuiZhenKongShiChang { get; set; } = ""; // 关水后继续真空时长
|
||||
public string ZkstChuJianWenDu { get; set; } = ""; // 出茧温度
|
||||
public string ZkstQuanJiHuiZhuanShiJian { get; set; } = ""; // 全机回转时间(分)
|
||||
|
||||
public string JiShuCuoShi { get; set; } = ""; // 主要技术措施
|
||||
public string SheJiRen { get; set; } = ""; // 设计
|
||||
public string ShenHeRen { get; set; } = ""; // 审核
|
||||
public string PiFuRen { get; set; } = ""; // 审批
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
}
|
||||
|
||||
/// <summary>分车间煮茧指标(对应 F9 biz_prod_qc_gyzxs_zhujian_chejian_zhujianduan)</summary>
|
||||
[Table(Name = "qc_gyzxs_zhujian_chejian")]
|
||||
public class GyzxsZhuJianCheJian : BaseEntity
|
||||
{
|
||||
public long ZhuJianId { get; set; } // 煮茧主表 Id
|
||||
public string CheJian { get; set; } = ""; // 车间
|
||||
public string DiWenShenTou { get; set; } = ""; // 低温渗透
|
||||
public string ZhengZhuShi1 { get; set; } = ""; // 蒸煮室1
|
||||
public string ZhengZhuShi2 { get; set; } = ""; // 蒸煮室2
|
||||
public string ZhengZhuShi3 { get; set; } = ""; // 蒸煮室3
|
||||
public string TiaoZhengBu1 { get; set; } = ""; // 调整部1
|
||||
public string TiaoZhengBu2 { get; set; } = ""; // 调整部2
|
||||
public string TiaoZhengBu3 { get; set; } = ""; // 调整部3
|
||||
public string TiaoZhengBu4 { get; set; } = ""; // 调整部4
|
||||
public string TiaoZhengBu5 { get; set; } = ""; // 调整部5
|
||||
public string ChuJianWenDu { get; set; } = ""; // 出茧温度
|
||||
public string HuiZhuanShiJian { get; set; } = ""; // 全机回转时间
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>副煮车间结果(对应 F9 biz_prod_qc_gyzxs_zhujian_chejian_fuzhu)</summary>
|
||||
[Table(Name = "qc_gyzxs_zhujian_fuzhu")]
|
||||
public class GyzxsZhuJianFuZhu : BaseEntity
|
||||
{
|
||||
public long ZhuJianId { get; set; } // 煮茧主表 Id
|
||||
public string CheJian { get; set; } = ""; // 附煮车间
|
||||
public string JieShuLv { get; set; } = ""; // 解舒
|
||||
public string WanDiao { get; set; } = ""; // 万吊
|
||||
public string SaoZhe { get; set; } = ""; // 缫折(kg)
|
||||
public string WenDu1 { get; set; } = ""; // 温度1
|
||||
public string WenDu2 { get; set; } = ""; // 温度2
|
||||
public string WenDu3 { get; set; } = ""; // 温度3
|
||||
public string WenDu4 { get; set; } = ""; // 温度4
|
||||
public string WenDu5 { get; set; } = ""; // 温度5
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>茧幅调查主表(对应 F9 biz_prod_qc_yjss_dc_jfdc_main)</summary>
|
||||
[Table(Name = "qc_yjss_dc_jfdc_main")]
|
||||
public class YjssDcJfdcMain : BaseEntity
|
||||
{
|
||||
public long YjssId { get; set; } // 主表 Id
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
public decimal PingJunJianFu { get; set; } // 平均茧幅(mm)
|
||||
public decimal ZuiDaJianFu { get; set; } // 最大茧幅(mm)
|
||||
public decimal ZhengQiLv { get; set; } // 茧幅整齐率(%)
|
||||
public decimal JunFangCha { get; set; } // 茧幅均方差
|
||||
}
|
||||
|
||||
/// <summary>茧幅调查明细(对应 F9 biz_prod_qc_yjss_dc_jfdc_item)</summary>
|
||||
[Table(Name = "qc_yjss_dc_jfdc_item")]
|
||||
public class YjssDcJfdcItem : BaseEntity
|
||||
{
|
||||
public long JfdcId { get; set; } // 茧幅调查主表 Id
|
||||
public long YjssId { get; set; } // 主表 Id(冗余,便于整表删除)
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
public decimal JianFu { get; set; } // 茧幅(mm)
|
||||
public decimal ShuLiang { get; set; } // 数量(粒)
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>茧丝百回纤度汇总(对应 F9 biz_prod_qc_yjss_dc_jsbhxdmain)</summary>
|
||||
[Table(Name = "qc_yjss_dc_jsbhxd_main")]
|
||||
public class YjssDcJsbhxdMain : BaseEntity
|
||||
{
|
||||
public long YjssId { get; set; } // 主表 Id
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
public decimal XianDuJunFangCha { get; set; } // 茧丝纤度均方差
|
||||
public decimal ZuiDaKaiCha { get; set; } // 最大开差
|
||||
public decimal NeiWaiKaiCha { get; set; } // 内外开差
|
||||
public decimal WaiSanBaiHui { get; set; } // 外三百回
|
||||
public decimal ZhongSanBaiHui { get; set; } // 中三百回
|
||||
public decimal NeiSanBaiHui { get; set; } // 内三百回
|
||||
public decimal BaiSeJianDanXian { get; set; } // 白色茧单纤
|
||||
public decimal HuiSeJianDanXian { get; set; } // 灰色茧单纤
|
||||
public decimal HongSeJianDanXian { get; set; } // 红色茧单纤
|
||||
}
|
||||
|
||||
/// <summary>茧丝百回纤度明细(对应 F9 biz_prod_qc_yjss_dc_jsbhxd)</summary>
|
||||
[Table(Name = "qc_yjss_dc_jsbhxd")]
|
||||
public class YjssDcJsbhxd : BaseEntity
|
||||
{
|
||||
public long JsbhxdMainId { get; set; } // 汇总表 Id
|
||||
public long YjssId { get; set; } // 主表 Id(冗余)
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
public int HuiShu { get; set; } // 回数
|
||||
public decimal XianDu { get; set; } // 纤度(D)
|
||||
public int Sort { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>
|
||||
/// 解舒调查(试缫,对应 F9 biz_prod_qc_yjss_dc_jsdc):一条主表可有多条本记录。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_yjss_dc_jsdc")]
|
||||
public class YjssDcJsdc : BaseEntity
|
||||
{
|
||||
public long YjssId { get; set; } // 主表 Id
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
public int XuHao { get; set; } // 序号
|
||||
|
||||
// ---- 缫丝工艺参数 ----
|
||||
public int XuShu { get; set; } // 绪数
|
||||
public int DingLi { get; set; } // 定粒
|
||||
public int ZhuJianTangWen { get; set; } // 煮茧汤温(℃)
|
||||
public int SaoSiTangWen { get; set; } // 缫丝汤温(℃)
|
||||
public int CheSu { get; set; } // 车速(转/分)
|
||||
public decimal XiaoSiGanLiang { get; set; } // 小丝干量(g)
|
||||
public decimal XiaoSiGongLiang { get; set; } // 小丝公量(g)
|
||||
|
||||
// ---- 落绪分布 ----
|
||||
public int LdfbWai { get; set; } // 外
|
||||
public int LdfbZhong { get; set; } // 中
|
||||
public int LdfbNei { get; set; } // 内
|
||||
public int LdfbSum { get; set; } // 总粒数
|
||||
|
||||
// ---- 解舒核心 ----
|
||||
public string XianJianYouXuLv { get; set; } = ""; // 鲜茧有绪率(区间文本)
|
||||
public int JiaoShu { get; set; } // 绞数
|
||||
public int LingHuiShu { get; set; } // 零回数
|
||||
public decimal ShengSiZongChang { get; set; } // 生丝总长(回)
|
||||
public decimal JianSiZongChang { get; set; } // 茧丝总长(回)
|
||||
public decimal YiJianSiChang { get; set; } // 一茧丝长(m)
|
||||
public decimal JieShuSiChang { get; set; } // 解舒丝长(m)
|
||||
public decimal JieShuLv { get; set; } // 解舒率(%)
|
||||
public decimal JieShuGuangZhe { get; set; } // 解舒光折(kg)
|
||||
public decimal ShengSiXianDu { get; set; } // 生丝纤度(D)
|
||||
public decimal JianSiXianDu { get; set; } // 茧丝纤度(D)
|
||||
|
||||
// ---- 蛹体粒数 / 比例 ----
|
||||
public int YtLsNeiYin { get; set; } // 内印
|
||||
public int YtLsMaoZu { get; set; } // 毛足
|
||||
public int YtLsChuanTou { get; set; } // 穿头
|
||||
public int YtLsShuangGong { get; set; } // 双宫
|
||||
public decimal YtBlNeiYin { get; set; } // 内印比例
|
||||
public decimal YtBlMaoZu { get; set; } // 毛足比例
|
||||
public decimal YtBlChuanTou { get; set; } // 穿头比例
|
||||
public decimal YtBlShuangGong { get; set; } // 双宫比例
|
||||
|
||||
// ---- 附煮 ----
|
||||
public string FzCheJian { get; set; } = ""; // 附煮车间
|
||||
public string FzGyzkName { get; set; } = ""; // 附煮工艺庄口
|
||||
public string FzZhengZhuWenDu { get; set; } = ""; // 蒸煮室温度
|
||||
public string FzTiaoZhengBuWenDu { get; set; } = ""; // 调整部温度
|
||||
public string FzCheSu { get; set; } = ""; // 附煮车速
|
||||
|
||||
// ---- 屑物 ----
|
||||
public decimal XwJianBaLiang { get; set; } // 总茧巴量
|
||||
public decimal XwYongYiZhongLiang { get; set; } // 蛹衣重量
|
||||
public decimal XwYongYiLv { get; set; } // 蛹衣率
|
||||
public decimal XwLeiJie { get; set; } // 类节
|
||||
public decimal XwWanMiDiaoCao { get; set; } // 万米吊糙
|
||||
public decimal XwJianBaBiLi { get; set; } // 茧巴比例
|
||||
|
||||
// ---- 样茧 ----
|
||||
public int JieShuLiShu { get; set; } // 解舒样茧粒数
|
||||
public decimal JieShuZhongLiang { get; set; } // 解舒样茧重量(g)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>
|
||||
/// 清洁洁净(对应 F9 biz_prod_qc_yjss_dc_qjjj):
|
||||
/// 匀度(一/二/三度) + 洁净档位片数(100~60) + 疵点粒数(次要/普通) + 计算出的清洁、洁净、片数。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_yjss_dc_qjjj")]
|
||||
public class YjssDcQjjj : BaseEntity
|
||||
{
|
||||
public long YjssId { get; set; } // 主表 Id
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
|
||||
// ---- 匀度 ----
|
||||
public int YdYiDu { get; set; } // 一度
|
||||
public int YdErDu { get; set; } // 二度
|
||||
public int YdSanDu { get; set; } // 三度
|
||||
|
||||
// ---- 洁净档位(片数)----
|
||||
public int Jj100 { get; set; }
|
||||
public int Jj95 { get; set; }
|
||||
public int Jj90 { get; set; }
|
||||
public int Jj85 { get; set; }
|
||||
public int Jj80 { get; set; }
|
||||
public int Jj75 { get; set; }
|
||||
public int Jj70 { get; set; }
|
||||
public int Jj65 { get; set; }
|
||||
public int Jj60 { get; set; }
|
||||
|
||||
// ---- 次要疵点 ----
|
||||
public int QjTdc { get; set; } // 特大糙
|
||||
public int QjFs { get; set; } // 废丝
|
||||
public int QjDc { get; set; } // 大糙
|
||||
public int QjNfc { get; set; } // 黏附糙
|
||||
public int QjDcj { get; set; } // 大长结
|
||||
public int QjZlx { get; set; } // 重螺旋
|
||||
|
||||
// ---- 普通疵点 ----
|
||||
public int QjXc { get; set; } // 小糙
|
||||
public int QjCj { get; set; } // 长结
|
||||
public int QjLx { get; set; } // 螺旋
|
||||
public int QjHjls { get; set; } // 环结及裂丝
|
||||
|
||||
// ---- 结果 ----
|
||||
public int PianShu { get; set; } // 片数
|
||||
public decimal QingJie { get; set; } // 清洁
|
||||
public decimal JingJie { get; set; } // 洁净
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>切剖调查(对应 F9 biz_prod_qc_yjss_dc_qpdc)</summary>
|
||||
[Table(Name = "qc_yjss_dc_qpdc")]
|
||||
public class YjssDcQpdc : BaseEntity
|
||||
{
|
||||
public long YjssId { get; set; } // 主表 Id
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样
|
||||
public decimal YlQuanJian { get; set; } // 原量-全茧
|
||||
public decimal YlJianCeng { get; set; } // 原量-茧层
|
||||
public decimal YlYongTi { get; set; } // 原量-蛹体
|
||||
public decimal JianCengLv { get; set; } // 茧层率(%)
|
||||
public decimal GlYongTi { get; set; } // 蛹体干量
|
||||
public decimal GlJianCeng { get; set; } // 茧层干量
|
||||
public decimal GlZhuJian { get; set; } // 煮茧后干量
|
||||
public decimal JianCengLvGongLiang { get; set; } // 公量茧层率(%)
|
||||
public decimal TuoJiaoLv { get; set; } // 脱胶率(%)
|
||||
public int LsJiangCan { get; set; } // 僵蚕粒数
|
||||
public int LsNeiRan { get; set; } // 内染粒数
|
||||
public int LsMeiYong { get; set; } // 霉蛹粒数
|
||||
public int LsMaoJiao { get; set; } // 毛脚粒数
|
||||
public decimal BlJiangCan { get; set; } // 僵蚕比例(%)
|
||||
public decimal BlNeiRan { get; set; } // 内染比例(%)
|
||||
public decimal BlMeiYong { get; set; } // 霉蛹比例(%)
|
||||
public decimal BlMaoJiao { get; set; } // 毛脚比例(%)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>
|
||||
/// 选茧调查(对应 F9 biz_prod_qc_yjss_dc_xjdc):
|
||||
/// 11 个茧别重量 + 11 个比例 + 公斤茧粒数 + 解舒样茧粒数/重量。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_yjss_dc_xjdc")]
|
||||
public class YjssDcXjdc : BaseEntity
|
||||
{
|
||||
public long YjssId { get; set; } // 主表 Id(原料小样 / 综合样)
|
||||
public int YjssType { get; set; } = 1; // 样品类型:1=原料小样 2=综合样(两张主表 Id 各自自增,需判别)
|
||||
|
||||
// ---- 茧重(g)----
|
||||
public decimal ZlLtj { get; set; } // 联统茧
|
||||
public decimal ZlHwj { get; set; } // 好五级茧
|
||||
public decimal ZlWj { get; set; } // 五级茧
|
||||
public decimal ZlMianJian { get; set; } // 绵茧
|
||||
public decimal ZlHuangBan { get; set; } // 黄斑
|
||||
public decimal ZlQuJian { get; set; } // 蛆茧
|
||||
public decimal ZlShuangGong { get; set; } // 双宫
|
||||
public decimal ZlXueJian { get; set; } // 血茧
|
||||
public decimal ZlBaoPi { get; set; } // 薄皮
|
||||
public decimal ZlJianYi { get; set; } // 茧衣
|
||||
public decimal ZlMeiJian { get; set; } // 霉茧
|
||||
|
||||
// ---- 比例(%)----
|
||||
public decimal BlLtj { get; set; }
|
||||
public decimal BlHwj { get; set; }
|
||||
public decimal BlWj { get; set; }
|
||||
public decimal BlMianJian { get; set; }
|
||||
public decimal BlHuangBan { get; set; }
|
||||
public decimal BlQuJian { get; set; }
|
||||
public decimal BlShuangGong { get; set; }
|
||||
public decimal BlXueJian { get; set; }
|
||||
public decimal BlBaoPi { get; set; }
|
||||
public decimal BlJianYi { get; set; }
|
||||
public decimal BlMeiJian { get; set; }
|
||||
|
||||
public int GongJinLiShu { get; set; } // 公斤茧粒数
|
||||
public int JieShuLiShu { get; set; } // 解舒样茧粒数
|
||||
public decimal JieShuZhongLiang { get; set; } // 解舒样茧重量(g)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.QC.Yjss;
|
||||
|
||||
/// <summary>
|
||||
/// 样茧试缫——原料小样主表(对应 F9 biz_prod_qc_yjss_main_ylxy)。
|
||||
/// 能力范围:选茧调查 / 解舒调查 / 清洁洁净。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_yjss_main_ylxy")]
|
||||
public class YjssMainYlxy : BaseEntity
|
||||
{
|
||||
public long YlzkId { get; set; } // 原料庄口 Id
|
||||
public DateTime Date { get; set; } = DateTime.Today; // 日期
|
||||
public int Type { get; set; } = 1; // 类型:1定价抽样 2入库抽样 3鲜茧抽样
|
||||
public decimal ZhongLiang { get; set; } // 样茧重量(g)
|
||||
public decimal HuiChaoLv { get; set; } // 回潮率(%)
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
public long XjdcId { get; set; } // 选茧调查 Id
|
||||
public int JsdcCt { get; set; } // 解舒调查记录数
|
||||
public long QjjjId { get; set; } // 清洁洁净 Id
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 样茧试缫——综合样主表(对应 F9 biz_prod_qc_yjss_main_zhy)。
|
||||
/// 能力范围:选茧 / 解舒 / 茧幅 / 切剖 / 清洁洁净 / 茧丝百回纤度。
|
||||
/// </summary>
|
||||
[Table(Name = "qc_yjss_main_zhy")]
|
||||
public class YjssMainZhy : BaseEntity
|
||||
{
|
||||
public long GyzkId { get; set; } // 工艺庄口 Id
|
||||
public DateTime Date { get; set; } = DateTime.Today; // 日期
|
||||
public decimal ZhongLiang { get; set; } // 样茧重量(g)
|
||||
public string Memo { get; set; } = ""; // 备注
|
||||
public long XjdcId { get; set; } // 选茧调查 Id
|
||||
public int JsdcCt { get; set; } // 解舒调查记录数
|
||||
public long JfdcId { get; set; } // 茧幅调查 Id
|
||||
public long QpdcId { get; set; } // 切剖调查 Id
|
||||
public long QjjjId { get; set; } // 清洁洁净 Id
|
||||
public int JsbhxdCt { get; set; } // 茧丝百回纤度记录数
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Events.SYS.Reports;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.SYS.Reports;
|
||||
|
||||
/// <summary>
|
||||
/// 报表定义(Grid++Report 模板)。每张报表对应一个 .grf 模板与一段数据查询 SQL。
|
||||
/// 权限模型:创建/更新报表时自动同步一条 Permission(ResourceType=报表),
|
||||
/// 角色在「角色管理 - 报表权限」中分配给用户后即可在报表中心查看。
|
||||
/// </summary>
|
||||
[Table(Name = "sys_reports")]
|
||||
public class Report : AggregateRoot
|
||||
{
|
||||
[Column(Name = "code")]
|
||||
public string Code { get; private set; } = ""; // 唯一标识,作为权限码的 resourceIdentity
|
||||
|
||||
[Column(Name = "name")]
|
||||
public string Name { get; private set; } = ""; // 显示名称
|
||||
|
||||
[Column(Name = "category")]
|
||||
public string Category { get; private set; } = "通用"; // 分类
|
||||
|
||||
[Column(Name = "description")]
|
||||
public string Description { get; private set; } = ""; // 描述
|
||||
|
||||
/// <summary>数据集 SQL(必须以 SELECT 开头)。FreeSql ExecuteDataTable 执行。</summary>
|
||||
[Column(Name = "data_sql")]
|
||||
public string DataSql { get; private set; } = "";
|
||||
|
||||
/// <summary>数据集查询参数定义 JSON:[{"name":"deptId","label":"部门","type":"int","default":0}]</summary>
|
||||
[Column(Name = "data_params")]
|
||||
public string DataParams { get; private set; } = "[]";
|
||||
|
||||
/// <summary>
|
||||
/// 查询配置 JSON(F9 ReportConfig 约定):
|
||||
/// { "DsName":"查询名(后端 IReportDataProvider 解析)", "ViewerType":0,
|
||||
/// "Parms":[{ "Name":"q_yearmonth","Label":"账期","Prompt":"","IsRequired":false,
|
||||
/// "ControlType":"AntdUI.DatePickerYearMonth","Ds_Static":null,"Ds_Service":null,"DefaultValue":"" }] }
|
||||
/// DsName 优先于 DataSql。
|
||||
/// </summary>
|
||||
[Column(Name = "config")]
|
||||
public string Config { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 报表模板文件 Id(对齐 F9 Report.ReportFileId)。
|
||||
/// 模板实例存储在 ReportFile(sys_report_files.DesignContent),与报表定义分离。
|
||||
/// </summary>
|
||||
[Column(Name = "report_file_id")]
|
||||
public long ReportFileId { get; private set; }
|
||||
|
||||
// ---------- 以下三个字段已废弃(历史遗留列,仅供启动时把旧模板迁移到 ReportFile 读取) ----------
|
||||
// 新代码一律通过 ReportFile.DesignContent 读写模板。
|
||||
|
||||
/// <summary>[已废弃] 旧版模板二进制,迁移到 ReportFile 后不再写入</summary>
|
||||
[Column(Name = "template_data", MapType = typeof(byte[]))]
|
||||
public byte[]? TemplateData { get; private set; }
|
||||
|
||||
[Column(Name = "template_name")]
|
||||
public string TemplateName { get; private set; } = "";
|
||||
|
||||
[Column(Name = "template_size")]
|
||||
public int TemplateSize { get; private set; }
|
||||
|
||||
[Column(Name = "sort_order")]
|
||||
public int SortOrder { get; private set; }
|
||||
|
||||
[Column(Name = "is_active")]
|
||||
public bool IsActive { get; private set; } = true;
|
||||
|
||||
public Report() { }
|
||||
|
||||
public Report(long id, string code, string name, string category, string description, string dataSql, string dataParams, int sortOrder, string config = "")
|
||||
{
|
||||
Id = id;
|
||||
Code = code ?? "";
|
||||
Name = name ?? "";
|
||||
Category = string.IsNullOrWhiteSpace(category) ? "通用" : category.Trim();
|
||||
Description = description ?? "";
|
||||
DataSql = dataSql ?? "";
|
||||
DataParams = string.IsNullOrWhiteSpace(dataParams) ? "[]" : dataParams;
|
||||
Config = config ?? "";
|
||||
SortOrder = sortOrder;
|
||||
IsActive = true;
|
||||
CreatedAt = DateTime.Now;
|
||||
AddDomainEvent(new Report_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
public void UpdateBasic(string code, string name, string category, string description, string dataSql, string dataParams, int sortOrder, string? config = null)
|
||||
{
|
||||
Code = code ?? Code;
|
||||
Name = name ?? Name;
|
||||
Category = string.IsNullOrWhiteSpace(category) ? Category : category.Trim();
|
||||
Description = description ?? Description;
|
||||
DataSql = dataSql ?? DataSql;
|
||||
DataParams = string.IsNullOrWhiteSpace(dataParams) ? DataParams : dataParams;
|
||||
Config = config ?? Config;
|
||||
SortOrder = sortOrder;
|
||||
UpdatedAt = DateTime.Now;
|
||||
AddDomainEvent(new Report_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>[已废弃] 旧版把模板二进制直接写在报表上;现模板存于 ReportFile.DesignContent</summary>
|
||||
public void UploadTemplate(byte[] data, string fileName)
|
||||
{
|
||||
TemplateData = data ?? Array.Empty<byte>();
|
||||
TemplateName = fileName ?? "";
|
||||
TemplateSize = TemplateData.Length;
|
||||
UpdatedAt = DateTime.Now;
|
||||
AddDomainEvent(new Report_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>绑定模板文件实例(对齐 F9:Report.ReportFileId → ReportFile)</summary>
|
||||
public void BindReportFile(long reportFileId)
|
||||
{
|
||||
ReportFileId = reportFileId;
|
||||
UpdatedAt = DateTime.Now;
|
||||
AddDomainEvent(new Report_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>迁移完成后清空废弃的旧模板字段</summary>
|
||||
public void ClearLegacyTemplate()
|
||||
{
|
||||
TemplateData = null;
|
||||
TemplateName = "";
|
||||
TemplateSize = 0;
|
||||
}
|
||||
|
||||
public void Activate() { IsActive = true; UpdatedAt = DateTime.Now; AddDomainEvent(new Report_ChangedDomainEvent(Id)); }
|
||||
public void Deactivate() { IsActive = false; UpdatedAt = DateTime.Now; AddDomainEvent(new Report_ChangedDomainEvent(Id)); }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.SYS.Reports;
|
||||
|
||||
/// <summary>
|
||||
/// 报表模板文件(对齐 BBIT.F9.Domain.Entities.SYS.Reports.ReportFile)。
|
||||
/// 报表模板作为独立实体与报表定义(Report)分离存储,Report 通过 ReportFileId 关联其模板实例。
|
||||
/// .grf 本质是 UTF-8 JSON 文本,故以文本(DesignContent)保存。
|
||||
/// </summary>
|
||||
[Table(Name = "sys_report_files")]
|
||||
public class ReportFile : BusinessEntity
|
||||
{
|
||||
[Column(Name = "report_id")]
|
||||
public long ReportId { get; private set; }
|
||||
|
||||
/// <summary>模板设计内容(.grf 的 JSON 文本)</summary>
|
||||
[Column(Name = "design_content", StringLength = -2)]
|
||||
public string DesignContent { get; private set; } = "";
|
||||
|
||||
public ReportFile() { }
|
||||
|
||||
public ReportFile(long id, long reportId, string designContent)
|
||||
{
|
||||
Id = id;
|
||||
ReportId = reportId;
|
||||
DesignContent = designContent ?? "";
|
||||
CreatedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
public void UpdateContent(string designContent)
|
||||
{
|
||||
DesignContent = designContent ?? "";
|
||||
UpdatedAt = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using F9WebAiTest.Domain.Entities;
|
||||
|
||||
namespace F9WebAiTest.Domain.Events.SYS.Reports;
|
||||
|
||||
/// <summary>报表变更领域事件(用于缓存刷新等)</summary>
|
||||
public record Report_ChangedDomainEvent(long ReportId) : DomainEvent;
|
||||
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"Version":"6.8.5.0",
|
||||
"Title":"职工花名册",
|
||||
"Author":"F9 报表中心",
|
||||
"Description":"示例报表:列出职工的基本信息(数据源由 DsName=RepDt_Sys_Users 提供)。",
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Weight":400,
|
||||
"Charset":134
|
||||
},
|
||||
"Printer":{
|
||||
"TopMargin":1.5,
|
||||
"BottomMargin":1.5
|
||||
},
|
||||
"DetailGrid":{
|
||||
"CenterView":true,
|
||||
"PrintAdaptMethod":"ResizeToFit",
|
||||
"Recordset":{
|
||||
"Field":[
|
||||
{
|
||||
"Name":"EmployeeCode"
|
||||
},
|
||||
{
|
||||
"Name":"EmployeeName"
|
||||
},
|
||||
{
|
||||
"Name":"Gender"
|
||||
},
|
||||
{
|
||||
"Name":"DepName"
|
||||
},
|
||||
{
|
||||
"Name":"WorktypeName"
|
||||
},
|
||||
{
|
||||
"Name":"Phone"
|
||||
},
|
||||
{
|
||||
"Name":"RuzhiDate"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Column":[
|
||||
{
|
||||
"Name":"EmployeeCode",
|
||||
"Width":2.5
|
||||
},
|
||||
{
|
||||
"Name":"EmployeeName",
|
||||
"Width":2.2
|
||||
},
|
||||
{
|
||||
"Name":"Gender",
|
||||
"Width":1.2
|
||||
},
|
||||
{
|
||||
"Name":"DepName",
|
||||
"Width":2.8
|
||||
},
|
||||
{
|
||||
"Name":"WorktypeName",
|
||||
"Width":2.4
|
||||
},
|
||||
{
|
||||
"Name":"Phone",
|
||||
"Width":2.6
|
||||
},
|
||||
{
|
||||
"Name":"RuzhiDate",
|
||||
"Width":2.4
|
||||
}
|
||||
],
|
||||
"ColumnContent":{
|
||||
"Height":0.79375,
|
||||
"ColumnContentCell":[
|
||||
{
|
||||
"Column":"EmployeeCode",
|
||||
"DataField":"EmployeeCode"
|
||||
},
|
||||
{
|
||||
"Column":"EmployeeName",
|
||||
"DataField":"EmployeeName"
|
||||
},
|
||||
{
|
||||
"Column":"Gender",
|
||||
"DataField":"Gender"
|
||||
},
|
||||
{
|
||||
"Column":"DepName",
|
||||
"DataField":"DepName"
|
||||
},
|
||||
{
|
||||
"Column":"WorktypeName",
|
||||
"DataField":"WorktypeName"
|
||||
},
|
||||
{
|
||||
"Column":"Phone",
|
||||
"DataField":"Phone"
|
||||
},
|
||||
{
|
||||
"Column":"RuzhiDate",
|
||||
"DataField":"RuzhiDate"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ColumnTitle":{
|
||||
"BackColor":"C0C0C0",
|
||||
"Height":0.79375,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Bold":true,
|
||||
"Charset":134
|
||||
},
|
||||
"RepeatStyle":"OnPage",
|
||||
"ColumnTitleCell":[
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"EmployeeCode",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"工号"
|
||||
},
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"EmployeeName",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"姓名"
|
||||
},
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"Gender",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"性别"
|
||||
},
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"DepName",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"部门"
|
||||
},
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"WorktypeName",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"工种"
|
||||
},
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"Phone",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"电话"
|
||||
},
|
||||
{
|
||||
"GroupTitle":false,
|
||||
"Column":"RuzhiDate",
|
||||
"BackColor":"FFFFFF",
|
||||
"Text":"入职日期"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"PageHeader":{
|
||||
"Height":1.32292,
|
||||
"Control":[
|
||||
{
|
||||
"Type":"MemoBox",
|
||||
"Name":"MemoBox1",
|
||||
"Anchor":"[Top|Right]",
|
||||
"Left":9.5,
|
||||
"Top":0.396875,
|
||||
"Width":6.3,
|
||||
"Height":0.47625,
|
||||
"Font":{
|
||||
"Name":"楷体",
|
||||
"Size":120000,
|
||||
"Bold":true,
|
||||
"Charset":134
|
||||
},
|
||||
"Text":"职工花名册 · 智慧缫丝系统"
|
||||
},
|
||||
{
|
||||
"Type":"Line",
|
||||
"Name":"Line1",
|
||||
"Anchor":"[Left|Top|Right]",
|
||||
"Top":1.00542,
|
||||
"Width":16.0073,
|
||||
"Pen":{
|
||||
"Width":2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"PageFooter":{
|
||||
"Height":1.32292,
|
||||
"Control":[
|
||||
{
|
||||
"Type":"MemoBox",
|
||||
"Name":"MemoBox2",
|
||||
"Anchor":"[Top|Right]",
|
||||
"Left":13.2027,
|
||||
"Top":0.396875,
|
||||
"Width":2.59292,
|
||||
"Height":0.608542,
|
||||
"Text":"第[#SystemVar(PageNumber)#]页 共[#SystemVar(PageCount)#]页"
|
||||
},
|
||||
{
|
||||
"Type":"MemoBox",
|
||||
"Name":"MemoBox3",
|
||||
"Left":0.211667,
|
||||
"Top":0.396875,
|
||||
"Width":6.19125,
|
||||
"Height":0.608542,
|
||||
"Text":"打印时间:[#SystemVar(CurrentDateTime):yyyy年MM月dd日 HH:mm:ss#]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReportHeader":[
|
||||
{
|
||||
"Height":0.978958,
|
||||
"Control":[
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"StaticBox1",
|
||||
"Center":"Horizontal",
|
||||
"Left":6.29708,
|
||||
"Top":0.185208,
|
||||
"Width":3.41313,
|
||||
"Height":0.582083,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":150000,
|
||||
"Bold":true,
|
||||
"Charset":134
|
||||
},
|
||||
"Text":"职工花名册"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Height":0.6,
|
||||
"Control":[
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"LabelDep",
|
||||
"Left":0.2,
|
||||
"Top":0.05,
|
||||
"Width":1.4,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Text":"部门:"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"ParamDep",
|
||||
"Left":1.7,
|
||||
"Top":0.05,
|
||||
"Width":2.2,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Parameter":"部门"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"LabelKeyword",
|
||||
"Left":4.0,
|
||||
"Top":0.05,
|
||||
"Width":1.6,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Text":"关键字:"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"ParamKeyword",
|
||||
"Left":5.7,
|
||||
"Top":0.05,
|
||||
"Width":1.8,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Parameter":"关键字"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"LabelCount",
|
||||
"Left":7.6,
|
||||
"Top":0.05,
|
||||
"Width":1.6,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Text":"记录数:"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"ParamCount",
|
||||
"Left":9.3,
|
||||
"Top":0.05,
|
||||
"Width":1.2,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Parameter":"记录数"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"LabelQueryTime",
|
||||
"Left":10.6,
|
||||
"Top":0.05,
|
||||
"Width":2.0,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Text":"查询时间:"
|
||||
},
|
||||
{
|
||||
"Type":"StaticBox",
|
||||
"Name":"ParamQueryTime",
|
||||
"Left":12.7,
|
||||
"Top":0.05,
|
||||
"Width":3.3,
|
||||
"Height":0.5,
|
||||
"Font":{
|
||||
"Name":"宋体",
|
||||
"Size":90000,
|
||||
"Charset":134
|
||||
},
|
||||
"Parameter":"查询时间"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Parameter":[
|
||||
{
|
||||
"Name":"部门",
|
||||
"DataType":"String"
|
||||
},
|
||||
{
|
||||
"Name":"关键字",
|
||||
"DataType":"String"
|
||||
},
|
||||
{
|
||||
"Name":"记录数",
|
||||
"DataType":"Integer"
|
||||
},
|
||||
{
|
||||
"Name":"查询时间",
|
||||
"DataType":"String"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"Version": "6.8.5.0",
|
||||
"Title": "落丝记录",
|
||||
"Author": "F9 报表中心",
|
||||
"Description": "生产-前缫:落丝记录明细(数据源 DsName=RepDt_Prod_Lsjl)。",
|
||||
"Font": {
|
||||
"Name": "宋体",
|
||||
"Size": 90000,
|
||||
"Weight": 400,
|
||||
"Charset": 134
|
||||
},
|
||||
"Printer": {
|
||||
"TopMargin": 1.5,
|
||||
"BottomMargin": 1.5
|
||||
},
|
||||
"DetailGrid": {
|
||||
"CenterView": true,
|
||||
"PrintAdaptMethod": "ResizeToFit",
|
||||
"Recordset": {
|
||||
"Field": [
|
||||
{ "Name": "日期" },
|
||||
{ "Name": "车间" },
|
||||
{ "Name": "机组" },
|
||||
{ "Name": "庄口" },
|
||||
{ "Name": "回数" },
|
||||
{ "Name": "颜色" },
|
||||
{ "Name": "半车" },
|
||||
{ "Name": "落丝时间" },
|
||||
{ "Name": "缫丝桶数" }
|
||||
]
|
||||
},
|
||||
"Column": [
|
||||
{ "Name": "日期", "Width": 2.0 },
|
||||
{ "Name": "车间", "Width": 3.0 },
|
||||
{ "Name": "机组", "Width": 1.4 },
|
||||
{ "Name": "庄口", "Width": 3.2 },
|
||||
{ "Name": "回数", "Width": 1.4 },
|
||||
{ "Name": "颜色", "Width": 1.6 },
|
||||
{ "Name": "半车", "Width": 1.4 },
|
||||
{ "Name": "落丝时间", "Width": 1.8 },
|
||||
{ "Name": "缫丝桶数", "Width": 1.5 }
|
||||
],
|
||||
"ColumnContent": {
|
||||
"Height": 0.79375,
|
||||
"ColumnContentCell": [
|
||||
{ "Column": "日期", "DataField": "日期" },
|
||||
{ "Column": "车间", "DataField": "车间" },
|
||||
{ "Column": "机组", "DataField": "机组" },
|
||||
{ "Column": "庄口", "DataField": "庄口" },
|
||||
{ "Column": "回数", "DataField": "回数" },
|
||||
{ "Column": "颜色", "DataField": "颜色" },
|
||||
{ "Column": "半车", "DataField": "半车" },
|
||||
{ "Column": "落丝时间", "DataField": "落丝时间" },
|
||||
{ "Column": "缫丝桶数", "DataField": "缫丝桶数" }
|
||||
]
|
||||
},
|
||||
"ColumnTitle": {
|
||||
"BackColor": "C0C0C0",
|
||||
"Height": 0.79375,
|
||||
"Font": {
|
||||
"Name": "宋体",
|
||||
"Size": 90000,
|
||||
"Bold": true,
|
||||
"Charset": 134
|
||||
},
|
||||
"RepeatStyle": "OnPage",
|
||||
"ColumnTitleCell": [
|
||||
{ "GroupTitle": false, "Column": "日期", "BackColor": "FFFFFF", "Text": "日期" },
|
||||
{ "GroupTitle": false, "Column": "车间", "BackColor": "FFFFFF", "Text": "车间" },
|
||||
{ "GroupTitle": false, "Column": "机组", "BackColor": "FFFFFF", "Text": "机组" },
|
||||
{ "GroupTitle": false, "Column": "庄口", "BackColor": "FFFFFF", "Text": "庄口" },
|
||||
{ "GroupTitle": false, "Column": "回数", "BackColor": "FFFFFF", "Text": "回数" },
|
||||
{ "GroupTitle": false, "Column": "颜色", "BackColor": "FFFFFF", "Text": "颜色" },
|
||||
{ "GroupTitle": false, "Column": "半车", "BackColor": "FFFFFF", "Text": "半车" },
|
||||
{ "GroupTitle": false, "Column": "落丝时间", "BackColor": "FFFFFF", "Text": "落丝时间" },
|
||||
{ "GroupTitle": false, "Column": "缫丝桶数", "BackColor": "FFFFFF", "Text": "缫丝桶数" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"PageHeader": {
|
||||
"Height": 1.32292,
|
||||
"Control": [
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "MemoBox1",
|
||||
"Anchor": "[Top|Right]",
|
||||
"Left": 9.5,
|
||||
"Top": 0.396875,
|
||||
"Width": 6.3,
|
||||
"Height": 0.47625,
|
||||
"Font": {
|
||||
"Name": "楷体",
|
||||
"Size": 120000,
|
||||
"Bold": true,
|
||||
"Charset": 134
|
||||
},
|
||||
"Text": "落丝记录 · 智慧缫丝系统"
|
||||
},
|
||||
{
|
||||
"Type": "Line",
|
||||
"Name": "Line1",
|
||||
"Anchor": "[Left|Top|Right]",
|
||||
"Top": 1.00542,
|
||||
"Width": 16.0073,
|
||||
"Pen": {
|
||||
"Width": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"PageFooter": {
|
||||
"Height": 1.32292,
|
||||
"Control": [
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "MemoBox2",
|
||||
"Anchor": "[Top|Right]",
|
||||
"Left": 13.2027,
|
||||
"Top": 0.396875,
|
||||
"Width": 2.59292,
|
||||
"Height": 0.608542,
|
||||
"Text": "第[#SystemVar(PageNumber)#]页 共[#SystemVar(PageCount)#]页"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "MemoBox3",
|
||||
"Left": 0.211667,
|
||||
"Top": 0.396875,
|
||||
"Width": 6.19125,
|
||||
"Height": 0.608542,
|
||||
"Text": "打印时间:[#SystemVar(CurrentDateTime):yyyy年MM月dd日 HH:mm:ss#]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReportHeader": [
|
||||
{
|
||||
"Height": 0.978958,
|
||||
"Control": [
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "StaticBoxTitle",
|
||||
"Center": "Horizontal",
|
||||
"Left": 6.29708,
|
||||
"Top": 0.185208,
|
||||
"Width": 3.41313,
|
||||
"Height": 0.582083,
|
||||
"Font": {
|
||||
"Name": "宋体",
|
||||
"Size": 150000,
|
||||
"Bold": true,
|
||||
"Charset": 134
|
||||
},
|
||||
"Text": "落丝记录"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Height": 1.2,
|
||||
"Control": [
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelDateStart",
|
||||
"Left": 0.3,
|
||||
"Top": 0.05,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "开始日期:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamDateStart",
|
||||
"Left": 1.6,
|
||||
"Top": 0.05,
|
||||
"Width": 2.5,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{开始日期}#]"
|
||||
},
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelDateEnd",
|
||||
"Left": 4.4,
|
||||
"Top": 0.05,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "结束日期:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamDateEnd",
|
||||
"Left": 5.7,
|
||||
"Top": 0.05,
|
||||
"Width": 2.5,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{结束日期}#]"
|
||||
},
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelDep",
|
||||
"Left": 8.5,
|
||||
"Top": 0.05,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "车间:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamDep",
|
||||
"Left": 9.8,
|
||||
"Top": 0.05,
|
||||
"Width": 2.5,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{车间}#]"
|
||||
},
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelJizu",
|
||||
"Left": 12.6,
|
||||
"Top": 0.05,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "机组:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamJizu",
|
||||
"Left": 13.9,
|
||||
"Top": 0.05,
|
||||
"Width": 2.5,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{机组}#]"
|
||||
},
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelZk",
|
||||
"Left": 0.3,
|
||||
"Top": 0.6,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "庄口:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamZk",
|
||||
"Left": 1.6,
|
||||
"Top": 0.6,
|
||||
"Width": 2.5,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{庄口}#]"
|
||||
},
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelCount",
|
||||
"Left": 4.4,
|
||||
"Top": 0.6,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "记录数:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamCount",
|
||||
"Left": 5.7,
|
||||
"Top": 0.6,
|
||||
"Width": 2.5,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{记录数}#]"
|
||||
},
|
||||
{
|
||||
"Type": "StaticBox",
|
||||
"Name": "LabelQueryTime",
|
||||
"Left": 8.5,
|
||||
"Top": 0.6,
|
||||
"Width": 1.3,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "查询时间:"
|
||||
},
|
||||
{
|
||||
"Type": "MemoBox",
|
||||
"Name": "ParamQueryTime",
|
||||
"Left": 9.8,
|
||||
"Top": 0.6,
|
||||
"Width": 3.6,
|
||||
"Height": 0.5,
|
||||
"Font": { "Name": "宋体", "Size": 90000, "Charset": 134 },
|
||||
"Text": "[#{查询时间}#]"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Parameter": [
|
||||
{ "Name": "开始日期", "DataType": "String" },
|
||||
{ "Name": "结束日期", "DataType": "String" },
|
||||
{ "Name": "车间", "DataType": "String" },
|
||||
{ "Name": "机组", "DataType": "String" },
|
||||
{ "Name": "庄口", "DataType": "String" },
|
||||
{ "Name": "记录数", "DataType": "Integer" },
|
||||
{ "Name": "查询时间", "DataType": "String" }
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Data;
|
||||
using System.Text;
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Application.Interfaces.SnowflakeID;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
@@ -10,6 +11,8 @@ using SysMenu = F9WebAiTest.Domain.Entities.SYS.RBAC.Menu;
|
||||
using SysRole = F9WebAiTest.Domain.Entities.SYS.RBAC.Role;
|
||||
using SysPermission = F9WebAiTest.Domain.Entities.SYS.RBAC.Permission;
|
||||
using SysRolePermission = F9WebAiTest.Domain.Entities.SYS.RBAC.RolePermission;
|
||||
using SysReport = F9WebAiTest.Domain.Entities.SYS.Reports.Report;
|
||||
using SysReportFile = F9WebAiTest.Domain.Entities.SYS.Reports.ReportFile;
|
||||
|
||||
namespace F9WebAiTest.Infrastructure.Data;
|
||||
|
||||
@@ -88,6 +91,20 @@ public static class DbSeeder
|
||||
// 6.69 修正前缫部门的职责类型(车间班次菜单随"生产-前缫"一起处理)
|
||||
FixQianxianDutyType(fsql);
|
||||
|
||||
// 6.695 质量模块(工艺执行书 / 样茧试缫)
|
||||
SeedQcMenus(fsql, snowflake);
|
||||
|
||||
// 6.71 报表中心菜单(/report 目录 + 报表中心/报表管理两个页面)
|
||||
SeedReportMenus(fsql, snowflake);
|
||||
// 6.72 旧版内嵌模板(sys_reports.template_data)迁移为独立模板实例 ReportFile
|
||||
MigrateReportTemplateToFile(fsql, snowflake);
|
||||
|
||||
// 6.72 报表中心示例报表(职工花名册)
|
||||
SeedSampleReport(fsql, snowflake);
|
||||
|
||||
// 6.73 落丝记录报表(生产-前缫,DsName=RepDt_Prod_Lsjl)
|
||||
SeedLsjlReport(fsql, snowflake);
|
||||
|
||||
// 6.7 为菜单创建权限单元(Permission),并修正旧菜单权限关联(统一权限模型)
|
||||
FixMenuPermissions(fsql, snowflake);
|
||||
|
||||
@@ -156,6 +173,7 @@ public static class DbSeeder
|
||||
EnsureColumn(fsql, "NotificationTypes", "RouteTemplate", "TEXT NOT NULL DEFAULT ''");
|
||||
EnsureColumn(fsql, "NotificationMessages", "Category", "INTEGER NOT NULL DEFAULT 0");
|
||||
EnsureColumn(fsql, "NotificationMessages", "RoutePath", "TEXT NOT NULL DEFAULT ''");
|
||||
EnsureColumn(fsql, "sys_reports", "report_file_id", "INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
|
||||
/// <summary>为已有表补加列(列已存在则忽略)</summary>
|
||||
@@ -735,6 +753,42 @@ public static class DbSeeder
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 质量模块菜单:一级目录「质量」+ 工艺执行书 / 样茧试缫 两个页面。
|
||||
/// 模块落在「生产-后缫」之后、消息中心之前。
|
||||
/// </summary>
|
||||
private static void SeedQcMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureTopMenuWithChildren(fsql, snowflake, "/qc", "质量", "Medal", 7,
|
||||
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
|
||||
{
|
||||
("工艺执行书", "/qc/gyzxs", "qc/gyzxs/index", "qc:gyzxs:list", "Document", 1),
|
||||
("样茧试缫", "/qc/yjss", "qc/yjss/index", "qc:yjss:list", "DataAnalysis", 2)
|
||||
});
|
||||
|
||||
foreach (var (path, sort) in new (string Path, int Sort)[]
|
||||
{
|
||||
("/attendance", 2), ("/base", 3), ("/employee", 4),
|
||||
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/qc", 7),
|
||||
("/notification", 8), ("/sys", 9), ("/report", 10)
|
||||
})
|
||||
{
|
||||
fsql.Update<SysMenu>()
|
||||
.Set(x => x.SortOrder, sort)
|
||||
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
|
||||
.ExecuteAffrows();
|
||||
}
|
||||
|
||||
Console.WriteLine("[DbSeeder] 质量模块菜单已就绪(工艺执行书 / 样茧试缫)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbSeeder] 质量模块菜单种子跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保一个一级目录菜单及其下属页面菜单存在(含父级、图标、启用状态校正),并给 admin 角色授权。
|
||||
/// 注意:图标名必须是 Element Plus 图标组件名。
|
||||
@@ -894,6 +948,22 @@ public static class DbSeeder
|
||||
// 2. 修正旧菜单权限关联(permission_id 旧值 = Menu.Id,改为 Permission.Id)
|
||||
fsql.Ado.ExecuteNonQuery($"UPDATE sys_rbac_role_permissions SET permission_id = {perm.Id} WHERE permission_id = {menu.Id};");
|
||||
}
|
||||
|
||||
// 3. 清理失效的菜单权限单元:ResourceId 不对应任何菜单
|
||||
// (前端曾用 Number() 传雪花 Id,超出 2^53 后精度丢失,写入的 resource_id 在菜单表里查不到,
|
||||
// 表现为「勾选保存后重新打开仍是未勾选、该角色登录也看不到菜单」)
|
||||
var menuIds = fsql.Select<SysMenu>().ToList().Select(m => m.Id).ToHashSet();
|
||||
var orphanPerms = fsql.Select<SysPermission>()
|
||||
.Where(p => p.ResourceType == Resource_Type_Enum.菜单).ToList()
|
||||
.Where(p => !menuIds.Contains(p.ResourceId)).ToList();
|
||||
foreach (var bad in orphanPerms)
|
||||
{
|
||||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {bad.Id};");
|
||||
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_permissions WHERE id = {bad.Id};");
|
||||
}
|
||||
if (orphanPerms.Count > 0)
|
||||
Console.WriteLine($"[DbSeeder] 已清理 {orphanPerms.Count} 条失效菜单权限单元(资源 Id 不存在)");
|
||||
|
||||
Console.WriteLine("[DbSeeder] 菜单权限单元同步完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -901,4 +971,399 @@ public static class DbSeeder
|
||||
Console.WriteLine($"[DbSeeder] 菜单权限单元同步跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>报表中心菜单(/report 目录 + 报表中心 / 报表管理 两个页面菜单,admin 授权)</summary>
|
||||
private static void SeedReportMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureTopMenuWithChildren(fsql, snowflake, "/report", "报表中心", "DataAnalysis", 9,
|
||||
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
|
||||
{
|
||||
("报表中心", "/report/center", "report/center/index", "report:center:list", "PieChart", 1),
|
||||
("报表管理", "/report/manage", "report/manage/index", "report:manage:list", "Document", 2)
|
||||
});
|
||||
Console.WriteLine("[DbSeeder] 报表中心菜单已就绪");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbSeeder] 报表中心菜单种子跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 旧版把模板二进制直接存在 sys_reports.template_data,现模板是独立实体 ReportFile(DesignContent 文本),
|
||||
/// Report 通过 ReportFileId 关联。启动时把旧数据迁移过去,迁移完成后清空旧字段。
|
||||
/// </summary>
|
||||
private static void MigrateReportTemplateToFile(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||||
{
|
||||
try
|
||||
{
|
||||
var legacy = fsql.Select<SysReport>()
|
||||
.Where(r => !r.IsDeleted && r.ReportFileId == 0 && r.TemplateData != null && r.TemplateData.Length > 0)
|
||||
.ToList();
|
||||
foreach (var report in legacy)
|
||||
{
|
||||
var text = Encoding.UTF8.GetString(report.TemplateData!);
|
||||
if (text.Length > 0 && text[0] == '\uFEFF') text = text.Substring(1);
|
||||
|
||||
var file = new SysReportFile(snowflake.CreateId(), report.Id, text);
|
||||
fsql.Insert(file).ExecuteAffrows();
|
||||
report.BindReportFile(file.Id);
|
||||
report.ClearLegacyTemplate();
|
||||
fsql.Update<SysReport>().SetSource(report).ExecuteAffrows();
|
||||
Console.WriteLine($"[DbSeeder] 报表「{report.Name}」模板已迁移到 ReportFile#{file.Id}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbSeeder] 报表模板迁移跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>报表中心示例报表:职工花名册(模板为独立 ReportFile 实例 + F9 ReportConfig 查询配置)</summary>
|
||||
private static void SeedSampleReport(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||||
{
|
||||
try
|
||||
{
|
||||
const string code = "employee_directory";
|
||||
const string name = "职工花名册";
|
||||
const string sql = "SELECT login_name AS LoginName, user_name AS UserName, phone_number AS PhoneNumber, email AS Email FROM sys_rbac_users WHERE is_deleted = 0 ORDER BY id LIMIT 1000";
|
||||
// F9 ReportConfig:DsName 指向后端查询提供者;Parms 定义动态查询控件
|
||||
const string config = "{\"DsName\":\"RepDt_Sys_Users\",\"ViewerType\":0,\"Parms\":[" +
|
||||
"{\"Name\":\"q_dep\",\"Label\":\"部门\",\"Prompt\":\"含子部门,留空为全部\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"AntdUI.Select\",\"Ds_Static\":null,\"Ds_Service\":\"/sys/department\",\"DefaultValue\":\"\"}," +
|
||||
"{\"Name\":\"q_like\",\"Label\":\"关键字\",\"Prompt\":\"按工号 / 姓名模糊查询\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"AntdUI.Input\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}]}";
|
||||
|
||||
var exist = fsql.Select<SysReport>().Where(r => r.Code == code && !r.IsDeleted).First();
|
||||
var templateJson = LoadEmbeddedTemplateJson("employee_directory.grf");
|
||||
|
||||
// 老版本种子生成的是手写 6.6.8.1 模板(设计器 6.8 无法打开),这里统一替换为 6.8.5.0 官方结构模板
|
||||
bool needReplaceTemplate = false;
|
||||
if (exist != null)
|
||||
{
|
||||
var file = fsql.Select<SysReportFile>().Where(f => f.ReportId == exist.Id && !f.IsDeleted).First();
|
||||
needReplaceTemplate = file == null || !file.DesignContent.Contains("6.8.5.0");
|
||||
}
|
||||
|
||||
if (exist == null)
|
||||
{
|
||||
var report = new SysReport(snowflake.CreateId(), code, name, "职工管理", "示例报表:按部门 / 关键字查询职工基本信息。", sql, "[]", 1, config);
|
||||
|
||||
// 模板实例:ReportFile(对齐 F9 Report.ReportFileId)
|
||||
if (templateJson != null)
|
||||
{
|
||||
var file = new SysReportFile(snowflake.CreateId(), report.Id, templateJson);
|
||||
fsql.Insert(file).ExecuteAffrows();
|
||||
report.BindReportFile(file.Id);
|
||||
}
|
||||
fsql.Insert(report).ExecuteAffrows();
|
||||
|
||||
// 创建对应权限单元
|
||||
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.报表, code, report.Id);
|
||||
perm.SetCode($"report:{code}:visble");
|
||||
fsql.Insert(perm).ExecuteAffrows();
|
||||
|
||||
// 授权给 admin 角色
|
||||
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
|
||||
if (adminRole != null)
|
||||
{
|
||||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, perm.Id)).ExecuteAffrows();
|
||||
}
|
||||
Console.WriteLine("[DbSeeder] 示例报表「职工花名册」已创建");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已存在:替换旧模板 / 补全缺失的模板与查询配置
|
||||
bool changed = false;
|
||||
if (needReplaceTemplate && templateJson != null)
|
||||
{
|
||||
var file = fsql.Select<SysReportFile>().Where(f => f.ReportId == exist.Id && !f.IsDeleted).First();
|
||||
if (file != null)
|
||||
{
|
||||
file.UpdateContent(templateJson);
|
||||
fsql.Update<SysReportFile>().SetSource(file).ExecuteAffrows();
|
||||
}
|
||||
else
|
||||
{
|
||||
file = new SysReportFile(snowflake.CreateId(), exist.Id, templateJson);
|
||||
fsql.Insert(file).ExecuteAffrows();
|
||||
exist.BindReportFile(file.Id);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (string.IsNullOrEmpty(exist.Config) || !exist.Config.Contains("q_dep"))
|
||||
{
|
||||
exist.UpdateBasic(exist.Code, exist.Name, exist.Category, exist.Description, exist.DataSql, exist.DataParams, exist.SortOrder, config);
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
fsql.Update<SysReport>().SetSource(exist).ExecuteAffrows();
|
||||
Console.WriteLine("[DbSeeder] 已更新示例报表模板/查询配置");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbSeeder] 示例报表种子跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 落丝记录报表(生产-前缫):DsName=RepDt_Prod_Lsjl,参数为日期范围 / 车间 / 机组 / 庄口 / 关键字。
|
||||
/// 已存在的报表只补齐查询配置,不覆盖用户在设计器里维护的 .grf 模板。
|
||||
/// </summary>
|
||||
private static void SeedLsjlReport(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
|
||||
{
|
||||
try
|
||||
{
|
||||
const string code = "PROD_QS_1";
|
||||
const string name = "落丝记录";
|
||||
const string category = "生产.前缫";
|
||||
const string dsName = "RepDt_Prod_Lsjl";
|
||||
|
||||
// 日期范围参数默认值用「不限」:避免 paramInitialValue 把空值默认成今天,导致只查当天
|
||||
const string config = "{\"DsName\":\"RepDt_Prod_Lsjl\",\"ViewerType\":0,\"Parms\":[" +
|
||||
"{\"Name\":\"q_date_start\",\"Label\":\"开始日期\",\"Prompt\":\"留空则不限开始日期\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"AntdUI.DatePicker\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"不限\"}," +
|
||||
"{\"Name\":\"q_date_end\",\"Label\":\"结束日期\",\"Prompt\":\"留空则不限结束日期\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"AntdUI.DatePicker\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"不限\"}," +
|
||||
"{\"Name\":\"q_dep\",\"Label\":\"车间\",\"Prompt\":\"含子车间,留空为全部\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"F9-DEP-QS-SELECT\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}," +
|
||||
"{\"Name\":\"q_jizu\",\"Label\":\"机组\",\"Prompt\":\"留空为全部\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"AntdUI.Input\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}," +
|
||||
"{\"Name\":\"q_zk\",\"Label\":\"庄口\",\"Prompt\":\"点击选择庄口\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"F9-GYZK-SELECT\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}," +
|
||||
"{\"Name\":\"q_like\",\"Label\":\"关键字\",\"Prompt\":\"庄口 / 规格 / 颜色\",\"IsRequired\":false," +
|
||||
"\"ControlType\":\"AntdUI.Input\",\"Ds_Static\":null,\"Ds_Service\":null,\"DefaultValue\":\"\"}]}";
|
||||
|
||||
const string description = "生产-前缫:按日期范围 / 车间 / 机组 / 庄口查询落丝记录明细。";
|
||||
|
||||
var exist = fsql.Select<SysReport>().Where(r => r.Code == code && !r.IsDeleted).First();
|
||||
|
||||
if (exist == null)
|
||||
{
|
||||
var report = new SysReport(snowflake.CreateId(), code, name, category, description, "", "[]", 2, config);
|
||||
|
||||
var newTemplate = LoadEmbeddedTemplateJson("lsjl.grf");
|
||||
if (newTemplate != null)
|
||||
{
|
||||
var newFile = new SysReportFile(snowflake.CreateId(), report.Id, newTemplate);
|
||||
fsql.Insert(newFile).ExecuteAffrows();
|
||||
report.BindReportFile(newFile.Id);
|
||||
}
|
||||
fsql.Insert(report).ExecuteAffrows();
|
||||
|
||||
var perm = new SysPermission(snowflake.CreateId(), Permission_Type_Enum.Visble, Resource_Type_Enum.报表, code, report.Id);
|
||||
perm.SetCode($"report:{code}:visble");
|
||||
fsql.Insert(perm).ExecuteAffrows();
|
||||
|
||||
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
|
||||
if (adminRole != null)
|
||||
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, perm.Id)).ExecuteAffrows();
|
||||
|
||||
Console.WriteLine("[DbSeeder] 落丝记录报表已创建(DsName=RepDt_Prod_Lsjl)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 已存在:DsName 未指向该 Provider、或参数标签全为空(程序生成的占位配置)时补齐查询配置
|
||||
// (数据 SQL 清空,避免与 DsName 混淆)
|
||||
var changed = false;
|
||||
if (string.IsNullOrEmpty(exist.Config) || !exist.Config.Contains(dsName)
|
||||
|| ConfigParamsMissingLabel(exist.Config))
|
||||
{
|
||||
exist.UpdateBasic(exist.Code, exist.Name,
|
||||
string.IsNullOrWhiteSpace(exist.Category) ? category : exist.Category,
|
||||
string.IsNullOrWhiteSpace(exist.Description) ? description : exist.Description,
|
||||
"", exist.DataParams, exist.SortOrder, config);
|
||||
changed = true;
|
||||
Console.WriteLine("[DbSeeder] 落丝记录报表查询配置已更新(DsName=RepDt_Prod_Lsjl)");
|
||||
}
|
||||
|
||||
// 模板是占位模板(无标题 / 无参数声明,多为早期上传测试产物)时,替换为内置规范模板;
|
||||
// 旧版内置模板有两类问题一并升级:
|
||||
// 1) 用 Parameter 属性绑定报表参数(GR 推荐 [#{参数名}#] 文本标记)
|
||||
// 2) 把「查询条件」(q_*) 误当成模板参数(那是查询面板的东西,不是报表参数)
|
||||
var templateJson = LoadEmbeddedTemplateJson("lsjl.grf");
|
||||
var file = fsql.Select<SysReportFile>().Where(f => f.ReportId == exist.Id && !f.IsDeleted).First();
|
||||
var oursTemplate = file != null && file.DesignContent.Contains("\"Author\": \"F9 报表中心\"");
|
||||
var legacyParamBinding = oursTemplate
|
||||
&& (file!.DesignContent.Contains("\"Parameter\": \"开始日期\"")
|
||||
|| TemplateHasQueryParams(file.DesignContent)
|
||||
|| TemplateHasStaticBoxParamBinding(file.DesignContent));
|
||||
if (templateJson != null && (file == null || legacyParamBinding || IsPlaceholderReportTemplate(file.DesignContent)))
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
file = new SysReportFile(snowflake.CreateId(), exist.Id, templateJson);
|
||||
fsql.Insert(file).ExecuteAffrows();
|
||||
}
|
||||
else
|
||||
{
|
||||
file.UpdateContent(templateJson);
|
||||
fsql.Update<SysReportFile>().SetSource(file).ExecuteAffrows();
|
||||
}
|
||||
exist.BindReportFile(file.Id);
|
||||
changed = true;
|
||||
Console.WriteLine("[DbSeeder] 落丝记录报表模板已更新为内置模板(标题 + 查询参数区)");
|
||||
}
|
||||
|
||||
if (changed) fsql.Update<SysReport>().SetSource(exist).ExecuteAffrows();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbSeeder] 落丝记录报表种子跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断查询条件配置是否缺少标签(Parms 为空,或所有 Label 都为空 → 程序生成的占位配置)。
|
||||
/// 用户手工配过标签的配置不会被种子覆盖。
|
||||
/// </summary>
|
||||
private static bool ConfigParamsMissingLabel(string config)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(config)) return true;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(config);
|
||||
if (!doc.RootElement.TryGetProperty("Parms", out var ps)
|
||||
|| ps.ValueKind != System.Text.Json.JsonValueKind.Array
|
||||
|| ps.GetArrayLength() == 0)
|
||||
return true;
|
||||
|
||||
foreach (var p in ps.EnumerateArray())
|
||||
{
|
||||
if (p.ValueKind != System.Text.Json.JsonValueKind.Object) continue;
|
||||
var label = p.TryGetProperty("Label", out var l) && l.ValueKind == System.Text.Json.JsonValueKind.String
|
||||
? l.GetString() ?? ""
|
||||
: "";
|
||||
if (!string.IsNullOrWhiteSpace(label)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断模板是否把报表参数绑在 StaticBox 上(实测 [#{参数}#] 只在 MemoBox 生效,StaticBox 原样输出)。
|
||||
/// </summary>
|
||||
private static bool TemplateHasStaticBoxParamBinding(string content)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith("_WR", StringComparison.Ordinal)) return false;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(content);
|
||||
return ContainsStaticBoxParamBinding(doc.RootElement);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ContainsStaticBoxParamBinding(System.Text.Json.JsonElement el)
|
||||
{
|
||||
if (el.ValueKind == System.Text.Json.JsonValueKind.Object)
|
||||
{
|
||||
var type = el.TryGetProperty("Type", out var t) && t.ValueKind == System.Text.Json.JsonValueKind.String
|
||||
? t.GetString() ?? ""
|
||||
: "";
|
||||
var text = el.TryGetProperty("Text", out var x) && x.ValueKind == System.Text.Json.JsonValueKind.String
|
||||
? x.GetString() ?? ""
|
||||
: "";
|
||||
if (type == "StaticBox" && text.Contains("[#{")) return true;
|
||||
|
||||
foreach (var prop in el.EnumerateObject())
|
||||
if (ContainsStaticBoxParamBinding(prop.Value)) return true;
|
||||
}
|
||||
else if (el.ValueKind == System.Text.Json.JsonValueKind.Array)
|
||||
{
|
||||
foreach (var item in el.EnumerateArray())
|
||||
if (ContainsStaticBoxParamBinding(item)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断模板是否把「查询条件」(q_* 参数) 误写成了模板参数。
|
||||
/// 模板参数应来自后端 Parameter_Data(如 车间 / 记录数 / 查询时间),不是查询面板的 q_*。
|
||||
/// </summary>
|
||||
private static bool TemplateHasQueryParams(string content)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith("_WR", StringComparison.Ordinal)) return false;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(content);
|
||||
if (!doc.RootElement.TryGetProperty("Parameter", out var ps)
|
||||
|| ps.ValueKind != System.Text.Json.JsonValueKind.Array)
|
||||
return false;
|
||||
|
||||
foreach (var p in ps.EnumerateArray())
|
||||
{
|
||||
if (p.ValueKind != System.Text.Json.JsonValueKind.Object) continue;
|
||||
if (!p.TryGetProperty("Name", out var n) || n.ValueKind != System.Text.Json.JsonValueKind.String) continue;
|
||||
if ((n.GetString() ?? "").StartsWith("q_", StringComparison.Ordinal)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断模板是否为占位模板:JSON 格式、Title 为空、且没有声明任何 Parameter。
|
||||
/// GR7 设计器保存的 _WR 压缩模板无法判断,一律视为正常模板(不覆盖)。
|
||||
/// </summary>
|
||||
private static bool IsPlaceholderReportTemplate(string content)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content) || content.StartsWith("_WR", StringComparison.Ordinal)) return false;
|
||||
try
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(content);
|
||||
var root = doc.RootElement;
|
||||
var title = root.TryGetProperty("Title", out var t) && t.ValueKind == System.Text.Json.JsonValueKind.String
|
||||
? t.GetString() ?? ""
|
||||
: "";
|
||||
var hasParamDecl = root.TryGetProperty("Parameter", out var p)
|
||||
&& p.ValueKind == System.Text.Json.JsonValueKind.Array
|
||||
&& p.GetArrayLength() > 0;
|
||||
return string.IsNullOrWhiteSpace(title) && !hasParamDecl;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从嵌入资源读取报表 .grf 模板 JSON 文本(.grf 为 UTF-8 JSON,GR7 向下兼容 GR6 格式)</summary>
|
||||
private static string? LoadEmbeddedTemplateJson(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var asm = typeof(DbSeeder).Assembly;
|
||||
var resName = asm.GetManifestResourceNames()
|
||||
.FirstOrDefault(n => n.EndsWith("." + fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (string.IsNullOrEmpty(resName)) return null;
|
||||
|
||||
using var stream = asm.GetManifestResourceStream(resName);
|
||||
if (stream == null) return null;
|
||||
using var reader = new StreamReader(stream, new UTF8Encoding(false));
|
||||
var json = reader.ReadToEnd();
|
||||
|
||||
// 统一去 BOM 后保存(ReportFile.DesignContent 存纯文本,输出 .grf 时再补 BOM)
|
||||
return json.Length > 0 && json[0] == '\uFEFF' ? json.Substring(1) : json;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DbSeeder] 读取嵌入模板失败:{ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
<ItemGroup>
|
||||
<!-- 工种基础数据种子(来源于 F9 系统 biz_base_worktype 三张表导出) -->
|
||||
<EmbeddedResource Include="Data\SeedData\*.json" />
|
||||
<!-- 报表中心示例 .grf 模板(GR6.8.5.0 格式;GR7 设计器向下兼容,可直接打开编辑) -->
|
||||
<EmbeddedResource Include="Assets\ReportTemplates\*.grf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+5
-1
@@ -20,7 +20,11 @@ internal class DepartmentWriteRepository : F9RepositoryBase, IDepartmentWriteRep
|
||||
public async Task<bool> UpdateAsync(Department dep)
|
||||
{
|
||||
dep.UpdatedAt = DateTime.Now;
|
||||
return await Orm.Update<Department>().SetSource(dep).ExecuteAffrowsAsync() > 0;
|
||||
// 编辑只覆盖业务字段:启用/禁用有独立命令,不能被 SetSource 连带重置;创建信息与删除标记同理
|
||||
return await Orm.Update<Department>()
|
||||
.SetSource(dep)
|
||||
.IgnoreColumns(x => new { x.IsActive, x.IsDeleted, x.CreatedAt, x.CreatedBy })
|
||||
.ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> SoftDeleteAsync(long id)
|
||||
|
||||
Reference in New Issue
Block a user