MFCで在庫管理システム作成 8(最終回)

前回までの記事。

MFCで在庫管理システム作成 7
前回までの記事。履歴検索条件画面今回は履歴検索。検索をかけるために必要な条件を設定する画面を作成します。画面追加4度目の画面追加。リソースビューからDialogを右クリック、新規追加します。プロパティから、ダイアログIDはIDD_DIALO...

履歴画面表示処理

履歴テーブルのデータを、履歴画面に表示させます。

まずは、LogMain.cppのOnInitDialogに、表示関数を追加。

BOOL CLogMain::OnInitDialog()
{
	CDialogEx::OnInitDialog();
    // 略

	SetLogList();	// リスト表示

	return TRUE;  // return TRUE unless you set the focus to a control
	// 例外 : OCX プロパティ ページは必ず FALSE を返します。
}

SetLogLost関数を呼応します。

続いて、その関数の処理内容。

やり方は、メイン画面の商品一覧を出すときと同様です。
whileで何回もSQL実行させます。

/**
 * @fn
 * @brief	リストコントロールにデータをセットする
 * @param	なし
 * @return	なし
 */
void CLogMain::SetLogList()
{
	// 何もないけど、一応全削除処理
	m_ctrlListCtrlLog.DeleteAllItems();

	CMySQLManager Manager;
	Manager.GetConnection();

	LVITEM lvItem{};
	lvItem.mask = LVIF_TEXT;
	int iRoop = 0;

	while (TRUE)
	{
		iRoop++;	// 行番号インクリメント

		CString strLogID = _T("");			// ログ番号
		CString strOperation = _T("");		// 操作内容
		CString strProductID = _T("");		// 商品ID
		CString strCategory = _T("");		// カテゴリー列
		CString strQuantity = _T("");		// 増減量
		CString strCurrentStock = _T("");	// 現段階在庫量
		CString strDateTime = _T("");		// ログ時間

		if (Manager.bGetLogData(strLogID, strOperation, strProductID, strCategory, strQuantity, strCurrentStock, strDateTime, iRoop, m_strProductID, m_strCateory, m_strTime) == TRUE)
		{
			lvItem.iItem = iRoop - 1;	// リストコントロールの行番号

			// ログ番号列
			lvItem.iSubItem = 0;
			lvItem.pszText = strLogID.GetBuffer();
			m_ctrlListCtrlLog.InsertItem(&lvItem);

			// 操作内容列
			lvItem.iSubItem = 1;
			lvItem.pszText = strOperation.GetBuffer();
			m_ctrlListCtrlLog.SetItem(&lvItem);

			// 商品ID列
			lvItem.iSubItem = 2;
			lvItem.pszText = strProductID.GetBuffer();
			m_ctrlListCtrlLog.SetItem(&lvItem);

			// カテゴリー列
			lvItem.iSubItem = 3;
			lvItem.pszText = strCategory.GetBuffer();
			m_ctrlListCtrlLog.SetItem(&lvItem);

			// 増減量列
			lvItem.iSubItem = 4;
			lvItem.pszText = strQuantity.GetBuffer();
			m_ctrlListCtrlLog.SetItem(&lvItem);

			// 現段階在庫量列
			lvItem.iSubItem = 5;
			lvItem.pszText = strCurrentStock.GetBuffer();
			m_ctrlListCtrlLog.SetItem(&lvItem);

			// ログ時間列
			lvItem.iSubItem = 6;
			lvItem.pszText = strDateTime.GetBuffer();
			m_ctrlListCtrlLog.SetItem(&lvItem);
		}
		else
		{
			// この場合は、これ以上データをリストに追加できない
			// SQLExceptionも含める
			break;
		}

	}
}

31行目の関数だけはまだ作成してないので、
今のところエラーになります。

引数後ろ3つは、条件です。
今回もWITH句を使いますが、
新しいテーブルを作る際に条件を付けます。

空白だったら、それは全件表示とみなす予定です。

履歴テーブルデータ取得処理

先の31行目の関数定義です。

MySQLManager.hを開き、定義します。

public:
	// 履歴テーブルデータ取得
	BOOL bGetLogData(CString& strLogID,
					CString& strOperation,
					CString& strProductID,
					CString& strCategory,
					CString& strQuantity,
					CString& strCurrentStock,
					CString& strDateTime,
					int iRoop,
					CString strConditionProductID,
					CString strConditionCategory,
					CString strConditionDateTime);

続いて、SQL処理作成。

MySQLManager.cppを開きます。

なお、自分史上、過去一複雑なSQLとなりました…。
仕事でもこんなの書かんぞ。

/**
 * @fn
 * @brief	履歴テーブル一覧ゲット
 * @param	CString&        strLogID            履歴ID
 * @param   CString&        strOperation        操作内容
 * @param   CString&        strProductID        商品ID
 * @param   CString&        strCategory         カテゴリー
 * @param   CString&        strQuantity         増減量
 * @param   CString&        strCurrentStock     操作時在庫量
 * @param   CString&        strDateTime         操作日時
 * @param   int             iRowNum             検索行
 * @param   CString         strConditionProductID    商品ID条件
 * @param   CString         strConditionCategory    カテゴリー条件
 * @param   CString         strConditionDateTime    操作日時条件
 * @return	BOOL    TRUE    検索成功
 *                  FALSE   検索失敗
 */
BOOL CMySQLManager::bGetLogData(CString& strLogID,
                                CString& strOperation,
                                CString& strProductID,
                                CString& strCategory,
                                CString& strQuantity,
                                CString& strCurrentStock,
                                CString& strDateTime,
                                int iRowNum,
                                CString strConditionProductID,
                                CString strConditionCategory,
                                CString strConditionDateTime)
{
    try
    {
        // スキーマセット
        m_con->setSchema(SCHEMA);

        CString strRowNum = _T("");
        strRowNum.Format(_T("%d"), iRowNum); // CString化
        sql::SQLString strSqlRowNum = strConverter(strRowNum);

        sql::SQLString sqlStrWithSelect =
            "WITH NumberedRows AS ( SELECT " + TABLE_LOGS + ".*," + TABLE_PRODUCT + "." + COL_PRODUCT_CATEGORY + ", ROW_NUMBER() OVER ( ORDER BY " + COL_LOGS_ID + " ) AS row_num";
        sql::SQLString sqlStrWithFrom = " FROM " + TABLE_LOGS;
        sql::SQLString sqlStrWithJoin = " JOIN " + TABLE_PRODUCT + " ON " + TABLE_LOGS + "." + COL_LOGS_PRODUCT_ID + " = " + TABLE_PRODUCT + "." + COL_PRODUCT_ID;

        sql::SQLString sqlStrWithWhere = "";    // WHERE句
        // なにか一つでも検索条件が必要か?
        if (strConditionProductID != _T("") || strConditionCategory != _T("") || strConditionDateTime != _T(""))
        {
            sqlStrWithWhere += " WHERE ";

            if (strConditionProductID != _T(""))
            {
                // 商品IDに条件アリ
                sqlStrWithWhere += (TABLE_LOGS + "." + COL_LOGS_PRODUCT_ID + " = '" + strConverter(strConditionProductID) + "'");

                if (strConditionCategory != _T("") || strConditionDateTime != _T(""))
                {
                    // まだ条件があるので、ANDが必要
                    sqlStrWithWhere += " AND ";
                }
            }

            if (strConditionCategory != _T(""))
            {
                // カテゴリーに条件アリ
                sqlStrWithWhere += (TABLE_PRODUCT + "." + COL_PRODUCT_CATEGORY + " = '" + strConverter(strConditionCategory) + "'");

                if (strConditionDateTime != _T(""))
                {
                    // まだ条件があるので、ANDが必要
                    sqlStrWithWhere += " AND ";
                }
            }

            if (strConditionDateTime != _T(""))
            {
                //ログ時間に条件アリ
                sqlStrWithWhere += TABLE_LOGS + "." + COL_LOGS_TIME_STAMP + " >= '" + strConverter(strConditionDateTime) + " 00:00:00' AND " + TABLE_LOGS + "." + COL_LOGS_TIME_STAMP + " <= '" + strConverter(strConditionDateTime) + " 23:59:59'";
            }
        }

        sql::SQLString sqlStrWithPhase = sqlStrWithSelect + sqlStrWithFrom + sqlStrWithJoin + sqlStrWithWhere;

        sql::SQLString sqlStrSelectPhase = " ) SELECT * FROM NumberedRows WHERE row_num = " + strSqlRowNum;

        sql::SQLString strSqlQuery = sqlStrWithPhase + sqlStrSelectPhase;

        //クエリ実行
        std::unique_ptr<sql::PreparedStatement> pstmt(m_con->prepareStatement(strSqlQuery));

        std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());

        if (!res->next())
        {
            // ここに入れば、もう検索するものはない...
            return FALSE;
        }
        else
        {
            // 該当行発見

           // ログ番号
            std::string strCol = res->getString(COL_LOGS_ID);
            strLogID = strConvertFromUTF8ToUTF16(strCol);

            // 操作内容
            strCol = res->getString(COL_LOGS_OPERATION);
            strOperation = strConvertFromUTF8ToUTF16(strCol);

            // 商品ID
            strCol = res->getString(COL_LOGS_PRODUCT_ID);
            strProductID = strConvertFromUTF8ToUTF16(strCol);

            // カテゴリー
            strCol = res->getString(COL_PRODUCT_CATEGORY);
            strCategory = strConvertFromUTF8ToUTF16(strCol);
            
            // 増減量
            strCol = res->getString(COL_LOGS_QUANTITY);
            strQuantity = strConvertFromUTF8ToUTF16(strCol);

            // 現段階在庫量
            strCol = res->getString(COL_LOGS_CURRENT_STOCK);
            strCurrentStock = strConvertFromUTF8ToUTF16(strCol);

            // ログ時間
            strCol = res->getString(COL_LOGS_TIME_STAMP);
            strDateTime = strConvertFromUTF8ToUTF16(strCol);
        }
    }
    catch (sql::SQLException& e)
    {
        ErrSQLException(e);
        return FALSE;
    }

    return TRUE;
}

88行目以降はいいでしょう。
いつも通りです。

問題(?)は、SQL。
変数を多用しているため、かなり複雑に見えてしまいます。

例えば、引数のstrConditionProductIDが22222、
strConditionCategoryがFood、
strConditionDateTimeが2025-01-01がわたってきたとします。

その場合、SQLはこのようになります。

WITH NumberedRows AS 
  ( SELECT logs.*, product.category, ROW_NUMBER() OVER ( ORDER BY log_id ) AS row_num
    FROM logs
    JOIN product ON logs.product_id = product.product_id
    WHERE logs.product_id = '22222'
      AND product.category = 'Food'
      AND logs.time_stamp >= '2025-01-01 00:00:00' AND logs.time_stamp <= '2025-01-01 23:59:59'
  )
SELECT * FROM NumberedRows WHERE row_num = i -- iは引数の検索行

NumberedRowsの作りは、
logsテーブルの列全部、productテーブルのカテゴリー、logsテーブルの行番号です。
4行目のJOIN句はlogsとproductを引っ付けてるだけ。
5~7行目は、条件です。

作ったNumberedRowsテーブルから、順に取り出します。

正直、これ以上ここで解説する余力はありません…。

これを何も見ずにもう一回書けと言われても、
自分自身書けるのか…?

ビルドして動作確認です。

動作確認

ひとまず、元々のlogsテーブルはこのような感じ。

まずは履歴条件画面で、
なにも条件を入れず検索ボタンを押します。

ちゃんと取れてそうです。

条件を付けてみます。
まずは商品IDが22222。

ちゃんと22222だけ表示されました。

続いて、カテゴリーがFood。
何も条件を付けてないとログIDが7, 8, 10, 13, 14, 16ですけど果たして。

最後に時間指定。
ログ番号1だけ日付が違うので、2024/12/21で検索しましょう。

良き!良き!

あんまり重要視してませんでしたが、
入力チェックも働いてくれてるみたいです。

終わりに

以上を持って、在庫管理システムの作成を終了します。

簡易的な処理が多いシステムでした。

ここら辺になると、
ほとんど何も参考にせず、一人でシステムを作ることができるなぁって自画自賛です。
(今回のSQLは除く…)

今回のソースはGitに上げてみたのですが…。
上げ方あってるかわからんなぁ。
誰か面と向かって教えてほしい…

デスクトップアプリはオワコンと聞く時代なので、
このスキルが役に立つのか、正直わかりませんね。

少しずつですが、
別のスキルも身に着けてる最中です。

今回はここまで。

次は何作りましょうかね。

コメント

タイトルとURLをコピーしました