using System.Data; using MES.Service.DB; using MES.Service.Dto.service; using MES.Service.Modes; using MES.Service.util; using SqlSugar; namespace MES.Service.service.QC; public class XJService { private static readonly OracleSQLHelper SQLHelper = new(); private readonly BaseService _baseService = new(); private readonly string[] statusArray = ["已开工", "未开工", "待开工"]; //生成最新的检验单号 public string getMaxReleaseNo() { var sql = string.Format( "select max(RELEASE_NO) from QS_QA_ITEM_XJ where RELEASE_NO like 'XJN%' and to_char(CREATE_DATE,'yyyy-mm-dd') = '{0}'", DateTime.Now.ToString("yyyy-MM-dd")); var executeScalar = SQLHelper.ExecuteScalar(sql); var date = DateTime.Now.ToString("yyMMdd"); var number = "0001"; var olReleaseNo = executeScalar.ToString(); //判断今天是否生成了巡检单 if (string.IsNullOrEmpty(olReleaseNo)) return "XJ" + date + number; //截取后四位的流水号累加 var substring = Convert.ToInt32(olReleaseNo.Substring(10)); substring += 1; number = substring.ToString("D4"); //如果为空直接返回0001 return "XJ" + date + number; } public List getItem(string daa001) { var db = SqlSugarHelper.GetInstance(); //in的写法 https://www.donet5.com/Home/Doc?typeId=1187 return db.Queryable() .Where(a => a.Daa001 == daa001 && statusArray.Contains(a.Daa018)) .ToList(); } public List getLineAll() { var db = SqlSugarHelper.GetInstance(); return db.Queryable((a, b) => new JoinQueryInfos(JoinType.Left, a.Daa015 == b.LineNo)) .Where((a, b) => b.LineName != null && statusArray.Contains(a.Daa018)) .GroupBy((a, b) => new { b.LineNo, b.LineName }) .Select((a, b) => new MesLine { LineNo = b.LineNo, LineName = b.LineName }).OrderBy("b.LINE_NO").ToList(); } public List getDaa001(string daa020) { var db = SqlSugarHelper.GetInstance(); return db.Queryable() .Where(a => a.Daa015 == daa020 && statusArray.Contains(a.Daa018)) .Select(a => new Womdaa { Id = a.Id, Daa001 = a.Daa001, Daa003 = a.Daa003, Daa008 = a.Daa008 }).ToList(); } /// /// 获取工单详细信息(包含物料信息) /// /// 线体编号 /// 工单列表(包含物料信息) public List getWorkOrderWithItem(string daa020) { var db = SqlSugarHelper.GetInstance(); return db.Queryable((a, b) => new JoinQueryInfos(JoinType.Inner, a.Daa002 == b.Id.ToString())) .Where((a, b) => a.Daa015 == daa020 && statusArray.Contains(a.Daa018)) .Select((a, b) => new WorkOrderWithItemDto { Id = a.Id, Daa001 = a.Daa001, Daa002 = a.Daa002, Daa003 = a.Daa003, Daa008 = a.Daa008, Daa015 = a.Daa015, Daa020 = a.Daa020, ItemNo = b.ItemNo, ItemName = b.ItemName, ItemModel = b.ItemModel }).ToList(); } public List getBoardItem(string lineNo) { var db = SqlSugarHelper.GetInstance(); return db.Queryable((a, b) => new JoinQueryInfos(JoinType.Inner, a.Daa002 == b.Id.ToString())) .Where((a, b) => statusArray.Contains(a.Daa018) && a.Daa015 == lineNo) .OrderBy((a, b) => b.ItemName) .Select((a, b) => new MesItems { Id = b.Id, ItemNo = b.ItemNo, ItemName = b.ItemName, ItemModel = b.ItemModel }).ToList(); } public (List item, int TotalCount) getPage(XJPageResult queryObj) { var db = SqlSugarHelper.GetInstance(); var id = Convert.ToDecimal(queryObj.id); string[]? lineNo = null; if (StringUtil.IsNotNullOrEmpty(queryObj.createUser)) lineNo = _baseService.getUserLineNo(queryObj.createUser); var totalCount = 0; // 构建基础查询 - 使用LEFT JOIN避免因关联数据缺失导致查询不到 var query = db .Queryable((s, a, c, b) => new JoinQueryInfos( JoinType.Left, s.BillNo == a.Daa001, JoinType.Left, a.Daa015 == c.LineNo, JoinType.Left, s.ItemId == b.Id ) ); // 添加线体筛选条件 if (lineNo != null && lineNo.Length > 0) { query = query.Where((s, a, c, b) => lineNo.Contains(c.LineNo)); } // 添加状态筛选条件 - 明确匹配前端传递的值 if (StringUtil.IsNotNullOrEmpty(queryObj.result)) { if ("未完成".Equals(queryObj.result)) { query = query.Where((s, a, c, b) => s.Fsubmit == 0); } else if ("已完成".Equals(queryObj.result)) { query = query.Where((s, a, c, b) => s.Fsubmit == 1); } } // 添加ID筛选条件 if (id > 0) { query = query.Where((s, a, c, b) => s.Id == id); } var data = query .Select((s, a, c, b) => new QsQaItemXj { Daa020 = SqlFunc.IsNull(c.LineNo, ""), Id = s.Id, PlanQty = SqlFunc.IsNull(a.Daa008, 0), CreateBy = s.CreateBy, CreateDate = s.CreateDate, ReleaseNo = s.ReleaseNo, ItemNo = s.ItemNo, BillNo = s.BillNo, ItemName = SqlFunc.IsNull(b.ItemName, ""), ItemModel = SqlFunc.IsNull(b.ItemModel, ""), FcheckBy = s.FcheckBy, FcheckResu = s.FcheckResu, Remarks = s.Remarks }).OrderBy(s => s.CreateDate, OrderByType.Desc) .ToPageList(queryObj.PageIndex, queryObj.Limit, ref totalCount); return (data, totalCount); } public List setJYItem(string itemNo) { var db = SqlSugarHelper.GetInstance(); var count = db.Queryable().Where(s => s.QsType == "2" && s.ItemNo == itemNo && s.Fsubmit == 1).Count(); if (count <= 0) return new List(); return db .Queryable() .Where(b => b.QsType == "2" && b.ItemNo == itemNo).Select( b => new QsQaItemXj01 { ProjName = b.ProjName, ItemMod = b.ItemMod, InspectionMethod = b.InspectionMethod, UsingInstruments = b.UsingInstruments, LevelNum = SqlFunc.IsNull( SqlFunc.IsNull(b.LevelNum * b.InspectionLevel, 1), b.InspectionLevel), MaxValue = b.MaxValue, StandardValue = b.StandardValue, MinValue = b.MinValue, Notes = b.Notes, FcheckLevel = b.FcheckLevel, FacLevel = b.FacLevel, QsCode = b.QsCode, QsName = b.QsName, result = "未检测", isCheck = 0, Picture = b.Picture, Picturename = b.Picturename }).ToList(); } public List getJYItem(decimal? pid, decimal? id) { var db = SqlSugarHelper.GetInstance(); var qsQaItemXj01s = db.Queryable() .WhereIF(pid > 0, a => a.Pid == pid) .WhereIF(id > 0, a => a.Id == id) .ToList(); var array = qsQaItemXj01s.Select(s => s.Id).ToArray(); var qsQaItemDetails = db.Queryable() .Where(s => array.Contains(s.Pid)) .GroupBy(s => s.Pid) .Select(s => new { s.Pid, count = SqlFunc.AggregateCount(s.Id) }).ToList(); qsQaItemXj01s.ForEach(s => { var find = qsQaItemDetails.Find(a => s.Id == a.Pid); if (find == null) { s.isCheck = 0; s.result = "未完成"; } else { s.isCheck = find.count; if (find.count == s.LevelNum && s.IsPass == 1) s.result = "合格"; else if (find.count == s.LevelNum && s.IsPass == 0) s.result = "不合格"; else s.result = "未完成"; } //过滤出图片id不为空的数据转为base64 if (s.Picture is { Length: > 0 }) s.imageData = Convert.ToBase64String(s.Picture); }); //排序,未完成的排在前面 qsQaItemXj01s = qsQaItemXj01s.OrderBy(s => s.isCheck).ToList(); return qsQaItemXj01s; } public int save(XJDto xjDto) { var xj = xjDto.from; var items = xjDto.items; var userNo = xjDto.userNo; SqlSugarHelper.UseTransactionWithOracle(db => { var commit = 0; xj.CreateBy = userNo; xj.CreateDate = DateTime.Now; xj.Fsubmit = 0; // 设置默认状态为未提交 var pid = db.Insertable(xj).ExecuteReturnIdentity(); xj.Id = pid; xjDto.gid = pid; foreach (var item in items) item.Pid = pid; commit += db.Insertable(items).ExecuteCommand(); return commit; }); // 重新获取检验项目(包含ID) xjDto.items = getJYItem(xj.Id, null); // 自动为没有上下限的检验项目生成默认的合格记录 xjDto.items.ForEach(s => { if (s.MaxValue != null || s.StandardValue != null || s.MinValue != null) return; var detail = new QsQaItemXj02(); detail.Pid = s.Id; detail.Gid = xjDto.gid; detail.Fstand = "√"; detail.FcheckResu = "1"; detail.UpdateBy = xjDto.userNo; detail.count = (int?)s.LevelNum; SetQSItemDetail(detail); }); // 重新获取检验项目(包含自动生成的结果) xjDto.items = getJYItem(xj.Id, null); return Convert.ToInt32(xj.Id); } public int saveItem(XJDto xjDto) { var items = xjDto.items; var userNo = xjDto.userNo; SqlSugarHelper.UseTransactionWithOracle(db => { foreach (var item in items) item.Pid = xjDto.gid; return db.Insertable(items).ExecuteCommand(); }); xjDto.items = getJYItem(xjDto.gid, null); xjDto.items.ForEach(s => { if (s.MaxValue != null || s.StandardValue != null || s.MinValue != null) return; var detail = new QsQaItemXj02(); detail.Pid = s.Id; detail.Gid = xjDto.gid; detail.Fstand = "√"; detail.FcheckResu = "1"; detail.UpdateBy = xjDto.userNo; detail.count = (int?)s.LevelNum; SetQSItemDetail(detail); }); return Convert.ToInt32(xjDto.gid); } public XJDto getXjDetail02ById(decimal? id) { var xjDto = new XJDto(); var db = SqlSugarHelper.GetInstance(); var qsQaItemXj01 = db.Queryable().Single(s => s.Id == id); if (qsQaItemXj01.IsPass == 0) qsQaItemXj01.result = "不合格"; else if (qsQaItemXj01.IsPass == 1) qsQaItemXj01.result = "合格"; else qsQaItemXj01.result = "未完成"; if (qsQaItemXj01.Picture is { Length: > 0 }) qsQaItemXj01.imageData = Convert.ToBase64String(qsQaItemXj01.Picture); xjDto.ItemXj01 = qsQaItemXj01; xjDto.ItemXj02s = db.Queryable().Where(s => s.Pid == id) .ToList(); return xjDto; } public int SetQSItemDetail(QsQaItemXj02 detail) { var oracle = SqlSugarHelper.UseTransactionWithOracle(db => { List result = new(); for (var i = 0; i < detail.count; i++) { var item = new QsQaItemXj02(); item.Gid = detail.Gid; item.Pid = detail.Pid; item.Fstand = detail.Fstand; item.FcheckResu = detail.FcheckResu; item.CreateBy = detail.UpdateBy; item.CreateDate = DateTime.Now; result.Add(item); } return db.Insertable(result).ExecuteCommand(); }); detail.CreateBy = detail.UpdateBy; autoResult(detail); return oracle; } private int autoResult(QsQaItemXj02 detail) { var db = SqlSugarHelper.GetInstance(); // Single 没有查询到结果时返回Null,如果结果大于1条会抛出错误 var QsQaItemXj01 = db.Queryable() .Single(s => s.Id == detail.Pid); if (QsQaItemXj01 == null) return 0; var count = db.Queryable() .Where(s => s.Pid == detail.Pid).Count(); var result = 0; if (QsQaItemXj01.LevelNum != count) return 0; var passCount = db.Queryable() .Where(s => s.Pid == detail.Pid && s.Fstand == "√").Count(); if (count == passCount) result = 1; var useTransactionWithOracle = SqlSugarHelper.UseTransactionWithOracle( db => { var commit = 0; commit += db.Updateable() .SetColumns(s => s.IsPass == result) .Where(s => s.Id == detail.Pid) .ExecuteCommand(); return commit; }); //获取检验单的检验项目理论个数 var sum = db.Queryable() .Where(s => s.Pid == detail.Gid).Sum(it => it.LevelNum); if (sum == null || sum == 0) return 1; //获取检验单下的检验项目实际个数 var icount = db.Queryable() .Where(s => s.Gid == detail.Gid).Count(); if (icount == 0) return 1; //实际个数等于理论个数时对检验单进行判定 if (sum == icount) { var FcheckResu = "不合格"; //获取这个检验单下的所有合格的检验结果 passCount = db.Queryable() .Where(s => s.Gid == detail.Gid && s.Fstand == "√").Count(); //合格的检验结果等于总检验数视为合格 if (icount == passCount) FcheckResu = "合格"; SqlSugarHelper.UseTransactionWithOracle(db => { return db.Updateable() .SetColumns(s => s.FcheckResu == FcheckResu) .SetColumns(s => s.FcheckDate == DateTime.Now) .SetColumns(s => s.FcheckBy == detail.CreateBy) .SetColumns(s => s.LastupdateBy == detail.CreateBy) .SetColumns(s => s.LastupdateDate == DateTime.Now) .Where(s => s.Id == detail.Gid) .ExecuteCommand(); }); } return useTransactionWithOracle; } public int UpdateQSItemDetail(QsQaItemXj02 detail) { var withOracle = SqlSugarHelper.UseTransactionWithOracle(db => { return db.Updateable() .SetColumns(s => s.UpdateBy == detail.UpdateBy) .SetColumns(s => s.UpdateDate == DateTime.Now) .SetColumnsIF(StringUtil.IsNotNullOrEmpty(detail.Fstand), s => s.Fstand == detail.Fstand) .SetColumnsIF(StringUtil.IsNotNullOrEmpty(detail.FcheckResu), s => s.FcheckResu == detail.FcheckResu) .Where(s => s.Id == detail.Id) .ExecuteCommand(); }); detail.CreateBy = detail.UpdateBy; withOracle += autoResult(detail); return withOracle; } //删除主表并且连级删除子表和孙表 public int removeXJ(decimal? id) { var withOracle = SqlSugarHelper.UseTransactionWithOracle(db => { var commit = 0; commit += db.Deleteable().Where(s => s.Id == id) .ExecuteCommand(); commit += db.Deleteable().Where(s => s.Pid == id) .ExecuteCommand(); commit += db.Deleteable().Where(s => s.Gid == id) .ExecuteCommand(); return commit; }); return withOracle; } //主表修改备注字段 public int saveRemarksGid(XJDto dto) { return SqlSugarHelper.UseTransactionWithOracle(db => { return db.Updateable() .SetColumns(it => it.Remarks == dto.Remarks) //SetColumns是可以叠加的 写2个就2个字段赋值 .Where(it => it.Id == dto.gid) .ExecuteCommand(); }); } //子表修改备注字段 public int saveRemarksPid(XJDto dto) { return SqlSugarHelper.UseTransactionWithOracle(db => { return db.Updateable() .SetColumns(it => it.Remarks == dto.Remarks) //SetColumns是可以叠加的 写2个就2个字段赋值 .Where(it => it.Id == dto.pid) .ExecuteCommand(); }); } //孙表修改备注字段 public int saveRemarksById(XJDto dto) { return SqlSugarHelper.UseTransactionWithOracle(db => { return db.Updateable() .SetColumns(it => it.Remarks == dto.Remarks) //SetColumns是可以叠加的 写2个就2个字段赋值 .Where(it => it.Id == dto.id) .ExecuteCommand(); }); } //刷新检验项目 public (int result, string message) GenUpdate(decimal? id, string? no, string? user) { var outputResult = new SugarParameter("PO_RESULT", null, System.Data.DbType.Int32, ParameterDirection.Output, 4000); var outputMessage = new SugarParameter("PO_TEXT", null, System.Data.DbType.String, ParameterDirection.Output, 4000); var parameters = new List { new("P_ID", id, System.Data.DbType.Decimal, ParameterDirection.Input), new("P_NO", no, System.Data.DbType.String, ParameterDirection.Input), new("P_USER", user, System.Data.DbType.String, ParameterDirection.Input), outputResult, outputMessage }; var db = SqlSugarHelper.GetInstance(); db.Ado.ExecuteCommand( "BEGIN PRC_GEN_UPDATE_XJ(:P_ID, :P_NO, :P_USER, :PO_RESULT, :PO_TEXT); END;", parameters.ToArray()); int result = outputResult.Value == null ? -1 : Convert.ToInt32(outputResult.Value); string message = outputMessage.Value?.ToString() ?? ""; return (result, message); } public bool SjSubmit(SJDto dto) { try { var db = SqlSugarHelper.GetInstance(); // 直接更新Fsubmit字段为1(已提交状态) var result = db.Updateable() .SetColumns(s => s.Fsubmit == 1) .SetColumns(s => s.FsubmitBy == dto.userNo) .SetColumns(s => s.FsubmitDate == DateTime.Now) .Where(s => s.Id == dto.id) .ExecuteCommand(); if (result > 0) { return true; } else { throw new Exception("提交失败:未找到对应的检验单"); } } catch (Exception ex) { throw new Exception(ex.Message); } } /// /// 获取有线体的部门列表 /// /// 部门列表 public List GetDepartmentsWithLines() { var db = SqlSugarHelper.GetInstance(); // 查询有线体的部门,链接条件是线体的DEPOT_ID等于部门的Departmentid var departments = db.Queryable((dept, line) => new JoinQueryInfos(JoinType.Inner, line.DepotId == dept.Departmentid)) .Where((dept, line) => line.LineNo != null && line.LineName != null) .GroupBy((dept, line) => new { dept.Departmentid, dept.Departmentname }) .Select((dept, line) => new SysDepartment { Departmentid = dept.Departmentid, Departmentname = dept.Departmentname }) .OrderBy(dept => dept.Departmentname) .ToList(); return departments; } /// /// 保存部门选择 /// /// 巡检单ID /// 部门ID /// 部门名称 /// 保存结果 public int SaveDepartmentSelection(decimal id, string departmentId, string departmentName) { return SqlSugarHelper.UseTransactionWithOracle(db => { return db.Updateable() .SetColumns(it => it.WorkShop == departmentName) .SetColumns(it => it.DepartmentId == departmentId) .Where(it => it.Id == id) .ExecuteCommand(); }); } /// /// 根据部门ID获取该部门下的线体列表 /// /// 部门ID /// 线体列表 public List GetLinesByDepartment(string departmentId) { var db = SqlSugarHelper.GetInstance(); // 将字符串转换为decimal进行比较 if (!decimal.TryParse(departmentId, out var deptId)) { return new List(); } return db.Queryable() .Where(line => line.DepotId == deptId && line.LineNo != null && line.LineName != null) .OrderBy(line => line.LineNo) .ToList(); } /// /// 获取附件信息 /// /// 物料编码 /// 附件列表 public List GetAttachments(string itemNo) { var db = SqlSugarHelper.GetInstance(); try { return db.Queryable() .Where(x => x.ItemNo == itemNo) .OrderBy(x => x.Fdate, OrderByType.Desc) .Select(x => new QamftpDto { Id = x.Id, itemNo = x.ItemNo, Ftype = x.Ftype, Fattach = x.Fattach, Fversion = x.Fversion, Fdate = x.Fdate, CreateBy = x.CreateBy, CreateDate = x.CreateDate, Company = x.Company, Factory = x.Factory, F_type = x.F_type, LastupdateBy = x.LastupdateBy, LastupdateDate = x.LastupdateDate, ItemId = x.ItemId }).ToList(); } catch (Exception ex) { throw new Exception($"查询附件信息失败: {ex.Message}"); } } /// /// 从FTP服务器获取文件(巡检使用OPC目录) /// /// 物料编码 /// 文件名 /// FTP服务器地址 /// 文件字节数组 public byte[] GetFtpFile(string itemNo, string fileName, string ftpServer) { // 参数验证 if (string.IsNullOrEmpty(itemNo) || string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(ftpServer)) { throw new ArgumentException("参数不能为空: itemNo, fileName, ftpServer"); } string ftpUser = "hm_ftp"; string ftpPwd = "dell_123"; // 标准化FTP服务器地址 string normalizedServer = NormalizeFtpServer(ftpServer); // 构建FTP文件路径 - 巡检使用OPC目录 string ftpPath = $"{normalizedServer}/OPC/{itemNo}/{fileName}"; try { var request = (System.Net.FtpWebRequest)System.Net.WebRequest.Create(ftpPath); request.Method = System.Net.WebRequestMethods.Ftp.DownloadFile; request.Credentials = new System.Net.NetworkCredential(ftpUser, ftpPwd); request.UseBinary = true; request.UsePassive = false; request.Timeout = 30000; // 30秒超时 request.ReadWriteTimeout = 30000; using (var response = (System.Net.FtpWebResponse)request.GetResponse()) using (var ftpStream = response.GetResponseStream()) using (var ms = new System.IO.MemoryStream()) { if (ftpStream == null) { throw new Exception("FTP响应流为空"); } ftpStream.CopyTo(ms); var fileBytes = ms.ToArray(); if (fileBytes.Length == 0) { return null; // 文件为空或不存在 } return fileBytes; } } catch (System.Net.WebException ex) { if (ex.Response is System.Net.FtpWebResponse ftpResponse) { switch (ftpResponse.StatusCode) { case System.Net.FtpStatusCode.ActionNotTakenFileUnavailable: return null; // 文件不存在 case System.Net.FtpStatusCode.NotLoggedIn: throw new Exception("FTP认证失败,请检查用户名和密码"); case System.Net.FtpStatusCode.ActionNotTakenFilenameNotAllowed: throw new Exception("文件名不被允许或路径无效"); default: throw new Exception($"FTP错误 ({ftpResponse.StatusCode}): {ftpResponse.StatusDescription}"); } } // 处理超时和网络错误 if (ex.Status == System.Net.WebExceptionStatus.Timeout) { throw new Exception("FTP连接超时,请稍后重试"); } throw new Exception($"FTP连接失败: {ex.Message}"); } catch (Exception ex) { throw new Exception($"获取文件失败: {ex.Message}"); } } /// /// 标准化FTP服务器地址 /// /// FTP服务器地址 /// 标准化后的FTP服务器地址 private string NormalizeFtpServer(string ftpServer) { if (string.IsNullOrEmpty(ftpServer)) { throw new ArgumentException("FTP服务器地址不能为空"); } // 确保以ftp://开头 string normalizedServer = ftpServer.StartsWith("ftp://") ? ftpServer : $"ftp://{ftpServer}"; // 特殊处理已知服务器地址 if (normalizedServer == "ftp://36.26.21.214") { normalizedServer = "ftp://36.26.21.214:21"; } else if (!normalizedServer.Contains(":") && normalizedServer.StartsWith("ftp://")) { normalizedServer += ":21"; // 默认FTP端口 } // 开发环境使用本地服务器 normalizedServer = "ftp://192.168.1.22:21"; return normalizedServer; } /// /// 获取文件的内容类型 /// /// 文件名 /// MIME类型 public string GetContentType(string fileName) { if (string.IsNullOrEmpty(fileName)) return "application/octet-stream"; var extension = System.IO.Path.GetExtension(fileName).ToLower(); return extension switch { // PDF文件 ".pdf" => "application/pdf", // 图片文件 ".jpg" or ".jpeg" => "image/jpeg", ".png" => "image/png", ".gif" => "image/gif", ".bmp" => "image/bmp", ".webp" => "image/webp", ".svg" => "image/svg+xml", ".ico" => "image/x-icon", // 文本文件 ".txt" => "text/plain", ".log" => "text/plain", ".md" => "text/markdown", ".html" or ".htm" => "text/html", ".css" => "text/css", ".js" => "application/javascript", ".json" => "application/json", ".xml" => "application/xml", // Office文档 ".doc" => "application/msword", ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".xls" => "application/vnd.ms-excel", ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".ppt" => "application/vnd.ms-powerpoint", ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", // 其他常见格式 ".csv" => "text/csv", ".zip" => "application/zip", ".rar" => "application/x-rar-compressed", ".7z" => "application/x-7z-compressed", ".tar" => "application/x-tar", ".gz" => "application/gzip", // 默认 _ => "application/octet-stream" }; } }