SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -6,9 +6,12 @@
#include "CPageGlassList.h"
#include "afxdialogex.h"
#include "GlassJson.h"
#include "CServoUtilsTool.h"
#include "ToolUnits.h"
#include <optional>
#define PAGE_SIZE                  100
#define PAGE_SIZE                  10
#define PAGE_BACKGROUND_COLOR         RGB(252, 252, 255)
// CPageGlassList 对话框
@@ -23,9 +26,8 @@
   m_pObserver = nullptr;
   m_strStatus = "";
   m_strKeyword = "";
   m_nCurPage = 0;
   m_nTotalPages = 0;
   m_nTotalPages = 1;
   memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
   memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
@@ -198,7 +200,7 @@
   //m_dateTimeEnd.ModifyStyle(0, DTS_TIMEFORMAT);
}
void CPageGlassList::LoadTransfers()
void CPageGlassList::LoadData()
{
   m_nCurPage = 1;
   UpdatePageData();
@@ -206,19 +208,77 @@
void CPageGlassList::UpdatePageData()
{
   /*
   TransferData filter;
   filter.strStatus = m_strStatus;
   filter.strDescription = m_strKeyword;
   filter.strCreateTime = m_szTimeStart;
   filter.strEndTime = m_szTimeEnd;
   auto vecData = TransferManager::getInstance().getTransfers(filter, m_nCurPage, PAGE_SIZE);
   FillDataToListCtrl(vecData);
   // 如果为第1页, 取出缓存Glass, 符合条件则显示;
   m_listCtrl.DeleteAllItems();
   UpdateWipData();
   int nTotalRecords = TransferManager::getInstance().getFilteredTransferCount(filter);
   m_nTotalPages = (nTotalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
   // 查询
   auto& db = GlassLogDb::Instance();
   auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
   for (const auto& r : page.items) {
      int index = m_listCtrl.InsertItem(m_listCtrl.GetItemCount(), std::to_string(r.id).c_str());
      m_listCtrl.SetItemText(index, 1, std::to_string(r.cassetteSeqNo).c_str());
      m_listCtrl.SetItemText(index, 2, std::to_string(r.jobSeqNo).c_str());
      m_listCtrl.SetItemText(index, 3, r.classId.c_str());
      m_listCtrl.SetItemText(index, 4, SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str());
      m_listCtrl.SetItemText(index, 5, SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str());
      m_listCtrl.SetItemText(index, 6, r.tStart.c_str());
      m_listCtrl.SetItemText(index, 7, r.tEnd.c_str());
      m_listCtrl.SetItemText(index, 8, r.buddyId.c_str());
      m_listCtrl.SetItemText(index, 9, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str());
      m_listCtrl.SetItemText(index, 10, r.path.c_str());
      m_listCtrl.SetItemText(index, 11, r.params.c_str());
      m_listCtrl.SetItemColor(index, RGB(0, 0, 0), RGB(255, 255, 0));
      // 测试反序列化
      /*
      SERVO::CGlass g2;
      std::string err;
      if (GlassJson::FromString(r.pretty, g2, &err)) {
         AfxMessageBox(r.pretty.c_str());
      }
      */
   }
   // 上一页 / 下一页
   UpdatePageControls();
   */
}
void CPageGlassList::UpdateWipData()
{
   if (m_nCurPage != 1) return;
   // 取出缓存Glass, 符合条件则显示;
   // 但要删除旧的数据
   std::vector<SERVO::CGlass*> wipGlasses;
   theApp.m_model.m_master.getWipGlasses(wipGlasses);
   std::vector<SERVO::CGlass*> tempGlasses = wipGlasses;
   int count = m_listCtrl.GetItemCount();
   if (count > 0) {
      for (int i = count - 1; i >= 0; i--) {
         SERVO::CGlass* pGlass = (SERVO::CGlass*)m_listCtrl.GetItemData(i);
         if (eraseGlassInVector(pGlass, wipGlasses)
            && GlassMatchesFilters(*pGlass, m_filters)) {
            // 更新
            UpdateWipRow(i, pGlass);
         }
         else {
            // 删除
            m_listCtrl.DeleteItem(i);
         }
      }
   }
   // 剩下的如符号插入
   for (auto* item : wipGlasses) {
      if (GlassMatchesFilters(*item, m_filters)) {
         InsertWipRow(item);
      }
   }
   for (auto* item : tempGlasses) {
      item->release();
   }
}
void CPageGlassList::UpdatePageControls()
@@ -228,91 +288,8 @@
   SetDlgItemText(IDC_LABEL_PAGE_NUMBER, strPage);
   GetDlgItem(IDC_BUTTON_PREV_PAGE)->EnableWindow(m_nCurPage > 1);
   GetDlgItem(IDC_BUTTON_NEXT_PAGE)->EnableWindow(m_nCurPage < m_nTotalPages);
   Resize();
}
void CPageGlassList::UpdateDateFilter()
{
   CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
   if (nullptr != pComboBox) {
      int nIndex = pComboBox->GetCurSel();
      if (nIndex == 0) {
         memset(m_szTimeStart, 0, sizeof(m_szTimeStart));
         memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd));
         m_szTimeStart[0] = '\0';
         m_szTimeEnd[0] = '\0';
      }
      else {
         CTime time = CTime::GetCurrentTime();
         if (nIndex == 1) {
            sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", time.GetYear(), time.GetMonth(), time.GetDay());
            sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
         }
         else if (nIndex == 2) {
            CTime time2 = time - CTimeSpan(7, 0, 0, 0);
            sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", time2.GetYear(), time2.GetMonth(), time2.GetDay());
            sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
         }
         else if (nIndex == 3) {
            sprintf_s(m_szTimeStart, 64, "%d-%02d-01 00:00:00", time.GetYear(), time.GetMonth());
            sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", time.GetYear(), time.GetMonth(), time.GetDay());
         }
         else if (nIndex == 4) {
            sprintf_s(m_szTimeStart, 64, "%d-01-01 00:00:00", time.GetYear());
            sprintf_s(m_szTimeEnd, 64, "%d-12-31 23:59:59", time.GetYear());
         }
         else if (nIndex == 5) {
            SYSTEMTIME t1, t2;
            m_dateTimeStart.GetTime(&t1);
            m_dateTimeEnd.GetTime(&t2);
            //sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d %02d:%02d:%02d", t1.wYear, t1.wMonth, t1.wDay, t1.wHour, t1.wMinute, t1.wSecond);
            //sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d %02d:%02d:%02d", t2.wYear, t2.wMonth, t2.wDay, t2.wHour, t2.wMinute, t2.wSecond);
            sprintf_s(m_szTimeStart, 64, "%d-%02d-%02d 00:00:00", t1.wYear, t1.wMonth, t1.wDay);
            sprintf_s(m_szTimeEnd, 64, "%d-%02d-%02d 23:59:59", t2.wYear, t2.wMonth, t2.wDay);
         }
      }
   }
}
/*
void CPageGlassList::FillDataToListCtrl(const std::vector<TransferData>& vecData)
{
   if (m_listCtrl.m_hWnd == nullptr) {
      return;
   }
   m_listCtrl.DeleteAllItems();
   for (const auto& item : vecData) {
      InsertTransferData(item);
   }
}
void CPageGlassList::InsertTransferData(const TransferData& data)
{
   if (m_listCtrl.m_hWnd == nullptr) {
      return;
   }
   int nIndex = m_listCtrl.GetItemCount();
   if (nIndex < 0) {
      return;
   }
   int nItem = m_listCtrl.InsertItem(nIndex, _T(""));
   CString str;
   str.Format(_T("%d"), data.nRecordId);
   m_listCtrl.SetItemText(nItem, 1, str);
   m_listCtrl.SetItemText(nItem, 2, CString(data.strStatus.c_str()));
   m_listCtrl.SetItemText(nItem, 3, CString(data.strClassID.c_str()));
   m_listCtrl.SetItemText(nItem, 4, CString(data.strCreateTime.c_str()));
   m_listCtrl.SetItemText(nItem, 5, CString(data.strPickTime.c_str()));
   m_listCtrl.SetItemText(nItem, 6, CString(data.strPlaceTime.c_str()));
   m_listCtrl.SetItemText(nItem, 7, CString(data.strEndTime.c_str()));
   m_listCtrl.SetItemText(nItem, 8, CString(data.strDescription.c_str()));
}
*/
// CPageTransferLog 消息处理程序
BOOL CPageGlassList::OnInitDialog()
@@ -321,6 +298,7 @@
   // TODO:  在此添加额外的初始化
   SetTimer(1, 3000, nullptr);
   SetTimer(2, 2000, nullptr);
   // 下拉框控件
   InitStatusCombo();
@@ -342,7 +320,7 @@
   ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
   CString headers[] = { 
      _T(""),
      _T("id"),
      _T("Cassette Sequence No"),
      _T("Job Sequence No"),
      _T("Class ID"),
@@ -355,24 +333,15 @@
      _T("路径"),
      _T("工艺参数") 
   };
   int widths[] = { 0, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 };
   int widths[] = { 80, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 };
   for (int i = 0; i < _countof(headers); ++i) {
      strItem.Format(_T("Col_%d_Width"), i);
      widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, widths[i], strIniFile);
      m_listCtrl.InsertColumn(i, headers[i], LVCFMT_LEFT, widths[i]);
   }
   m_listCtrl.SetColumnWidth(10, LVSCW_AUTOSIZE_USEHEADER);
   // 计算总页数
   /*
   int nTotalRecords = TransferManager::getInstance().getTotalTransferCountAll();
   m_nTotalPages = (nTotalRecords + PAGE_SIZE - 1) / PAGE_SIZE;
   m_nCurPage = 1;
   */
   Resize();
   LoadTransfers();
   OnBnClickedButtonSearch();
   return TRUE;  // return TRUE unless you set the focus to a control
   // 异常: OCX 属性页应返回 FALSE
@@ -428,6 +397,11 @@
      KillTimer(1);
      InitRxWindow();
   }
   else if (nIDEvent == 2) {
      UpdateWipData();
   }
   CDialogEx::OnTimer(nIDEvent);
}
@@ -440,8 +414,8 @@
   m_dateTimeEnd.EnableWindow(nIndex == nCount - 1);
   // 更新日期过滤器和页面数据
   UpdateDateFilter();
   LoadTransfers();
   // UpdateDateFilter();
   // LoadTransfers();
}
void CPageGlassList::OnCbnSelchangeComboStatusFilter()
@@ -456,7 +430,7 @@
      pComboBox->GetLBText(nIndex, cstrText);
      m_strStatus = CT2A(cstrText);
   }
   LoadTransfers();
   // LoadTransfers();
}
void CPageGlassList::OnBnClickedButtonSearch()
@@ -464,11 +438,47 @@
   // 获取关键字输入框内容
   CString strKeyword;
   GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword);
   m_strKeyword = CT2A(strKeyword);
   m_filters.keyword = CT2A(strKeyword);
   // 更新日期过滤器和页面数据
   UpdateDateFilter();
   LoadTransfers();
   CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME);
   int index = pComboBox->GetCurSel();
   if (index == 0) {
      // 不限
      m_filters.tStartFrom = std::nullopt;
      m_filters.tStartTo = std::nullopt;
   }
   else if (index == 1) {
      auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Today);
      m_filters.tStartFrom = fromUtc;
      m_filters.tStartTo = toUtc;
   }
   else if (index == 2) {
      auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Last7Days);
      m_filters.tStartFrom = fromUtc;
      m_filters.tStartTo = toUtc;
   }
   else if (index == 3) {
      auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisMonth);
      m_filters.tStartFrom = fromUtc;
      m_filters.tStartTo = toUtc;
   }
   else if (index == 4) {
      auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisYear);
      m_filters.tStartFrom = fromUtc;
      m_filters.tStartTo = toUtc;
   }
   else if(index == 5){
      // 自定义
      std::chrono::system_clock::time_point tp;
      if (CToolUnits::GetCtrlDateRangeUtc_StartOfDay(m_dateTimeStart, tp)) m_filters.tStartFrom = tp;
      if (CToolUnits::GetCtrlDateRangeUtc_EndOfDay(m_dateTimeEnd, tp))   m_filters.tStartTo = tp;
   }
   auto& db = GlassLogDb::Instance();
   long long total = db.count(m_filters);
   m_nTotalPages = (PAGE_SIZE > 0) ? int((total + PAGE_SIZE - 1) / PAGE_SIZE) : 1;
   LoadData();
}
void CPageGlassList::OnBnClickedButtonExport()
@@ -478,90 +488,22 @@
      return;
   }
   CStdioFile file;
   if (!file.Open(fileDialog.GetPathName(), CFile::modeCreate | CFile::modeWrite | CFile::typeText)) {
      AfxMessageBox(_T("创建文件失败!"));
      return;
   // 导出 CSV:导出符合 filters 的“全部记录”(不受分页限制)
      // 返回导出的行数(不含表头)
      // csvPath:目标文件路径(UTF-8)
   auto& db = GlassLogDb::Instance();
   std::string csvPath((LPTSTR)(LPCTSTR)fileDialog.GetPathName());
   if (db.exportCsv(csvPath, m_filters) > 0) {
      AfxMessageBox("导出CSV成功!");
   }
   CString strHeader = _T("任务ID,状态,ClassID,创建时间,取片时间,放片时间,结束时间,描述\n");
   file.WriteString(strHeader);
   for (int i = 0; i < m_listCtrl.GetItemCount(); ++i) {
      CString row;
      for (int j = 1; j <= 8; ++j) {
         row += m_listCtrl.GetItemText(i, j);
         if (j != 8) {
            row += ",";
         }
      }
      row += "\n";
      file.WriteString(row);
   }
   file.Close();
}
void CPageGlassList::OnBnClickedButtonPrevPage()
{
   SERVO::CGlass g;
   g.setID("GLS-001");
   g.setType(SERVO::MaterialsType::G1);
   g.setOriginPort(1, 5);
   g.setScheduledForProcessing(TRUE);
   g.m_failReason = "none";
   g.markQueued();
   g.markStart();
   // 添加参数
   CParam p1("校正对位延时", "P001", "ms", 123);
   CParam p2("温度", "P002", "degC", 25.5);
   g.getParams().push_back(p1);
   g.getParams().push_back(p2);
   // 设置 JobDataS
   SERVO::CJobDataS* js = g.getJobDataS();
   js->setCassetteSequenceNo(10);
   js->setJobSequenceNo(20);
   js->setLotId("LOT-ABC");
   js->setGlass1Id("GLS-001");
   // 添加 Path
   g.addPath(100, 1);
   SERVO::CPath* tail = g.getPath()->getTailPath();
   tail->setInTime(111111);
   tail->setOutTime(222222);
   tail->setInspResult(SERVO::InspResult::Pass);
   tail->processEnd();
   return;
   // 2. 转为 JSON
   std::string jsonText = GlassJson::ToPrettyString(g);
   TRACE("序列化结果:\n%s\n\n", jsonText.c_str());
   // 3. 反序列化
   SERVO::CGlass g2;
   std::string err;
   if (!GlassJson::FromString(jsonText, g2, &err)) {
      TRACE("解析失败: %s\n", err.c_str());
      return;
   }
   // 4. 打印验证
   TRACE("反序列化后的ID: %s\n", g2.getID().c_str());
   TRACE("反序列化后的参数数量: %d\n", (int)g2.getParams().size());
   if (!g2.getParams().empty()) {
      TRACE("第一个参数名: %s 值=%d\n",
         g2.getParams()[0].getName().c_str(),
         g2.getParams()[0].getIntValue());
   }
   if (m_nCurPage > 1) {
      m_nCurPage--;
      UpdatePageData();
   }
   }
}
void CPageGlassList::OnBnClickedButtonNextPage()
@@ -570,4 +512,72 @@
      m_nCurPage++;
      UpdatePageData();
   }
}
// 核心:WIP 的 CGlass 是否命中当前 Filters
// useEndTime=true 时用 tEnd 判时间(比如“完成列表”用 t_end),默认按 tStart。
bool CPageGlassList::GlassMatchesFilters(const SERVO::CGlass& g,
   const GlassLogDb::Filters& f,
   bool useEndTime/* = false*/)
{
   // 1) 精确字段
   if (f.classId && g.getID() != *f.classId)      return false;
   if (f.cassetteSeqNo && g.getCassetteSequenceNo() != *f.cassetteSeqNo)return false;
   if (f.jobSeqNo && g.getJobSequenceNo() != *f.jobSeqNo)     return false;
   // 2) 关键字(与 DB 保持一致:class_id / buddy_id / path / params / pretty)
   if (f.keyword) {
      const std::string& kw = *f.keyword;
      if (!(CToolUnits::containsCI(g.getID(), kw)
         || CToolUnits::containsCI(g.getBuddyId(), kw)
         || CToolUnits::containsCI(g.getPathDescription(), kw)
         || CToolUnits::containsCI(g.getParamsDescription(), kw)))
         return false;
   }
   // 3) 时间(与 DB 保持一致:默认按 t_start 过滤;需要可切到 t_end)
   if (f.tStartFrom || f.tStartTo) {
      std::optional<std::chrono::system_clock::time_point> tp = useEndTime ? g.tEnd() : g.tStart();
      // 约定:若没有对应时间戳,则视为不命中(与 DB 相同:NULL 不会命中范围)
      if (!tp) return false;
      if (f.tStartFrom && *tp < *f.tStartFrom) return false;
      if (f.tStartTo && *tp > *f.tStartTo)   return false;
   }
   return true;
}
void CPageGlassList::InsertWipRow(SERVO::CGlass* pGlass)
{
   int index = m_listCtrl.InsertItem(0, "");
   UpdateWipRow(index, pGlass);
}
void CPageGlassList::UpdateWipRow(unsigned int index, SERVO::CGlass* pGlass)
{
   ASSERT(index < m_listCtrl.GetItemCount());
   m_listCtrl.SetItemData(index, (DWORD_PTR)pGlass);
   m_listCtrl.SetItemColor(index, RGB(0, 0, 0), RGB(255, 255, 255));
   m_listCtrl.SetItemText(index, 1, std::to_string(pGlass->getCassetteSequenceNo()).c_str());
   m_listCtrl.SetItemText(index, 2, std::to_string(pGlass->getJobSequenceNo()).c_str());
   m_listCtrl.SetItemText(index, 3, pGlass->getID().c_str());
   m_listCtrl.SetItemText(index, 4, SERVO::CServoUtilsTool::getMaterialsTypeText(pGlass->getType()).c_str());
   m_listCtrl.SetItemText(index, 5, SERVO::CServoUtilsTool::getGlassStateText(pGlass->state()).c_str());
   m_listCtrl.SetItemText(index, 6, CToolUnits::TimePointToLocalString(pGlass->tStart()).c_str());
   m_listCtrl.SetItemText(index, 7, CToolUnits::TimePointToLocalString(pGlass->tEnd()).c_str());
   m_listCtrl.SetItemText(index, 8, pGlass->getBuddyId().c_str());
   m_listCtrl.SetItemText(index, 9, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)pGlass->getAOIInspResult()).c_str());
   m_listCtrl.SetItemText(index, 10, pGlass->getPathDescription().c_str());
   m_listCtrl.SetItemText(index, 11, pGlass->getParamsDescription().c_str());
}
bool CPageGlassList::eraseGlassInVector(SERVO::CGlass* pGlass, std::vector<SERVO::CGlass*>& glasses)
{
   auto iter = std::find(glasses.begin(), glasses.end(), pGlass);
   if (iter != glasses.end()) {
      glasses.erase(iter);
      return true;
   }
   return false;
}