Add org credit lines and multi-source store checkout state
- Wire org portal credit-line requests/responses through SQF bridge and UI events - Sync `creditLines` in org payloads and refresh portal state after org sync - Add store payment sources (cash, bank, org funds, credit line) and expose selection in cart UI - Scaffold server-side store addon initialization/config files
This commit is contained in:
parent
09ab290b5a
commit
9771e375b6
@ -11,12 +11,14 @@ if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initOrgUIBridge); };
|
|||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
GVAR(OrgClass) call ["sync", [_data, true]];
|
GVAR(OrgClass) call ["sync", [_data, true]];
|
||||||
|
GVAR(OrgUIBridge) call ["refreshPortal", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseSyncOrg), {
|
[QGVAR(responseSyncOrg), {
|
||||||
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
params [["_data", createHashMap, [createHashMap]], ["_jip", false, [false]]];
|
||||||
|
|
||||||
GVAR(OrgClass) call ["sync", [_data, _jip]];
|
GVAR(OrgClass) call ["sync", [_data, _jip]];
|
||||||
|
GVAR(OrgUIBridge) call ["refreshPortal", []];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(responseCreateOrg), {
|
[QGVAR(responseCreateOrg), {
|
||||||
@ -37,6 +39,12 @@ if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initOrgUIBridge); };
|
|||||||
GVAR(OrgUIBridge) call ["handleLeaveResponse", [_payload]];
|
GVAR(OrgUIBridge) call ["handleLeaveResponse", [_payload]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseCreditLine), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) call ["handleCreditLineResponse", [_payload]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(actor,ActorClass) get "isLoaded";
|
EGVAR(actor,ActorClass) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@ -38,6 +38,9 @@ switch (_event) do {
|
|||||||
case "org::leave::request": {
|
case "org::leave::request": {
|
||||||
GVAR(OrgUIBridge) call ["requestLeave", []];
|
GVAR(OrgUIBridge) call ["requestLeave", []];
|
||||||
};
|
};
|
||||||
|
case "org::credit::request": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestCreditLine", [_data]];
|
||||||
|
};
|
||||||
case "org::ready": {
|
case "org::ready": {
|
||||||
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -35,6 +35,7 @@ GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_org set ["name", ""];
|
_org set ["name", ""];
|
||||||
_org set ["funds", 0];
|
_org set ["funds", 0];
|
||||||
_org set ["reputation", 0];
|
_org set ["reputation", 0];
|
||||||
|
_org set ["credit_lines", createHashMap];
|
||||||
_org set ["assets", createHashMap];
|
_org set ["assets", createHashMap];
|
||||||
_org set ["fleet", createHashMap];
|
_org set ["fleet", createHashMap];
|
||||||
_org set ["members", createHashMap];
|
_org set ["members", createHashMap];
|
||||||
@ -78,6 +79,7 @@ GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _ownerUid = _orgData get "owner";
|
private _ownerUid = _orgData get "owner";
|
||||||
private _funds = _orgData get "funds";
|
private _funds = _orgData get "funds";
|
||||||
private _reputation = _orgData get "reputation";
|
private _reputation = _orgData get "reputation";
|
||||||
|
private _creditLinesRaw = _orgData getOrDefault ["credit_lines", createHashMap];
|
||||||
private _assetsRaw = _orgData get "assets";
|
private _assetsRaw = _orgData get "assets";
|
||||||
private _fleetRaw = _orgData get "fleet";
|
private _fleetRaw = _orgData get "fleet";
|
||||||
private _membersRaw = _orgData get "members";
|
private _membersRaw = _orgData get "members";
|
||||||
@ -132,6 +134,16 @@ GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
]);
|
]);
|
||||||
} forEach _fleetRaw;
|
} forEach _fleetRaw;
|
||||||
|
|
||||||
|
private _creditLinesList = [];
|
||||||
|
{
|
||||||
|
private _creditLineData = _y;
|
||||||
|
_creditLinesList pushBack (createHashMapFromArray [
|
||||||
|
["uid", _creditLineData getOrDefault ["uid", _x]],
|
||||||
|
["member", _creditLineData getOrDefault ["name", "Unknown Member"]],
|
||||||
|
["amount", _creditLineData getOrDefault ["amount", 0]]
|
||||||
|
]);
|
||||||
|
} forEach _creditLinesRaw;
|
||||||
|
|
||||||
createHashMapFromArray [
|
createHashMapFromArray [
|
||||||
["session", createHashMapFromArray [
|
["session", createHashMapFromArray [
|
||||||
["actorName", _playerName],
|
["actorName", _playerName],
|
||||||
@ -149,6 +161,7 @@ GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
]],
|
]],
|
||||||
["funds", _funds],
|
["funds", _funds],
|
||||||
["reputation", _reputation],
|
["reputation", _reputation],
|
||||||
|
["creditLines", _creditLinesList],
|
||||||
["members", _membersList],
|
["members", _membersList],
|
||||||
["fleet", _fleetList],
|
["fleet", _fleetList],
|
||||||
["assets", _assetsList],
|
["assets", _assetsList],
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initOrgUIBridge.sqf
|
* File: fnc_initOrgUIBridge.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-03-10
|
* Date: 2026-03-10
|
||||||
* Last Update: 2026-03-10
|
* Last Update: 2026-03-12
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -150,12 +150,37 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_self call ["sendBridgeEvent", [_eventName, _payload]];
|
_self call ["sendBridgeEvent", [_eventName, _payload]];
|
||||||
}],
|
}],
|
||||||
|
["handleCreditLineResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = [
|
||||||
|
"org::credit::failure",
|
||||||
|
"org::credit::success"
|
||||||
|
] select (_payload getOrDefault ["success", false]);
|
||||||
|
|
||||||
|
_self call ["sendBridgeEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
["requestDisband", compileFinal {
|
["requestDisband", compileFinal {
|
||||||
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
}],
|
}],
|
||||||
["requestLeave", compileFinal {
|
["requestLeave", compileFinal {
|
||||||
[SRPC(org,requestLeaveOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
[SRPC(org,requestLeaveOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
}],
|
}],
|
||||||
|
["requestCreditLine", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _memberUid = _data getOrDefault ["memberUid", ""];
|
||||||
|
private _memberName = _data getOrDefault ["memberName", ""];
|
||||||
|
private _amount = _data getOrDefault ["amount", 0];
|
||||||
|
|
||||||
|
[SRPC(org,requestAssignCreditLine), [getPlayerUID player, _memberUid, _memberName, _amount]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["refreshPortal", compileFinal {
|
||||||
|
private _control = _self call ["getActiveBrowserControl", []];
|
||||||
|
if (isNull _control) exitWith { false };
|
||||||
|
|
||||||
|
_self call ["sendBridgeEvent", ["org::sync", GVAR(OrgClass) call ["buildPortalPayload", []], _control]]
|
||||||
|
}],
|
||||||
["handleReady", compileFinal {
|
["handleReady", compileFinal {
|
||||||
params [["_control", controlNull, [controlNull]]];
|
params [["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
|
|||||||
@ -71,6 +71,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestCreditLine(payload) {
|
||||||
|
const sent = sendEvent("org::credit::request", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma credit line bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function receive(eventOrPayload, data = {}) {
|
function receive(eventOrPayload, data = {}) {
|
||||||
const event =
|
const event =
|
||||||
typeof eventOrPayload === "object" && eventOrPayload !== null
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
@ -103,7 +120,38 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event === "org::sync") {
|
||||||
|
if (store && typeof store.hydratePortal === "function") {
|
||||||
|
store.hydratePortal(payloadData);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const OrgPortal = window.OrgPortal;
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (event === "org::credit::success") {
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
payloadData.message || "Credit line assigned.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::credit::failure") {
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Unable to assign credit line.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event === "org::disband::success") {
|
if (event === "org::disband::success") {
|
||||||
if (OrgPortal && OrgPortal.store) {
|
if (OrgPortal && OrgPortal.store) {
|
||||||
OrgPortal.store.setModal(null);
|
OrgPortal.store.setModal(null);
|
||||||
@ -170,6 +218,7 @@
|
|||||||
requestCreateOrg,
|
requestCreateOrg,
|
||||||
requestDisbandOrg,
|
requestDisbandOrg,
|
||||||
requestLeaveOrg,
|
requestLeaveOrg,
|
||||||
|
requestCreditLine,
|
||||||
receive,
|
receive,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
};
|
};
|
||||||
@ -179,6 +228,7 @@
|
|||||||
requestCreateOrg,
|
requestCreateOrg,
|
||||||
requestDisbandOrg,
|
requestDisbandOrg,
|
||||||
requestLeaveOrg,
|
requestLeaveOrg,
|
||||||
|
requestCreditLine,
|
||||||
receive,
|
receive,
|
||||||
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
||||||
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
||||||
|
|||||||
@ -156,7 +156,7 @@
|
|||||||
"select",
|
"select",
|
||||||
{ id: "treasury-credit-member", ...memberSelectProps },
|
{ id: "treasury-credit-member", ...memberSelectProps },
|
||||||
...members.map((member) =>
|
...members.map((member) =>
|
||||||
h("option", { value: member.name }, member.name),
|
h("option", { value: member.uid }, member.name),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -103,7 +103,11 @@
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
store.setCreditLines((currentLines) =>
|
store.setCreditLines((currentLines) =>
|
||||||
currentLines.filter((line) => line.member !== memberName),
|
currentLines.filter((line) =>
|
||||||
|
memberUid
|
||||||
|
? line.uid !== memberUid
|
||||||
|
: line.member !== memberName,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -240,7 +244,7 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
grantCreditLine(memberName, amount) {
|
grantCreditLine(memberUid, amount) {
|
||||||
if (!getters.canManageTreasury()) {
|
if (!getters.canManageTreasury()) {
|
||||||
this.showTreasuryNotice(
|
this.showTreasuryNotice(
|
||||||
"error",
|
"error",
|
||||||
@ -249,7 +253,7 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!memberName) {
|
if (!memberUid) {
|
||||||
this.showTreasuryNotice(
|
this.showTreasuryNotice(
|
||||||
"error",
|
"error",
|
||||||
"Select a member for the credit line.",
|
"Select a member for the credit line.",
|
||||||
@ -265,30 +269,41 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setCreditLines((currentLines) => {
|
const member = store
|
||||||
const existingIndex = currentLines.findIndex(
|
.getMembers()
|
||||||
(line) => line.member === memberName,
|
.find(
|
||||||
|
(entry) =>
|
||||||
|
getters.getMemberUid(entry) === memberUid,
|
||||||
);
|
);
|
||||||
if (existingIndex === -1) {
|
const memberName = member
|
||||||
return [
|
? getters.getMemberName(member)
|
||||||
...currentLines,
|
: "";
|
||||||
{ member: memberName, amount },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedLines = [...currentLines];
|
if (!memberName) {
|
||||||
updatedLines[existingIndex] = {
|
this.showTreasuryNotice(
|
||||||
member: memberName,
|
"error",
|
||||||
amount,
|
"Selected member was not found in the organization roster.",
|
||||||
};
|
);
|
||||||
return updatedLines;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestCreditLine !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Credit line bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridge.requestCreditLine({
|
||||||
|
memberUid,
|
||||||
|
memberName,
|
||||||
|
amount,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.showTreasuryNotice(
|
|
||||||
"success",
|
|
||||||
`Credit line of ${getters.formatCurrency(amount)} assigned to ${memberName}.`,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,9 @@
|
|||||||
[this.getMembers, this.setMembers] = createSignal([
|
[this.getMembers, this.setMembers] = createSignal([
|
||||||
...portalData.members,
|
...portalData.members,
|
||||||
]);
|
]);
|
||||||
[this.getCreditLines, this.setCreditLines] = createSignal([]);
|
[this.getCreditLines, this.setCreditLines] = createSignal([
|
||||||
|
...portalData.creditLines,
|
||||||
|
]);
|
||||||
[this.getTreasuryNotice, this.setTreasuryNotice] = createSignal(
|
[this.getTreasuryNotice, this.setTreasuryNotice] = createSignal(
|
||||||
{
|
{
|
||||||
type: "",
|
type: "",
|
||||||
@ -26,7 +28,7 @@
|
|||||||
hydrateFromPayload(payload) {
|
hydrateFromPayload(payload) {
|
||||||
this.setFunds(payload.portalData.funds || 0);
|
this.setFunds(payload.portalData.funds || 0);
|
||||||
this.setMembers([...(payload.portalData.members || [])]);
|
this.setMembers([...(payload.portalData.members || [])]);
|
||||||
this.setCreditLines([]);
|
this.setCreditLines([...(payload.portalData.creditLines || [])]);
|
||||||
this.setTreasuryNotice({ type: "", text: "" });
|
this.setTreasuryNotice({ type: "", text: "" });
|
||||||
this.setModal(null);
|
this.setModal(null);
|
||||||
this.setOrgDisbanded(false);
|
this.setOrgDisbanded(false);
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
),
|
),
|
||||||
funds: 0,
|
funds: 0,
|
||||||
reputation: 0,
|
reputation: 0,
|
||||||
|
creditLines: [],
|
||||||
members: [],
|
members: [],
|
||||||
fleet: [],
|
fleet: [],
|
||||||
assets: [],
|
assets: [],
|
||||||
@ -77,6 +78,10 @@
|
|||||||
);
|
);
|
||||||
this.portalData.funds = payload.portalData.funds || 0;
|
this.portalData.funds = payload.portalData.funds || 0;
|
||||||
this.portalData.reputation = payload.portalData.reputation || 0;
|
this.portalData.reputation = payload.portalData.reputation || 0;
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.creditLines,
|
||||||
|
payload.portalData.creditLines || [],
|
||||||
|
);
|
||||||
|
|
||||||
replaceArray(
|
replaceArray(
|
||||||
this.portalData.members,
|
this.portalData.members,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initStoreClass.sqf
|
* File: fnc_initStoreClass.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-01-28
|
* Date: 2026-01-28
|
||||||
* Last Update: 2026-03-11
|
* Last Update: 2026-03-12
|
||||||
* Public: Yes
|
* Public: Yes
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -28,6 +28,7 @@ GVAR(StoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_self set ["store", createHashMap];
|
_self set ["store", createHashMap];
|
||||||
_self set ["workspace", createHashMapFromArray [
|
_self set ["workspace", createHashMapFromArray [
|
||||||
["budget", 48000],
|
["budget", 48000],
|
||||||
|
["creditLine", 0],
|
||||||
["availability", "Open"],
|
["availability", "Open"],
|
||||||
["approval", "Field Access"],
|
["approval", "Field Access"],
|
||||||
["moduleState", "Preview"],
|
["moduleState", "Preview"],
|
||||||
@ -41,19 +42,106 @@ GVAR(StoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
}],
|
}],
|
||||||
["buildUIPayload", compileFinal {
|
["buildUIPayload", compileFinal {
|
||||||
private _workspace = _self getOrDefault ["workspace", createHashMap];
|
private _workspace = _self getOrDefault ["workspace", createHashMap];
|
||||||
|
private _budget = _workspace getOrDefault ["budget", 48000];
|
||||||
|
private _creditLine = _workspace getOrDefault ["creditLine", 0];
|
||||||
|
private _cashBalance = 0;
|
||||||
|
private _bankBalance = 0;
|
||||||
|
private _orgFunds = 0;
|
||||||
|
private _orgId = "";
|
||||||
|
private _orgName = "";
|
||||||
|
private _orgOwnerUid = "";
|
||||||
|
private _orgCreditLines = createHashMap;
|
||||||
|
private _playerUid = getPlayerUID player;
|
||||||
|
private _playerVar = toLowerANSI (vehicleVarName player);
|
||||||
|
private _isOrgLeader = false;
|
||||||
|
private _isDefaultOrg = false;
|
||||||
|
private _isDefaultOrgCeo = false;
|
||||||
|
|
||||||
|
if !(isNil QEGVAR(bank,BankClass)) then {
|
||||||
|
_cashBalance = EGVAR(bank,BankClass) call ["get", ["cash", 0]];
|
||||||
|
_bankBalance = EGVAR(bank,BankClass) call ["get", ["bank", 0]];
|
||||||
|
};
|
||||||
|
|
||||||
|
if !(isNil QEGVAR(org,OrgClass)) then {
|
||||||
|
_orgId = EGVAR(org,OrgClass) call ["get", ["id", ""]];
|
||||||
|
_orgName = EGVAR(org,OrgClass) call ["get", ["name", ""]];
|
||||||
|
_orgOwnerUid = EGVAR(org,OrgClass) call ["get", ["owner", ""]];
|
||||||
|
_orgFunds = EGVAR(org,OrgClass) call ["get", ["funds", 0]];
|
||||||
|
_orgCreditLines = EGVAR(org,OrgClass) call ["get", ["credit_lines", createHashMap]];
|
||||||
|
_isDefaultOrg = (_orgId isEqualTo "default") || { toLowerANSI _orgOwnerUid isEqualTo "server" };
|
||||||
|
_isOrgLeader = _orgOwnerUid isEqualTo _playerUid;
|
||||||
|
_isDefaultOrgCeo = _isDefaultOrg && { _playerVar isEqualTo "ceo" };
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_orgCreditLines isEqualType createHashMap) then {
|
||||||
|
private _playerCreditLine = _orgCreditLines getOrDefault [_playerUid, createHashMap];
|
||||||
|
if (_playerCreditLine isEqualType createHashMap) then {
|
||||||
|
_creditLine = _playerCreditLine getOrDefault ["amount", _creditLine];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _canUseOrgFunds = _isOrgLeader || _isDefaultOrgCeo;
|
||||||
|
private _orgFundsEnabled = _canUseOrgFunds && {_orgFunds > 0};
|
||||||
|
private _paymentSources = [
|
||||||
|
createHashMapFromArray [
|
||||||
|
["id", "cash"],
|
||||||
|
["label", "Cash"],
|
||||||
|
["balance", _cashBalance],
|
||||||
|
["enabled", _cashBalance > 0],
|
||||||
|
["detail", "Use on-hand cash carried by the player."]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [
|
||||||
|
["id", "bank"],
|
||||||
|
["label", "Bank"],
|
||||||
|
["balance", _bankBalance],
|
||||||
|
["enabled", _bankBalance > 0],
|
||||||
|
["detail", "Charge the player bank account."]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [
|
||||||
|
["id", "org_funds"],
|
||||||
|
["label", "Org Funds"],
|
||||||
|
["balance", _orgFunds],
|
||||||
|
["enabled", _orgFundsEnabled],
|
||||||
|
["detail", [
|
||||||
|
"Only organization leaders or the default-org CEO can use treasury funds.",
|
||||||
|
[
|
||||||
|
"Charge organization treasury funds.",
|
||||||
|
"No organization funds are currently available."
|
||||||
|
] select _orgFundsEnabled
|
||||||
|
] select _canUseOrgFunds]
|
||||||
|
],
|
||||||
|
createHashMapFromArray [
|
||||||
|
["id", "credit_line"],
|
||||||
|
["label", "Credit Line"],
|
||||||
|
["balance", _creditLine],
|
||||||
|
["enabled", _creditLine > 0],
|
||||||
|
["detail", [
|
||||||
|
"No approved credit line is assigned to this member.",
|
||||||
|
"Use the approved procurement credit line."
|
||||||
|
] select (_creditLine > 0)]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
createHashMapFromArray [
|
createHashMapFromArray [
|
||||||
["session", createHashMapFromArray [
|
["session", createHashMapFromArray [
|
||||||
["actorName", name player],
|
["actorName", name player],
|
||||||
["actorUid", getPlayerUID player],
|
["actorUid", getPlayerUID player],
|
||||||
["approvalRole", _workspace getOrDefault ["approval", "Field Access"]]
|
["approvalRole", _workspace getOrDefault ["approval", "Field Access"]],
|
||||||
|
["orgId", _orgId],
|
||||||
|
["orgName", _orgName],
|
||||||
|
["orgLeader", _isOrgLeader],
|
||||||
|
["defaultOrgCeo", _isDefaultOrgCeo],
|
||||||
|
["canUseOrgFunds", _canUseOrgFunds]
|
||||||
]],
|
]],
|
||||||
["workspace", createHashMapFromArray [
|
["workspace", createHashMapFromArray [
|
||||||
["budget", _workspace getOrDefault ["budget", 48000]],
|
["budget", _budget],
|
||||||
|
["creditLine", _creditLine],
|
||||||
["availability", _workspace getOrDefault ["availability", "Open"]],
|
["availability", _workspace getOrDefault ["availability", "Open"]],
|
||||||
["approval", _workspace getOrDefault ["approval", "Field Access"]],
|
["approval", _workspace getOrDefault ["approval", "Field Access"]],
|
||||||
["moduleState", _workspace getOrDefault ["moduleState", "Preview"]],
|
["moduleState", _workspace getOrDefault ["moduleState", "Preview"]],
|
||||||
["searchTags", _workspace getOrDefault ["searchTags", ["Field", "Logistics", "Issued", "Restricted"]]]
|
["searchTags", _workspace getOrDefault ["searchTags", ["Field", "Logistics", "Issued", "Restricted"]]],
|
||||||
|
["paymentSources", _paymentSources],
|
||||||
|
["defaultPaymentSource", "cash"]
|
||||||
]],
|
]],
|
||||||
["cartItems", []]
|
["cartItems", []]
|
||||||
]
|
]
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
* File: fnc_initStoreUIBridge.sqf
|
* File: fnc_initStoreUIBridge.sqf
|
||||||
* Author: IDSolutions
|
* Author: IDSolutions
|
||||||
* Date: 2026-03-10
|
* Date: 2026-03-10
|
||||||
* Last Update: 2026-03-11
|
* Last Update: 2026-03-12
|
||||||
* Public: No
|
* Public: No
|
||||||
*
|
*
|
||||||
* Description:
|
* Description:
|
||||||
@ -302,7 +302,8 @@ GVAR(StoreUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
params [["_data", createHashMap, [createHashMap]]];
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _items = _data getOrDefault ["items", []];
|
private _items = _data getOrDefault ["items", []];
|
||||||
private _message = format ["Checkout integration is not wired yet. Received %1 queued line(s).", count _items];
|
private _paymentMethod = _data getOrDefault ["paymentMethod", "cash"];
|
||||||
|
private _message = format ["Checkout integration is not wired yet. Received %1 queued line(s) using %2.", count _items, _paymentMethod];
|
||||||
|
|
||||||
diag_log format ["[FORGE:Client:Store] Checkout request received: %1", _data];
|
diag_log format ["[FORGE:Client:Store] Checkout request received: %1", _data];
|
||||||
_self call ["sendBridgeEvent", ["store::checkout::failure", createHashMapFromArray [["message", _message]]]];
|
_self call ["sendBridgeEvent", ["store::checkout::failure", createHashMapFromArray [["message", _message]]]];
|
||||||
|
|||||||
@ -491,6 +491,11 @@ ${scopeSelector} .store-toast.is-error {
|
|||||||
getters.getSelectionKey(state) || "Catalog",
|
getters.getSelectionKey(state) || "Catalog",
|
||||||
)
|
)
|
||||||
: actions.formatTitle(state.view);
|
: actions.formatTitle(state.view);
|
||||||
|
const selectedPaymentSource =
|
||||||
|
getters.getPaymentSourceById(
|
||||||
|
storeConfig,
|
||||||
|
state.selectedPaymentSource,
|
||||||
|
) || null;
|
||||||
|
|
||||||
ensureScopedStyle("storefront-app-shell", appShellCss);
|
ensureScopedStyle("storefront-app-shell", appShellCss);
|
||||||
|
|
||||||
@ -687,6 +692,27 @@ ${scopeSelector} .store-toast.is-error {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "filter-group" },
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "filter-label" },
|
||||||
|
"Payment",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "filter-value" },
|
||||||
|
h("span", null, "Checkout Source"),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "filter-placeholder" },
|
||||||
|
selectedPaymentSource
|
||||||
|
? selectedPaymentSource.label
|
||||||
|
: "Cash",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -55,8 +55,39 @@ ${scopeSelector} .cart-header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
${scopeSelector} .cart-close {
|
${scopeSelector} .cart-close {
|
||||||
min-width: 2rem;
|
min-width: 2.1rem;
|
||||||
height: 2rem;
|
height: 2.1rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
border: 1px solid rgb(173 48 48 / 0.9);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgb(214 92 92) 0%,
|
||||||
|
rgb(175 52 52) 100%
|
||||||
|
);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgb(255 255 255 / 0.26),
|
||||||
|
0 8px 18px rgb(138 61 61 / 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .cart-close:hover {
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgb(226 107 107) 0%,
|
||||||
|
rgb(187 61 61) 100%
|
||||||
|
);
|
||||||
|
border-color: rgb(173 48 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .cart-close:focus-visible {
|
||||||
|
outline: 2px solid rgb(191 80 80 / 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
${scopeSelector} .cart-status,
|
${scopeSelector} .cart-status,
|
||||||
@ -175,6 +206,60 @@ ${scopeSelector} .cart-summary {
|
|||||||
gap: 0.7rem;
|
gap: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-field {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-select {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 2.9rem;
|
||||||
|
padding: 0 0.95rem;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
border: 1px solid var(--store-border);
|
||||||
|
background: rgb(255 255 255 / 0.78);
|
||||||
|
color: var(--store-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-meta,
|
||||||
|
${scopeSelector} .payment-source-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-meta {
|
||||||
|
padding: 0.85rem 0.9rem;
|
||||||
|
border-radius: 0.95rem;
|
||||||
|
border: 1px solid var(--store-border);
|
||||||
|
background: rgb(255 255 255 / 0.44);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-detail {
|
||||||
|
margin: 0.2rem 0 0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--store-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-label {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--store-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-balance {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--store-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .payment-source-state {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--store-text-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
${scopeSelector} .summary-row.total {
|
${scopeSelector} .summary-row.total {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -217,9 +302,24 @@ ${scopeSelector} .cart-empty {
|
|||||||
StorefrontApp.componentFns.Cart = function Cart() {
|
StorefrontApp.componentFns.Cart = function Cart() {
|
||||||
const state = getters.getStoreState(store);
|
const state = getters.getStoreState(store);
|
||||||
const summary = getters.summarizeCart(state.cartItems);
|
const summary = getters.summarizeCart(state.cartItems);
|
||||||
const remainingBudget = Math.max(
|
const paymentSources = getters.getPaymentSources(storeConfig);
|
||||||
|
const selectedPaymentSource =
|
||||||
|
getters.getPaymentSourceById(
|
||||||
|
storeConfig,
|
||||||
|
state.selectedPaymentSource,
|
||||||
|
) || paymentSources[0] || null;
|
||||||
|
const availablePaymentSourceCount = paymentSources.filter(
|
||||||
|
(source) => source.enabled !== false,
|
||||||
|
).length;
|
||||||
|
const selectedPaymentLabel = selectedPaymentSource
|
||||||
|
? selectedPaymentSource.label
|
||||||
|
: "Unavailable";
|
||||||
|
const selectedPaymentBalance = selectedPaymentSource
|
||||||
|
? Number(selectedPaymentSource.balance || 0)
|
||||||
|
: 0;
|
||||||
|
const remainingSourceBalance = Math.max(
|
||||||
0,
|
0,
|
||||||
Number(storeConfig.budget || 0) - summary.total,
|
selectedPaymentBalance - summary.total,
|
||||||
);
|
);
|
||||||
|
|
||||||
ensureScopedStyle("storefront-cart", cartCss);
|
ensureScopedStyle("storefront-cart", cartCss);
|
||||||
@ -254,8 +354,7 @@ ${scopeSelector} .cart-empty {
|
|||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
className:
|
className: "cart-close",
|
||||||
"window-control-btn cart-close is-close",
|
|
||||||
"aria-label": "Close cart",
|
"aria-label": "Close cart",
|
||||||
title: "Close cart",
|
title: "Close cart",
|
||||||
onClick: () => actions.closeCart(),
|
onClick: () => actions.closeCart(),
|
||||||
@ -263,25 +362,13 @@ ${scopeSelector} .cart-empty {
|
|||||||
"X",
|
"X",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{ className: "cart-status" },
|
|
||||||
h("span", { className: "eyebrow" }, "Status"),
|
|
||||||
h(
|
|
||||||
"p",
|
|
||||||
{ className: "section-copy" },
|
|
||||||
state.isCheckingOut
|
|
||||||
? "Checkout request sent through the browser bridge."
|
|
||||||
: "Local cart state is active. Checkout is routed through the same bridge contract used by the org UI.",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "cart-kpi" },
|
{ className: "cart-kpi" },
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "cart-kpi-card" },
|
{ className: "cart-kpi-card" },
|
||||||
h("span", { className: "kpi-label" }, "Lines"),
|
h("span", { className: "kpi-label" }, "Items"),
|
||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "kpi-value" },
|
{ className: "kpi-value" },
|
||||||
@ -291,14 +378,114 @@ ${scopeSelector} .cart-empty {
|
|||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "cart-kpi-card" },
|
{ className: "cart-kpi-card" },
|
||||||
h("span", { className: "kpi-label" }, "Budget"),
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "kpi-label" },
|
||||||
|
"Payment",
|
||||||
|
),
|
||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "kpi-value" },
|
{ className: "kpi-value" },
|
||||||
getters.formatCurrency(storeConfig.budget),
|
selectedPaymentLabel,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "cart-status" },
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "eyebrow" },
|
||||||
|
"Payment Source",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "payment-source-field" },
|
||||||
|
h(
|
||||||
|
"select",
|
||||||
|
{
|
||||||
|
className: "payment-source-select",
|
||||||
|
value: state.selectedPaymentSource,
|
||||||
|
onChange: (event) =>
|
||||||
|
actions.selectPaymentSource(
|
||||||
|
event.target.value,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
paymentSources.map((source) =>
|
||||||
|
h(
|
||||||
|
"option",
|
||||||
|
{
|
||||||
|
value: source.id,
|
||||||
|
disabled:
|
||||||
|
source.enabled === false,
|
||||||
|
},
|
||||||
|
source.enabled === false
|
||||||
|
? `${source.label} (Locked)`
|
||||||
|
: source.label,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
selectedPaymentSource
|
||||||
|
? h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"payment-source-meta",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"payment-source-row",
|
||||||
|
},
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"payment-source-label",
|
||||||
|
},
|
||||||
|
selectedPaymentSource.label,
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"payment-source-balance",
|
||||||
|
},
|
||||||
|
getters.formatCurrency(
|
||||||
|
selectedPaymentSource.balance,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"payment-source-detail",
|
||||||
|
},
|
||||||
|
selectedPaymentSource.detail,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
"payment-source-state",
|
||||||
|
},
|
||||||
|
availablePaymentSourceCount > 0
|
||||||
|
? selectedPaymentSource.enabled ===
|
||||||
|
false
|
||||||
|
? "Locked"
|
||||||
|
: "Available"
|
||||||
|
: "Unavailable",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
@ -436,12 +623,14 @@ ${scopeSelector} .cart-empty {
|
|||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "summary-label" },
|
{ className: "summary-label" },
|
||||||
"Remaining Budget",
|
"Remaining Source",
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
"span",
|
"span",
|
||||||
{ className: "summary-value" },
|
{ className: "summary-value" },
|
||||||
getters.formatCurrency(remainingBudget),
|
getters.formatCurrency(
|
||||||
|
remainingSourceBalance,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
|
|||||||
@ -5,14 +5,51 @@
|
|||||||
actorName: "",
|
actorName: "",
|
||||||
actorUid: "",
|
actorUid: "",
|
||||||
approvalRole: "Field Access",
|
approvalRole: "Field Access",
|
||||||
|
orgId: "",
|
||||||
|
orgName: "",
|
||||||
|
orgLeader: false,
|
||||||
|
defaultOrgCeo: false,
|
||||||
|
canUseOrgFunds: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultStoreConfig = {
|
const defaultStoreConfig = {
|
||||||
budget: 48000,
|
budget: 48000,
|
||||||
|
creditLine: 0,
|
||||||
availability: "Open",
|
availability: "Open",
|
||||||
approval: "Field Access",
|
approval: "Field Access",
|
||||||
moduleState: "Preview",
|
moduleState: "Preview",
|
||||||
searchTags: ["Field", "Logistics", "Issued", "Restricted"],
|
searchTags: ["Field", "Logistics", "Issued", "Restricted"],
|
||||||
|
paymentSources: [
|
||||||
|
{
|
||||||
|
id: "cash",
|
||||||
|
label: "Cash",
|
||||||
|
balance: 0,
|
||||||
|
enabled: false,
|
||||||
|
detail: "Use on-hand cash carried by the player.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "bank",
|
||||||
|
label: "Bank",
|
||||||
|
balance: 0,
|
||||||
|
enabled: false,
|
||||||
|
detail: "Charge the player bank account.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "org_funds",
|
||||||
|
label: "Org Funds",
|
||||||
|
balance: 0,
|
||||||
|
enabled: false,
|
||||||
|
detail: "Only organization leaders or the default-org CEO can use treasury funds.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "credit_line",
|
||||||
|
label: "Credit Line",
|
||||||
|
balance: 0,
|
||||||
|
enabled: false,
|
||||||
|
detail: "No approved credit line is assigned to this member.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultPaymentSource: "cash",
|
||||||
};
|
};
|
||||||
|
|
||||||
function cloneValue(value) {
|
function cloneValue(value) {
|
||||||
|
|||||||
@ -165,7 +165,6 @@
|
|||||||
return nextItems;
|
return nextItems;
|
||||||
});
|
});
|
||||||
|
|
||||||
store.setCartOpen(true);
|
|
||||||
showNotice("success", `${item.name} added to the acquisition queue.`);
|
showNotice("success", `${item.name} added to the acquisition queue.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +198,31 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectPaymentSource(paymentSourceId) {
|
||||||
|
const sourceId = String(paymentSourceId || "").trim();
|
||||||
|
const paymentSources = getters.getPaymentSources(storeConfig);
|
||||||
|
const selectedSource = paymentSources.find(
|
||||||
|
(source) => source.id === sourceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!selectedSource) {
|
||||||
|
showNotice("error", "Selected payment source is unavailable.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedSource.enabled === false) {
|
||||||
|
showNotice(
|
||||||
|
"error",
|
||||||
|
selectedSource.detail ||
|
||||||
|
"Selected payment source is not available.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setSelectedPaymentSource(sourceId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function requestCheckout() {
|
function requestCheckout() {
|
||||||
const cartItems = store.getCartItems();
|
const cartItems = store.getCartItems();
|
||||||
if (cartItems.length === 0) {
|
if (cartItems.length === 0) {
|
||||||
@ -207,10 +231,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const summary = getters.summarizeCart(cartItems);
|
const summary = getters.summarizeCart(cartItems);
|
||||||
if (summary.total > Number(storeConfig.budget || 0)) {
|
const selectedPaymentSource = getters.getPaymentSourceById(
|
||||||
|
storeConfig,
|
||||||
|
store.getSelectedPaymentSource(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!selectedPaymentSource) {
|
||||||
|
showNotice("error", "Select a payment source before checkout.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPaymentSource.enabled === false) {
|
||||||
showNotice(
|
showNotice(
|
||||||
"error",
|
"error",
|
||||||
"Checkout total exceeds the current procurement budget.",
|
selectedPaymentSource.detail ||
|
||||||
|
"Selected payment source is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.total > Number(selectedPaymentSource.balance || 0)) {
|
||||||
|
showNotice(
|
||||||
|
"error",
|
||||||
|
`${selectedPaymentSource.label} cannot cover this checkout total.`,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -226,6 +269,8 @@
|
|||||||
const sent = bridge.requestCheckout({
|
const sent = bridge.requestCheckout({
|
||||||
actorUid: session.actorUid,
|
actorUid: session.actorUid,
|
||||||
actorName: session.actorName,
|
actorName: session.actorName,
|
||||||
|
paymentMethod: selectedPaymentSource.id,
|
||||||
|
paymentLabel: selectedPaymentSource.label,
|
||||||
items: cartItems,
|
items: cartItems,
|
||||||
subtotal: summary.subtotal,
|
subtotal: summary.subtotal,
|
||||||
total: summary.total,
|
total: summary.total,
|
||||||
@ -257,6 +302,7 @@
|
|||||||
incrementCartItem,
|
incrementCartItem,
|
||||||
decrementCartItem,
|
decrementCartItem,
|
||||||
removeCartItem,
|
removeCartItem,
|
||||||
|
selectPaymentSource,
|
||||||
requestCheckout,
|
requestCheckout,
|
||||||
formatTitle: getters.formatTitle,
|
formatTitle: getters.formatTitle,
|
||||||
formatCurrency: getters.formatCurrency,
|
formatCurrency: getters.formatCurrency,
|
||||||
|
|||||||
@ -29,6 +29,8 @@
|
|||||||
});
|
});
|
||||||
[this.getIsCheckingOut, this.setIsCheckingOut] =
|
[this.getIsCheckingOut, this.setIsCheckingOut] =
|
||||||
createSignal(false);
|
createSignal(false);
|
||||||
|
[this.getSelectedPaymentSource, this.setSelectedPaymentSource] =
|
||||||
|
createSignal("cash");
|
||||||
}
|
}
|
||||||
|
|
||||||
resetToCategories() {
|
resetToCategories() {
|
||||||
@ -163,6 +165,58 @@
|
|||||||
this.finishCategoryRequest(categoryKey);
|
this.finishCategoryRequest(categoryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureSelectedPaymentSource(workspace) {
|
||||||
|
const paymentSources = Array.isArray(workspace?.paymentSources)
|
||||||
|
? workspace.paymentSources
|
||||||
|
: [];
|
||||||
|
const currentSource = String(
|
||||||
|
this.getSelectedPaymentSource() || "",
|
||||||
|
).trim();
|
||||||
|
const defaultSource = String(
|
||||||
|
workspace?.defaultPaymentSource || "",
|
||||||
|
).trim();
|
||||||
|
const sourceIds = paymentSources.map((source) =>
|
||||||
|
String(source?.id || "").trim(),
|
||||||
|
);
|
||||||
|
const enabledSource = paymentSources.find(
|
||||||
|
(source) => source && source.enabled !== false,
|
||||||
|
);
|
||||||
|
const defaultAvailable =
|
||||||
|
defaultSource && sourceIds.includes(defaultSource)
|
||||||
|
? paymentSources.find(
|
||||||
|
(source) =>
|
||||||
|
String(source?.id || "").trim() ===
|
||||||
|
defaultSource,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentSource &&
|
||||||
|
sourceIds.includes(currentSource) &&
|
||||||
|
paymentSources.some(
|
||||||
|
(source) =>
|
||||||
|
String(source?.id || "").trim() === currentSource &&
|
||||||
|
source?.enabled !== false,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultAvailable && defaultAvailable.enabled !== false) {
|
||||||
|
this.setSelectedPaymentSource(defaultSource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabledSource) {
|
||||||
|
this.setSelectedPaymentSource(
|
||||||
|
String(enabledSource.id || "cash"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSelectedPaymentSource(defaultSource || "cash");
|
||||||
|
}
|
||||||
|
|
||||||
navigateToBreadcrumb(target) {
|
navigateToBreadcrumb(target) {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case "categories":
|
case "categories":
|
||||||
@ -198,6 +252,9 @@
|
|||||||
this.setCatalogRequestKey("");
|
this.setCatalogRequestKey("");
|
||||||
this.setIsCatalogLoading(false);
|
this.setIsCatalogLoading(false);
|
||||||
this.setCatalogPage(1);
|
this.setCatalogPage(1);
|
||||||
|
this.ensureSelectedPaymentSource(
|
||||||
|
payload?.workspace || payload?.storeConfig || {},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const StorefrontApp = (window.StorefrontApp = window.StorefrontApp || {});
|
const StorefrontApp = (window.StorefrontApp = window.StorefrontApp || {});
|
||||||
const CATALOG_PAGE_SIZE = 24;
|
const CATALOG_PAGE_SIZE = 6;
|
||||||
|
|
||||||
function getSelectionKey(state) {
|
function getSelectionKey(state) {
|
||||||
return (
|
return (
|
||||||
@ -54,6 +54,7 @@
|
|||||||
selectedCategory: store.getSelectedCategory(),
|
selectedCategory: store.getSelectedCategory(),
|
||||||
selectedWeaponSlot: store.getSelectedWeaponSlot(),
|
selectedWeaponSlot: store.getSelectedWeaponSlot(),
|
||||||
selectedVehicleSlot: store.getSelectedVehicleSlot(),
|
selectedVehicleSlot: store.getSelectedVehicleSlot(),
|
||||||
|
selectedPaymentSource: store.getSelectedPaymentSource(),
|
||||||
cartOpen: store.getCartOpen(),
|
cartOpen: store.getCartOpen(),
|
||||||
searchQuery: store.getSearchQuery(),
|
searchQuery: store.getSearchQuery(),
|
||||||
cartItems: store.getCartItems(),
|
cartItems: store.getCartItems(),
|
||||||
@ -241,6 +242,27 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPaymentSources(storeConfig) {
|
||||||
|
const paymentSources = Array.isArray(storeConfig?.paymentSources)
|
||||||
|
? storeConfig.paymentSources
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return paymentSources.map((source) => ({
|
||||||
|
id: String(source?.id || "").trim(),
|
||||||
|
label: String(source?.label || source?.id || "").trim(),
|
||||||
|
balance: Number(source?.balance || 0),
|
||||||
|
enabled: source?.enabled !== false,
|
||||||
|
detail: String(source?.detail || "").trim(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPaymentSourceById(storeConfig, paymentSourceId) {
|
||||||
|
const sourceId = String(paymentSourceId || "").trim();
|
||||||
|
return getPaymentSources(storeConfig).find(
|
||||||
|
(source) => source.id === sourceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
StorefrontApp.getters = {
|
StorefrontApp.getters = {
|
||||||
formatTitle,
|
formatTitle,
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
@ -255,5 +277,7 @@
|
|||||||
getVisibleItemsPage,
|
getVisibleItemsPage,
|
||||||
getCatalogPagination,
|
getCatalogPagination,
|
||||||
summarizeCart,
|
summarizeCart,
|
||||||
|
getPaymentSources,
|
||||||
|
getPaymentSourceById,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -69,6 +69,39 @@ PREP_RECOMPILE_END;
|
|||||||
GVAR(OrgStore) call ["mset", [GVAR(Registry), "org:update", _key, _fieldValuePairs, _sync]];
|
GVAR(OrgStore) call ["mset", [GVAR(Registry), "org:update", _key, _fieldValuePairs, _sync]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestAssignCreditLine), {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_memberUid", "", [""]],
|
||||||
|
["_memberName", "", [""]],
|
||||||
|
["_amount", 0, [0]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _memberUid isEqualTo "" } || { _amount <= 0 }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid credit line request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requester = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_requester isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["assignCreditLine", [_uid, _memberUid, _memberName, _amount]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
private _patch = _result getOrDefault ["patch", createHashMap];
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull && { _patch isNotEqualTo createHashMap }) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [_patch], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach (_result getOrDefault ["memberUids", []]);
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseCreditLine), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to assign credit line."]]
|
||||||
|
]], _requester] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestSaveOrg), {
|
[QGVAR(requestSaveOrg), {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
|
|||||||
_org set ["name", ""];
|
_org set ["name", ""];
|
||||||
_org set ["funds", 0];
|
_org set ["funds", 0];
|
||||||
_org set ["reputation", 0];
|
_org set ["reputation", 0];
|
||||||
|
_org set ["credit_lines", createHashMap];
|
||||||
_org set ["assets", createHashMap];
|
_org set ["assets", createHashMap];
|
||||||
_org set ["fleet", createHashMap];
|
_org set ["fleet", createHashMap];
|
||||||
_org set ["members", createHashMap];
|
_org set ["members", createHashMap];
|
||||||
@ -57,13 +58,15 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
|
|||||||
private _name = _org get "name";
|
private _name = _org get "name";
|
||||||
private _funds = _org get "funds";
|
private _funds = _org get "funds";
|
||||||
private _reputation = _org get "reputation";
|
private _reputation = _org get "reputation";
|
||||||
|
private _creditLines = _org getOrDefault ["credit_lines", createHashMap];
|
||||||
|
|
||||||
[_id, _owner, _name, _funds, _reputation] try {
|
[_id, _owner, _name, _funds, _reputation, _creditLines] try {
|
||||||
if (_id isEqualTo "" || !(_id isEqualType "")) then { throw "Invalid ID!"; };
|
if (_id isEqualTo "" || !(_id isEqualType "")) then { throw "Invalid ID!"; };
|
||||||
if (_owner isEqualTo "" || !(_owner isEqualType "")) then { throw "Invalid Owner!"; };
|
if (_owner isEqualTo "" || !(_owner isEqualType "")) then { throw "Invalid Owner!"; };
|
||||||
if (_name isEqualTo "" || !(_name isEqualType "")) then { throw "Invalid Name!"; };
|
if (_name isEqualTo "" || !(_name isEqualType "")) then { throw "Invalid Name!"; };
|
||||||
if (_funds isEqualTo 0 || !(_funds isEqualType 0)) then { throw "Invalid Funds!"; };
|
if (_funds isEqualTo 0 || !(_funds isEqualType 0)) then { throw "Invalid Funds!"; };
|
||||||
if (_reputation isEqualTo 0 || !(_reputation isEqualType 0)) then { throw "Invalid Reputation!"; };
|
if (_reputation isEqualTo 0 || !(_reputation isEqualType 0)) then { throw "Invalid Reputation!"; };
|
||||||
|
if !(_creditLines isEqualType createHashMap) then { throw "Invalid Credit Lines!"; };
|
||||||
} catch {
|
} catch {
|
||||||
["ERROR", format ["Failed to validate org %1!", _exception]] call EFUNC(common,log);
|
["ERROR", format ["Failed to validate org %1!", _exception]] call EFUNC(common,log);
|
||||||
false
|
false
|
||||||
@ -91,6 +94,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["name", "Forge Dynamics"],
|
["name", "Forge Dynamics"],
|
||||||
["funds", 200000],
|
["funds", 200000],
|
||||||
["reputation", 0],
|
["reputation", 0],
|
||||||
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap]
|
||||||
@ -110,6 +114,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_finalOrg set ["name", "Forge Dynamics"];
|
_finalOrg set ["name", "Forge Dynamics"];
|
||||||
_finalOrg set ["funds", 200000];
|
_finalOrg set ["funds", 200000];
|
||||||
_finalOrg set ["reputation", 0];
|
_finalOrg set ["reputation", 0];
|
||||||
|
_finalOrg set ["credit_lines", createHashMap];
|
||||||
|
|
||||||
private _json = _self call ["toJSON", [_finalOrg]];
|
private _json = _self call ["toJSON", [_finalOrg]];
|
||||||
["org:create", ["default", _json]] call EFUNC(extension,extCall);
|
["org:create", ["default", _json]] call EFUNC(extension,extCall);
|
||||||
@ -153,18 +158,15 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _org = _self call ["fetch", ["org:get", _orgID]];
|
private _org = _self call ["fetch", ["org:get", _orgID]];
|
||||||
if (_org isEqualTo createHashMap) exitWith { _org };
|
if (_org isEqualTo createHashMap) exitWith { _org };
|
||||||
|
_org = GVAR(OrgModel) call ["migrate", [_org]];
|
||||||
|
|
||||||
private _memberRows = _self call ["fetch", ["org:members:get", _orgID]];
|
private _memberRows = _self call ["fetch", ["org:members:get", _orgID]];
|
||||||
if !(_memberRows isEqualType []) then {
|
if !(_memberRows isEqualType []) then { _memberRows = []; };
|
||||||
_memberRows = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _memberMap = createHashMap;
|
private _memberMap = createHashMap;
|
||||||
{
|
{
|
||||||
private _memberUid = _x getOrDefault ["uid", ""];
|
private _memberUid = _x getOrDefault ["uid", ""];
|
||||||
if (_memberUid isNotEqualTo "") then {
|
if (_memberUid isNotEqualTo "") then { _memberMap set [_memberUid, _x]; };
|
||||||
_memberMap set [_memberUid, _x];
|
|
||||||
};
|
|
||||||
} forEach _memberRows;
|
} forEach _memberRows;
|
||||||
|
|
||||||
_org set ["members", _memberMap];
|
_org set ["members", _memberMap];
|
||||||
@ -407,6 +409,86 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result set ["members", _memberResults];
|
_result set ["members", _memberResults];
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
|
["assignCreditLine", compileFinal {
|
||||||
|
params [
|
||||||
|
["_requesterUid", "", [""]],
|
||||||
|
["_memberUid", "", [""]],
|
||||||
|
["_memberName", "", [""]],
|
||||||
|
["_amount", 0, [0]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["patch", createHashMap],
|
||||||
|
["memberUids", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
_requesterUid isEqualTo ""
|
||||||
|
|| { _memberUid isEqualTo "" }
|
||||||
|
|| { _amount <= 0 }
|
||||||
|
) exitWith {
|
||||||
|
_result set ["message", "A valid requester, member, and credit amount are required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
||||||
|
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
||||||
|
if (_orgID isEqualTo "") then {
|
||||||
|
_orgID = "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _org = _self call ["loadById", [_orgID]];
|
||||||
|
if (_org isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Unable to load organization data for credit line assignment."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _isDefaultOrg = (_orgID isEqualTo "default") || { toLower _ownerUid isEqualTo "server" };
|
||||||
|
private _isDefaultOrgCeo = _isDefaultOrg
|
||||||
|
&& { _requesterPlayer isNotEqualTo objNull }
|
||||||
|
&& { toLowerANSI (vehicleVarName _requesterPlayer) isEqualTo "ceo" };
|
||||||
|
private _canManageTreasury = (_ownerUid isEqualTo _requesterUid) || _isDefaultOrgCeo;
|
||||||
|
|
||||||
|
if !_canManageTreasury exitWith {
|
||||||
|
_result set ["message", "Only the organization leader or CEO can manage treasury actions."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _members = _org getOrDefault ["members", createHashMap];
|
||||||
|
private _memberRecord = _members getOrDefault [_memberUid, createHashMap];
|
||||||
|
if (_memberRecord isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Selected member was not found in the organization roster."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _resolvedMemberName = _memberRecord getOrDefault ["name", _memberName];
|
||||||
|
if (_resolvedMemberName isEqualTo "") then {
|
||||||
|
_resolvedMemberName = _memberName;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _creditLines = +(_org getOrDefault ["credit_lines", createHashMap]);
|
||||||
|
_creditLines set [_memberUid, createHashMapFromArray [
|
||||||
|
["uid", _memberUid],
|
||||||
|
["name", _resolvedMemberName],
|
||||||
|
["amount", _amount]
|
||||||
|
]];
|
||||||
|
|
||||||
|
private _patch = _self call ["set", [GVAR(Registry), "org:update", _orgID, "credit_lines", _creditLines, true]];
|
||||||
|
private _memberUids = keys _members;
|
||||||
|
if !(_requesterUid in _memberUids) then {
|
||||||
|
_memberUids pushBack _requesterUid;
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", format ["Credit line of $%1 assigned to %2.", [_amount] call BIS_fnc_numberText, _resolvedMemberName]];
|
||||||
|
_result set ["patch", _patch];
|
||||||
|
_result set ["memberUids", _memberUids];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["register", compileFinal {
|
["register", compileFinal {
|
||||||
params [["_uid", "", [""]], ["_orgName", "", [""]]];
|
params [["_uid", "", [""]], ["_orgName", "", [""]]];
|
||||||
|
|
||||||
@ -454,6 +536,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["name", _orgName],
|
["name", _orgName],
|
||||||
["funds", 0],
|
["funds", 0],
|
||||||
["reputation", 0],
|
["reputation", 0],
|
||||||
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap]
|
||||||
@ -466,9 +549,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_createResult isNotEqualTo "") then {
|
if (_createResult isNotEqualTo "") then { _org = _self call ["toHashMap", [_createResult]]; };
|
||||||
_org = _self call ["toHashMap", [_createResult]];
|
|
||||||
};
|
|
||||||
|
|
||||||
_org set ["members", createHashMap];
|
_org set ["members", createHashMap];
|
||||||
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||||
|
|||||||
1
arma/server/addons/store/$PBOPREFIX$
Normal file
1
arma/server/addons/store/$PBOPREFIX$
Normal file
@ -0,0 +1 @@
|
|||||||
|
forge\forge_server\addons\store
|
||||||
17
arma/server/addons/store/CfgEventHandlers.hpp
Normal file
17
arma/server/addons/store/CfgEventHandlers.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class Extended_PreStart_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_preStart));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Extended_PreInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Extended_PostInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_postInit));
|
||||||
|
};
|
||||||
|
};
|
||||||
3
arma/server/addons/store/README.md
Normal file
3
arma/server/addons/store/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# forge_server_store
|
||||||
|
|
||||||
|
Description for this addon
|
||||||
2
arma/server/addons/store/XEH_PREP.hpp
Normal file
2
arma/server/addons/store/XEH_PREP.hpp
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// PREP(initStore);
|
||||||
|
// PREP(initStoreStore);
|
||||||
3
arma/server/addons/store/XEH_postInit.sqf
Normal file
3
arma/server/addons/store/XEH_postInit.sqf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
// call FUNC(initStore);
|
||||||
7
arma/server/addons/store/XEH_preInit.sqf
Normal file
7
arma/server/addons/store/XEH_preInit.sqf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
PREP_RECOMPILE_START;
|
||||||
|
#include "XEH_PREP.hpp"
|
||||||
|
PREP_RECOMPILE_END;
|
||||||
|
|
||||||
|
// private _category = [QUOTE(MOD_NAME), LLSTRING(displayName)];
|
||||||
2
arma/server/addons/store/XEH_preStart.sqf
Normal file
2
arma/server/addons/store/XEH_preStart.sqf
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
#include "XEH_PREP.hpp"
|
||||||
20
arma/server/addons/store/config.cpp
Normal file
20
arma/server/addons/store/config.cpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
class CfgPatches {
|
||||||
|
class ADDON {
|
||||||
|
author = AUTHOR;
|
||||||
|
authors[] = {"J.Schmidt"};
|
||||||
|
url = ECSTRING(main,url);
|
||||||
|
name = COMPONENT_NAME;
|
||||||
|
requiredVersion = REQUIRED_VERSION;
|
||||||
|
requiredAddons[] = {
|
||||||
|
"forge_server_main",
|
||||||
|
"forge_server_common"
|
||||||
|
};
|
||||||
|
units[] = {};
|
||||||
|
weapons[] = {};
|
||||||
|
VERSION_CONFIG;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "CfgEventHandlers.hpp"
|
||||||
9
arma/server/addons/store/script_component.hpp
Normal file
9
arma/server/addons/store/script_component.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#define COMPONENT store
|
||||||
|
#define COMPONENT_BEAUTIFIED Store
|
||||||
|
#include "\forge\forge_server\addons\main\script_mod.hpp"
|
||||||
|
|
||||||
|
// #define DEBUG_MODE_FULL
|
||||||
|
// #define DISABLE_COMPILE_CACHE
|
||||||
|
// #define ENABLE_PERFORMANCE_COUNTERS
|
||||||
|
|
||||||
|
#include "\forge\forge_server\addons\main\script_macros.hpp"
|
||||||
8
arma/server/addons/store/stringtable.xml
Normal file
8
arma/server/addons/store/stringtable.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project name="FFE">
|
||||||
|
<Package name="Store">
|
||||||
|
<Key ID="STR_forge_server_store_displayName">
|
||||||
|
<English>Store</English>
|
||||||
|
</Key>
|
||||||
|
</Package>
|
||||||
|
</Project>
|
||||||
@ -10,6 +10,6 @@ pub use actor::Actor;
|
|||||||
pub use bank::Bank;
|
pub use bank::Bank;
|
||||||
pub use garage::{Garage, HitPoints, Vehicle};
|
pub use garage::{Garage, HitPoints, Vehicle};
|
||||||
pub use locker::{Item, Locker};
|
pub use locker::{Item, Locker};
|
||||||
pub use org::{MemberSummary, Org};
|
pub use org::{CreditLineSummary, MemberSummary, Org};
|
||||||
pub use v_garage::{VGarage, VehicleCategory};
|
pub use v_garage::{VGarage, VehicleCategory};
|
||||||
pub use v_locker::{EquipmentCategory, VLocker};
|
pub use v_locker::{EquipmentCategory, VLocker};
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
use arma_rs::{FromArma, IntoArma};
|
use arma_rs::{FromArma, IntoArma};
|
||||||
use forge_shared::OrgValidationError;
|
use forge_shared::OrgValidationError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CreditLineSummary {
|
||||||
|
pub uid: String,
|
||||||
|
pub name: String,
|
||||||
|
pub amount: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Org {
|
pub struct Org {
|
||||||
@ -12,6 +20,8 @@ pub struct Org {
|
|||||||
pub funds: f64,
|
pub funds: f64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reputation: i64,
|
pub reputation: i64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub credit_lines: HashMap<String, CreditLineSummary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -28,6 +38,7 @@ impl Org {
|
|||||||
name: name.into(),
|
name: name.into(),
|
||||||
funds: 0.0,
|
funds: 0.0,
|
||||||
reputation: 0,
|
reputation: 0,
|
||||||
|
credit_lines: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
org.validate()?;
|
org.validate()?;
|
||||||
@ -65,6 +76,26 @@ impl Org {
|
|||||||
return Err(OrgValidationError::InvalidName(self.name.clone()));
|
return Err(OrgValidationError::InvalidName(self.name.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (uid, credit_line) in &self.credit_lines {
|
||||||
|
let resolved_uid = if credit_line.uid.trim().is_empty() {
|
||||||
|
uid
|
||||||
|
} else {
|
||||||
|
&credit_line.uid
|
||||||
|
};
|
||||||
|
|
||||||
|
if !resolved_uid.chars().all(|c| c.is_numeric()) || resolved_uid.len() != 17 {
|
||||||
|
return Err(OrgValidationError::InvalidCreditLineUid(
|
||||||
|
resolved_uid.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if credit_line.amount < 0.0 {
|
||||||
|
return Err(OrgValidationError::NegativeCreditLine(
|
||||||
|
resolved_uid.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,9 @@
|
|||||||
//!
|
//!
|
||||||
//! For full documentation, architecture, and examples, see the [crate README](../README.md).
|
//! For full documentation, architecture, and examples, see the [crate README](../README.md).
|
||||||
|
|
||||||
use forge_models::{MemberSummary, Org};
|
use forge_models::{CreditLineSummary, MemberSummary, Org};
|
||||||
use forge_repositories::OrgRepository;
|
use forge_repositories::OrgRepository;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Service layer implementation for organization business logic and operations.
|
/// Service layer implementation for organization business logic and operations.
|
||||||
///
|
///
|
||||||
@ -138,6 +139,21 @@ impl<R: OrgRepository> OrgService<R> {
|
|||||||
return Err("Reputation must be an integer".to_string());
|
return Err("Reputation must be an integer".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"credit_lines" => {
|
||||||
|
if value.is_null() {
|
||||||
|
updated_org.credit_lines = HashMap::new();
|
||||||
|
} else {
|
||||||
|
updated_org.credit_lines = serde_json::from_value::<
|
||||||
|
HashMap<String, CreditLineSummary>,
|
||||||
|
>(value.clone())
|
||||||
|
.map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"Credit lines must be an object of member credit entries: {}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("Unknown field: {}", field));
|
return Err(format!("Unknown field: {}", field));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,9 +80,11 @@ pub enum OrgValidationError {
|
|||||||
EmptyOwner,
|
EmptyOwner,
|
||||||
EmptyName,
|
EmptyName,
|
||||||
NegativeFunds,
|
NegativeFunds,
|
||||||
|
NegativeCreditLine(String),
|
||||||
InvalidId(String),
|
InvalidId(String),
|
||||||
InvalidOwner(String),
|
InvalidOwner(String),
|
||||||
InvalidName(String),
|
InvalidName(String),
|
||||||
|
InvalidCreditLineUid(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for OrgValidationError {
|
impl fmt::Display for OrgValidationError {
|
||||||
@ -94,6 +96,9 @@ impl fmt::Display for OrgValidationError {
|
|||||||
OrgValidationError::NegativeFunds => {
|
OrgValidationError::NegativeFunds => {
|
||||||
write!(f, "Organization funds cannot be negative")
|
write!(f, "Organization funds cannot be negative")
|
||||||
}
|
}
|
||||||
|
OrgValidationError::NegativeCreditLine(uid) => {
|
||||||
|
write!(f, "Credit line for '{}' cannot be negative", uid)
|
||||||
|
}
|
||||||
OrgValidationError::InvalidId(id) => write!(
|
OrgValidationError::InvalidId(id) => write!(
|
||||||
f,
|
f,
|
||||||
"Invalid organization ID '{}' - must contain only alphanumeric characters and underscores",
|
"Invalid organization ID '{}' - must contain only alphanumeric characters and underscores",
|
||||||
@ -107,6 +112,11 @@ impl fmt::Display for OrgValidationError {
|
|||||||
"Invalid organization name '{}' - cannot exceed 100 characters or contain control characters",
|
"Invalid organization name '{}' - cannot exceed 100 characters or contain control characters",
|
||||||
name
|
name
|
||||||
),
|
),
|
||||||
|
OrgValidationError::InvalidCreditLineUid(uid) => write!(
|
||||||
|
f,
|
||||||
|
"Invalid credit line UID '{}' - must be a 17-digit Steam ID",
|
||||||
|
uid
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user