## Summary This finishes the org credit line workflow so it behaves like reserved treasury-backed credit instead of a simple member allowance. ## What changed - reserve org funds immediately when a credit line is assigned - track credit lines with: - approved amount - available amount - outstanding principal - interest rate - amount due - consume reserved credit during store checkout without charging org funds a second time - add credit line repayment through the bank app - sync richer credit line state into org and bank payloads/UI - keep legacy `amount` compatibility mapped to available credit for older consumers ## User-facing behavior - assigning a credit line now reduces available org funds immediately - spending on `credit_line` reduces available credit and creates debt with interest - the bank app now shows outstanding credit debt and allows repayment from personal bank funds - the org treasury view now shows reserved credit and outstanding due totals ## Validation - `cargo fmt` - `npm run build:webui` - `cargo test -p forge-services --quiet` - `cargo test -p forge-server --quiet` ## Follow-up checks - validate in-game that assigning a credit line reduces org funds immediately - validate store checkout with `credit_line` updates available credit and debt correctly - validate bank repayment decreases player bank balance, increases org funds, and reduces amount due Co-authored-by: Jacob Schmidt <innovativestudios@outlook.com> Reviewed-on: #2
373 lines
16 KiB
HTML
373 lines
16 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
</head>
|
|
<body>
|
|
<div class="dispatch-shell">
|
|
<header class="dispatch-header">
|
|
<div>
|
|
<p class="dispatch-kicker">Dispatch Dashboard</p>
|
|
<h2>Operational Board</h2>
|
|
</div>
|
|
</header>
|
|
|
|
<div id="dispatcherStatusMessage" class="dispatch-status"></div>
|
|
<div
|
|
id="dispatcherDangerAlert"
|
|
class="dispatch-danger-alert is-hidden"
|
|
></div>
|
|
<div
|
|
id="dispatcherRequestAlert"
|
|
class="dispatch-warning-alert is-hidden"
|
|
></div>
|
|
|
|
<section class="dispatch-metrics">
|
|
<div class="metric-card">
|
|
<span class="metric-label">Open Contracts</span>
|
|
<strong id="metricOpenContracts">0</strong>
|
|
</div>
|
|
<div class="metric-card">
|
|
<span class="metric-label">Assigned Contracts</span>
|
|
<strong id="metricAssignedContracts">0</strong>
|
|
</div>
|
|
<div class="metric-card">
|
|
<span class="metric-label">Active Groups</span>
|
|
<strong id="metricActiveGroups">0</strong>
|
|
</div>
|
|
<div id="metricOpenRequestsCard" class="metric-card">
|
|
<span class="metric-label">Open Requests</span>
|
|
<strong id="metricOpenRequests">0</strong>
|
|
</div>
|
|
<div id="metricDangerGroupsCard" class="metric-card">
|
|
<span class="metric-label">Groups In Danger</span>
|
|
<strong id="metricDangerGroups">0</strong>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="dispatch-grid">
|
|
<section class="dispatch-panel dispatch-panel-open">
|
|
<div class="dispatch-panel-header">
|
|
<h3>Available Contracts</h3>
|
|
<button
|
|
id="dispatcherCreateOrderBtn"
|
|
type="button"
|
|
class="dispatch-icon-btn"
|
|
aria-label="Create dispatch order"
|
|
title="Create dispatch order"
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
<div
|
|
id="dispatcherOpenContracts"
|
|
class="dispatch-list"
|
|
></div>
|
|
</section>
|
|
|
|
<section class="dispatch-panel dispatch-panel-assigned">
|
|
<div class="dispatch-panel-header">
|
|
<h3>Assigned Contracts</h3>
|
|
</div>
|
|
<div
|
|
id="dispatcherAssignedContracts"
|
|
class="dispatch-list"
|
|
></div>
|
|
</section>
|
|
|
|
<section class="dispatch-panel dispatch-panel-groups">
|
|
<div class="dispatch-panel-header">
|
|
<h3>Group Board</h3>
|
|
</div>
|
|
<div id="dispatcherGroups" class="dispatch-list"></div>
|
|
</section>
|
|
|
|
<section class="dispatch-panel dispatch-panel-activity">
|
|
<div class="dispatch-panel-header">
|
|
<h3>Requests & Activity</h3>
|
|
</div>
|
|
<div id="dispatcherActivity" class="dispatch-list"></div>
|
|
</section>
|
|
</div>
|
|
|
|
<div id="dispatcherGroupModal" class="dispatch-modal is-hidden">
|
|
<div class="dispatch-modal-backdrop"></div>
|
|
<div
|
|
class="dispatch-modal-dialog"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="dispatcherGroupModalTitle"
|
|
>
|
|
<div class="dispatch-modal-header">
|
|
<div>
|
|
<p class="dispatch-kicker">Group Editor</p>
|
|
<h3 id="dispatcherGroupModalTitle">Manage Group</h3>
|
|
</div>
|
|
<button
|
|
id="dispatcherGroupModalCloseBtn"
|
|
class="dispatch-icon-btn"
|
|
type="button"
|
|
aria-label="Close group editor"
|
|
>
|
|
x
|
|
</button>
|
|
</div>
|
|
<div class="dispatch-modal-body">
|
|
<div class="dispatch-meta-grid">
|
|
<div>
|
|
<span class="metric-label">Callsign</span>
|
|
<strong id="dispatcherModalGroupCallsign"
|
|
>-</strong
|
|
>
|
|
</div>
|
|
<div>
|
|
<span class="metric-label">Leader</span>
|
|
<strong id="dispatcherModalGroupLeader"
|
|
>-</strong
|
|
>
|
|
</div>
|
|
<div>
|
|
<span class="metric-label">Current Task</span>
|
|
<strong id="dispatcherModalGroupTask"
|
|
>None</strong
|
|
>
|
|
</div>
|
|
<div>
|
|
<span class="metric-label">Org</span>
|
|
<strong id="dispatcherModalGroupOrg"
|
|
>default</strong
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="dispatch-modal-fields">
|
|
<label class="dispatch-field">
|
|
<span>Role</span>
|
|
<select
|
|
id="dispatcherModalRoleSelect"
|
|
class="dispatch-select"
|
|
></select>
|
|
</label>
|
|
<label class="dispatch-field">
|
|
<span>Status</span>
|
|
<select
|
|
id="dispatcherModalStatusSelect"
|
|
class="dispatch-select"
|
|
></select>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="dispatch-modal-actions">
|
|
<button
|
|
id="dispatcherGroupModalSaveBtn"
|
|
type="button"
|
|
class="dispatch-btn"
|
|
>
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="dispatcherOrderModal" class="dispatch-modal is-hidden">
|
|
<div class="dispatch-modal-backdrop"></div>
|
|
<div
|
|
class="dispatch-modal-dialog"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="dispatcherOrderModalTitle"
|
|
>
|
|
<div class="dispatch-modal-header">
|
|
<div>
|
|
<p class="dispatch-kicker">Dispatch Order</p>
|
|
<h3 id="dispatcherOrderModalTitle">
|
|
Create Support Order
|
|
</h3>
|
|
</div>
|
|
<button
|
|
id="dispatcherOrderModalCloseBtn"
|
|
class="dispatch-icon-btn"
|
|
type="button"
|
|
aria-label="Close dispatch order editor"
|
|
>
|
|
x
|
|
</button>
|
|
</div>
|
|
<div class="dispatch-modal-body">
|
|
<div class="dispatch-modal-fields">
|
|
<label class="dispatch-field">
|
|
<span>Assignee Group</span>
|
|
<select
|
|
id="dispatcherOrderAssigneeSelect"
|
|
class="dispatch-select"
|
|
></select>
|
|
</label>
|
|
<label class="dispatch-field">
|
|
<span>Target Group</span>
|
|
<select
|
|
id="dispatcherOrderTargetSelect"
|
|
class="dispatch-select"
|
|
></select>
|
|
</label>
|
|
<label class="dispatch-field">
|
|
<span>Priority</span>
|
|
<select
|
|
id="dispatcherOrderPrioritySelect"
|
|
class="dispatch-select"
|
|
>
|
|
<option value="routine">routine</option>
|
|
<option value="priority" selected>
|
|
priority
|
|
</option>
|
|
<option value="emergency">emergency</option>
|
|
</select>
|
|
</label>
|
|
<label class="dispatch-field">
|
|
<span>Order Note</span>
|
|
<textarea
|
|
id="dispatcherOrderNoteInput"
|
|
class="dispatch-textarea"
|
|
rows="4"
|
|
placeholder="Optional order note for the assigned group."
|
|
></textarea>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="dispatch-modal-actions">
|
|
<button
|
|
id="dispatcherOrderModalSaveBtn"
|
|
type="button"
|
|
class="dispatch-btn"
|
|
>
|
|
Create Order
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="dispatcherRequestModal" class="dispatch-modal is-hidden">
|
|
<div class="dispatch-modal-backdrop"></div>
|
|
<div
|
|
class="dispatch-modal-dialog"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="dispatcherRequestModalTitle"
|
|
>
|
|
<div class="dispatch-modal-header">
|
|
<div>
|
|
<p class="dispatch-kicker">Support Request</p>
|
|
<h3 id="dispatcherRequestModalTitle">
|
|
Request Details
|
|
</h3>
|
|
</div>
|
|
<button
|
|
id="dispatcherRequestModalCloseBtn"
|
|
class="dispatch-icon-btn"
|
|
type="button"
|
|
aria-label="Close support request details"
|
|
>
|
|
x
|
|
</button>
|
|
</div>
|
|
<div class="dispatch-modal-body">
|
|
<div class="dispatch-meta-grid">
|
|
<div>
|
|
<span class="metric-label">Title</span>
|
|
<strong id="dispatcherRequestTitle"
|
|
>Support Request</strong
|
|
>
|
|
</div>
|
|
<div>
|
|
<span class="metric-label">Priority</span>
|
|
<strong id="dispatcherRequestPriority"
|
|
>priority</strong
|
|
>
|
|
</div>
|
|
<div>
|
|
<span class="metric-label">Group</span>
|
|
<strong id="dispatcherRequestGroup"
|
|
>Unknown</strong
|
|
>
|
|
</div>
|
|
<div>
|
|
<span class="metric-label">Type</span>
|
|
<strong id="dispatcherRequestType"
|
|
>request</strong
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="dispatch-field">
|
|
<span>Summary</span>
|
|
<div
|
|
id="dispatcherRequestSummary"
|
|
class="dispatch-detail-block"
|
|
></div>
|
|
</div>
|
|
<div class="dispatch-field">
|
|
<span>Submitted Fields</span>
|
|
<div
|
|
id="dispatcherRequestFields"
|
|
class="dispatch-detail-list"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div class="dispatch-modal-actions">
|
|
<button
|
|
id="dispatcherRequestConvertBtn"
|
|
type="button"
|
|
class="dispatch-btn dispatch-btn-secondary"
|
|
>
|
|
Convert to Order
|
|
</button>
|
|
<button
|
|
id="dispatcherRequestModalDoneBtn"
|
|
type="button"
|
|
class="dispatch-btn"
|
|
>
|
|
Done
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
window.MapLoader = {
|
|
loadCSS(path) {
|
|
return A3API.RequestFile(path).then((css) => {
|
|
const style = document.createElement("style");
|
|
style.textContent = css;
|
|
document.head.appendChild(style);
|
|
});
|
|
},
|
|
loadJS(path) {
|
|
return A3API.RequestFile(path).then((js) => {
|
|
eval(js);
|
|
});
|
|
},
|
|
loadAll(resources) {
|
|
return resources.reduce((promise, resource) => {
|
|
return promise.then(() => {
|
|
if (resource.endsWith(".css")) {
|
|
return this.loadCSS(resource);
|
|
}
|
|
|
|
if (resource.endsWith(".js")) {
|
|
return this.loadJS(resource);
|
|
}
|
|
|
|
return Promise.resolve();
|
|
});
|
|
}, Promise.resolve());
|
|
},
|
|
};
|
|
|
|
MapLoader.loadAll([
|
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css",
|
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-dispatcher.css",
|
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
|
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-dispatcher.js",
|
|
]).catch((err) => console.error("[DISPATCHER] Load error:", err));
|
|
</script>
|
|
</body>
|
|
</html>
|