feat(gui): faner Oversigt/Forsyning/Udkørsel; udkørsels-run med kilder, varepolitik, regler og status

This commit is contained in:
2026-09-23 10:02:24 +02:00
parent cb115f9588
commit 337db16178
6 changed files with 508 additions and 126 deletions
@@ -0,0 +1,194 @@
-- Smart Pickup — fanen Udkørsel
-- Kilder (afkrydsning), varer med politik (Fra/Lager/Salg/Salg, ellers lager) og aktuel pris i
-- % af årets top, regler (pristærskel, lagertærskel, opsamlingsrunde) og status/advarsler.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
-- Bedste pris-% for varen over alle salgssteder (uanset vejnet — det er til visning; turen
-- bruger kun nåelige). {percent, stationName} eller nil.
function SmartPickupFrame:getProductQuote(adEnv, fillType)
if ADSources == nil or ADPrices == nil then
return nil
end
local best = nil
for _, entry in ipairs(ADSources.getSellMarkers(adEnv, g_currentMission:getFarmId())) do
if ADSources.stationAccepts(entry.station, fillType) then
local quote = ADPrices.getQuote(entry.station, fillType)
if quote ~= nil and (best == nil or quote.percent > best.percent) then
best = {percent = quote.percent, stationName = tostring(entry.marker.name)}
end
end
end
return best
end
-- "Sukker — 150.0k l — 78 % af top (Grain Elevator)"
function SmartPickupFrame:describeProduct(adEnv, product)
local price = self.i18n:getText("spu_price_none")
local quote = self:getProductQuote(adEnv, product.fillType)
if quote ~= nil then
price = string.format(self.i18n:getText("spu_price_pct"), quote.percent, quote.stationName)
end
return string.format(self.i18n:getText("spu_product_row"), tostring(product.title), SmartPickupFrame.formatCompact(product.available or 0), price)
end
function SmartPickupFrame:getPolicyIndex(policy)
for index, name in ipairs(SmartPickupFrame.POLICIES) do
if name == policy then
return index
end
end
return 1
end
function SmartPickupFrame:fillSourceRows(adEnv, run)
local chosen = {}
for _, id in ipairs(run.sourceWayPointIds or {}) do
chosen[id] = true
end
local entries = ADRunsController.getSourceMarkers(adEnv, g_currentMission:getFarmId())
for _, entry in ipairs(entries) do
local row = self:addRow(self.ingredientPrefab, {kind = "source", wayPointId = entry.marker.id})
local kindText = self.i18n:getText("spu_source_kind_" .. tostring(entry.kind))
row:getDescendantByName("title"):setText(string.format(" %s — %s (%s)", tostring(entry.marker.name), tostring(entry.name), kindText))
row:getDescendantByName("option"):setState(chosen[entry.marker.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
if #entries == 0 then
self:addTextRow(self.i18n:getText("spu_noSources"))
end
end
function SmartPickupFrame:fillProductRows(adEnv, run)
self.productRows = {}
local policyTexts = {}
for _, policy in ipairs(SmartPickupFrame.POLICIES) do
table.insert(policyTexts, self.i18n:getText("spu_policy_" .. policy))
end
local products = ADRunsController.getRunProducts(adEnv, run)
for _, product in ipairs(products) do
local row = self:addRow(self.productPrefab, {kind = "product", name = product.name})
local option = row:getDescendantByName("option")
option:setTexts(policyTexts)
option:setState(self:getPolicyIndex(product.policy), false)
local title = row:getDescendantByName("title")
title:setText(self:describeProduct(adEnv, product))
table.insert(self.productRows, {name = product.name, title = title, option = option})
end
if #products == 0 then
self:addTextRow(#(run.sourceWayPointIds or {}) == 0 and self.i18n:getText("spu_noSources") or self.i18n:getText("spu_noProducts"))
end
end
function SmartPickupFrame:fillOutboundStatusRows(adEnv, run)
self.outboundStatusRows = {}
local status = ADRunsController.getOutboundStatus(adEnv, run)
for _, reason in ipairs(status.reasons or {}) do
table.insert(self.outboundStatusRows, self:addTextRow(string.format(self.i18n:getText("spu_outbound_waiting"), reason)))
end
if status.warning ~= nil then
table.insert(self.outboundStatusRows, self:addTextRow(string.format(self.i18n:getText("spu_outbound_warning"), status.warning)))
end
end
function SmartPickupFrame:rebuildOutboundTab(adEnv, run)
self.sellAtOption:setState(SmartPickupFrame.getStepIndex(self.sellAtSteps, run.sellAtPercent, ADRuns.DEFAULT_SELL_AT_PERCENT), false)
self.storeAboveOption:setState(SmartPickupFrame.getStepIndex(self.storeAboveSteps, run.storeAbovePercent, ADRuns.DEFAULT_STORE_ABOVE_PERCENT), false)
self.collectRoundOption:setState(run.collectRound and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
self:addTextRow(self.i18n:getText("spu_section_sources"))
self:fillSourceRows(adEnv, run)
self:addTextRow(self.i18n:getText("spu_section_products"))
self:fillProductRows(adEnv, run)
self:fillOutboundStatusRows(adEnv, run)
end
-- Hvert sekund: produktrækkernes mængde/pris og statuslinjerne (antal linjer ændres kun ved rebuild).
function SmartPickupFrame:refreshOutboundRows(adEnv, run)
local byName = {}
for _, product in ipairs(ADRunsController.getRunProducts(adEnv, run)) do
byName[product.name] = product
end
for _, row in ipairs(self.productRows or {}) do
if byName[row.name] ~= nil then
row.title:setText(self:describeProduct(adEnv, byName[row.name]))
end
end
local status = ADRunsController.getOutboundStatus(adEnv, run)
local lines = {}
for _, reason in ipairs(status.reasons or {}) do
table.insert(lines, string.format(self.i18n:getText("spu_outbound_waiting"), reason))
end
if status.warning ~= nil then
table.insert(lines, string.format(self.i18n:getText("spu_outbound_warning"), status.warning))
end
for index, row in ipairs(self.outboundStatusRows or {}) do
row:setText(lines[index] or "")
end
end
-- Oversigt: "SUGAR 78 % af top (kræver 90), lager 41 % · ⚠ …" eller "Sukker 150.0k l — 92 % af top (…)".
function SmartPickupFrame:describeOutboundOverview(adEnv, run)
local status = ADRunsController.getOutboundStatus(adEnv, run)
local parts = {}
for _, reason in ipairs(status.reasons or {}) do
table.insert(parts, reason)
end
if #parts == 0 then
for _, product in ipairs(status.products or {}) do
if product.policy ~= "off" then
table.insert(parts, self:describeProduct(adEnv, product))
end
end
end
if status.warning ~= nil then
table.insert(parts, string.format(self.i18n:getText("spu_outbound_warning"), status.warning))
end
if #parts == 0 then
return self.i18n:getText("spu_noSources")
end
return table.concat(parts, " · ")
end
-- ---- callbacks ----
function SmartPickupFrame:onClickSource(state, element)
self:safe(function(frame)
local run, data = frame:getRun(), frame.rowData[element]
if run == nil or data == nil then return end
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, run.id, data.wayPointId)
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': sources=%s", tostring(run.name), table.concat(ADRuns.getRun(ADRunsController.store, run.id).sourceWayPointIds, " "))
frame:requestRebuild()
end)
end
function SmartPickupFrame:onClickProduct(state, element)
self:safe(function(frame)
local run, data = frame:getRun(), frame.rowData[element]
if run == nil or data == nil or data.kind ~= "product" then return end
local policy = SmartPickupFrame.POLICIES[state] or "off"
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, run.id, data.name, policy)
Logging.info(SmartPickupFrame.LOG_PREFIX .. "run '%s': %s=%s", tostring(run.name), tostring(data.name), policy)
end)
end
function SmartPickupFrame:onClickSellAt(state)
self:safe(function(frame)
local run = frame:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {sellAtPercent = frame.sellAtSteps[state] or ADRuns.DEFAULT_SELL_AT_PERCENT})
end)
end
function SmartPickupFrame:onClickStoreAbove(state)
self:safe(function(frame)
local run = frame:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {storeAbovePercent = frame.storeAboveSteps[state] or ADRuns.DEFAULT_STORE_ABOVE_PERCENT})
end)
end
function SmartPickupFrame:onClickCollectRound(state)
self:safe(function(frame)
local run = frame:getRun()
if run == nil then return end
ADRunsController.updateRun(run.id, {collectRound = state == SmartPickupFrame.STATE_ON})
end)
end