diff options
author | rtk0c <[email protected]> | 2021-05-08 21:10:36 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-05-08 21:10:36 -0700 |
commit | 850067a6db2443c1ff1ced793135796ef7ac490f (patch) | |
tree | ddef664def68b9868d2d04655654cd9854c5d825 /core | |
parent | 1fd1e4b5f2418e3ac2909658993bfedb615537ec (diff) |
Deliveries table working, item column
Diffstat (limited to 'core')
-rw-r--r-- | core/locale/zh_CN.json | 6 | ||||
-rw-r--r-- | core/src/Model/TransactionsModel.cpp | 27 | ||||
-rw-r--r-- | core/src/Model/TransactionsModel.hpp | 3 | ||||
-rw-r--r-- | core/src/UI/Localization.hpp | 13 | ||||
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 212 |
5 files changed, 201 insertions, 60 deletions
diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json index 97dba91..513ac7a 100644 --- a/core/locale/zh_CN.json +++ b/core/locale/zh_CN.json @@ -34,11 +34,15 @@ "Database.SalesView.Edit.DialogTitle": "编辑销售记录", "Database.PurchasesView.TabName": "采购", "Database.PurchasesView.Edit.DialogTitle": "编辑采购记录", + "Database.Column.Items": "项目", "Database.Column.Customer": "客户", "Database.Column.Factory": "工厂", "Database.Column.Deadline": "交货期限", "Database.Column.OrderTime": "下单时间", - "Database.Column.DeliveryTime": "交货时间", + "Database.Column.CompletionTime": "交货时间", + "Database.Column.ShipmentTime": "发货时间", + "Database.Column.ArrivalTime": "实际到达时间", + "Database.Message.NoOrderSelected": "选择任意一个订单以查看与其相关的批次", "Database.Message.NotDelivered": "N/A", "Item.Add.DialogTitle": "新建物品项", "Item.Edit.DialogTitle": "编辑物品项", diff --git a/core/src/Model/TransactionsModel.cpp b/core/src/Model/TransactionsModel.cpp index 6d0989e..3df7990 100644 --- a/core/src/Model/TransactionsModel.cpp +++ b/core/src/Model/TransactionsModel.cpp @@ -11,7 +11,9 @@ SalesTable::SalesTable(TransactionModel& db) // language=SQLite : GetRowCount(db.GetSQLite(), "SELECT Count(*) FROM Sales") // language=SQLite - , GetRows(db.GetSQLite(), "SELECT * FROM Sales WHERE rowid >= ? AND rowid < ?") + , GetRows(db.GetSQLite(), "SELECT * FROM Sales WHERE Id >= ? AND Id < ?") + // language=SQLite + , GetItems(db.GetSQLite(), "SELECT * FROM SalesItems WHERE SaleId == ?") { } @@ -19,13 +21,17 @@ PurchasesTable::PurchasesTable(TransactionModel& db) // language=SQLite : GetRowCount(db.GetSQLite(), "SELECT Count(*) FROM Purchases") // language=SQLite - , GetRows(db.GetSQLite(), "SELECT * FROM Purchases WHERE rowid >= ? AND rowid < ?") + , GetRows(db.GetSQLite(), "SELECT * FROM Purchases WHERE Id >= ? AND Id < ?") + // language=SQLite + , GetItems(db.GetSQLite(), "SELECT * FROM PurchasesItems WHERE PurchaseId == ?") { } DeliveryTable::DeliveryTable(TransactionModel& db) // language=SQLite : FilterByTypeAndId(db.GetSQLite(), "SELECT * FROM Deliveries WHERE AssociatedOrder == ? AND Outgoing = ?") + // language=SQLite + , GetItems(db.GetSQLite(), "SELECT * FROM DeliveriesItems WHERE DeliveryId == ?") { } @@ -40,7 +46,7 @@ static std::string GetDatabaseFilePath(const Project& project) /// Wrapper for SQLite::Database that creates the default tables TransactionModel::DatabaseWrapper::DatabaseWrapper(TransactionModel& self) - : mSqlite(GetDatabaseFilePath(*self.mProject), SQLite::OPEN_READWRITE) + : mSqlite(GetDatabaseFilePath(*self.mProject), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) { // If this table doesn't exist, the database probably just got initialized if (mSqlite.tableExists("Sales")) { @@ -58,15 +64,18 @@ TransactionModel::DatabaseWrapper::DatabaseWrapper(TransactionModel& self) // - DeliveryTime: the time this order was completed (through a set of deliveries) // 'Deliveries' schema - // - ShipmentTime: unix epoch time of sending to delivery - // - ArrivalTime: unix epoch time of delivery arrived at warehouse; 0 if not arrived yet - // - AssociatedOrder: rowid of the order that this delivery is completing (which table: Outgoing=true -> Sales, Outgoing=false -> Purchases) + // - ShipmentTime: unix epoch time stamp of sending to delivery + // - ArrivalTime: unix epoch time stamp of delivery arrived at warehouse; 0 if not arrived yet + // - AssociatedOrder: Id of the order that this delivery is completing (which table: Outgoing=true -> Sales, Outgoing=false -> Purchases) // - Outgoing: true if the delivery is from warehouse to customer; false if the delivery is from factory to warehouse + // Note: the 'Id' key would be unique (not recycled after row deletion) because it's explicit + // https://www.sqlite.org/rowidtable.html + // language=SQLite mSqlite.exec(R"""( CREATE TABLE IF NOT EXISTS Sales( - INT PRIMARY KEY, + Id INT PRIMARY KEY, Customer INT, Deadline DATETIME, DeliveryTime DATETIME @@ -78,7 +87,7 @@ CREATE TABLE IF NOT EXISTS SalesItems( ); CREATE TABLE IF NOT EXISTS Purchases( - INT PRIMARY KEY, + Id INT PRIMARY KEY, Factory INT, OrderTime DATETIME, DeliveryTime DATETIME @@ -90,7 +99,7 @@ CREATE TABLE IF NOT EXISTS PurchasesItems( ); CREATE TABLE IF NOT EXISTS Deliveries( - INT PRIMARY KEY, + Id INT PRIMARY KEY, ShipmentTime DATETIME, ArrivalTime DATETIME, AssociatedOrder INT, diff --git a/core/src/Model/TransactionsModel.hpp b/core/src/Model/TransactionsModel.hpp index 86611cf..cd19241 100644 --- a/core/src/Model/TransactionsModel.hpp +++ b/core/src/Model/TransactionsModel.hpp @@ -11,6 +11,7 @@ class SalesTable public: SQLite::Statement GetRowCount; SQLite::Statement GetRows; + SQLite::Statement GetItems; public: SalesTable(TransactionModel& db); @@ -21,6 +22,7 @@ class PurchasesTable public: SQLite::Statement GetRowCount; SQLite::Statement GetRows; + SQLite::Statement GetItems; public: PurchasesTable(TransactionModel& db); @@ -30,6 +32,7 @@ class DeliveryTable { public: SQLite::Statement FilterByTypeAndId; + SQLite::Statement GetItems; public: DeliveryTable(TransactionModel& db); diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index 043170a..46cf740 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -64,13 +64,22 @@ public: BasicTranslation PurchasesViewTab{ "Database.PurchasesView.TabName"sv }; BasicTranslation EditPurchaseEntryDialogTitle{ "Database.PurchasesView.Edit.DialogTitle"sv }; + BasicTranslation DatabaseItemsColumn{ "Database.Column.Items"sv }; BasicTranslation DatabaseCustomerColumn{ "Database.Column.Customer"sv }; BasicTranslation DatabaseFactoryColumn{ "Database.Column.Factory"sv }; + /// 销售订单的交货期限 BasicTranslation DatabaseDeadlineColumn{ "Database.Column.Deadline"sv }; + /// 采购订单的下单时间 BasicTranslation DatabaseOrderTimeColumn{ "Database.Column.OrderTime"sv }; - BasicTranslation DatabaseDeliveryTimeColumn{ "Database.Column.DeliveryTime"sv }; + /// 所有订单的“完成”时间。对于销售来说是实际交货时间,对于采购来说是收货时间。 + BasicTranslation DatabaseCompletionTimeColumn{ "Database.Column.CompletionTime"sv }; + /// (运输)批次的发货时间,适用于采购和销售批次。 + BasicTranslation DatabaseShipmentTimeColumn{ "Database.Column.ShipmentTime"sv }; + /// (运输)批次的收获时间,适用于采购和销售批次。 + BasicTranslation DatabaseArrivalTimeColumn{ "Database.Column.ArrivalTime"sv }; - BasicTranslation NotDelievered{ "Database.Message.NotDelivered"sv }; + BasicTranslation SelectOrderToShowAssociatedDeliveries{ "Database.Message.NoOrderSelected"sv }; + BasicTranslation NotDelivered{ "Database.Message.NotDelivered"sv }; /* Items tab */ diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index bc458da..b9d9d81 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -20,6 +20,8 @@ namespace { // TODO move to Settings constexpr int kMaxEntriesPerPage = 20; +constexpr int kSummaryItemCount = 3; +constexpr int kSummaryMaxLength = 25; enum class DeliveryDirection { @@ -27,8 +29,16 @@ enum class DeliveryDirection WarehouseToCustomer, }; +struct ItemEntry +{ + int ItemId; + int Count; +}; + struct DeliveryEntry { + std::vector<ItemEntry> Items; + std::string ItemsSummary; std::string ShipmentTime; std::string ArriveTime; DeliveryDirection Direction; @@ -129,14 +139,13 @@ public: virtual void OnFilterChanged() { auto& stmt = *mFilterRowsStatement; - DEFER - { - stmt.reset(); - }; + // clang-format off + DEFER { stmt.reset(); }; + // clang-format on // TODO lazy loading when too many results mActiveEntries.clear(); - int columnIdx = stmt.getColumnIndex("rowid"); + int columnIdx = stmt.getColumnIndex("Id"); while (stmt.executeStep()) { mActiveEntries.push_back(stmt.getColumn(columnIdx).getInt()); } @@ -187,19 +196,29 @@ public: // TODO } - if (mSelectedEntryRowId == -1) { - DrawMainTable(); - } else { - // TODO better layout + ImGui::SameLine(); + if (ImGui::Button(ls->Delete.Get(), mSelectedEntryRowId == -1)) { + // TODO + } + + ImGui::Columns(2); + { DrawMainTable(); - ImGui::SameLine(); - DrawDeliveriesTable(); + ImGui::NextColumn(); + + if (mSelectedEntryRowId == -1) { + ImGui::TextWrapped("%s", ls->SelectOrderToShowAssociatedDeliveries.Get()); + } else { + DrawDeliveriesTable(); + } + ImGui::NextColumn(); } + ImGui::Columns(1); } void DrawMainTable() { - if (ImGui::BeginTable("DataTable", GetTableColumnCount(), ImGuiTableFlags_ScrollX)) { + if (ImGui::BeginTable("DataTable", GetTableColumnCount(), ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) { SetupTableColumns(); ImGui::TableHeadersRow(); @@ -223,10 +242,12 @@ public: void DrawDeliveriesTable() { - if (ImGui::BeginTable("DeliveriesTable", 2)) { + auto ls = LocaleStrings::Instance.get(); + if (ImGui::BeginTable("DeliveriesTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX)) { - ImGui::TableSetupColumn("Shipment time"); - ImGui::TableSetupColumn("Arrival time"); + ImGui::TableSetupColumn(ls->DatabaseShipmentTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseArrivalTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseItemsColumn.Get()); ImGui::TableHeadersRow(); auto& deliveries = GetEntryAssociatedDeliveries(mSelectedEntryRowId); @@ -238,6 +259,12 @@ public: ImGui::TableNextColumn(); ImGui::TextUnformatted(delivery.ArriveTime.c_str()); + + ImGui::TableNextColumn(); + if (ImGui::TreeNode(delivery.ItemsSummary.c_str())) { + DrawItems(delivery.Items); + ImGui::TreePop(); + } } ImGui::EndTable(); @@ -260,6 +287,53 @@ public: return index + mFirstCachedRowId; } + std::vector<ItemEntry> LoadItems(SQLite::Statement& stmt, int64_t rowId) + { + // clang-format off + DEFER { stmt.reset(); }; + // clang-format on + + stmt.bind(1, rowId); + + std::vector<ItemEntry> entries; + int itemIdCol = stmt.getColumnIndex("ItemId"); + int countCol = stmt.getColumnIndex("Count"); + while (stmt.executeStep()) { + entries.push_back(ItemEntry{ + .ItemId = stmt.getColumn(itemIdCol).getInt(), + .Count = stmt.getColumn(countCol).getInt(), + }); + } + + return entries; + } + + std::string CreateItemsSummary(const std::vector<ItemEntry>& items) + { + if (items.empty()) { + return "<empty>"; + } + + std::string result; + for (int i = 0, max = std::min((int)items.size(), kSummaryItemCount); i < max; ++i) { + auto& name = mProject->Products.Find(items[i].ItemId)->GetName(); + if (result.length() + name.length() > kSummaryMaxLength) { + break; + } + + result += name; + result += ", "; + } + + // Remove ", " + result.pop_back(); + result.pop_back(); + + result += "..."; + + return result; + } + std::vector<DeliveryEntry> LoadDeliveriesEntries(int64_t orderRowId, DeliveryDirection type) { bool outgoingFlag; @@ -269,19 +343,26 @@ public: } auto& stmt = mProject->GetTransactionsModel().GetDeliveries().FilterByTypeAndId; - DEFER - { - stmt.reset(); - }; + // clang-format off + DEFER { stmt.reset(); }; + // clang-format on stmt.bind(1, orderRowId); - stmt.bind(2, static_cast<int>(type)); + stmt.bind(2, outgoingFlag); std::vector<DeliveryEntry> entries; + int rowIdCol = stmt.getColumnIndex("Id"); int sendTimeCol = stmt.getColumnIndex("ShipmentTime"); int arrivalTimeCol = stmt.getColumnIndex("ArrivalTime"); while (stmt.executeStep()) { + auto items = LoadItems( + mProject->GetTransactionsModel().GetDeliveries().GetItems, + stmt.getColumn(rowIdCol).getInt64()); + auto summary = CreateItemsSummary(items); + entries.push_back(DeliveryEntry{ + .Items = std::move(items), + .ItemsSummary = std::move(summary), .ShipmentTime = StringifyTimeStamp(stmt.getColumn(arrivalTimeCol).getInt64()), .ArriveTime = StringifyTimeStamp(stmt.getColumn(sendTimeCol).getInt64()), .Direction = type, @@ -301,6 +382,14 @@ protected: virtual void ClearEntries() = 0; + void DrawItems(const std::vector<ItemEntry>& items) + { + for (auto& item : items) { + auto& name = mProject->Products.Find(item.ItemId)->GetName(); + ImGui::Text("%s × %d", name.c_str(), item.Count); + } + } + void EnsureCacheCoversPage(int page) { auto [begin, end] = CalcRangeForPage(page); @@ -360,10 +449,9 @@ protected: result.reserve(size); - DEFER - { - mGetRowsStatement->reset(); - }; + // clang-format off + DEFER { mGetRowsStatement->reset(); }; + // clang-format on mGetRowsStatement->bind(1, begin); mGetRowsStatement->bind(2, end); @@ -380,10 +468,11 @@ private: } }; -class SaleEntry +struct SaleEntry { -public: std::vector<DeliveryEntry> AssociatedDeliveries; + std::vector<ItemEntry> Items; + std::string ItemsSummary; std::string Customer; std::string Deadline; std::string DeliveryTime; @@ -416,7 +505,7 @@ public: protected: virtual int GetTableColumnCount() const override { - return 3; + return 4; } virtual void SetupTableColumns() override @@ -424,7 +513,8 @@ protected: auto ls = LocaleStrings::Instance.get(); ImGui::TableSetupColumn(ls->DatabaseCustomerColumn.Get()); ImGui::TableSetupColumn(ls->DatabaseDeadlineColumn.Get()); - ImGui::TableSetupColumn(ls->DatabaseDeliveryTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseCompletionTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseItemsColumn.Get()); } virtual const std::vector<DeliveryEntry>& GetEntryAssociatedDeliveries(int rowId) override @@ -455,17 +545,23 @@ protected: ImGui::TableNextColumn(); if (entry.DeliveryTime.empty()) { - ImGui::TextUnformatted(ls->NotDelievered.Get()); + ImGui::TextUnformatted(ls->NotDelivered.Get()); } else { ImGui::TextUnformatted(entry.DeliveryTime.c_str()); } + ImGui::TableNextColumn(); + if (ImGui::TreeNode(entry.ItemsSummary.c_str())) { + DrawItems(entry.Items); + ImGui::TreePop(); + } + ImGui::PopID(); } virtual void EditEntry(int rowId) override { - // `TODO` + // TODO } virtual void ClearEntries() override @@ -477,18 +573,24 @@ protected: { auto CollectRows = [&](std::vector<SaleEntry>& result) { auto& stmt = *mGetRowsStatement; + int rowIdCol = stmt.getColumnIndex("Id"); int customerCol = stmt.getColumnIndex("Customer"); int deadlineCol = stmt.getColumnIndex("Deadline"); int deliveryTimeCol = stmt.getColumnIndex("DeliveryTime"); while (stmt.executeStep()) { - auto customer = stmt.getColumn(customerCol).getInt(); - auto deadline = stmt.getColumn(deadlineCol).getInt64(); - auto deliveryTime = stmt.getColumn(deliveryTimeCol).getInt64(); + auto customerId = stmt.getColumn(customerCol).getInt(); + auto items = LoadItems( + mProject->GetTransactionsModel().GetSales().GetItems, + stmt.getColumn(rowIdCol).getInt64()); + auto itemsSummary = CreateItemsSummary(items); + result.push_back(SaleEntry{ - .Customer = mProject->Customers.Find(customer)->GetName(), - .Deadline = StringifyTimeStamp(deadline), - .DeliveryTime = StringifyTimeStamp(deliveryTime), + .Items = std::move(items), + .ItemsSummary = std::move(itemsSummary), + .Customer = mProject->Customers.Find(customerId)->GetName(), + .Deadline = StringifyTimeStamp(stmt.getColumn(deadlineCol).getInt64()), + .DeliveryTime = StringifyTimeStamp(stmt.getColumn(deliveryTimeCol).getInt64()), }); } }; @@ -497,10 +599,11 @@ protected: } }; -class PurchaseEntry +struct PurchaseEntry { -public: std::vector<DeliveryEntry> AssociatedDeliveries; + std::vector<ItemEntry> Items; + std::string ItemsSummary; std::string Factory; std::string OrderTime; std::string DeliveryTime; @@ -532,7 +635,7 @@ public: protected: virtual int GetTableColumnCount() const override { - return 3; + return 4; } virtual void SetupTableColumns() override @@ -540,7 +643,8 @@ protected: auto ls = LocaleStrings::Instance.get(); ImGui::TableSetupColumn(ls->DatabaseFactoryColumn.Get()); ImGui::TableSetupColumn(ls->DatabaseOrderTimeColumn.Get()); - ImGui::TableSetupColumn(ls->DatabaseDeliveryTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseCompletionTimeColumn.Get()); + ImGui::TableSetupColumn(ls->DatabaseItemsColumn.Get()); } virtual const std::vector<DeliveryEntry>& GetEntryAssociatedDeliveries(int rowId) override @@ -568,18 +672,24 @@ protected: ImGui::TableNextColumn(); if (entry.OrderTime.empty()) { - ImGui::TextUnformatted(ls->NotDelievered.Get()); + ImGui::TextUnformatted(ls->NotDelivered.Get()); } else { ImGui::TextUnformatted(entry.OrderTime.c_str()); } ImGui::TableNextColumn(); if (entry.DeliveryTime.empty()) { - ImGui::TextUnformatted(ls->NotDelievered.Get()); + ImGui::TextUnformatted(ls->NotDelivered.Get()); } else { ImGui::TextUnformatted(entry.DeliveryTime.c_str()); } + ImGui::TableNextColumn(); + if (ImGui::TreeNode(entry.ItemsSummary.c_str())) { + DrawItems(entry.Items); + ImGui::TreePop(); + } + ImGui::PopID(); } @@ -597,18 +707,24 @@ protected: { auto CollectRows = [&](std::vector<PurchaseEntry>& result) { auto& stmt = *mGetRowsStatement; + int rowIdCol = stmt.getColumnIndex("Id"); int factoryCol = stmt.getColumnIndex("Factory"); int orderTimeCol = stmt.getColumnIndex("OrderTime"); int deliveryTimeCol = stmt.getColumnIndex("DeliveryTime"); while (stmt.executeStep()) { - auto factory = stmt.getColumn(factoryCol).getInt(); - auto orderTime = stmt.getColumn(orderTimeCol).getInt64(); - auto deliveryTime = stmt.getColumn(deliveryTimeCol).getInt64(); + auto factoryId = stmt.getColumn(factoryCol).getInt(); + auto items = LoadItems( + mProject->GetTransactionsModel().GetPurchases().GetItems, + stmt.getColumn(rowIdCol).getInt64()); + auto itemsSummary = CreateItemsSummary(items); + result.push_back(PurchaseEntry{ - .Factory = mProject->Factories.Find(factory)->GetName(), - .OrderTime = StringifyTimeStamp(orderTime), - .DeliveryTime = StringifyTimeStamp(deliveryTime), + .Items = std::move(items), + .ItemsSummary = std::move(itemsSummary), + .Factory = mProject->Factories.Find(factoryId)->GetName(), + .OrderTime = StringifyTimeStamp(stmt.getColumn(orderTimeCol).getInt64()), + .DeliveryTime = StringifyTimeStamp(stmt.getColumn(deliveryTimeCol).getInt64()), }); } }; |