188 lines
8.9 KiB
Lua
188 lines
8.9 KiB
Lua
-- AD Chain View (ren logik)
|
|
-- Rækkerne til fanen Kæder ud fra ADChain.last: rod -> noder rekursivt (kilde, behov pr. måned, flowstatus),
|
|
-- forslag/auto-skift under afgrøden, og nederst frie marker + antal automatiske skift. Ingen spiladgang:
|
|
-- titleOf(navn) -> visningstitel, fmt(liter) -> kompakt tal, textOf(l10n-nøgle) -> tekst gives udefra.
|
|
-- Række: {kind="root"|"node"|"suggest"|"dismiss"|"auto"|"info"|"empty", indent, text, level, cells={c1..c5},
|
|
-- action={kind="approve"|"dismiss"|"pause"|"resume", fieldId|rootKey}, fillType}
|
|
|
|
ADChainView = {}
|
|
ADChainView.INDENT = " "
|
|
-- nøgler ud over spu_tab_chains/spu_chain_empty/... som rækkerne bruger (til l10n-testen)
|
|
ADChainView.TEXT_KEYS = {"spu_chain_perMonth", "spu_chain_noField", "spu_chain_buySource", "spu_chain_cycle",
|
|
"spu_chain_fieldShort", "spu_chain_flowOk", "spu_chain_flowNone", "spu_chain_flowStopped", "spu_chain_approve",
|
|
"spu_chain_dismiss", "spu_chain_switches", "spu_chain_rootOk", "spu_chain_rootError", "spu_chain_rootWaiting",
|
|
"spu_chain_rootPaused"}
|
|
|
|
local FLOW_KEYS = {ok = "spu_chain_flowOk", missing = "spu_chain_flowNone", stopped = "spu_chain_flowStopped", noVehicle = "spu_chain_flowMissing"}
|
|
|
|
local function identity(value) return tostring(value) end
|
|
|
|
local function plannedFruit(field)
|
|
if field.targetFruit ~= nil and field.targetFruit ~= "" then return field.targetFruit end
|
|
return field.fruit
|
|
end
|
|
|
|
local function haText(ha)
|
|
return string.format("%.0f", ha or 0)
|
|
end
|
|
|
|
local function edgeKey(node)
|
|
return tostring(node.consumer) .. "|" .. tostring(node.fillType)
|
|
end
|
|
|
|
local function shortageByFruit(last)
|
|
local result = {}
|
|
for _, shortage in ipairs(last.shortages or {}) do result[shortage.fruit] = shortage end
|
|
return result
|
|
end
|
|
|
|
-- Alle noder under roden (dybde først).
|
|
local function eachNode(nodes, fn, depth)
|
|
for _, node in ipairs(nodes or {}) do
|
|
fn(node, depth or 1)
|
|
eachNode(node.children, fn, (depth or 1) + 1)
|
|
end
|
|
end
|
|
|
|
function ADChainView.rootLevel(root, last)
|
|
last = last or {}
|
|
if (last.paused or {})[root.key] then return "stopped" end
|
|
for _, problem in ipairs((last.chain or {}).problems or {}) do
|
|
if problem.rootKey == root.key then return "error" end
|
|
end
|
|
local short, flowStatus = shortageByFruit(last), last.flowStatus or {}
|
|
local hasShortage, hasWaiting = false, false
|
|
eachNode(root.children, function(node)
|
|
if node.fruit ~= nil and short[node.fruit] ~= nil then hasShortage = true end
|
|
if (node.shortPerMonth or 0) > 0 then hasShortage = true end
|
|
local status = node.consumer ~= nil and flowStatus[edgeKey(node)] or nil
|
|
if status ~= nil and status ~= "ok" then hasWaiting = true end
|
|
end)
|
|
if hasShortage then return "error" end
|
|
if hasWaiting then return "waiting" end
|
|
return "running"
|
|
end
|
|
|
|
-- Kilden som tekst: fabrikker / stalde / marker med planlagt afgrøde / køb.
|
|
local function sourceText(node, last, textOf)
|
|
local names = {}
|
|
if node.source == "production" then
|
|
for _, producer in ipairs(node.producers or {}) do table.insert(names, tostring(producer.buildingName)) end
|
|
elseif node.source == "husbandry" then
|
|
for _, herd in ipairs(node.herds or {}) do table.insert(names, tostring(herd.buildingName)) end
|
|
elseif node.source == "field" then
|
|
for _, field in ipairs(last.fields or {}) do
|
|
if plannedFruit(field) == node.fruit then
|
|
table.insert(names, string.format("F%s (%s ha)", tostring(field.id), haText(field.areaHa)))
|
|
end
|
|
end
|
|
if #names == 0 then return textOf("spu_chain_noField") end
|
|
elseif node.source == "buy" then
|
|
return textOf("spu_chain_buySource")
|
|
else
|
|
return "-"
|
|
end
|
|
return table.concat(names, ", ")
|
|
end
|
|
|
|
-- Nodens status (tekst, niveau): mangel/flaskehals/køb/kredsløb først, ellers flowets status.
|
|
local function nodeStatus(node, last, titleOf, fmt, textOf, short)
|
|
local title = titleOf(node.fillType)
|
|
if node.source == "cycle" then return textOf("spu_chain_cycle"), "error" end
|
|
if (node.shortPerMonth or 0) > 0 then
|
|
if node.source == "production" then
|
|
return string.format(textOf("spu_chain_bottleneck"), title, fmt(node.shortPerMonth)), "error"
|
|
end
|
|
return string.format(textOf("spu_chain_buy"), title, fmt(node.shortPerMonth)), "error"
|
|
end
|
|
local shortage = node.fruit ~= nil and short[node.fruit] or nil
|
|
if shortage ~= nil then
|
|
return string.format(textOf("spu_chain_fieldShort"), haText(shortage.missingHa)), "error"
|
|
end
|
|
local status = node.consumer ~= nil and (last.flowStatus or {})[edgeKey(node)] or nil
|
|
if status == nil then return "", "running" end
|
|
return textOf(FLOW_KEYS[status] or "spu_chain_flowNone"), status == "ok" and "running" or "waiting"
|
|
end
|
|
|
|
local function joinCells(cells)
|
|
local parts = {}
|
|
for _, cell in ipairs(cells) do
|
|
if cell ~= nil and cell ~= "" then table.insert(parts, cell) end
|
|
end
|
|
return table.concat(parts, " · ")
|
|
end
|
|
|
|
local function makeRow(kind, indent, level, cells, action)
|
|
return {kind = kind, indent = indent, level = level, cells = cells, action = action, text = joinCells(cells)}
|
|
end
|
|
|
|
-- Forslag (godkend + afvis) og auto-skift for afgrøden, lige under dens node.
|
|
local function actionRows(rows, fruit, indent, last, titleOf, textOf, shown)
|
|
if fruit == nil or shown[fruit] then return end
|
|
shown[fruit] = true
|
|
local pad = string.rep(ADChainView.INDENT, indent)
|
|
for _, action in ipairs(last.actions or {}) do
|
|
if action.to == fruit then
|
|
local fromTitle = (action.from ~= nil and action.from ~= "") and titleOf(action.from) or "-"
|
|
if action.mode == "suggest" then
|
|
local text = string.format(textOf("spu_chain_suggest"), action.fieldId, fromTitle, titleOf(action.to), action.hectares or 0)
|
|
table.insert(rows, makeRow("suggest", indent, "waiting", {pad, "", "", text, textOf("spu_chain_approve")},
|
|
{kind = "approve", fieldId = action.fieldId}))
|
|
table.insert(rows, makeRow("dismiss", indent, "waiting", {pad, "", "", "", textOf("spu_chain_dismiss")},
|
|
{kind = "dismiss", fieldId = action.fieldId}))
|
|
else
|
|
local text = string.format(textOf("spu_chain_auto"), action.fieldId, titleOf(action.to))
|
|
table.insert(rows, makeRow("auto", indent, "running", {pad, "", "", text, ""}, nil))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local ROOT_STATUS = {running = "spu_chain_rootOk", error = "spu_chain_rootError", waiting = "spu_chain_rootWaiting", stopped = "spu_chain_rootPaused"}
|
|
|
|
local function rootRows(rows, root, last, titleOf, fmt, textOf, short)
|
|
local level = ADChainView.rootLevel(root, last)
|
|
local paused = (last.paused or {})[root.key] == true
|
|
table.insert(rows, makeRow("root", 0, level, {titleOf(root.name), tostring(root.buildingName or ""), "",
|
|
textOf(ROOT_STATUS[level]), textOf(paused and "spu_chain_resume" or "spu_chain_pause")},
|
|
{kind = paused and "resume" or "pause", rootKey = root.key}))
|
|
local shown = {}
|
|
eachNode(root.children, function(node, depth)
|
|
local status, nodeLevel = nodeStatus(node, last, titleOf, fmt, textOf, short)
|
|
local row = makeRow("node", depth, nodeLevel, {string.rep(ADChainView.INDENT, depth) .. titleOf(node.fillType),
|
|
sourceText(node, last, textOf), string.format(textOf("spu_chain_perMonth"), fmt(node.perMonth or 0)), status, ""}, nil)
|
|
row.fillType = node.fillType
|
|
table.insert(rows, row)
|
|
if node.source == "field" then actionRows(rows, node.fruit, depth + 1, last, titleOf, textOf, shown) end
|
|
end)
|
|
end
|
|
|
|
local function footerRows(rows, last, textOf)
|
|
local free = {}
|
|
for _, field in ipairs(last.fields or {}) do
|
|
if field.chainFree then table.insert(free, field.id) end
|
|
end
|
|
table.sort(free)
|
|
local names = {}
|
|
for _, id in ipairs(free) do table.insert(names, "F" .. tostring(id)) end
|
|
local freeText = string.format(textOf("spu_chain_free"), #names > 0 and table.concat(names, ", ") or "-")
|
|
table.insert(rows, {kind = "info", indent = 0, text = freeText})
|
|
table.insert(rows, {kind = "info", indent = 0, text = string.format(textOf("spu_chain_switches"), last.switchCount or 0)})
|
|
end
|
|
|
|
function ADChainView.rows(last, titleOf, fmt, textOf)
|
|
textOf, fmt = textOf or identity, fmt or identity
|
|
local titles = last ~= nil and last.titles or {}
|
|
titleOf = titleOf or function(name) return (titles or {})[name] or tostring(name) end
|
|
local rows = {}
|
|
local roots = last ~= nil and last.chain ~= nil and last.chain.roots or {}
|
|
if #roots == 0 then
|
|
table.insert(rows, {kind = "empty", indent = 0, text = textOf("spu_chain_empty")})
|
|
end
|
|
if last == nil then return rows end
|
|
local short = shortageByFruit(last)
|
|
for _, root in ipairs(roots) do rootRows(rows, root, last, titleOf, fmt, textOf, short) end
|
|
footerRows(rows, last, textOf)
|
|
return rows
|
|
end
|