## 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
191 lines
7.2 KiB
HTML
191 lines
7.2 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
</head>
|
|
<body>
|
|
<div class="panel-header">
|
|
<h3>CAD System</h3>
|
|
</div>
|
|
<div class="panel-content">
|
|
<div id="cadStatusMessage" class="task-status-message"></div>
|
|
<div id="cadDangerAlert" class="cad-danger-alert is-hidden"></div>
|
|
<div id="cadRequestAlert" class="cad-warning-alert is-hidden"></div>
|
|
<div class="cad-tabs" role="tablist" aria-label="CAD Sections">
|
|
<button
|
|
id="tabContractsBtn"
|
|
class="cad-tab is-active"
|
|
type="button"
|
|
data-tab="contracts"
|
|
>
|
|
Contracts
|
|
</button>
|
|
<button
|
|
id="tabRosterBtn"
|
|
class="cad-tab"
|
|
type="button"
|
|
data-tab="roster"
|
|
>
|
|
Roster
|
|
</button>
|
|
<button
|
|
id="tabRequestsBtn"
|
|
class="cad-tab"
|
|
type="button"
|
|
data-tab="requests"
|
|
>
|
|
Requests
|
|
</button>
|
|
<button
|
|
id="tabActivityBtn"
|
|
class="cad-tab"
|
|
type="button"
|
|
data-tab="activity"
|
|
>
|
|
Activity
|
|
</button>
|
|
</div>
|
|
<div class="cad-tab-panels">
|
|
<div
|
|
id="contractsPanel"
|
|
class="cad-section is-active"
|
|
data-panel="contracts"
|
|
>
|
|
<div class="cad-section-header">Contracts</div>
|
|
<div id="taskList" class="task-list">
|
|
<div class="placeholder-message">
|
|
<p>Loading contracts...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="rosterPanel" class="cad-section" data-panel="roster">
|
|
<div class="cad-section-header">Roster</div>
|
|
<div id="rosterList" class="task-list">
|
|
<div class="placeholder-message">
|
|
<p>Loading roster...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
id="requestsPanel"
|
|
class="cad-section"
|
|
data-panel="requests"
|
|
>
|
|
<div class="cad-section-header">Support Requests</div>
|
|
<div id="requestList" class="task-list">
|
|
<div class="placeholder-message">
|
|
<p>No support requests.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
id="activityPanel"
|
|
class="cad-section"
|
|
data-panel="activity"
|
|
>
|
|
<div class="cad-section-header">Activity</div>
|
|
<div id="activityList" class="task-list">
|
|
<div class="placeholder-message">
|
|
<p>No recent activity.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="cadRequestModal" class="cad-modal is-hidden">
|
|
<div class="cad-modal-backdrop"></div>
|
|
<div
|
|
class="cad-modal-dialog"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="cadRequestModalTitle"
|
|
>
|
|
<div class="cad-modal-header">
|
|
<div>
|
|
<div class="cad-section-header">Support Request</div>
|
|
<h3 id="cadRequestModalTitle">Submit Request</h3>
|
|
</div>
|
|
<button
|
|
id="cadRequestModalCloseBtn"
|
|
class="cad-icon-btn"
|
|
type="button"
|
|
aria-label="Close support request form"
|
|
>
|
|
x
|
|
</button>
|
|
</div>
|
|
<div class="cad-modal-body">
|
|
<div class="cad-modal-fields">
|
|
<label class="cad-field">
|
|
<span>Priority</span>
|
|
<select
|
|
id="cadRequestPrioritySelect"
|
|
class="cad-select"
|
|
>
|
|
<option value="routine">routine</option>
|
|
<option value="priority" selected>
|
|
priority
|
|
</option>
|
|
<option value="emergency">emergency</option>
|
|
</select>
|
|
</label>
|
|
<div
|
|
id="cadRequestFields"
|
|
class="cad-modal-fields"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div class="cad-modal-actions">
|
|
<button
|
|
id="cadRequestModalSaveBtn"
|
|
type="button"
|
|
class="task-accept-btn"
|
|
>
|
|
Submit Request
|
|
</button>
|
|
</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-sidepanel.css",
|
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js",
|
|
"forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js",
|
|
]).catch((err) => console.error("[SIDEPANEL] Load error:", err));
|
|
</script>
|
|
</body>
|
|
</html>
|