-- 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