Implement org credit line debt and bank repayment flow #2
@ -37,10 +37,6 @@ GVAR(BankRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||
["markLoaded", compileFinal {
|
||||
if !(_self getOrDefault ["isLoaded", false]) then { _self set ["isLoaded", true]; };
|
||||
true
|
||||
}],
|
||||
["save", compileFinal {
|
||||
[SRPC(bank,requestSaveBank), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||
_self set ["lastSave", time];
|
||||
}]
|
||||
];
|
||||
|
||||
|
||||
@ -37,10 +37,6 @@ GVAR(OrgRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||
["markLoaded", compileFinal {
|
||||
if !(_self getOrDefault ["isLoaded", false]) then { _self set ["isLoaded", true]; };
|
||||
true
|
||||
}],
|
||||
["save", compileFinal {
|
||||
[SRPC(org,requestSaveOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||
_self set ["lastSave", time];
|
||||
}]
|
||||
];
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -19,6 +19,45 @@
|
||||
target.splice(0, target.length, ...cloneValue(source));
|
||||
}
|
||||
|
||||
function normalizeRecord(value) {
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const isEntryArray = value.every(
|
||||
(entry) =>
|
||||
Array.isArray(entry) &&
|
||||
entry.length >= 2 &&
|
||||
typeof entry[0] === "string",
|
||||
);
|
||||
|
||||
if (isEntryArray) {
|
||||
return Object.fromEntries(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === "string" && value.trim() !== "") {
|
||||
try {
|
||||
return normalizeRecord(JSON.parse(value));
|
||||
} catch (_error) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeCollection(value) {
|
||||
const source = Array.isArray(value)
|
||||
? value
|
||||
: value && typeof value === "object"
|
||||
? Object.values(value)
|
||||
: [];
|
||||
|
||||
return source.map(normalizeRecord).filter(Boolean);
|
||||
}
|
||||
|
||||
OrgPortal.data = {
|
||||
portalData: {
|
||||
org: Object.assign(
|
||||
@ -80,25 +119,28 @@
|
||||
this.portalData.reputation = payload.portalData.reputation || 0;
|
||||
replaceArray(
|
||||
this.portalData.creditLines,
|
||||
payload.portalData.creditLines || [],
|
||||
normalizeCollection(payload.portalData.creditLines),
|
||||
);
|
||||
|
||||
replaceArray(
|
||||
this.portalData.members,
|
||||
payload.portalData.members || [],
|
||||
normalizeCollection(payload.portalData.members),
|
||||
);
|
||||
replaceArray(
|
||||
this.portalData.fleet,
|
||||
normalizeCollection(payload.portalData.fleet),
|
||||
);
|
||||
replaceArray(this.portalData.fleet, payload.portalData.fleet || []);
|
||||
replaceArray(
|
||||
this.portalData.assets,
|
||||
payload.portalData.assets || [],
|
||||
normalizeCollection(payload.portalData.assets),
|
||||
);
|
||||
replaceArray(
|
||||
this.portalData.activity,
|
||||
payload.portalData.activity || [],
|
||||
normalizeCollection(payload.portalData.activity),
|
||||
);
|
||||
replaceArray(
|
||||
this.portalData.roadmap,
|
||||
payload.portalData.roadmap || [],
|
||||
normalizeCollection(payload.portalData.roadmap),
|
||||
);
|
||||
|
||||
replaceObject(this.session, payload.session || {});
|
||||
|
||||
@ -3,6 +3,45 @@
|
||||
const { createSignal } = window.RegistryApp.runtime;
|
||||
const { portalData } = OrgPortal.data;
|
||||
|
||||
function normalizeRecord(value) {
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const isEntryArray = value.every(
|
||||
(entry) =>
|
||||
Array.isArray(entry) &&
|
||||
entry.length >= 2 &&
|
||||
typeof entry[0] === "string",
|
||||
);
|
||||
|
||||
if (isEntryArray) {
|
||||
return Object.fromEntries(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value === "string" && value.trim() !== "") {
|
||||
try {
|
||||
return normalizeRecord(JSON.parse(value));
|
||||
} catch (_error) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeCollection(value) {
|
||||
const source = Array.isArray(value)
|
||||
? value
|
||||
: value && typeof value === "object"
|
||||
? Object.values(value)
|
||||
: [];
|
||||
|
||||
return source.map(normalizeRecord).filter(Boolean);
|
||||
}
|
||||
|
||||
class OrgPortalStore {
|
||||
constructor() {
|
||||
[this.getFunds, this.setFunds] = createSignal(portalData.funds);
|
||||
@ -37,11 +76,13 @@
|
||||
|
||||
this.setFunds(nextPortalData.funds || 0);
|
||||
this.setReputation(nextPortalData.reputation || 0);
|
||||
this.setMembers([...(nextPortalData.members || [])]);
|
||||
this.setCreditLines([...(nextPortalData.creditLines || [])]);
|
||||
this.setFleet([...(nextPortalData.fleet || [])]);
|
||||
this.setAssets([...(nextPortalData.assets || [])]);
|
||||
this.setActivity([...(nextPortalData.activity || [])]);
|
||||
this.setMembers([...normalizeCollection(nextPortalData.members)]);
|
||||
this.setCreditLines([
|
||||
...normalizeCollection(nextPortalData.creditLines),
|
||||
]);
|
||||
this.setFleet([...normalizeCollection(nextPortalData.fleet)]);
|
||||
this.setAssets([...normalizeCollection(nextPortalData.assets)]);
|
||||
this.setActivity([...normalizeCollection(nextPortalData.activity)]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,15 +20,6 @@ PREP_RECOMPILE_END;
|
||||
GVAR(BankStore) call ["hydrateSession", [_uid, _mode, _resetAuthorization]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSaveBank), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Bank] Empty/Invalid UID!" };
|
||||
|
||||
private _finalData = GVAR(BankStore) call ["save", [_uid]];
|
||||
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalData]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestDeposit), {
|
||||
params [["_uid", "", [""]], ["_amount", 0, [0]]];
|
||||
|
||||
|
||||
@ -13,40 +13,6 @@ PREP_RECOMPILE_END;
|
||||
GVAR(GarageStore) call ["init", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestGetGarage), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Garage] Empty/Invalid UID!" };
|
||||
|
||||
private _finalData = GVAR(GarageStore) call ["get", [_uid, _field]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(garage,responseSyncGarage), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSetGarage), {
|
||||
params [["_uid", "", [""]], ["_key", "", [""]], ["_value", nil, [[], "", 0, false, createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "" || _key isEqualTo "") exitWith { diag_log "[FORGE:Server:Garage] Empty/Invalid UID or Key!" };
|
||||
|
||||
private _hashMap = GVAR(GarageStore) call ["set", [_uid, _key, _value, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(garage,responseSyncGarage), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestMSetGarage), {
|
||||
params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Garage] Empty/Invalid UID!" };
|
||||
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:Garage] Empty/Invalid field pairs!" };
|
||||
|
||||
private _hashMap = GVAR(GarageStore) call ["mset", [_uid, _fieldValuePairs, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(garage,responseSyncGarage), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSaveGarage), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
@ -58,13 +24,6 @@ PREP_RECOMPILE_END;
|
||||
[CRPC(garage,responseSyncGarage), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestRemoveGarage), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Garage] Empty/Invalid UID!" };
|
||||
GVAR(GarageStore) call ["remove", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestStoreVehicle), {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
@ -144,40 +103,6 @@ PREP_RECOMPILE_END;
|
||||
GVAR(VGarageStore) call ["init", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestGetVG), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:VGarage] Empty/Invalid UID!" };
|
||||
|
||||
private _finalData = GVAR(VGarageStore) call ["get", [_uid, _field]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(garage,responseSyncVG), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSetVG), {
|
||||
params [["_uid", "", [""]], ["_key", "", [""]], ["_value", nil, [[], "", 0, false, createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "" || _key isEqualTo "") exitWith { diag_log "[FORGE:Server:VGarage] Empty/Invalid UID or Key!" };
|
||||
|
||||
private _hashMap = GVAR(VGarageStore) call ["set", [_uid, _key, _value, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(garage,responseSyncVG), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestMSetVG), {
|
||||
params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:VGarage] Empty/Invalid UID!" };
|
||||
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:VGarage] Empty/Invalid field pairs!" };
|
||||
|
||||
private _hashMap = GVAR(VGarageStore) call ["mset", [_uid, _fieldValuePairs, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(garage,responseSyncVG), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSaveVG), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
@ -189,9 +114,3 @@ PREP_RECOMPILE_END;
|
||||
[CRPC(garage,responseSyncVG), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestRemoveVG), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:VGarage] Empty/Invalid UID!" };
|
||||
GVAR(VGarageStore) call ["remove", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
@ -67,93 +67,12 @@ GVAR(GarageBaseStore) = compileFinal createHashMapFromArray [
|
||||
[CRPC(garage,responseInitGarage), [_garage], _player] call CFUNC(targetEvent);
|
||||
_garage
|
||||
}],
|
||||
["get", compileFinal {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
private _garage = _self call ["loadHotGarage", [_uid, false]];
|
||||
if (_garage isEqualTo createHashMap) then {
|
||||
_garage = _self call ["loadHotGarage", [_uid, true]];
|
||||
};
|
||||
|
||||
if (_field isEqualTo "") exitWith { _garage };
|
||||
_garage getOrDefault [_field, createHashMap]
|
||||
}],
|
||||
["override", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_data", createHashMap, [createHashMap]],
|
||||
["_save", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_data isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _garage = _self call ["callHotGarage", ["garage:hot:override", [_uid, toJSON _data]]];
|
||||
if (_save && { _garage isNotEqualTo createHashMap }) then {
|
||||
private _savedGarage = _self call ["callHotGarage", ["garage:hot:save", [_uid]]];
|
||||
if (_savedGarage isNotEqualTo createHashMap) then {
|
||||
_garage = _savedGarage;
|
||||
} else {
|
||||
_garage = createHashMap;
|
||||
};
|
||||
};
|
||||
|
||||
_garage
|
||||
}],
|
||||
["set", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_field", "", [""]],
|
||||
["_value", nil, [0, "", [], false, createHashMap, objNull, grpNull]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _garage = _self call ["get", [_uid, ""]];
|
||||
if !(_garage isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_garage set [_field, _value];
|
||||
private _updatedGarage = _self call ["override", [_uid, _garage, _sync]];
|
||||
if !(_updatedGarage isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedGarage isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
createHashMapFromArray [[_field, _updatedGarage getOrDefault [_field, _value]]]
|
||||
}],
|
||||
["mset", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_fieldValuePairs", createHashMap, [createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_fieldValuePairs isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _garage = _self call ["get", [_uid, ""]];
|
||||
if !(_garage isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
{ _garage set [_x, _y]; } forEach _fieldValuePairs;
|
||||
private _updatedGarage = _self call ["override", [_uid, _garage, _sync]];
|
||||
if !(_updatedGarage isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedGarage isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
+_fieldValuePairs
|
||||
}],
|
||||
["save", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
_self call ["callHotGarage", ["garage:hot:save", [_uid]]]
|
||||
}],
|
||||
["remove", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { false };
|
||||
|
||||
["garage:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
_isSuccess && { _result isEqualTo "OK" }
|
||||
}],
|
||||
["storeVehicle", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
|
||||
@ -84,159 +84,11 @@ GVAR(VGBaseStore) = compileFinal createHashMapFromArray [
|
||||
[CRPC(garage,responseInitVG), [_garage], _player] call CFUNC(targetEvent);
|
||||
_garage
|
||||
}],
|
||||
["get", compileFinal {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
private _garage = _self call ["loadHotVGarage", [_uid, false]];
|
||||
if (_garage isEqualTo createHashMap) then {
|
||||
_garage = _self call ["loadHotVGarage", [_uid, true]];
|
||||
};
|
||||
|
||||
if (_field isEqualTo "") exitWith { _garage };
|
||||
_garage getOrDefault [_field, []]
|
||||
}],
|
||||
["override", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_data", createHashMap, [createHashMap]],
|
||||
["_save", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_data isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _garage = _self call ["callHotVGarage", ["owned:garage:hot:override", [_uid, toJSON _data]]];
|
||||
if (_save && { _garage isNotEqualTo createHashMap }) then {
|
||||
private _savedGarage = _self call ["callHotVGarage", ["owned:garage:hot:save", [_uid]]];
|
||||
if (_savedGarage isNotEqualTo createHashMap) then {
|
||||
_garage = _savedGarage;
|
||||
} else {
|
||||
_garage = createHashMap;
|
||||
};
|
||||
};
|
||||
|
||||
_garage
|
||||
}],
|
||||
["set", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_field", "", [""]],
|
||||
["_value", nil, [[], "", 0, false, createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _garage = _self call ["loadHotVGarage", [_uid, false]];
|
||||
if !(_garage isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_garage set [_field, _value];
|
||||
private _updatedGarage = _self call ["override", [_uid, _garage, _sync]];
|
||||
if !(_updatedGarage isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedGarage isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
createHashMapFromArray [[_field, _updatedGarage getOrDefault [_field, _value]]]
|
||||
}],
|
||||
["mset", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_fieldValuePairs", createHashMap, [createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_fieldValuePairs isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _garage = _self call ["loadHotVGarage", [_uid, false]];
|
||||
if !(_garage isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
{ _garage set [_x, _y]; } forEach _fieldValuePairs;
|
||||
private _updatedGarage = _self call ["override", [_uid, _garage, _sync]];
|
||||
if !(_updatedGarage isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedGarage isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
+_fieldValuePairs
|
||||
}],
|
||||
["save", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
_self call ["callHotVGarage", ["owned:garage:hot:save", [_uid]]]
|
||||
}],
|
||||
["remove", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { false };
|
||||
|
||||
["owned:garage:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
_isSuccess && { _result isEqualTo "OK" }
|
||||
}],
|
||||
["grantVehicles", compileFinal {
|
||||
params [["_uid", "", [""]], ["_vehicles", [], [[]]], ["_commit", false, [false]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Virtual garage grant failed."],
|
||||
["patch", createHashMap],
|
||||
["granted", []],
|
||||
["garage", createHashMap]
|
||||
];
|
||||
|
||||
private _garage = +(_self call ["loadHotVGarage", [_uid, false]]);
|
||||
if (_garage isEqualTo createHashMap) then {
|
||||
_garage = GVAR(VGarageModel) call ["defaults", []];
|
||||
};
|
||||
|
||||
private _patch = createHashMap;
|
||||
private _granted = [];
|
||||
private _categoriesToSync = [];
|
||||
|
||||
{
|
||||
private _className = _x getOrDefault ["classname", ""];
|
||||
private _category = toLowerANSI (_x getOrDefault ["category", ""]);
|
||||
|
||||
if (_className isEqualTo "") exitWith {
|
||||
_result set ["message", "Vehicle checkout entry was missing a classname."];
|
||||
};
|
||||
|
||||
if !(_category in ["cars", "armor", "helis", "planes", "naval", "other"]) exitWith {
|
||||
_result set ["message", format ["Vehicle category '%1' is unsupported.", _category]];
|
||||
};
|
||||
|
||||
private _categoryUnlocks = +(_garage getOrDefault [_category, []]);
|
||||
_categoryUnlocks pushBackUnique _className;
|
||||
_garage set [_category, _categoryUnlocks];
|
||||
_categoriesToSync pushBackUnique _category;
|
||||
_granted pushBack (createHashMapFromArray [
|
||||
["classname", _className],
|
||||
["category", _category]
|
||||
]);
|
||||
} forEach _vehicles;
|
||||
|
||||
{
|
||||
private _category = _x;
|
||||
_patch set [_category, _garage getOrDefault [_category, []]];
|
||||
} forEach _categoriesToSync;
|
||||
|
||||
if (_commit) then {
|
||||
private _savedGarage = _self call ["override", [_uid, _garage, false]];
|
||||
if !(_savedGarage isEqualType createHashMap) exitWith {
|
||||
_result set ["message", "Virtual garage cache update returned invalid data."];
|
||||
_result
|
||||
};
|
||||
if (_savedGarage isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to update virtual garage cache."];
|
||||
_result
|
||||
};
|
||||
_garage = _savedGarage;
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["granted", _granted];
|
||||
_result set ["garage", _garage];
|
||||
_result
|
||||
}]
|
||||
];
|
||||
|
||||
|
||||
@ -13,40 +13,6 @@ PREP_RECOMPILE_END;
|
||||
GVAR(LockerStore) call ["init", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestGetLocker), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Locker] Empty/Invalid UID!" };
|
||||
|
||||
private _finalData = GVAR(LockerStore) call ["get", [_uid, _field]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(locker,responseSyncLocker), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSetLocker), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]], ["_value", nil, [[], "", 0, false, createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "" || _field isEqualTo "") exitWith { diag_log "[FORGE:Server:Locker] Empty/Invalid UID or Field!" };
|
||||
|
||||
private _hashMap = GVAR(LockerStore) call ["set", [_uid, _field, _value, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(locker,responseSyncLocker), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestMSetLocker), {
|
||||
params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Locker] Empty/Invalid UID!" };
|
||||
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:Locker] Empty/Invalid field pairs!" };
|
||||
|
||||
private _hashMap = GVAR(LockerStore) call ["mset", [_uid, _fieldValuePairs, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(locker,responseSyncLocker), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSaveLocker), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
@ -68,13 +34,6 @@ PREP_RECOMPILE_END;
|
||||
[CRPC(locker,responseSyncLocker), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestRemoveLocker), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Locker] Empty/Invalid UID!" };
|
||||
GVAR(LockerStore) call ["remove", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestInitVA), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
@ -82,40 +41,6 @@ PREP_RECOMPILE_END;
|
||||
GVAR(VAStore) call ["init", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestGetVA), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:VArsenal] Empty/Invalid UID!" };
|
||||
|
||||
private _finalData = GVAR(VAStore) call ["get", [_uid, _field]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(locker,responseSyncVA), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSetVA), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]], ["_value", nil, [[], "", 0, false, createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "" || _field isEqualTo "") exitWith { diag_log "[FORGE:Server:VArsenal] Empty/Invalid UID or Field!" };
|
||||
|
||||
private _hashMap = GVAR(VAStore) call ["set", [_uid, _field, _value, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(locker,responseSyncVA), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestMSetVA), {
|
||||
params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:VArsenal] Empty/Invalid UID!" };
|
||||
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:VArsenal] Empty/Invalid field pairs!" };
|
||||
|
||||
private _hashMap = GVAR(VAStore) call ["mset", [_uid, _fieldValuePairs, _sync]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(locker,responseSyncVA), [_hashMap], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSaveVA), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
@ -127,9 +52,3 @@ PREP_RECOMPILE_END;
|
||||
[CRPC(locker,responseSyncVA), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestRemoveVA), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:VArsenal] Empty/Invalid UID!" };
|
||||
GVAR(VAStore) call ["remove", [_uid]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
@ -67,17 +67,6 @@ GVAR(LockerBaseStore) = compileFinal createHashMapFromArray [
|
||||
[CRPC(locker,responseInitLocker), [_locker], _player] call CFUNC(targetEvent);
|
||||
_locker
|
||||
}],
|
||||
["get", compileFinal {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
private _locker = _self call ["loadHotLocker", [_uid, false]];
|
||||
if (_locker isEqualTo createHashMap) then {
|
||||
_locker = _self call ["loadHotLocker", [_uid, true]];
|
||||
};
|
||||
|
||||
if (_field isEqualTo "") exitWith { _locker };
|
||||
_locker getOrDefault [_field, createHashMap]
|
||||
}],
|
||||
["override", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
@ -100,133 +89,11 @@ GVAR(LockerBaseStore) = compileFinal createHashMapFromArray [
|
||||
|
||||
_locker
|
||||
}],
|
||||
["set", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_field", "", [""]],
|
||||
["_value", nil, [0, "", [], false, createHashMap, objNull, grpNull]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _locker = _self call ["get", [_uid, ""]];
|
||||
if !(_locker isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_locker set [_field, _value];
|
||||
private _updatedLocker = _self call ["override", [_uid, _locker, _sync]];
|
||||
if !(_updatedLocker isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedLocker isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
createHashMapFromArray [[_field, _updatedLocker getOrDefault [_field, _value]]]
|
||||
}],
|
||||
["mset", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_fieldValuePairs", createHashMap, [createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_fieldValuePairs isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _locker = _self call ["get", [_uid, ""]];
|
||||
if !(_locker isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
{ _locker set [_x, _y]; } forEach _fieldValuePairs;
|
||||
private _updatedLocker = _self call ["override", [_uid, _locker, _sync]];
|
||||
if !(_updatedLocker isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedLocker isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
+_fieldValuePairs
|
||||
}],
|
||||
["save", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
_self call ["callHotLocker", ["locker:hot:save", [_uid]]]
|
||||
}],
|
||||
["remove", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { false };
|
||||
|
||||
["locker:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
_isSuccess && { _result isEqualTo "OK" }
|
||||
}],
|
||||
["grantItems", compileFinal {
|
||||
params [["_uid", "", [""]], ["_items", [], [[]]], ["_commit", false, [false]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Locker grant failed."],
|
||||
["patch", createHashMap],
|
||||
["granted", []],
|
||||
["locker", createHashMap]
|
||||
];
|
||||
|
||||
private _locker = +(_self call ["get", [_uid, ""]]);
|
||||
private _patch = createHashMap;
|
||||
private _granted = [];
|
||||
|
||||
{
|
||||
private _className = _x getOrDefault ["classname", ""];
|
||||
private _category = toLowerANSI (_x getOrDefault ["category", ""]);
|
||||
private _quantity = floor ((_x getOrDefault ["quantity", 0]) max 0);
|
||||
private _lockerCategory = switch (_category) do {
|
||||
case "item";
|
||||
case "attachment": { "item" };
|
||||
case "weapon": { "weapon" };
|
||||
case "magazine": { "magazine" };
|
||||
case "backpack": { "backpack" };
|
||||
default { "" };
|
||||
};
|
||||
|
||||
if (_className isEqualTo "" || { _lockerCategory isEqualTo "" } || { _quantity <= 0 }) then {
|
||||
["WARN", format ["Skipping invalid locker grant entry: %1 (category: %2)", _className, _category]] call EFUNC(common,log);
|
||||
} else {
|
||||
private _entry = +(_locker getOrDefault [_className, createHashMap]);
|
||||
private _amount = _entry getOrDefault ["amount", 0];
|
||||
private _updatedEntry = createHashMapFromArray [
|
||||
["amount", (_amount + _quantity)],
|
||||
["classname", _className],
|
||||
["category", _lockerCategory]
|
||||
];
|
||||
|
||||
_locker set [_className, _updatedEntry];
|
||||
_patch set [_className, _updatedEntry];
|
||||
_granted pushBack (createHashMapFromArray [
|
||||
["classname", _className],
|
||||
["category", _lockerCategory],
|
||||
["quantity", _quantity]
|
||||
]);
|
||||
};
|
||||
} forEach _items;
|
||||
|
||||
if ((count (keys _locker)) > 25) exitWith {
|
||||
_result set ["message", "Locker capacity would exceed 25 unique items. Clear space before checkout."];
|
||||
_result
|
||||
};
|
||||
|
||||
if (_commit) then {
|
||||
private _savedLocker = _self call ["override", [_uid, _locker, false]];
|
||||
if !(_savedLocker isEqualType createHashMap) exitWith {
|
||||
_result set ["message", "Locker cache update returned invalid data."];
|
||||
_result
|
||||
};
|
||||
if (_savedLocker isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to update locker cache."];
|
||||
_result
|
||||
};
|
||||
_locker = _savedLocker;
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["granted", _granted];
|
||||
_result set ["locker", _locker];
|
||||
_result
|
||||
}]
|
||||
];
|
||||
|
||||
|
||||
@ -82,152 +82,11 @@ GVAR(VABaseStore) = compileFinal createHashMapFromArray [
|
||||
[CRPC(locker,responseInitVA), [_arsenal], _player] call CFUNC(targetEvent);
|
||||
_arsenal
|
||||
}],
|
||||
["get", compileFinal {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
private _arsenal = _self call ["loadHotVArsenal", [_uid, false]];
|
||||
if (_arsenal isEqualTo createHashMap) then {
|
||||
_arsenal = _self call ["loadHotVArsenal", [_uid, true]];
|
||||
};
|
||||
|
||||
if (_field isEqualTo "") exitWith { _arsenal };
|
||||
_arsenal getOrDefault [_field, []]
|
||||
}],
|
||||
["override", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_data", createHashMap, [createHashMap]],
|
||||
["_save", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_data isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _arsenal = _self call ["callHotVArsenal", ["owned:locker:hot:override", [_uid, toJSON _data]]];
|
||||
if (_save && { _arsenal isNotEqualTo createHashMap }) then {
|
||||
private _savedArsenal = _self call ["callHotVArsenal", ["owned:locker:hot:save", [_uid]]];
|
||||
if (_savedArsenal isNotEqualTo createHashMap) then {
|
||||
_arsenal = _savedArsenal;
|
||||
} else {
|
||||
_arsenal = createHashMap;
|
||||
};
|
||||
};
|
||||
|
||||
_arsenal
|
||||
}],
|
||||
["set", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_field", "", [""]],
|
||||
["_value", nil, [[], "", 0, false, createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _arsenal = _self call ["get", [_uid, ""]];
|
||||
if !(_arsenal isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_arsenal set [_field, _value];
|
||||
private _updatedArsenal = _self call ["override", [_uid, _arsenal, _sync]];
|
||||
if !(_updatedArsenal isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedArsenal isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
createHashMapFromArray [[_field, _updatedArsenal getOrDefault [_field, _value]]]
|
||||
}],
|
||||
["mset", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_fieldValuePairs", createHashMap, [createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
if !(_fieldValuePairs isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _arsenal = _self call ["get", [_uid, ""]];
|
||||
if !(_arsenal isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
{ _arsenal set [_x, _y]; } forEach _fieldValuePairs;
|
||||
private _updatedArsenal = _self call ["override", [_uid, _arsenal, _sync]];
|
||||
if !(_updatedArsenal isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedArsenal isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
+_fieldValuePairs
|
||||
}],
|
||||
["save", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
_self call ["callHotVArsenal", ["owned:locker:hot:save", [_uid]]]
|
||||
}],
|
||||
["remove", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { false };
|
||||
|
||||
["owned:locker:hot:remove", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
_isSuccess && { _result isEqualTo "OK" }
|
||||
}],
|
||||
["unlockItems", compileFinal {
|
||||
params [["_uid", "", [""]], ["_items", [], [[]]], ["_commit", false, [false]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "VA unlock failed."],
|
||||
["patch", createHashMap],
|
||||
["arsenal", createHashMap]
|
||||
];
|
||||
|
||||
private _arsenal = +(_self call ["get", [_uid, ""]]);
|
||||
if (_arsenal isEqualTo createHashMap) then {
|
||||
_arsenal = GVAR(VArsenalModel) call ["defaults", []];
|
||||
};
|
||||
private _patch = createHashMap;
|
||||
private _categoriesToSync = [];
|
||||
|
||||
{
|
||||
private _item = _x;
|
||||
private _className = _item getOrDefault ["classname", ""];
|
||||
private _category = toLowerANSI (_item getOrDefault ["category", ""]);
|
||||
private _arsenalCategory = switch (_category) do {
|
||||
case "item": { "items" };
|
||||
case "weapon": { "weapons" };
|
||||
case "magazine": { "magazines" };
|
||||
case "backpack": { "backpacks" };
|
||||
default { "items" };
|
||||
};
|
||||
|
||||
private _categoryUnlocks = +(_arsenal getOrDefault [_arsenalCategory, []]);
|
||||
_categoryUnlocks pushBackUnique _className;
|
||||
_arsenal set [_arsenalCategory, _categoryUnlocks];
|
||||
_categoriesToSync pushBackUnique _arsenalCategory;
|
||||
} forEach _items;
|
||||
|
||||
{
|
||||
private _category = _x;
|
||||
private _categoryUnlocks = _arsenal getOrDefault [_category, []];
|
||||
_patch set [_category, _categoryUnlocks];
|
||||
} forEach _categoriesToSync;
|
||||
|
||||
if (_commit) then {
|
||||
private _savedArsenal = _self call ["override", [_uid, _arsenal, false]];
|
||||
if !(_savedArsenal isEqualType createHashMap) exitWith {
|
||||
_result set ["message", "Virtual arsenal cache update returned invalid data."];
|
||||
_result
|
||||
};
|
||||
if (_savedArsenal isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to update virtual arsenal cache."];
|
||||
_result
|
||||
};
|
||||
_arsenal = _savedArsenal;
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["arsenal", _arsenal];
|
||||
_result
|
||||
}]
|
||||
];
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ if (isNil QEGVAR(locker,LockerStore)) then { call EFUNC(locker,initLockerStore);
|
||||
if (isNil QEGVAR(locker,VAStore)) then { call EFUNC(locker,initVAStore); };
|
||||
|
||||
// Org
|
||||
if (isNil QEGVAR(org,OrgPayloadBuilder)) then { call EFUNC(org,initPayloadBuilder); };
|
||||
if (isNil QEGVAR(org,OrgStore)) then { call EFUNC(org,initOrgStore); };
|
||||
|
||||
// Store
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
PREP(initPayloadBuilder);
|
||||
PREP(initOrgStore);
|
||||
PREP(memberService);
|
||||
PREP(treasuryService);
|
||||
|
||||
@ -52,38 +52,6 @@ PREP_RECOMPILE_END;
|
||||
[CRPC(org,responseCreateOrg), [_result], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestGetOrg), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID!" };
|
||||
|
||||
private _key = GVAR(OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
private _finalData = GVAR(OrgStore) call ["get", [_key, _field]];
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
|
||||
[CRPC(org,responseSyncOrg), [_finalData], _player] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSetOrg), {
|
||||
params [["_uid", "", [""]], ["_field", "", [""]], ["_value", nil, [[], "", 0, false, createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "" || _field isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID or Field!" };
|
||||
|
||||
private _key = GVAR(OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
GVAR(OrgStore) call ["set", [_key, _field, _value, _sync]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestMSetOrg), {
|
||||
params [["_uid", "", [""]], ["_fieldValuePairs", createHashMap, [createHashMap]], ["_sync", false, [false]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID!" };
|
||||
if ((_fieldValuePairs isEqualTo createHashMap) || !(_fieldValuePairs isEqualType createHashMap)) exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid field pairs!" };
|
||||
|
||||
private _key = GVAR(OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
|
||||
GVAR(OrgStore) call ["mset", [_key, _fieldValuePairs, _sync]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestAssignCreditLine), {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
@ -117,24 +85,6 @@ PREP_RECOMPILE_END;
|
||||
]], _requester] call CFUNC(targetEvent);
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSaveOrg), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID!" };
|
||||
|
||||
private _key = GVAR(OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
GVAR(OrgStore) call ["saveById", [_key]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestRemoveOrg), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { diag_log "[FORGE:Server:Org] Empty/Invalid UID!" };
|
||||
|
||||
private _key = GVAR(OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
GVAR(OrgStore) call ["delete", [_key]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestLeaveOrg), {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
|
||||
@ -21,9 +21,6 @@
|
||||
* call forge_server_org_fnc_initOrgStore
|
||||
*/
|
||||
|
||||
if (isNil QGVAR(OrgMembershipService)) then { call FUNC(memberService); };
|
||||
if (isNil QGVAR(OrgTreasuryService)) then { call FUNC(treasuryService); };
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(OrgModel) = compileFinal createHashMapObject [[
|
||||
["#type", "OrgModel"],
|
||||
@ -190,6 +187,31 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
|
||||
_self call ["syncHotOrg", [_data]]
|
||||
}],
|
||||
["callHotOrgEnvelope", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||
|
||||
if (_function isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
[_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !(_isSuccess) exitWith { createHashMap };
|
||||
if !(_result isEqualType "") exitWith { createHashMap };
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Org extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
|
||||
createHashMap
|
||||
};
|
||||
|
||||
private _data = fromJSON _result;
|
||||
if !(_data isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
if ("org" in _data) then {
|
||||
private _syncedOrg = _self call ["syncHotOrg", [_data getOrDefault ["org", createHashMap]]];
|
||||
if (_syncedOrg isNotEqualTo createHashMap) then {
|
||||
_data set ["org", _syncedOrg];
|
||||
};
|
||||
};
|
||||
|
||||
_data
|
||||
}],
|
||||
["syncHotOrg", compileFinal {
|
||||
params [["_org", createHashMap, [createHashMap]]];
|
||||
|
||||
@ -211,10 +233,48 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
_orgID
|
||||
}],
|
||||
["loadForUid", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
private _orgID = _self call ["resolveOrgIdForUid", [_uid]];
|
||||
_self call ["loadById", [_orgID]]
|
||||
["resolveActorName", compileFinal {
|
||||
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
||||
|
||||
private _memberName = _actor getOrDefault ["name", ""];
|
||||
if (_memberName isEqualTo "" && { _player isNotEqualTo objNull }) then {
|
||||
_memberName = name _player;
|
||||
};
|
||||
if (_memberName isEqualTo "") then { _memberName = "Unknown"; };
|
||||
_memberName
|
||||
}],
|
||||
["applyActorOrganization", compileFinal {
|
||||
params [["_uid", "", [""]], ["_orgID", "", [""]], ["_actor", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", _orgID, false]];
|
||||
private _updatedActor = EGVAR(actor,ActorStore) call ["get", [_uid, ""]];
|
||||
if (
|
||||
!(_updatedActor isEqualType createHashMap)
|
||||
|| { _updatedActor isEqualTo createHashMap }
|
||||
|| { (_updatedActor getOrDefault ["organization", ""]) isNotEqualTo _orgID }
|
||||
) then {
|
||||
private _forcedActor = +_actor;
|
||||
if !(_forcedActor isEqualType createHashMap) then {
|
||||
_forcedActor = EGVAR(actor,ActorModel) call ["defaults", []];
|
||||
_forcedActor set ["uid", _uid];
|
||||
};
|
||||
|
||||
_forcedActor set ["organization", _orgID];
|
||||
_updatedActor = EGVAR(actor,ActorStore) call ["override", [_uid, _forcedActor, false]];
|
||||
if (_updatedActor isEqualType createHashMap && { _updatedActor isNotEqualTo createHashMap }) then {
|
||||
_actorPatch = createHashMapFromArray [["organization", _orgID]];
|
||||
};
|
||||
};
|
||||
|
||||
if (
|
||||
!(_updatedActor isEqualType createHashMap)
|
||||
|| { _updatedActor isEqualTo createHashMap }
|
||||
|| { (_updatedActor getOrDefault ["organization", ""]) isNotEqualTo _orgID }
|
||||
) exitWith { createHashMap };
|
||||
|
||||
_actorPatch
|
||||
}],
|
||||
["loadHotOrg", compileFinal {
|
||||
params [["_orgID", "", [""]], ["_initialize", false, [false]]];
|
||||
@ -235,80 +295,6 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
if (_field isEqualTo "") exitWith { _org };
|
||||
_org getOrDefault [_field, createHashMap]
|
||||
}],
|
||||
["override", compileFinal {
|
||||
params [
|
||||
["_orgID", "", [""]],
|
||||
["_org", createHashMap, [createHashMap]],
|
||||
["_save", false, [false]]
|
||||
];
|
||||
|
||||
if (_orgID isEqualTo "") exitWith { createHashMap };
|
||||
if !(_org isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _normalizedOrg = +_org;
|
||||
_normalizedOrg set ["id", _normalizedOrg getOrDefault ["id", _orgID]];
|
||||
|
||||
private _result = _self call ["callHotOrg", ["org:hot:override", [_orgID, toJSON _normalizedOrg]]];
|
||||
if (_save && { _result isNotEqualTo createHashMap }) then {
|
||||
private _savedOrg = _self call ["callHotOrg", ["org:hot:save", [_orgID]]];
|
||||
if (_savedOrg isNotEqualTo createHashMap) then {
|
||||
_result = _savedOrg;
|
||||
} else {
|
||||
_result = createHashMap;
|
||||
};
|
||||
};
|
||||
|
||||
_result
|
||||
}],
|
||||
["set", compileFinal {
|
||||
params [
|
||||
["_orgID", "", [""]],
|
||||
["_field", "", [""]],
|
||||
["_value", nil, [[], "", 0, false, createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_orgID isEqualTo "" || { _field isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _org = _self call ["get", [_orgID, ""]];
|
||||
if !(_org isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
_org set [_field, _value];
|
||||
private _updatedOrg = _self call ["override", [_orgID, _org, _sync]];
|
||||
if !(_updatedOrg isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedOrg isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
createHashMapFromArray [[_field, _updatedOrg getOrDefault [_field, _value]]]
|
||||
}],
|
||||
["mset", compileFinal {
|
||||
params [
|
||||
["_orgID", "", [""]],
|
||||
["_fieldValuePairs", createHashMap, [createHashMap]],
|
||||
["_sync", false, [false]]
|
||||
];
|
||||
|
||||
if (_orgID isEqualTo "") exitWith { createHashMap };
|
||||
if !(_fieldValuePairs isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _org = _self call ["get", [_orgID, ""]];
|
||||
if !(_org isEqualType createHashMap) exitWith { createHashMap };
|
||||
|
||||
{ _org set [_x, _y]; } forEach _fieldValuePairs;
|
||||
private _updatedOrg = _self call ["override", [_orgID, _org, _sync]];
|
||||
if !(_updatedOrg isEqualType createHashMap) exitWith { createHashMap };
|
||||
if (_updatedOrg isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
+_fieldValuePairs
|
||||
}],
|
||||
["verifyMember", compileFinal {
|
||||
GVAR(OrgMembershipService) call ["verifyMember", _this]
|
||||
}],
|
||||
["addMember", compileFinal {
|
||||
GVAR(OrgMembershipService) call ["addMember", _this]
|
||||
}],
|
||||
["removeMember", compileFinal {
|
||||
GVAR(OrgMembershipService) call ["removeMember", _this]
|
||||
}],
|
||||
["delete", compileFinal {
|
||||
params [["_orgID", "", [""]]];
|
||||
|
||||
@ -332,158 +318,213 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
_result set ["success", true];
|
||||
_result
|
||||
}],
|
||||
["restoreDefaultMembership", compileFinal {
|
||||
GVAR(OrgMembershipService) call ["restoreDefaultMembership", _this]
|
||||
["ensureMember", compileFinal {
|
||||
params [["_orgID", "", [""]], ["_uid", "", [""]], ["_memberName", "", [""]]];
|
||||
|
||||
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["orgId", _orgID],
|
||||
["memberUid", _uid],
|
||||
["memberName", _memberName]
|
||||
];
|
||||
|
||||
_self call ["callHotOrg", ["org:hot:ensure_member", [toJSON _context]]]
|
||||
}],
|
||||
["leave", compileFinal {
|
||||
GVAR(OrgMembershipService) call ["leave", _this]
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", ""],
|
||||
["actorPatch", createHashMap],
|
||||
["notification", []]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
||||
private _memberName = _self call ["resolveActorName", [_uid, _player, _actor]];
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _uid],
|
||||
["requesterName", _memberName],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:leave", [toJSON _context]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Unable to leave the organization."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _actorOrg = _envelope getOrDefault ["actorOrganization", "default"];
|
||||
private _actorPatch = _self call ["applyActorOrganization", [_uid, _actorOrg, _actor]];
|
||||
if (_actorPatch isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to restore default organization membership."];
|
||||
_result
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", _envelope getOrDefault ["message", "You returned to the default organization."]];
|
||||
_result set ["actorPatch", _actorPatch];
|
||||
_result set ["notification", ["info", "Organization Left", _result get "message", 6000]];
|
||||
_result
|
||||
}],
|
||||
["disband", compileFinal {
|
||||
GVAR(OrgMembershipService) call ["disband", _this]
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", ""],
|
||||
["members", []]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
||||
private _memberName = _self call ["resolveActorName", [_uid, _player, _actor]];
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _uid],
|
||||
["requesterName", _memberName],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:disband", [toJSON _context]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to disband organization."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _memberResults = [];
|
||||
{
|
||||
private _memberUid = _x getOrDefault ["uid", ""];
|
||||
if (_memberUid isEqualTo "") then { continue; };
|
||||
|
||||
private _memberActor = EGVAR(actor,Registry) getOrDefault [_memberUid, createHashMap];
|
||||
private _actorPatch = _self call ["applyActorOrganization", [_memberUid, _x getOrDefault ["actorOrganization", "default"], _memberActor]];
|
||||
if (_actorPatch isEqualTo createHashMap) then {
|
||||
["WARNING", format ["Failed to restore actor organization for %1 after org disband.", _memberUid]] call EFUNC(common,log);
|
||||
};
|
||||
|
||||
private _responseMessage = _x getOrDefault ["message", _envelope getOrDefault ["message", "Organization disbanded."]];
|
||||
private _notificationParams = [
|
||||
["warning", "Organization Disbanded", _responseMessage, 6000],
|
||||
["success", "Organization Disbanded", _responseMessage, 6000]
|
||||
] select (_x getOrDefault ["requester", false]);
|
||||
|
||||
_memberResults pushBack (createHashMapFromArray [
|
||||
["uid", _memberUid],
|
||||
["requester", _x getOrDefault ["requester", false]],
|
||||
["message", _responseMessage],
|
||||
["notification", _notificationParams],
|
||||
["actorPatch", _actorPatch]
|
||||
]);
|
||||
} forEach (_envelope getOrDefault ["members", []]);
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", _envelope getOrDefault ["message", "Organization disbanded."]];
|
||||
_result set ["members", _memberResults];
|
||||
_result
|
||||
}],
|
||||
["assignCreditLine", compileFinal {
|
||||
GVAR(OrgTreasuryService) call ["assignCreditLine", _this]
|
||||
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 _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||
private _requesterIsDefaultOrgCeo = (
|
||||
_requesterPlayer isNotEqualTo objNull
|
||||
&& { _orgID isEqualTo "default" }
|
||||
&& { toLowerANSI (vehicleVarName _requesterPlayer) isEqualTo "ceo" }
|
||||
);
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID],
|
||||
["requesterIsDefaultOrgCeo", _requesterIsDefaultOrgCeo],
|
||||
["memberUid", _memberUid],
|
||||
["memberName", _memberName],
|
||||
["amount", _amount]
|
||||
];
|
||||
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:assign_credit_line", [toJSON _context]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Unable to assign credit line."];
|
||||
_result
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", _envelope getOrDefault ["message", "Credit line assigned."]];
|
||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||
_result
|
||||
}],
|
||||
["buildPortalPayload", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) exitWith { createHashMap };
|
||||
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _org = _self call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) then {
|
||||
_org = _self call ["init", [_uid]];
|
||||
};
|
||||
if (_org isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
// Ensure the requesting player's membership is present in the cached roster
|
||||
// before shaping the portal payload. This prevents stale org caches from
|
||||
// omitting the current member while still resolving owner metadata.
|
||||
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||
|
||||
private _name = _org getOrDefault ["name", ""];
|
||||
private _id = _org getOrDefault ["id", _orgID];
|
||||
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||
private _funds = _org getOrDefault ["funds", 0];
|
||||
private _reputation = _org getOrDefault ["reputation", 0];
|
||||
private _creditLinesRaw = _org getOrDefault ["credit_lines", createHashMap];
|
||||
private _assetsRaw = _org getOrDefault ["assets", createHashMap];
|
||||
private _fleetRaw = _org getOrDefault ["fleet", createHashMap];
|
||||
private _membersRaw = _org getOrDefault ["members", createHashMap];
|
||||
private _isDefaultOrg = (_org getOrDefault ["default", false])
|
||||
|| { toLower _id isEqualTo "default" }
|
||||
|| { toLower _ownerUid isEqualTo "server" };
|
||||
|
||||
private _playerName = name _player;
|
||||
private _playerVar = vehicleVarName _player;
|
||||
private _sessionRole = "Member";
|
||||
private _sessionIsCeo = _isDefaultOrg && { _playerVar isEqualTo "ceo" };
|
||||
private _ownerName = ["", "Server"] select (toLower _ownerUid isEqualTo "server");
|
||||
|
||||
private _membersList = [];
|
||||
{
|
||||
private _memberData = _y;
|
||||
private _memberName = _memberData getOrDefault ["name", "Unknown"];
|
||||
private _memberUid = _memberData getOrDefault ["uid", ""];
|
||||
|
||||
if (_memberUid isEqualTo _ownerUid && { _ownerName isEqualTo "" }) then { _ownerName = _memberName; };
|
||||
if (_memberUid isEqualTo _uid) then { _sessionRole = "Member"; };
|
||||
|
||||
_membersList pushBack (createHashMapFromArray [
|
||||
["uid", _memberUid],
|
||||
["name", _memberName]
|
||||
]);
|
||||
} forEach _membersRaw;
|
||||
|
||||
if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _uid }) then { _ownerName = _playerName; };
|
||||
if (_ownerName isEqualTo "" && { _ownerUid isNotEqualTo "" }) then { _ownerName = "Unknown Owner"; };
|
||||
if (_ownerUid isEqualTo _uid) then { _sessionRole = "Leader"; };
|
||||
|
||||
private _assetsList = [];
|
||||
{
|
||||
private _category = _x;
|
||||
{
|
||||
private _assetData = _y;
|
||||
private _className = _assetData getOrDefault ["classname", ""];
|
||||
private _displayName = _className;
|
||||
{
|
||||
private _cfg = _x >> _className;
|
||||
if (isClass _cfg) exitWith {
|
||||
private _resolvedName = getText (_cfg >> "displayName");
|
||||
if (_resolvedName isNotEqualTo "") then { _displayName = _resolvedName; };
|
||||
};
|
||||
} forEach [
|
||||
configFile >> "CfgWeapons",
|
||||
configFile >> "CfgMagazines",
|
||||
configFile >> "CfgVehicles",
|
||||
configFile >> "CfgGlasses"
|
||||
];
|
||||
|
||||
_assetsList pushBack (createHashMapFromArray [
|
||||
["name", _displayName],
|
||||
["type", _assetData getOrDefault ["type", _category]],
|
||||
["quantity", str (_assetData getOrDefault ["quantity", 0])]
|
||||
]);
|
||||
} forEach _y;
|
||||
} forEach _assetsRaw;
|
||||
|
||||
private _fleetList = [];
|
||||
{
|
||||
private _vehicleData = _y;
|
||||
_fleetList pushBack (createHashMapFromArray [
|
||||
["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]],
|
||||
["type", _vehicleData getOrDefault ["type", "other"]],
|
||||
["status", _vehicleData getOrDefault ["status", "Unknown"]],
|
||||
["damage", _vehicleData getOrDefault ["damage", "0%"]]
|
||||
]);
|
||||
} 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 [
|
||||
["session", createHashMapFromArray [
|
||||
["actorName", _playerName],
|
||||
["actorUid", _uid],
|
||||
["role", _sessionRole],
|
||||
["ceo", _sessionIsCeo]
|
||||
]],
|
||||
["portalData", createHashMapFromArray [
|
||||
["org", createHashMapFromArray [
|
||||
["name", _name],
|
||||
["tag", _id],
|
||||
["owner", _ownerName],
|
||||
["ownerUid", _ownerUid],
|
||||
["isDefault", _isDefaultOrg]
|
||||
]],
|
||||
["funds", _funds],
|
||||
["reputation", _reputation],
|
||||
["creditLines", _creditLinesList],
|
||||
["members", _membersList],
|
||||
["fleet", _fleetList],
|
||||
["assets", _assetsList],
|
||||
["activity", []]
|
||||
]]
|
||||
]
|
||||
}],
|
||||
["buildChargeResult", compileFinal {
|
||||
GVAR(OrgTreasuryService) call ["buildChargeResult", _this]
|
||||
GVAR(OrgPayloadBuilder) call ["buildPortalPayload", [_uid]]
|
||||
}],
|
||||
["chargeCheckout", compileFinal {
|
||||
GVAR(OrgTreasuryService) call ["chargeCheckout", _this]
|
||||
params [["_requesterUid", "", [""]], ["_requesterPlayer", objNull, [objNull]], ["_source", "org_funds", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Unable to process organization payment."],
|
||||
["patch", createHashMap],
|
||||
["memberUids", []]
|
||||
];
|
||||
|
||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
||||
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _requesterIsDefaultOrgCeo = (
|
||||
_requesterPlayer isNotEqualTo objNull
|
||||
&& { _orgID isEqualTo "default" }
|
||||
&& { toLowerANSI (vehicleVarName _requesterPlayer) isEqualTo "ceo" }
|
||||
);
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID],
|
||||
["requesterIsDefaultOrgCeo", _requesterIsDefaultOrgCeo],
|
||||
["source", _source],
|
||||
["amount", _amount],
|
||||
["commit", _commit]
|
||||
];
|
||||
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:charge_checkout", [toJSON _context]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith { _result };
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||
_result
|
||||
}],
|
||||
["saveById", compileFinal {
|
||||
params [["_orgID", "", [""]]];
|
||||
@ -515,42 +556,29 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
};
|
||||
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
|
||||
|
||||
private _org = _self call ["loadById", [_resolvedOrgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Organization data is unavailable for asset updates."];
|
||||
_result
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _resolvedOrgID],
|
||||
["commit", _commit]
|
||||
];
|
||||
private _assetSeeds = _assets apply {
|
||||
createHashMapFromArray [
|
||||
["classname", _x getOrDefault ["classname", ""]],
|
||||
["category", toLowerANSI (_x getOrDefault ["category", "items"])],
|
||||
["quantity", floor ((_x getOrDefault ["quantity", 0]) max 0)]
|
||||
]
|
||||
};
|
||||
|
||||
private _assetMap = +(_org getOrDefault ["assets", createHashMap]);
|
||||
|
||||
{
|
||||
private _className = _x getOrDefault ["classname", ""];
|
||||
private _category = toLowerANSI (_x getOrDefault ["category", "items"]);
|
||||
private _quantity = floor ((_x getOrDefault ["quantity", 0]) max 0);
|
||||
if (_className isEqualTo "" || { _quantity <= 0 }) then { continue; };
|
||||
|
||||
private _categoryMap = +(_assetMap getOrDefault [_category, createHashMap]);
|
||||
private _assetEntry = +(_categoryMap getOrDefault [_className, createHashMap]);
|
||||
|
||||
private _existingQuantity = _assetEntry getOrDefault ["quantity", 0];
|
||||
_categoryMap set [_className, createHashMapFromArray [
|
||||
["classname", _className],
|
||||
["type", _category],
|
||||
["quantity", (_existingQuantity + _quantity)]
|
||||
]];
|
||||
_assetMap set [_category, _categoryMap];
|
||||
} forEach _assets;
|
||||
|
||||
private _patch = _self call ["mset", [_resolvedOrgID, createHashMapFromArray [["assets", _assetMap]], false]];
|
||||
if (_patch isEqualTo createHashMap) exitWith {
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:add_assets", [toJSON _context, toJSON _assetSeeds]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to update organization asset cache."];
|
||||
_result
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["memberUids", GVAR(OrgTreasuryService) call ["resolveOrgMemberUids", [_org, _requesterUid]]];
|
||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||
_result
|
||||
}],
|
||||
["addFleetVehicles", compileFinal {
|
||||
@ -576,52 +604,28 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
};
|
||||
if (_resolvedOrgID isEqualTo "") then { _resolvedOrgID = "default"; };
|
||||
|
||||
private _org = _self call ["loadById", [_resolvedOrgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Organization data is unavailable for fleet updates."];
|
||||
_result
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _resolvedOrgID],
|
||||
["commit", _commit]
|
||||
];
|
||||
private _fleetSeeds = _vehicles apply {
|
||||
createHashMapFromArray [
|
||||
["classname", _x getOrDefault ["classname", ""]],
|
||||
["category", toLowerANSI (_x getOrDefault ["category", "other"])]
|
||||
]
|
||||
};
|
||||
|
||||
private _fleet = +(_org getOrDefault ["fleet", createHashMap]);
|
||||
private _fleetIndex = count (keys _fleet);
|
||||
|
||||
{
|
||||
private _className = _x getOrDefault ["classname", ""];
|
||||
private _category = toLowerANSI (_x getOrDefault ["category", "other"]);
|
||||
if (_className isEqualTo "") exitWith {
|
||||
_result set ["message", "Vehicle fleet entry was missing a classname."];
|
||||
};
|
||||
|
||||
private _fleetKey = format ["%1_%2", _className, _fleetIndex];
|
||||
while { _fleetKey in (keys _fleet) } do {
|
||||
_fleetIndex = _fleetIndex + 1;
|
||||
_fleetKey = format ["%1_%2", _className, _fleetIndex];
|
||||
};
|
||||
|
||||
private _displayName = getText (configFile >> "CfgVehicles" >> _className >> "displayName");
|
||||
if (_displayName isEqualTo "") then { _displayName = _className; };
|
||||
|
||||
_fleet set [_fleetKey, createHashMapFromArray [
|
||||
["classname", _className],
|
||||
["name", _displayName],
|
||||
["type", _category],
|
||||
["status", "Ready"],
|
||||
["damage", "0%"]
|
||||
]];
|
||||
|
||||
_fleetIndex = _fleetIndex + 1;
|
||||
} forEach _vehicles;
|
||||
|
||||
private _patch = _self call ["mset", [_resolvedOrgID, createHashMapFromArray [["fleet", _fleet]], false]];
|
||||
if (_patch isEqualTo createHashMap) exitWith {
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:add_fleet", [toJSON _context, toJSON _fleetSeeds]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to update organization fleet cache."];
|
||||
_result
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["memberUids", GVAR(OrgTreasuryService) call ["resolveOrgMemberUids", [_org, _requesterUid]]];
|
||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||
_result set ["patch", _envelope getOrDefault ["patch", createHashMap]];
|
||||
_result set ["memberUids", _envelope getOrDefault ["memberUids", []]];
|
||||
_result
|
||||
}],
|
||||
["loadById", compileFinal {
|
||||
@ -646,13 +650,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
_result
|
||||
};
|
||||
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _existingOrgID = _actor getOrDefault ["organization", ""];
|
||||
if (_existingOrgID isNotEqualTo "" && { toLower _existingOrgID isNotEqualTo "default" }) exitWith {
|
||||
_result set ["message", "Player already belongs to an organization."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = _actor getOrDefault ["phone_number", ""];
|
||||
if (_orgID isEqualTo "") exitWith {
|
||||
@ -660,80 +659,29 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
_result
|
||||
};
|
||||
|
||||
["org:exists", [_orgID]] call EFUNC(extension,extCall) params ["_existsResult", "_existsSuccess"];
|
||||
if (!_existsSuccess) exitWith {
|
||||
_result set ["message", "Unable to verify organization ID availability."];
|
||||
_result
|
||||
};
|
||||
|
||||
if (_existsResult isEqualTo "true") exitWith {
|
||||
_result set ["message", "An organization already exists for this phone number."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _org = createHashMapFromArray [
|
||||
["id", _orgID],
|
||||
["owner", _uid],
|
||||
["name", _orgName],
|
||||
["funds", 0],
|
||||
["reputation", 0],
|
||||
["credit_lines", createHashMap],
|
||||
["members", createHashMap]
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _uid],
|
||||
["requesterName", _self call ["resolveActorName", [_uid, [_uid] call EFUNC(common,getPlayer), _actor]]],
|
||||
["orgId", _orgID],
|
||||
["orgName", _orgName],
|
||||
["existingOrgId", _existingOrgID]
|
||||
];
|
||||
|
||||
private _json = _self call ["toJSON", [_org]];
|
||||
["org:create", [_orgID, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"];
|
||||
if (!_createSuccess) exitWith {
|
||||
_result set ["message", format ["Failed to create organization: %1", _createResult]];
|
||||
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:register", [toJSON _context]]];
|
||||
if (_envelope isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Organization registration failed."];
|
||||
_result
|
||||
};
|
||||
|
||||
if (_createResult isNotEqualTo "") then {
|
||||
_org = _self call ["toHashMap", [_createResult]];
|
||||
};
|
||||
|
||||
_org set ["members", createHashMap];
|
||||
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||
|
||||
if (toLower _existingOrgID isEqualTo "default") then {
|
||||
private _defaultOrg = _self call ["removeMember", ["default", _uid]];
|
||||
if (_defaultOrg isEqualTo createHashMap) then {
|
||||
["WARNING", format ["Failed to remove %1 from default org members after creating org %2.", _uid, _orgID]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
|
||||
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", _orgID, false]];
|
||||
private _updatedActor = EGVAR(actor,ActorStore) call ["get", [_uid, ""]];
|
||||
if (
|
||||
!(_updatedActor isEqualType createHashMap)
|
||||
|| { _updatedActor isEqualTo createHashMap }
|
||||
|| { (_updatedActor getOrDefault ["organization", ""]) isNotEqualTo _orgID }
|
||||
) then {
|
||||
private _forcedActor = +_actor;
|
||||
if !(_forcedActor isEqualType createHashMap) then {
|
||||
_forcedActor = EGVAR(actor,ActorModel) call ["defaults", []];
|
||||
_forcedActor set ["uid", _uid];
|
||||
};
|
||||
|
||||
_forcedActor set ["organization", _orgID];
|
||||
_updatedActor = EGVAR(actor,ActorStore) call ["override", [_uid, _forcedActor, false]];
|
||||
if (_updatedActor isEqualType createHashMap && { _updatedActor isNotEqualTo createHashMap }) then {
|
||||
_actorPatch = createHashMapFromArray [["organization", _orgID]];
|
||||
};
|
||||
};
|
||||
|
||||
if (
|
||||
!(_updatedActor isEqualType createHashMap)
|
||||
|| { _updatedActor isEqualTo createHashMap }
|
||||
|| { (_updatedActor getOrDefault ["organization", ""]) isNotEqualTo _orgID }
|
||||
) exitWith {
|
||||
private _actorPatch = _self call ["applyActorOrganization", [_uid, _envelope getOrDefault ["actorOrganization", _orgID], _actor]];
|
||||
if (_actorPatch isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to assign the player to the new organization."];
|
||||
_result
|
||||
};
|
||||
|
||||
_org = _self call ["override", [_orgID, _org, false]];
|
||||
_result set ["success", true];
|
||||
_result set ["org", _org];
|
||||
_result set ["message", _envelope getOrDefault ["message", ""]];
|
||||
_result set ["org", _envelope getOrDefault ["org", createHashMap]];
|
||||
_result set ["actorPatch", _actorPatch];
|
||||
_result
|
||||
}],
|
||||
@ -754,9 +702,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
||||
_orgID = "default";
|
||||
};
|
||||
|
||||
private _finalOwner = _finalOrg getOrDefault ["owner", ""];
|
||||
if (_orgID isEqualTo "default" || { _finalOwner isEqualTo _uid }) then {
|
||||
_finalOrg = _self call ["verifyMember", [_finalOrg, _orgID, _uid, _player, _actor]];
|
||||
private _verifiedOrg = _self call ["ensureMember", [_orgID, _uid, _self call ["resolveActorName", [_uid, _player, _actor]]]];
|
||||
if (_verifiedOrg isNotEqualTo createHashMap) then {
|
||||
_finalOrg = _verifiedOrg;
|
||||
};
|
||||
|
||||
[CRPC(org,responseInitOrg), [_finalOrg], _player] call CFUNC(targetEvent);
|
||||
|
||||
213
arma/server/addons/org/functions/fnc_initPayloadBuilder.sqf
Normal file
213
arma/server/addons/org/functions/fnc_initPayloadBuilder.sqf
Normal file
@ -0,0 +1,213 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
/*
|
||||
* File: fnc_initPayloadBuilder.sqf
|
||||
* Author: IDSolutions
|
||||
* Date: 2026-04-02
|
||||
* Public: No
|
||||
*
|
||||
* Description:
|
||||
* Initializes the org payload builder for portal/read-model shaping.
|
||||
* Keeps hydrate construction out of OrgStore so the store can focus on
|
||||
* extension-backed org operations and actor coordination.
|
||||
*/
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
||||
["#type", "OrgPayloadBuilder"],
|
||||
["resolveOrgForUid", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _orgID = GVAR(OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
private _org = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) then {
|
||||
_org = GVAR(OrgStore) call ["init", [_uid]];
|
||||
};
|
||||
|
||||
_org
|
||||
}],
|
||||
["resolveOwnerName", compileFinal {
|
||||
params [["_ownerUid", "", [""]], ["_uid", "", [""]], ["_playerName", "", [""]], ["_membersRaw", createHashMap, [createHashMap]]];
|
||||
|
||||
private _ownerName = ["", "Server"] select (toLowerANSI _ownerUid isEqualTo "server");
|
||||
{
|
||||
private _memberData = _y;
|
||||
private _memberUid = _memberData getOrDefault ["uid", ""];
|
||||
if (_memberUid isEqualTo _ownerUid && { _ownerName isEqualTo "" }) exitWith {
|
||||
_ownerName = _memberData getOrDefault ["name", "Unknown"];
|
||||
};
|
||||
} forEach _membersRaw;
|
||||
|
||||
if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _uid }) then { _ownerName = _playerName; };
|
||||
if (_ownerName isEqualTo "" && { _ownerUid isNotEqualTo "" }) then { _ownerName = "Unknown Owner"; };
|
||||
if !(_ownerName isEqualType "") then { _ownerName = str _ownerName; };
|
||||
_ownerName
|
||||
}],
|
||||
["buildMembersList", compileFinal {
|
||||
params [["_membersRaw", createHashMap, [createHashMap]], ["_uid", "", [""]], ["_ownerUid", "", [""]]];
|
||||
|
||||
private _sessionRole = "Member";
|
||||
private _membersList = [];
|
||||
|
||||
{
|
||||
private _memberData = _y;
|
||||
private _memberName = _memberData getOrDefault ["name", "Unknown"];
|
||||
private _memberUid = _memberData getOrDefault ["uid", ""];
|
||||
|
||||
if (_memberUid isEqualTo _uid) then { _sessionRole = "Member"; };
|
||||
if (_memberUid isEqualTo _ownerUid) then { _sessionRole = ["Member", "Leader"] select (_ownerUid isEqualTo _uid); };
|
||||
|
||||
_membersList pushBack [
|
||||
["uid", _memberUid],
|
||||
["name", _memberName]
|
||||
];
|
||||
} forEach _membersRaw;
|
||||
|
||||
createHashMapFromArray [
|
||||
["members", _membersList],
|
||||
["sessionRole", _sessionRole]
|
||||
]
|
||||
}],
|
||||
["resolveDisplayName", compileFinal {
|
||||
params [["_className", "", [""]], ["_configRoots", [], [[]]]];
|
||||
|
||||
if (_className isEqualTo "") exitWith { "" };
|
||||
|
||||
private _displayName = _className;
|
||||
{
|
||||
private _cfg = _x >> _className;
|
||||
if (isClass _cfg) exitWith {
|
||||
private _resolvedName = getText (_cfg >> "displayName");
|
||||
if (_resolvedName isNotEqualTo "") then { _displayName = _resolvedName; };
|
||||
};
|
||||
} forEach _configRoots;
|
||||
|
||||
_displayName
|
||||
}],
|
||||
["buildAssetsList", compileFinal {
|
||||
params [["_assetsRaw", createHashMap, [createHashMap]]];
|
||||
|
||||
private _assetsList = [];
|
||||
{
|
||||
private _category = _x;
|
||||
{
|
||||
private _assetData = _y;
|
||||
private _className = _assetData getOrDefault ["classname", ""];
|
||||
private _displayName = _self call ["resolveDisplayName", [_className, [
|
||||
configFile >> "CfgWeapons",
|
||||
configFile >> "CfgMagazines",
|
||||
configFile >> "CfgVehicles",
|
||||
configFile >> "CfgGlasses"
|
||||
]]];
|
||||
|
||||
_assetsList pushBack [
|
||||
["name", _displayName],
|
||||
["type", _assetData getOrDefault ["type", _category]],
|
||||
["quantity", str (_assetData getOrDefault ["quantity", 0])]
|
||||
];
|
||||
} forEach _y;
|
||||
} forEach _assetsRaw;
|
||||
|
||||
_assetsList
|
||||
}],
|
||||
["buildFleetList", compileFinal {
|
||||
params [["_fleetRaw", createHashMap, [createHashMap]]];
|
||||
|
||||
private _fleetList = [];
|
||||
{
|
||||
private _vehicleData = _y;
|
||||
_fleetList pushBack [
|
||||
["name", _vehicleData getOrDefault ["name", "Unknown Vehicle"]],
|
||||
["type", _vehicleData getOrDefault ["type", "other"]],
|
||||
["status", _vehicleData getOrDefault ["status", "Unknown"]],
|
||||
["damage", _vehicleData getOrDefault ["damage", "0%"]]
|
||||
];
|
||||
} forEach _fleetRaw;
|
||||
|
||||
_fleetList
|
||||
}],
|
||||
["buildCreditLinesList", compileFinal {
|
||||
params [["_creditLinesRaw", createHashMap, [createHashMap]]];
|
||||
|
||||
private _creditLinesList = [];
|
||||
{
|
||||
private _creditLineData = _y;
|
||||
_creditLinesList pushBack [
|
||||
["uid", _creditLineData getOrDefault ["uid", _x]],
|
||||
["member", _creditLineData getOrDefault ["name", "Unknown Member"]],
|
||||
["amount", _creditLineData getOrDefault ["amount", 0]]
|
||||
];
|
||||
} forEach _creditLinesRaw;
|
||||
|
||||
_creditLinesList
|
||||
}],
|
||||
["buildPortalPayload", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) exitWith { createHashMap };
|
||||
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _org = _self call ["resolveOrgForUid", [_uid]];
|
||||
if (_org isEqualTo createHashMap) exitWith { createHashMap };
|
||||
|
||||
private _verifiedOrg = GVAR(OrgStore) call ["ensureMember", [_orgID, _uid, GVAR(OrgStore) call ["resolveActorName", [_uid, _player, _actor]]]];
|
||||
if (_verifiedOrg isNotEqualTo createHashMap) then { _org = _verifiedOrg; };
|
||||
|
||||
private _name = _org getOrDefault ["name", ""];
|
||||
private _id = _org getOrDefault ["id", _orgID];
|
||||
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||
private _funds = _org getOrDefault ["funds", 0];
|
||||
private _reputation = _org getOrDefault ["reputation", 0];
|
||||
private _creditLinesRaw = _org getOrDefault ["credit_lines", createHashMap];
|
||||
private _assetsRaw = _org getOrDefault ["assets", createHashMap];
|
||||
private _fleetRaw = _org getOrDefault ["fleet", createHashMap];
|
||||
private _membersRaw = _org getOrDefault ["members", createHashMap];
|
||||
private _isDefaultOrg = (_org getOrDefault ["default", false])
|
||||
|| { toLowerANSI _id isEqualTo "default" }
|
||||
|| { toLowerANSI _ownerUid isEqualTo "server" };
|
||||
|
||||
private _playerName = name _player;
|
||||
private _playerVar = vehicleVarName _player;
|
||||
private _sessionIsCeo = _isDefaultOrg && { _playerVar isEqualTo "ceo" };
|
||||
private _memberShape = _self call ["buildMembersList", [_membersRaw, _uid, _ownerUid]];
|
||||
private _sessionRole = _memberShape getOrDefault ["sessionRole", "Member"];
|
||||
private _ownerName = _self call ["resolveOwnerName", [_ownerUid, _uid, _playerName, _membersRaw]];
|
||||
|
||||
if (_ownerUid isEqualTo _uid) then { _sessionRole = "Leader"; };
|
||||
|
||||
createHashMapFromArray [
|
||||
["session", createHashMapFromArray [
|
||||
["actorName", _playerName],
|
||||
["actorUid", _uid],
|
||||
["role", _sessionRole],
|
||||
["ceo", _sessionIsCeo]
|
||||
]],
|
||||
["portalData", createHashMapFromArray [
|
||||
["org", createHashMapFromArray [
|
||||
["name", _name],
|
||||
["tag", _id],
|
||||
["owner", _ownerName],
|
||||
["ownerUid", _ownerUid],
|
||||
["isDefault", _isDefaultOrg]
|
||||
]],
|
||||
["funds", _funds],
|
||||
["reputation", _reputation],
|
||||
["creditLines", _self call ["buildCreditLinesList", [_creditLinesRaw]]],
|
||||
["members", _memberShape getOrDefault ["members", []]],
|
||||
["fleet", _self call ["buildFleetList", [_fleetRaw]]],
|
||||
["assets", _self call ["buildAssetsList", [_assetsRaw]]],
|
||||
["activity", []]
|
||||
]]
|
||||
]
|
||||
}]
|
||||
]];
|
||||
|
||||
GVAR(OrgPayloadBuilder)
|
||||
@ -1,273 +0,0 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(OrgMembershipServiceBase) = compileFinal createHashMapFromArray [
|
||||
["#type", "OrgMembershipService"],
|
||||
["buildMembershipResult", compileFinal {
|
||||
params [["_message", "", [""]]];
|
||||
|
||||
createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", _message],
|
||||
["actorPatch", createHashMap]
|
||||
]
|
||||
}],
|
||||
["verifyMember", compileFinal {
|
||||
params [["_org", createHashMap, [createHashMap]], ["_orgID", "", [""]], ["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { _org };
|
||||
|
||||
private _members = _org getOrDefault ["members", createHashMap];
|
||||
if ((_members getOrDefault [_uid, objNull]) isNotEqualTo objNull) exitWith { _org };
|
||||
|
||||
["org:members:add", [_orgID, _uid]] call EFUNC(extension,extCall) params ["_memberResult", "_memberSuccess"];
|
||||
if (!_memberSuccess) then {
|
||||
["WARNING", format ["Failed to add %1 to org %2 members: %3", _uid, _orgID, _memberResult]] call EFUNC(common,log);
|
||||
};
|
||||
|
||||
private _memberName = _actor getOrDefault ["name", ""];
|
||||
if (_memberName isEqualTo "" && { _player isNotEqualTo objNull }) then {
|
||||
_memberName = name _player;
|
||||
};
|
||||
if (_memberName isEqualTo "") then {
|
||||
_memberName = "Unknown";
|
||||
};
|
||||
|
||||
private _updatedMembers = +_members;
|
||||
_updatedMembers set [_uid, createHashMapFromArray [["uid", _uid], ["name", _memberName]]];
|
||||
_org set ["members", _updatedMembers];
|
||||
_org = GVAR(OrgStore) call ["override", [_orgID, _org, false]];
|
||||
|
||||
_org
|
||||
}],
|
||||
["addMember", compileFinal {
|
||||
params [["_orgID", "", [""]], ["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _org = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith { _org };
|
||||
|
||||
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||
|
||||
_org
|
||||
}],
|
||||
["removeMember", compileFinal {
|
||||
params [["_orgID", "", [""]], ["_uid", "", [""]]];
|
||||
|
||||
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { createHashMap };
|
||||
|
||||
private _org = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith { _org };
|
||||
|
||||
["org:members:remove", [_orgID, _uid]] call EFUNC(extension,extCall) params ["_memberResult", "_memberSuccess"];
|
||||
if (!_memberSuccess) exitWith {
|
||||
["WARNING", format ["Failed to remove %1 from org %2 members: %3", _uid, _orgID, _memberResult]] call EFUNC(common,log);
|
||||
createHashMap
|
||||
};
|
||||
|
||||
private _updatedMembers = +(_org getOrDefault ["members", createHashMap]);
|
||||
_updatedMembers deleteAt _uid;
|
||||
_org set ["members", _updatedMembers];
|
||||
_org = GVAR(OrgStore) call ["override", [_orgID, _org, false]];
|
||||
|
||||
_org
|
||||
}],
|
||||
["restoreDefaultMembership", compileFinal {
|
||||
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_actor", createHashMap, [createHashMap]]];
|
||||
|
||||
private _result = _self call ["buildMembershipResult", []];
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _resolvedPlayer = _player;
|
||||
if (_resolvedPlayer isEqualTo objNull) then {
|
||||
_resolvedPlayer = [_uid] call EFUNC(common,getPlayer);
|
||||
};
|
||||
|
||||
private _resolvedActor = EGVAR(actor,Registry) getOrDefault [_uid, _actor];
|
||||
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", "default", false]];
|
||||
private _defaultActor = EGVAR(actor,ActorStore) call ["get", [_uid, ""]];
|
||||
|
||||
if !(_defaultActor isEqualType createHashMap) then {
|
||||
_defaultActor = +_resolvedActor;
|
||||
};
|
||||
|
||||
if (
|
||||
(_defaultActor isEqualTo createHashMap)
|
||||
|| { toLowerANSI (_defaultActor getOrDefault ["organization", ""]) isNotEqualTo "default" }
|
||||
) then {
|
||||
private _forcedActor = +_resolvedActor;
|
||||
if (_forcedActor isEqualTo createHashMap) then {
|
||||
_forcedActor = EGVAR(actor,ActorModel) call ["defaults", []];
|
||||
_forcedActor set ["uid", _uid];
|
||||
};
|
||||
|
||||
_forcedActor set ["organization", "default"];
|
||||
_defaultActor = EGVAR(actor,ActorStore) call ["override", [_uid, _forcedActor, false]];
|
||||
if (_defaultActor isEqualType createHashMap && { _defaultActor isNotEqualTo createHashMap }) then {
|
||||
_actorPatch = createHashMapFromArray [["organization", "default"]];
|
||||
};
|
||||
};
|
||||
|
||||
if (
|
||||
!(_defaultActor isEqualType createHashMap)
|
||||
|| { _defaultActor isEqualTo createHashMap }
|
||||
|| { toLowerANSI (_defaultActor getOrDefault ["organization", ""]) isNotEqualTo "default" }
|
||||
) exitWith {
|
||||
_result set ["message", "Failed to restore default organization membership."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _defaultOrg = _self call ["addMember", ["default", _uid, _resolvedPlayer, _defaultActor]];
|
||||
if (_defaultOrg isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to restore default organization membership."];
|
||||
_result
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["actorPatch", _actorPatch];
|
||||
_result
|
||||
}],
|
||||
["leave", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", ""],
|
||||
["actorPatch", createHashMap],
|
||||
["notification", []]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _orgID = _actor getOrDefault ["organization", ""];
|
||||
if (_orgID isEqualTo "" || { toLower _orgID isEqualTo "default" }) exitWith {
|
||||
_result set ["message", "You are already assigned to the default organization."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _org = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Unable to load organization data for leave request."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||
if (_ownerUid isEqualTo _uid) exitWith {
|
||||
_result set ["message", "Organization owners must disband the organization instead of leaving it."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgName = _org getOrDefault ["name", "Organization"];
|
||||
private _updatedOrg = _self call ["removeMember", [_orgID, _uid]];
|
||||
if (_updatedOrg isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Failed to remove you from the organization roster."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _defaultResult = _self call ["restoreDefaultMembership", [_uid, _player, _actor]];
|
||||
if !(_defaultResult getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _defaultResult getOrDefault ["message", "Failed to restore default organization membership."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _message = format ["You left %1 and returned to the default organization.", _orgName];
|
||||
_result set ["success", true];
|
||||
_result set ["message", _message];
|
||||
_result set ["actorPatch", _defaultResult getOrDefault ["actorPatch", createHashMap]];
|
||||
_result set ["notification", ["info", "Organization Left", _message, 6000]];
|
||||
_result
|
||||
}],
|
||||
["disband", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", ""],
|
||||
["members", []]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "") exitWith {
|
||||
_result set ["message", "A valid player UID is required."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||
private _orgID = _actor getOrDefault ["organization", ""];
|
||||
if (_orgID isEqualTo "" || { toLower _orgID isEqualTo "default" }) exitWith {
|
||||
_result set ["message", "Only active player organizations can be disbanded."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _org = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Unable to load organization data for disbanding."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||
if (_ownerUid isEqualTo "" || { _ownerUid isNotEqualTo _uid }) exitWith {
|
||||
_result set ["message", "Only the organization owner can disband this organization."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgName = _org getOrDefault ["name", "Organization"];
|
||||
private _memberMap = _org getOrDefault ["members", createHashMap];
|
||||
private _memberUids = keys _memberMap;
|
||||
if !(_uid in _memberUids) then {
|
||||
_memberUids pushBack _uid;
|
||||
};
|
||||
|
||||
private _deleteResult = GVAR(OrgStore) call ["delete", [_orgID]];
|
||||
if !(_deleteResult getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _deleteResult getOrDefault ["message", "Failed to disband organization."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _memberResults = [];
|
||||
{
|
||||
private _memberUid = _x;
|
||||
if (_memberUid isNotEqualTo "") then {
|
||||
private _memberPlayer = [_memberUid] call EFUNC(common,getPlayer);
|
||||
private _memberActor = EGVAR(actor,Registry) getOrDefault [_memberUid, createHashMap];
|
||||
private _defaultResult = _self call ["restoreDefaultMembership", [_memberUid, _memberPlayer, _memberActor]];
|
||||
if !(_defaultResult getOrDefault ["success", false]) then {
|
||||
["WARNING", format ["Failed to restore default org for %1 after disbanding %2: %3", _memberUid, _orgID, _defaultResult getOrDefault ["message", "Unknown error."]]] call EFUNC(common,log);
|
||||
};
|
||||
|
||||
private _responseMessage = [
|
||||
format ["%1 has been disbanded.", _orgName],
|
||||
format ["Your organization, %1, has been disbanded.", _orgName]
|
||||
] select (_memberUid isEqualTo _uid);
|
||||
|
||||
private _notificationParams = [
|
||||
["warning", "Organization Disbanded", _responseMessage, 6000],
|
||||
["success", "Organization Disbanded", _responseMessage, 6000]
|
||||
] select (_memberUid isEqualTo _uid);
|
||||
|
||||
_memberResults pushBack (createHashMapFromArray [
|
||||
["uid", _memberUid],
|
||||
["requester", _memberUid isEqualTo _uid],
|
||||
["message", _responseMessage],
|
||||
["notification", _notificationParams],
|
||||
["actorPatch", _defaultResult getOrDefault ["actorPatch", createHashMap]]
|
||||
]);
|
||||
};
|
||||
} forEach _memberUids;
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", format ["%1 has been disbanded.", _orgName]];
|
||||
_result set ["members", _memberResults];
|
||||
_result
|
||||
}]
|
||||
];
|
||||
|
||||
GVAR(OrgMembershipService) = createHashMapObject [GVAR(OrgMembershipServiceBase)];
|
||||
@ -1,166 +0,0 @@
|
||||
#include "..\script_component.hpp"
|
||||
|
||||
#pragma hemtt ignore_variables ["_self"]
|
||||
GVAR(OrgTreasuryServiceBase) = compileFinal createHashMapFromArray [
|
||||
["#type", "OrgTreasuryService"],
|
||||
["buildChargeResult", compileFinal {
|
||||
params [["_message", "Unable to process organization payment.", [""]]];
|
||||
|
||||
createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", _message],
|
||||
["patch", createHashMap],
|
||||
["memberUids", []]
|
||||
]
|
||||
}],
|
||||
["resolveOrgMemberUids", compileFinal {
|
||||
params [["_org", createHashMap, [createHashMap]], ["_requesterUid", "", [""]]];
|
||||
|
||||
private _memberUids = keys (_org getOrDefault ["members", createHashMap]);
|
||||
if (_requesterUid isNotEqualTo "" && { !(_requesterUid in _memberUids) }) then {
|
||||
_memberUids pushBack _requesterUid;
|
||||
};
|
||||
|
||||
_memberUids
|
||||
}],
|
||||
["canManageTreasury", compileFinal {
|
||||
params [["_orgID", "", [""]], ["_org", createHashMap, [createHashMap]], ["_requesterUid", "", [""]], ["_requesterPlayer", objNull, [objNull]]];
|
||||
|
||||
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||
private _isDefaultOrg = (_orgID isEqualTo "default") || { toLowerANSI _ownerUid isEqualTo "server" };
|
||||
private _isDefaultOrgCeo = _isDefaultOrg
|
||||
&& { _requesterPlayer isNotEqualTo objNull }
|
||||
&& { toLowerANSI (vehicleVarName _requesterPlayer) isEqualTo "ceo" };
|
||||
|
||||
(_ownerUid isEqualTo _requesterUid) || _isDefaultOrgCeo
|
||||
}],
|
||||
["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 = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Unable to load organization data for credit line assignment."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||
if !(_self call ["canManageTreasury", [_orgID, _org, _requesterUid, _requesterPlayer]]) 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 = GVAR(OrgStore) call ["set", [_orgID, "credit_lines", _creditLines, false]];
|
||||
private _memberUids = _self call ["resolveOrgMemberUids", [_org, _requesterUid]];
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", format ["Credit line of $%1 assigned to %2.", [_amount] call EFUNC(common,formatNumber), _resolvedMemberName]];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["memberUids", _memberUids];
|
||||
_result
|
||||
}],
|
||||
["chargeCheckout", compileFinal {
|
||||
params [["_requesterUid", "", [""]], ["_requesterPlayer", objNull, [objNull]], ["_source", "org_funds", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]];
|
||||
|
||||
private _result = _self call ["buildChargeResult", []];
|
||||
private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
||||
private _orgID = _requesterActor getOrDefault ["organization", "default"];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _org = GVAR(OrgStore) call ["loadById", [_orgID]];
|
||||
if (_org isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Organization data is unavailable for checkout."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _memberUids = _self call ["resolveOrgMemberUids", [_org, _requesterUid]];
|
||||
|
||||
switch (toLowerANSI _source) do {
|
||||
case "org_funds": {
|
||||
if !(_self call ["canManageTreasury", [_orgID, _org, _requesterUid, _requesterPlayer]]) exitWith {
|
||||
_result set ["message", "Only the organization leader or CEO can charge org funds."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _funds = _org getOrDefault ["funds", 0];
|
||||
if (_funds < _amount) exitWith {
|
||||
_result set ["message", "Organization funds cannot cover this checkout."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _patch = createHashMapFromArray [["funds", (_funds - _amount)]];
|
||||
if (_commit) then { _patch = GVAR(OrgStore) call ["mset", [_orgID, _patch, false]]; };
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["memberUids", _memberUids];
|
||||
_result
|
||||
};
|
||||
case "credit_line": {
|
||||
private _creditLines = +(_org getOrDefault ["credit_lines", createHashMap]);
|
||||
private _memberCredit = +(_creditLines getOrDefault [_requesterUid, createHashMap]);
|
||||
private _creditAmount = _memberCredit getOrDefault ["amount", 0];
|
||||
if (_creditAmount < _amount) exitWith {
|
||||
_result set ["message", "Assigned credit line cannot cover this checkout."];
|
||||
_result
|
||||
};
|
||||
|
||||
_memberCredit set ["uid", _requesterUid];
|
||||
_memberCredit set ["amount", (_creditAmount - _amount)];
|
||||
_creditLines set [_requesterUid, _memberCredit];
|
||||
|
||||
private _patch = createHashMapFromArray [["credit_lines", _creditLines]];
|
||||
if (_commit) then { _patch = GVAR(OrgStore) call ["mset", [_orgID, _patch, false]]; };
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
_result set ["memberUids", _memberUids];
|
||||
_result
|
||||
};
|
||||
default {
|
||||
_result set ["message", "Selected organization payment source is unsupported."];
|
||||
_result
|
||||
};
|
||||
};
|
||||
}]
|
||||
];
|
||||
|
||||
GVAR(OrgTreasuryService) = createHashMapObject [GVAR(OrgTreasuryServiceBase)];
|
||||
@ -399,12 +399,20 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
|
||||
_resolved
|
||||
}],
|
||||
["calculateCheckoutTotal", compileFinal {
|
||||
["buildCheckoutRequest", compileFinal {
|
||||
params [["_items", [], [[]]], ["_vehicles", [], [[]]]];
|
||||
|
||||
private _result = createHashMapFromArray [["success", false], ["total", 0], ["message", "Checkout total must be greater than zero."]];
|
||||
private _result = createHashMapFromArray [
|
||||
["success", false],
|
||||
["total", 0],
|
||||
["message", "Checkout total must be greater than zero."],
|
||||
["items", []],
|
||||
["vehicles", []]
|
||||
];
|
||||
private _total = 0;
|
||||
private _message = "";
|
||||
private _resolvedItems = [];
|
||||
private _resolvedVehicles = [];
|
||||
|
||||
{
|
||||
if (_message isEqualTo "") then {
|
||||
@ -419,7 +427,14 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
if (_catalogEntry isEqualTo createHashMap) then {
|
||||
_message = format ["Unsupported store item: %1", _className];
|
||||
} else {
|
||||
_total = _total + ((_catalogEntry getOrDefault ["priceValue", 0]) * _quantity);
|
||||
private _priceValue = _catalogEntry getOrDefault ["priceValue", 0];
|
||||
_total = _total + (_priceValue * _quantity);
|
||||
_resolvedItems pushBack (createHashMapFromArray [
|
||||
["classname", _className],
|
||||
["category", _catalogEntry getOrDefault ["category", "item"]],
|
||||
["priceValue", _priceValue],
|
||||
["quantity", _quantity]
|
||||
]);
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -436,7 +451,13 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
if (_catalogEntry isEqualTo createHashMap) then {
|
||||
_message = format ["Unsupported store vehicle: %1", _className];
|
||||
} else {
|
||||
_total = _total + (_catalogEntry getOrDefault ["priceValue", 0]);
|
||||
private _priceValue = _catalogEntry getOrDefault ["priceValue", 0];
|
||||
_total = _total + _priceValue;
|
||||
_resolvedVehicles pushBack (createHashMapFromArray [
|
||||
["classname", _className],
|
||||
["category", _catalogEntry getOrDefault ["category", _x getOrDefault ["category", "other"]]],
|
||||
["priceValue", _priceValue]
|
||||
]);
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -452,7 +473,19 @@ GVAR(StoreCatalogServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||
_result set ["success", true];
|
||||
_result set ["total", floor _total];
|
||||
_result set ["message", ""];
|
||||
_result set ["items", _resolvedItems];
|
||||
_result set ["vehicles", _resolvedVehicles];
|
||||
_result
|
||||
}],
|
||||
["calculateCheckoutTotal", compileFinal {
|
||||
params [["_items", [], [[]]], ["_vehicles", [], [[]]]];
|
||||
|
||||
private _checkout = _self call ["buildCheckoutRequest", [_items, _vehicles]];
|
||||
createHashMapFromArray [
|
||||
["success", _checkout getOrDefault ["success", false]],
|
||||
["total", _checkout getOrDefault ["total", 0]],
|
||||
["message", _checkout getOrDefault ["message", "Checkout total must be greater than zero."]]
|
||||
]
|
||||
}]
|
||||
];
|
||||
|
||||
|
||||
@ -160,45 +160,92 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
||||
|
||||
format ["$%1", [_amount max 0] call EFUNC(common,formatNumber)]
|
||||
}],
|
||||
["applyPaymentPatch", compileFinal {
|
||||
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_paymentMethod", "cash", [""]], ["_total", 0, [0]], ["_commit", false, [false]]];
|
||||
["callCheckoutBackendEnvelope", compileFinal {
|
||||
params [["_context", createHashMap, [createHashMap]]];
|
||||
|
||||
private _result = _self call ["buildResult", ["Unable to process payment.", _paymentMethod]];
|
||||
private _payment = switch (toLowerANSI _paymentMethod) do {
|
||||
case "cash";
|
||||
case "bank": {
|
||||
EGVAR(bank,BankStore) call ["chargeCheckout", [_uid, _paymentMethod, _total, _commit]]
|
||||
};
|
||||
case "org_funds";
|
||||
case "credit_line": {
|
||||
EGVAR(org,OrgStore) call ["chargeCheckout", [_uid, _player, _paymentMethod, _total, _commit]]
|
||||
};
|
||||
default {
|
||||
createHashMapFromArray [
|
||||
["success", false],
|
||||
["message", "Selected payment source is unsupported."],
|
||||
["patch", createHashMap],
|
||||
["memberUids", []]
|
||||
]
|
||||
};
|
||||
private _envelope = createHashMapFromArray [["data", createHashMap], ["error", ""]];
|
||||
if (_context isEqualTo createHashMap) exitWith {
|
||||
_envelope set ["error", "Checkout request was invalid."];
|
||||
_envelope
|
||||
};
|
||||
|
||||
if !(_payment getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _payment getOrDefault ["message", "Unable to process payment."]];
|
||||
_result
|
||||
["store:checkout", [toJSON _context]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !(_isSuccess) exitWith {
|
||||
_envelope set ["error", "Store backend call failed."];
|
||||
_envelope
|
||||
};
|
||||
if !(_result isEqualType "") exitWith {
|
||||
_envelope set ["error", "Store backend returned an invalid response."];
|
||||
_envelope
|
||||
};
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Store extension checkout failed: %1", _result]] call EFUNC(common,log);
|
||||
_envelope set ["error", _result select [7]];
|
||||
_envelope
|
||||
};
|
||||
|
||||
private _patch = _payment getOrDefault ["patch", createHashMap];
|
||||
if ((_paymentMethod isEqualTo "cash") || { _paymentMethod isEqualTo "bank" }) then {
|
||||
_result set ["bankPatch", _patch];
|
||||
} else {
|
||||
_result set ["orgPatch", _patch];
|
||||
_result set ["orgTargetUids", _payment getOrDefault ["memberUids", []]];
|
||||
private _data = fromJSON _result;
|
||||
if !(_data isEqualType createHashMap) exitWith {
|
||||
_envelope set ["error", "Store backend returned unreadable JSON."];
|
||||
_envelope
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result
|
||||
_envelope set ["data", _data];
|
||||
_envelope
|
||||
}],
|
||||
["buildCheckoutContext", compileFinal {
|
||||
params [
|
||||
["_uid", "", [""]],
|
||||
["_player", objNull, [objNull]],
|
||||
["_paymentMethod", "cash", [""]],
|
||||
["_items", [], [[]]],
|
||||
["_vehicles", [], [[]]]
|
||||
];
|
||||
|
||||
if (_uid isEqualTo "" || { isNull _player }) exitWith { createHashMap };
|
||||
|
||||
private _orgID = EGVAR(org,OrgStore) call ["resolveOrgIdForUid", [_uid]];
|
||||
private _requesterIsDefaultOrgCeo = (
|
||||
_orgID isEqualTo "default"
|
||||
&& { toLowerANSI (vehicleVarName _player) isEqualTo "ceo" }
|
||||
);
|
||||
|
||||
createHashMapFromArray [
|
||||
["requesterUid", _uid],
|
||||
["requesterName", name _player],
|
||||
["orgId", _orgID],
|
||||
["requesterIsDefaultOrgCeo", _requesterIsDefaultOrgCeo],
|
||||
["paymentMethod", toLowerANSI _paymentMethod],
|
||||
["items", _items],
|
||||
["vehicles", _vehicles]
|
||||
]
|
||||
}],
|
||||
["syncCheckoutResult", compileFinal {
|
||||
params [["_player", objNull, [objNull]], ["_result", createHashMap, [createHashMap]]];
|
||||
|
||||
if (isNull _player || { _result isEqualTo createHashMap }) exitWith { false };
|
||||
|
||||
private _lockerPatch = _result getOrDefault ["lockerPatch", createHashMap];
|
||||
private _vaPatch = _result getOrDefault ["vaPatch", createHashMap];
|
||||
private _vgPatch = _result getOrDefault ["vgaragePatch", createHashMap];
|
||||
private _bankPatch = _result getOrDefault ["bankPatch", createHashMap];
|
||||
private _orgPatch = _result getOrDefault ["orgPatch", createHashMap];
|
||||
|
||||
if (keys _lockerPatch isNotEqualTo []) then { [CRPC(locker,responseSyncLocker), [_lockerPatch], _player] call CFUNC(targetEvent); };
|
||||
if (keys _vaPatch isNotEqualTo []) then { [CRPC(locker,responseSyncVA), [_vaPatch], _player] call CFUNC(targetEvent); };
|
||||
if (keys _vgPatch isNotEqualTo []) then { [CRPC(garage,responseSyncVG), [_vgPatch], _player] call CFUNC(targetEvent); };
|
||||
if (keys _bankPatch isNotEqualTo []) then { [CRPC(bank,responseSyncBank), [_bankPatch], _player] call CFUNC(targetEvent); };
|
||||
|
||||
if (keys _orgPatch isNotEqualTo []) then {
|
||||
{
|
||||
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||
if (_memberPlayer isNotEqualTo objNull) then {
|
||||
[CRPC(org,responseSyncOrg), [_orgPatch], _memberPlayer] call CFUNC(targetEvent);
|
||||
};
|
||||
} forEach (_result getOrDefault ["orgTargetUids", []]);
|
||||
};
|
||||
|
||||
true
|
||||
}],
|
||||
["checkout", compileFinal {
|
||||
params [["_uid", "", [""]], ["_player", objNull, [objNull]], ["_payloadJson", "", [""]]];
|
||||
@ -219,8 +266,8 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
||||
_result
|
||||
};
|
||||
|
||||
private _priceResult = GVAR(StoreCatalogService) call ["calculateCheckoutTotal", [_items, _vehicles]];
|
||||
private _totalPrice = _priceResult getOrDefault ["total", 0];
|
||||
private _checkoutRequest = GVAR(StoreCatalogService) call ["buildCheckoutRequest", [_items, _vehicles]];
|
||||
private _totalPrice = _checkoutRequest getOrDefault ["total", 0];
|
||||
|
||||
_result set ["paymentMethod", _paymentMethod];
|
||||
_result set ["chargedTotal", _totalPrice];
|
||||
@ -230,90 +277,41 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [
|
||||
_result
|
||||
};
|
||||
|
||||
if !(_priceResult getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _priceResult getOrDefault ["message", "Checkout total must be greater than zero."]];
|
||||
if !(_checkoutRequest getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _checkoutRequest getOrDefault ["message", "Checkout total must be greater than zero."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _lockerPreview = EGVAR(locker,LockerStore) call ["grantItems", [_uid, _items, false]];
|
||||
if !(_lockerPreview getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _lockerPreview getOrDefault ["message", "Locker grant failed."]];
|
||||
private _checkoutContext = _self call ["buildCheckoutContext", [
|
||||
_uid,
|
||||
_player,
|
||||
_paymentMethod,
|
||||
_checkoutRequest getOrDefault ["items", []],
|
||||
_checkoutRequest getOrDefault ["vehicles", []]
|
||||
]];
|
||||
if (_checkoutContext isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Checkout request context was invalid."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _vaPreview = EGVAR(locker,VAStore) call ["unlockItems", [_uid, _items, false]];
|
||||
if !(_vaPreview getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _vaPreview getOrDefault ["message", "VA unlock failed."]];
|
||||
private _envelope = _self call ["callCheckoutBackendEnvelope", [_checkoutContext]];
|
||||
private _backendResult = _envelope getOrDefault ["data", createHashMap];
|
||||
if (_backendResult isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Checkout failed."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _vgPreview = EGVAR(garage,VGarageStore) call ["grantVehicles", [_uid, _vehicles, false]];
|
||||
if !(_vgPreview getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _vgPreview getOrDefault ["message", "Vehicle unlock failed."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgFleetPreview = createHashMapFromArray [["success", true], ["message", ""], ["patch", createHashMap], ["memberUids", []]];
|
||||
if (_paymentMethod isEqualTo "org_funds" && { _vehicles isNotEqualTo [] }) then {
|
||||
_orgFleetPreview = EGVAR(org,OrgStore) call ["addFleetVehicles", [_uid, _vehicles, false]];
|
||||
if !(_orgFleetPreview getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _orgFleetPreview getOrDefault ["message", "Organization fleet update failed."]];
|
||||
_result
|
||||
};
|
||||
};
|
||||
|
||||
_result set ["lockerGranted", _lockerPreview getOrDefault ["granted", []]];
|
||||
_result set ["vehicleGranted", _vgPreview getOrDefault ["granted", []]];
|
||||
|
||||
private _paymentPreview = _self call ["applyPaymentPatch", [_uid, _player, _paymentMethod, _totalPrice, false]];
|
||||
if !(_paymentPreview getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _paymentPreview getOrDefault ["message", "Payment failed."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _payment = _self call ["applyPaymentPatch", [_uid, _player, _paymentMethod, _totalPrice, true]];
|
||||
private _lockerResult = EGVAR(locker,LockerStore) call ["grantItems", [_uid, _items, true]];
|
||||
private _vaResult = EGVAR(locker,VAStore) call ["unlockItems", [_uid, _items, true]];
|
||||
private _vgResult = EGVAR(garage,VGarageStore) call ["grantVehicles", [_uid, _vehicles, true]];
|
||||
private _orgFleetResult = createHashMapFromArray [["success", true], ["message", ""], ["patch", createHashMap], ["memberUids", []]];
|
||||
if (_paymentMethod isEqualTo "org_funds" && { _vehicles isNotEqualTo [] }) then {
|
||||
_orgFleetResult = EGVAR(org,OrgStore) call ["addFleetVehicles", [_uid, _vehicles, true]];
|
||||
};
|
||||
|
||||
private _lockerPatch = _lockerResult getOrDefault ["patch", createHashMap];
|
||||
private _vaPatch = _vaResult getOrDefault ["patch", createHashMap];
|
||||
private _vgPatch = _vgResult getOrDefault ["patch", createHashMap];
|
||||
if (keys _lockerPatch isNotEqualTo []) then { [CRPC(locker,responseSyncLocker), [_lockerPatch], _player] call CFUNC(targetEvent); };
|
||||
if (keys _vaPatch isNotEqualTo []) then { [CRPC(locker,responseSyncVA), [_vaPatch], _player] call CFUNC(targetEvent); };
|
||||
if (keys _vgPatch isNotEqualTo []) then { [CRPC(garage,responseSyncVG), [_vgPatch], _player] call CFUNC(targetEvent); };
|
||||
|
||||
private _bankPatch = _payment getOrDefault ["bankPatch", createHashMap];
|
||||
if (keys _bankPatch isNotEqualTo []) then { [CRPC(bank,responseSyncBank), [_bankPatch], _player] call CFUNC(targetEvent); };
|
||||
|
||||
private _orgPatch = _payment getOrDefault ["orgPatch", createHashMap];
|
||||
private _orgFleetPatch = _orgFleetResult getOrDefault ["patch", createHashMap];
|
||||
if (keys _orgFleetPatch isNotEqualTo []) then { { _orgPatch set [_x, _y]; } forEach _orgFleetPatch; };
|
||||
if (keys _orgPatch isNotEqualTo []) then {
|
||||
private _orgTargetUids = _payment getOrDefault ["orgTargetUids", []];
|
||||
{
|
||||
if !(_x in _orgTargetUids) then { _orgTargetUids pushBack _x; };
|
||||
} forEach (_orgFleetResult getOrDefault ["memberUids", []]);
|
||||
|
||||
{
|
||||
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||
if (_memberPlayer isNotEqualTo objNull) then { [CRPC(org,responseSyncOrg), [_orgPatch], _memberPlayer] call CFUNC(targetEvent); };
|
||||
} forEach _orgTargetUids;
|
||||
};
|
||||
_self call ["syncCheckoutResult", [_player, _backendResult]];
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", format [
|
||||
_result set ["message", _backendResult getOrDefault ["message", format [
|
||||
"Checkout completed. %1 charged, %2 locker grant(s), %3 vehicle unlock(s).",
|
||||
_self call ["formatCurrency", [_totalPrice]],
|
||||
count (_lockerResult getOrDefault ["granted", []]),
|
||||
count (_vgResult getOrDefault ["granted", []])
|
||||
]];
|
||||
_result set ["lockerGranted", _lockerResult getOrDefault ["granted", []]];
|
||||
_result set ["vehicleGranted", _vgResult getOrDefault ["granted", []]];
|
||||
count (_backendResult getOrDefault ["lockerGranted", []]),
|
||||
count (_backendResult getOrDefault ["vehicleGranted", []])
|
||||
]]];
|
||||
_result set ["lockerGranted", _backendResult getOrDefault ["lockerGranted", []]];
|
||||
_result set ["vehicleGranted", _backendResult getOrDefault ["vehicleGranted", []]];
|
||||
_result
|
||||
}]
|
||||
];
|
||||
|
||||
@ -31,6 +31,14 @@ static HOT_ACTOR_SERVICE: LazyLock<
|
||||
ActorHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn hot_service() -> &'static ActorHotStateService<
|
||||
RedisActorRepository<ExtensionRedisClient>,
|
||||
InMemoryActorHotRepository,
|
||||
> {
|
||||
&HOT_ACTOR_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for actor operations.
|
||||
///
|
||||
/// Registers commands: `get`, `exists`, `create`, `update`, `delete`.
|
||||
|
||||
@ -35,6 +35,13 @@ static HOT_BANK_SERVICE: LazyLock<
|
||||
BankHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
pub(crate) fn hot_service() -> &'static BankHotStateService<
|
||||
RedisBankRepository<ExtensionRedisClient>,
|
||||
InMemoryBankHotRepository,
|
||||
> {
|
||||
&HOT_BANK_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for bank operations.
|
||||
///
|
||||
/// Registers commands: `get`, `exists`, `create`, `update`, `delete`.
|
||||
|
||||
@ -30,6 +30,14 @@ static HOT_GARAGE_SERVICE: LazyLock<
|
||||
GarageHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn hot_service() -> &'static GarageHotStateService<
|
||||
RedisGarageRepository<ExtensionRedisClient>,
|
||||
InMemoryGarageHotRepository,
|
||||
> {
|
||||
&HOT_GARAGE_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for garage operations.
|
||||
///
|
||||
/// Registers commands: `create`, `get`, `add`, `update`, `remove`, `delete`, `exists`.
|
||||
|
||||
@ -22,6 +22,7 @@ pub mod locker;
|
||||
mod log;
|
||||
pub mod org;
|
||||
pub mod redis;
|
||||
pub mod store;
|
||||
pub mod terrain;
|
||||
pub mod transport;
|
||||
pub mod v_garage;
|
||||
@ -85,6 +86,7 @@ fn init() -> Extension {
|
||||
.group("icom", icom::group())
|
||||
.group("locker", locker::group())
|
||||
.group("org", org::group())
|
||||
.group("store", store::group())
|
||||
.group("terrain", terrain::group())
|
||||
.group("transport", transport::group())
|
||||
.group(
|
||||
|
||||
@ -25,6 +25,13 @@ static HOT_LOCKER_SERVICE: LazyLock<
|
||||
LockerHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
pub(crate) fn hot_service() -> &'static LockerHotStateService<
|
||||
RedisLockerRepository<ExtensionRedisClient>,
|
||||
InMemoryLockerHotRepository,
|
||||
> {
|
||||
&HOT_LOCKER_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for locker operations.
|
||||
///
|
||||
/// Registers commands: `create`, `get`, `add`, `update`, `remove`, `delete`, `exists`.
|
||||
|
||||
@ -4,7 +4,11 @@
|
||||
//! Handles SQF command mapping and parameter validation.
|
||||
|
||||
use arma_rs::Group;
|
||||
use forge_models::HotOrgRecord;
|
||||
use forge_models::{
|
||||
HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext, OrgDisbandResult,
|
||||
OrgEnsureMemberContext, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext, OrgLeaveResult,
|
||||
OrgRegisterContext,
|
||||
};
|
||||
use forge_repositories::{InMemoryOrgHotRepository, RedisOrgRepository};
|
||||
use forge_services::{OrgHotStateService, OrgService};
|
||||
use std::sync::LazyLock;
|
||||
@ -31,6 +35,11 @@ static HOT_ORG_SERVICE: LazyLock<
|
||||
OrgHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
pub(crate) fn hot_service()
|
||||
-> &'static OrgHotStateService<RedisOrgRepository<ExtensionRedisClient>, InMemoryOrgHotRepository> {
|
||||
&HOT_ORG_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for organization operations.
|
||||
///
|
||||
/// Registers commands: `get`, `exists`, `create`, `update`, `delete`.
|
||||
@ -47,6 +56,14 @@ pub fn group() -> Group {
|
||||
.command("init", init_hot_org)
|
||||
.command("get", get_hot_org)
|
||||
.command("override", override_hot_org)
|
||||
.command("ensure_member", ensure_hot_org_member)
|
||||
.command("register", register_hot_org)
|
||||
.command("assign_credit_line", assign_credit_line_hot_org)
|
||||
.command("charge_checkout", charge_checkout_hot_org)
|
||||
.command("add_assets", add_assets_hot_org)
|
||||
.command("add_fleet", add_fleet_hot_org)
|
||||
.command("leave", leave_hot_org)
|
||||
.command("disband", disband_hot_org)
|
||||
.command("save", save_hot_org)
|
||||
.command("remove", remove_hot_org),
|
||||
)
|
||||
@ -78,6 +95,13 @@ fn serialize_hot_org(org: HotOrgRecord) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_result<T: serde::Serialize>(value: &T, label: &str) -> String {
|
||||
match serde_json::to_string(value) {
|
||||
Ok(json) => json,
|
||||
Err(error) => format!("Error: Failed to serialize {}: {}", label, error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn init_hot_org(org_id: String) -> String {
|
||||
match HOT_ORG_SERVICE.init_org(org_id) {
|
||||
Ok(org) => serialize_hot_org(org),
|
||||
@ -104,6 +128,110 @@ pub(crate) fn override_hot_org(org_id: String, json_data: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_hot_org_member(json_data: String) -> String {
|
||||
let context: OrgEnsureMemberContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid ensure-member JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.ensure_member(context) {
|
||||
Ok(org) => serialize_hot_org(org),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_hot_org(json_data: String) -> String {
|
||||
let context: OrgRegisterContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid register org JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.register_org(context) {
|
||||
Ok(result) => serialize_result(&result, "org register result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn assign_credit_line_hot_org(json_data: String) -> String {
|
||||
let context: OrgCreditLineContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org credit-line JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.assign_credit_line(context) {
|
||||
Ok(result) => serialize_result(&result, "org mutation result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn charge_checkout_hot_org(json_data: String) -> String {
|
||||
let context: OrgCheckoutContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org checkout JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.charge_checkout(context) {
|
||||
Ok(result) => serialize_result(&result, "org mutation result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_assets_hot_org(context_json: String, assets_json: String) -> String {
|
||||
let context: OrgGrantContext = match serde_json::from_str(&context_json) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org asset context JSON: {}", error),
|
||||
};
|
||||
let assets: Vec<OrgAssetGrantSeed> = match serde_json::from_str(&assets_json) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org asset seed JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.add_assets(context, assets) {
|
||||
Ok(result) => serialize_result(&result, "org mutation result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_fleet_hot_org(context_json: String, fleet_json: String) -> String {
|
||||
let context: OrgGrantContext = match serde_json::from_str(&context_json) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org fleet context JSON: {}", error),
|
||||
};
|
||||
let fleet: Vec<OrgFleetGrantSeed> = match serde_json::from_str(&fleet_json) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org fleet seed JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.add_fleet_vehicles(context, fleet) {
|
||||
Ok(result) => serialize_result(&result, "org mutation result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn leave_hot_org(json_data: String) -> String {
|
||||
let context: OrgLeaveContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org leave JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.leave_org(context) {
|
||||
Ok(result) => serialize_result::<OrgLeaveResult>(&result, "org leave result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn disband_hot_org(json_data: String) -> String {
|
||||
let context: OrgLeaveContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid org disband JSON: {}", error),
|
||||
};
|
||||
|
||||
match HOT_ORG_SERVICE.disband_org(context) {
|
||||
Ok(result) => serialize_result::<OrgDisbandResult>(&result, "org disband result"),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn save_hot_org(org_id: String) -> String {
|
||||
match HOT_ORG_SERVICE.get_org(org_id.clone()) {
|
||||
Ok(org) => {
|
||||
|
||||
37
arma/server/extension/src/store.rs
Normal file
37
arma/server/extension/src/store.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use arma_rs::Group;
|
||||
use forge_models::{StoreCheckoutContext, StoreCheckoutResult};
|
||||
use forge_services::StoreService;
|
||||
|
||||
pub fn group() -> Group {
|
||||
Group::new().command("checkout", checkout)
|
||||
}
|
||||
|
||||
fn serialize_result(result: &StoreCheckoutResult) -> String {
|
||||
match serde_json::to_string(result) {
|
||||
Ok(json) => json,
|
||||
Err(error) => format!(
|
||||
"Error: Failed to serialize store checkout result: {}",
|
||||
error
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checkout(json_data: String) -> String {
|
||||
let context: StoreCheckoutContext = match serde_json::from_str(&json_data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return format!("Error: Invalid store checkout JSON: {}", error),
|
||||
};
|
||||
|
||||
let service = StoreService::new(
|
||||
crate::bank::hot_service(),
|
||||
crate::org::hot_service(),
|
||||
crate::locker::hot_service(),
|
||||
crate::v_locker::hot_service(),
|
||||
crate::v_garage::hot_service(),
|
||||
);
|
||||
|
||||
match service.checkout(context) {
|
||||
Ok(result) => serialize_result(&result),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
@ -351,6 +351,44 @@ fn route_command(
|
||||
arguments[1].clone(),
|
||||
))
|
||||
}
|
||||
"org:hot:ensure_member" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::ensure_hot_org_member(arguments[0].clone()))
|
||||
}
|
||||
"org:hot:register" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::register_hot_org(arguments[0].clone()))
|
||||
}
|
||||
"org:hot:assign_credit_line" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
|
||||
}
|
||||
"org:hot:charge_checkout" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::charge_checkout_hot_org(arguments[0].clone()))
|
||||
}
|
||||
"org:hot:add_assets" => {
|
||||
expect_arg_count(function_name, &arguments, 2)?;
|
||||
Ok(org::add_assets_hot_org(
|
||||
arguments[0].clone(),
|
||||
arguments[1].clone(),
|
||||
))
|
||||
}
|
||||
"org:hot:add_fleet" => {
|
||||
expect_arg_count(function_name, &arguments, 2)?;
|
||||
Ok(org::add_fleet_hot_org(
|
||||
arguments[0].clone(),
|
||||
arguments[1].clone(),
|
||||
))
|
||||
}
|
||||
"org:hot:leave" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::leave_hot_org(arguments[0].clone()))
|
||||
}
|
||||
"org:hot:disband" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::disband_hot_org(arguments[0].clone()))
|
||||
}
|
||||
"org:hot:save" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(org::save_hot_org(arguments[0].clone()))
|
||||
@ -396,6 +434,10 @@ fn route_command(
|
||||
arguments[1].clone(),
|
||||
))
|
||||
}
|
||||
"store:checkout" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(crate::store::checkout(arguments[0].clone()))
|
||||
}
|
||||
"garage:create" => {
|
||||
expect_arg_count(function_name, &arguments, 1)?;
|
||||
Ok(garage::create_garage(call_context, arguments[0].clone()))
|
||||
|
||||
@ -27,6 +27,13 @@ static HOT_VGARAGE_SERVICE: LazyLock<
|
||||
VGarageHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
pub(crate) fn hot_service() -> &'static VGarageHotStateService<
|
||||
RedisVGarageRepository<ExtensionRedisClient>,
|
||||
InMemoryVGarageHotRepository,
|
||||
> {
|
||||
&HOT_VGARAGE_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for virtual garage operations.
|
||||
///
|
||||
/// Registers commands: `create`, `fetch`, `get`, `add`, `remove`, `delete`, `exists`.
|
||||
|
||||
@ -27,6 +27,13 @@ static HOT_VLOCKER_SERVICE: LazyLock<
|
||||
VLockerHotStateService::new(repository, hot_repository)
|
||||
});
|
||||
|
||||
pub(crate) fn hot_service() -> &'static VLockerHotStateService<
|
||||
RedisVLockerRepository<ExtensionRedisClient>,
|
||||
InMemoryVLockerHotRepository,
|
||||
> {
|
||||
&HOT_VLOCKER_SERVICE
|
||||
}
|
||||
|
||||
/// Creates the Arma 3 command group for virtual locker operations.
|
||||
///
|
||||
/// Registers commands: `create`, `fetch`, `get`, `add`, `remove`, `delete`, `exists`.
|
||||
|
||||
@ -4,6 +4,7 @@ pub mod cad;
|
||||
pub mod garage;
|
||||
pub mod locker;
|
||||
pub mod org;
|
||||
pub mod store;
|
||||
pub mod v_garage;
|
||||
pub mod v_locker;
|
||||
|
||||
@ -20,6 +21,15 @@ pub use cad::{
|
||||
};
|
||||
pub use garage::{Garage, HitPoints, Vehicle};
|
||||
pub use locker::{Item, Locker};
|
||||
pub use org::{CreditLineSummary, HotOrgRecord, MemberSummary, Org, OrgAssetEntry, OrgFleetEntry};
|
||||
pub use org::{
|
||||
CreditLineSummary, HotOrgRecord, MemberSummary, Org, OrgAssetEntry, OrgAssetGrantSeed,
|
||||
OrgCheckoutContext, OrgCreditLineContext, OrgDisbandMemberResult, OrgDisbandResult,
|
||||
OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext,
|
||||
OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult,
|
||||
};
|
||||
pub use store::{
|
||||
StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed,
|
||||
StoreGrantedItem, StoreGrantedVehicle,
|
||||
};
|
||||
pub use v_garage::{VGarage, VehicleCategory};
|
||||
pub use v_locker::{EquipmentCategory, VLocker};
|
||||
|
||||
@ -65,6 +65,117 @@ pub struct HotOrgRecord {
|
||||
pub members: HashMap<String, MemberSummary>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgEnsureMemberContext {
|
||||
pub org_id: String,
|
||||
pub member_uid: String,
|
||||
pub member_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgRegisterContext {
|
||||
pub requester_uid: String,
|
||||
pub requester_name: String,
|
||||
pub org_id: String,
|
||||
pub org_name: String,
|
||||
pub existing_org_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgRegisterResult {
|
||||
pub org: HotOrgRecord,
|
||||
pub actor_organization: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgCreditLineContext {
|
||||
pub requester_uid: String,
|
||||
pub org_id: String,
|
||||
pub requester_is_default_org_ceo: bool,
|
||||
pub member_uid: String,
|
||||
pub member_name: String,
|
||||
pub amount: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgCheckoutContext {
|
||||
pub requester_uid: String,
|
||||
pub org_id: String,
|
||||
pub requester_is_default_org_ceo: bool,
|
||||
pub source: String,
|
||||
pub amount: f64,
|
||||
pub commit: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgAssetGrantSeed {
|
||||
pub classname: String,
|
||||
pub category: String,
|
||||
pub quantity: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgFleetGrantSeed {
|
||||
pub classname: String,
|
||||
pub category: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgGrantContext {
|
||||
pub requester_uid: String,
|
||||
pub org_id: String,
|
||||
pub commit: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgMutationResult {
|
||||
pub org: HotOrgRecord,
|
||||
pub patch: HashMap<String, serde_json::Value>,
|
||||
pub member_uids: Vec<String>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgLeaveContext {
|
||||
pub requester_uid: String,
|
||||
pub requester_name: String,
|
||||
pub org_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgLeaveResult {
|
||||
pub actor_organization: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgDisbandMemberResult {
|
||||
pub uid: String,
|
||||
pub requester: bool,
|
||||
pub actor_organization: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrgDisbandResult {
|
||||
pub message: String,
|
||||
pub members: Vec<OrgDisbandMemberResult>,
|
||||
}
|
||||
|
||||
impl Org {
|
||||
pub fn new<S: Into<String>>(id: S, owner: S, name: S) -> Result<Self, OrgValidationError> {
|
||||
let org = Self {
|
||||
|
||||
72
lib/models/src/store.rs
Normal file
72
lib/models/src/store.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoreCheckoutItemSeed {
|
||||
pub classname: String,
|
||||
pub category: String,
|
||||
pub price_value: f64,
|
||||
pub quantity: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoreCheckoutVehicleSeed {
|
||||
pub classname: String,
|
||||
pub category: String,
|
||||
pub price_value: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoreCheckoutContext {
|
||||
pub requester_uid: String,
|
||||
pub requester_name: String,
|
||||
pub org_id: String,
|
||||
pub requester_is_default_org_ceo: bool,
|
||||
pub payment_method: String,
|
||||
#[serde(default)]
|
||||
pub items: Vec<StoreCheckoutItemSeed>,
|
||||
#[serde(default)]
|
||||
pub vehicles: Vec<StoreCheckoutVehicleSeed>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoreGrantedItem {
|
||||
pub classname: String,
|
||||
pub category: String,
|
||||
pub quantity: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoreGrantedVehicle {
|
||||
pub classname: String,
|
||||
pub category: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoreCheckoutResult {
|
||||
pub charged_total: f64,
|
||||
pub payment_method: String,
|
||||
pub message: String,
|
||||
#[serde(default)]
|
||||
pub locker_granted: Vec<StoreGrantedItem>,
|
||||
#[serde(default)]
|
||||
pub vehicle_granted: Vec<StoreGrantedVehicle>,
|
||||
#[serde(default)]
|
||||
pub locker_patch: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub va_patch: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub vgarage_patch: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub bank_patch: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub org_patch: HashMap<String, serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub org_target_uids: Vec<String>,
|
||||
}
|
||||
@ -4,6 +4,7 @@ pub mod cad;
|
||||
pub mod garage;
|
||||
pub mod locker;
|
||||
pub mod org;
|
||||
pub mod store;
|
||||
pub mod v_garage;
|
||||
pub mod v_locker;
|
||||
|
||||
@ -13,5 +14,6 @@ pub use cad::{CadStateService, CadViewService};
|
||||
pub use garage::{GarageHotStateService, GarageService};
|
||||
pub use locker::{LockerHotStateService, LockerService};
|
||||
pub use org::{OrgHotStateService, OrgService};
|
||||
pub use store::StoreService;
|
||||
pub use v_garage::{VGarageHotStateService, VGarageService};
|
||||
pub use v_locker::{VLockerHotStateService, VLockerService};
|
||||
|
||||
@ -6,9 +6,13 @@
|
||||
//! For full documentation, architecture, and examples, see the [crate README](../README.md).
|
||||
|
||||
use forge_models::{
|
||||
CreditLineSummary, HotOrgRecord, MemberSummary, Org, OrgAssetEntry, OrgFleetEntry,
|
||||
CreditLineSummary, HotOrgRecord, MemberSummary, Org, OrgAssetEntry, OrgAssetGrantSeed,
|
||||
OrgCheckoutContext, OrgCreditLineContext, OrgDisbandMemberResult, OrgDisbandResult,
|
||||
OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext,
|
||||
OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult,
|
||||
};
|
||||
use forge_repositories::{OrgHotRepository, OrgRepository};
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
/// Service layer implementation for organization business logic and operations.
|
||||
@ -328,6 +332,19 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
||||
|
||||
pub fn init_org(&self, id: String) -> Result<HotOrgRecord, String> {
|
||||
if let Some(org) = self.repository.get(&id)? {
|
||||
if !org.members.is_empty() || !org.assets.is_empty() || !org.fleet.is_empty() {
|
||||
return Ok(org);
|
||||
}
|
||||
|
||||
let hydrated_org = self.hydrate_org(&id)?;
|
||||
if !hydrated_org.members.is_empty()
|
||||
|| !hydrated_org.assets.is_empty()
|
||||
|| !hydrated_org.fleet.is_empty()
|
||||
{
|
||||
self.repository.save(&hydrated_org)?;
|
||||
return Ok(hydrated_org);
|
||||
}
|
||||
|
||||
return Ok(org);
|
||||
}
|
||||
|
||||
@ -392,6 +409,428 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
||||
self.repository.delete(&id)
|
||||
}
|
||||
|
||||
pub fn ensure_member(&self, context: OrgEnsureMemberContext) -> Result<HotOrgRecord, String> {
|
||||
if context.org_id.trim().is_empty() || context.member_uid.trim().is_empty() {
|
||||
return Err("A valid organization and member UID are required.".to_string());
|
||||
}
|
||||
|
||||
let mut org = self.get_org(context.org_id)?;
|
||||
if !org.members.contains_key(&context.member_uid) {
|
||||
let member_name = if context.member_name.trim().is_empty() {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
context.member_name
|
||||
};
|
||||
org.members.insert(
|
||||
context.member_uid.clone(),
|
||||
MemberSummary {
|
||||
uid: context.member_uid,
|
||||
name: member_name,
|
||||
},
|
||||
);
|
||||
self.repository.save(&org)?;
|
||||
}
|
||||
|
||||
Ok(org)
|
||||
}
|
||||
|
||||
pub fn register_org(&self, context: OrgRegisterContext) -> Result<OrgRegisterResult, String> {
|
||||
if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() {
|
||||
return Err("A valid requester and organization ID are required.".to_string());
|
||||
}
|
||||
if context.org_name.trim().is_empty() {
|
||||
return Err("Organization name cannot be empty.".to_string());
|
||||
}
|
||||
if !context.existing_org_id.trim().is_empty()
|
||||
&& !context.existing_org_id.eq_ignore_ascii_case("default")
|
||||
{
|
||||
return Err("Player already belongs to an organization.".to_string());
|
||||
}
|
||||
if self.service.org_exists(context.org_id.clone())?
|
||||
|| self.repository.get(&context.org_id)?.is_some()
|
||||
{
|
||||
return Err("An organization already exists for this phone number.".to_string());
|
||||
}
|
||||
|
||||
let org = Org {
|
||||
id: context.org_id.clone(),
|
||||
owner: context.requester_uid.clone(),
|
||||
name: context.org_name,
|
||||
funds: 0.0,
|
||||
reputation: 0,
|
||||
credit_lines: HashMap::new(),
|
||||
};
|
||||
org.validate()
|
||||
.map_err(|error| format!("Validation failed: {}", error))?;
|
||||
|
||||
let json_data = serde_json::to_string(&org)
|
||||
.map_err(|error| format!("Failed to serialize org: {}", error))?;
|
||||
let persisted_org = self.service.create_org(context.org_id.clone(), json_data)?;
|
||||
let mut hot_org =
|
||||
HotOrgRecord::from_parts(persisted_org, HashMap::new(), HashMap::new(), Vec::new());
|
||||
hot_org.members.insert(
|
||||
context.requester_uid.clone(),
|
||||
MemberSummary {
|
||||
uid: context.requester_uid.clone(),
|
||||
name: if context.requester_name.trim().is_empty() {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
context.requester_name
|
||||
},
|
||||
},
|
||||
);
|
||||
self.repository.save(&hot_org)?;
|
||||
|
||||
if context.existing_org_id.eq_ignore_ascii_case("default") {
|
||||
let mut default_org = self.init_org("default".to_string())?;
|
||||
default_org.members.remove(&context.requester_uid);
|
||||
self.repository.save(&default_org)?;
|
||||
}
|
||||
|
||||
Ok(OrgRegisterResult {
|
||||
org: hot_org,
|
||||
actor_organization: context.org_id,
|
||||
message: String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assign_credit_line(
|
||||
&self,
|
||||
context: OrgCreditLineContext,
|
||||
) -> Result<OrgMutationResult, String> {
|
||||
if context.requester_uid.trim().is_empty()
|
||||
|| context.member_uid.trim().is_empty()
|
||||
|| context.org_id.trim().is_empty()
|
||||
{
|
||||
return Err("A valid requester, member, and organization are required.".to_string());
|
||||
}
|
||||
if context.amount <= 0.0 {
|
||||
return Err("A valid credit amount is required.".to_string());
|
||||
}
|
||||
|
||||
let mut org = self.get_org(context.org_id)?;
|
||||
if !can_manage_treasury(
|
||||
&org,
|
||||
&context.requester_uid,
|
||||
context.requester_is_default_org_ceo,
|
||||
) {
|
||||
return Err(
|
||||
"Only the organization leader or CEO can manage treasury actions.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let member_record = org
|
||||
.members
|
||||
.get(&context.member_uid)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
"Selected member was not found in the organization roster.".to_string()
|
||||
})?;
|
||||
let member_name = if context.member_name.trim().is_empty() {
|
||||
member_record.name
|
||||
} else {
|
||||
context.member_name
|
||||
};
|
||||
|
||||
org.credit_lines.insert(
|
||||
context.member_uid.clone(),
|
||||
CreditLineSummary {
|
||||
uid: context.member_uid.clone(),
|
||||
name: member_name.clone(),
|
||||
amount: context.amount,
|
||||
},
|
||||
);
|
||||
self.repository.save(&org)?;
|
||||
|
||||
Ok(OrgMutationResult {
|
||||
patch: build_org_patch(&org, &["credit_lines"])?,
|
||||
member_uids: resolve_member_uids(&org, Some(&context.requester_uid)),
|
||||
message: format!(
|
||||
"Credit line of ${} assigned to {}.",
|
||||
format_currency(context.amount),
|
||||
member_name
|
||||
),
|
||||
org,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn charge_checkout(
|
||||
&self,
|
||||
context: OrgCheckoutContext,
|
||||
) -> Result<OrgMutationResult, String> {
|
||||
if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() {
|
||||
return Err("A valid requester and organization are required.".to_string());
|
||||
}
|
||||
if context.amount <= 0.0 {
|
||||
return Err("Checkout amount must be greater than zero.".to_string());
|
||||
}
|
||||
|
||||
let mut org = self.get_org(context.org_id)?;
|
||||
let member_uids = resolve_member_uids(&org, Some(&context.requester_uid));
|
||||
|
||||
match context.source.trim().to_ascii_lowercase().as_str() {
|
||||
"org_funds" => {
|
||||
if !can_manage_treasury(
|
||||
&org,
|
||||
&context.requester_uid,
|
||||
context.requester_is_default_org_ceo,
|
||||
) {
|
||||
return Err(
|
||||
"Only the organization leader or CEO can charge org funds.".to_string()
|
||||
);
|
||||
}
|
||||
if org.funds < context.amount {
|
||||
return Err("Organization funds cannot cover this checkout.".to_string());
|
||||
}
|
||||
|
||||
org.funds -= context.amount;
|
||||
self.repository.save(&org)?;
|
||||
|
||||
Ok(OrgMutationResult {
|
||||
patch: build_org_patch(&org, &["funds"])?,
|
||||
member_uids,
|
||||
message: String::new(),
|
||||
org,
|
||||
})
|
||||
}
|
||||
"credit_line" => {
|
||||
let mut credit_line = org
|
||||
.credit_lines
|
||||
.get(&context.requester_uid)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
"Assigned credit line cannot cover this checkout.".to_string()
|
||||
})?;
|
||||
|
||||
if credit_line.amount < context.amount {
|
||||
return Err("Assigned credit line cannot cover this checkout.".to_string());
|
||||
}
|
||||
|
||||
credit_line.amount -= context.amount;
|
||||
org.credit_lines
|
||||
.insert(context.requester_uid.clone(), credit_line);
|
||||
self.repository.save(&org)?;
|
||||
|
||||
Ok(OrgMutationResult {
|
||||
patch: build_org_patch(&org, &["credit_lines"])?,
|
||||
member_uids,
|
||||
message: String::new(),
|
||||
org,
|
||||
})
|
||||
}
|
||||
_ => Err("Selected organization payment source is unsupported.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_assets(
|
||||
&self,
|
||||
context: OrgGrantContext,
|
||||
assets: Vec<OrgAssetGrantSeed>,
|
||||
) -> Result<OrgMutationResult, String> {
|
||||
if context.org_id.trim().is_empty() {
|
||||
return Err("A valid organization is required for asset updates.".to_string());
|
||||
}
|
||||
if assets.is_empty() {
|
||||
let org = self.get_org(context.org_id)?;
|
||||
return Ok(OrgMutationResult {
|
||||
org,
|
||||
patch: HashMap::new(),
|
||||
member_uids: Vec::new(),
|
||||
message: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut org = self.get_org(context.org_id)?;
|
||||
for asset in assets {
|
||||
if asset.classname.trim().is_empty() || asset.quantity <= 0 {
|
||||
continue;
|
||||
}
|
||||
let category = asset.category.trim().to_ascii_lowercase();
|
||||
let category_assets = org.assets.entry(category.clone()).or_default();
|
||||
let entry = category_assets
|
||||
.entry(asset.classname.clone())
|
||||
.or_insert_with(|| OrgAssetEntry {
|
||||
classname: asset.classname.clone(),
|
||||
asset_type: category.clone(),
|
||||
quantity: 0,
|
||||
});
|
||||
entry.quantity += asset.quantity;
|
||||
}
|
||||
|
||||
self.repository.save(&org)?;
|
||||
|
||||
Ok(OrgMutationResult {
|
||||
patch: build_org_patch(&org, &["assets"])?,
|
||||
member_uids: resolve_member_uids(&org, Some(&context.requester_uid)),
|
||||
message: String::new(),
|
||||
org,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_fleet_vehicles(
|
||||
&self,
|
||||
context: OrgGrantContext,
|
||||
vehicles: Vec<OrgFleetGrantSeed>,
|
||||
) -> Result<OrgMutationResult, String> {
|
||||
if context.org_id.trim().is_empty() {
|
||||
return Err("A valid organization is required for fleet updates.".to_string());
|
||||
}
|
||||
if vehicles.is_empty() {
|
||||
let org = self.get_org(context.org_id)?;
|
||||
return Ok(OrgMutationResult {
|
||||
org,
|
||||
patch: HashMap::new(),
|
||||
member_uids: Vec::new(),
|
||||
message: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut org = self.get_org(context.org_id)?;
|
||||
let mut fleet_index = org.fleet.len();
|
||||
for vehicle in vehicles {
|
||||
if vehicle.classname.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let fleet_type = vehicle.category.trim().to_ascii_lowercase();
|
||||
let mut fleet_key = format!("{}_{}", vehicle.classname, fleet_index);
|
||||
while org.fleet.contains_key(&fleet_key) {
|
||||
fleet_index += 1;
|
||||
fleet_key = format!("{}_{}", vehicle.classname, fleet_index);
|
||||
}
|
||||
|
||||
org.fleet.insert(
|
||||
fleet_key,
|
||||
OrgFleetEntry {
|
||||
classname: vehicle.classname.clone(),
|
||||
name: vehicle.classname,
|
||||
fleet_type,
|
||||
status: "Ready".to_string(),
|
||||
damage: "0%".to_string(),
|
||||
},
|
||||
);
|
||||
fleet_index += 1;
|
||||
}
|
||||
|
||||
self.repository.save(&org)?;
|
||||
|
||||
Ok(OrgMutationResult {
|
||||
patch: build_org_patch(&org, &["fleet"])?,
|
||||
member_uids: resolve_member_uids(&org, Some(&context.requester_uid)),
|
||||
message: String::new(),
|
||||
org,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn leave_org(&self, context: OrgLeaveContext) -> Result<OrgLeaveResult, String> {
|
||||
if context.requester_uid.trim().is_empty() {
|
||||
return Err("A valid player UID is required.".to_string());
|
||||
}
|
||||
if context.org_id.trim().is_empty() || context.org_id.eq_ignore_ascii_case("default") {
|
||||
return Err("You are already assigned to the default organization.".to_string());
|
||||
}
|
||||
|
||||
let mut org = self.get_org(context.org_id)?;
|
||||
if org.owner == context.requester_uid {
|
||||
return Err(
|
||||
"Organization owners must disband the organization instead of leaving it."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let org_name = org.name.clone();
|
||||
org.members.remove(&context.requester_uid);
|
||||
self.repository.save(&org)?;
|
||||
|
||||
let mut default_org = self.init_org("default".to_string())?;
|
||||
let requester_uid = context.requester_uid.clone();
|
||||
default_org.members.insert(
|
||||
requester_uid.clone(),
|
||||
MemberSummary {
|
||||
uid: requester_uid,
|
||||
name: if context.requester_name.trim().is_empty() {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
context.requester_name
|
||||
},
|
||||
},
|
||||
);
|
||||
self.repository.save(&default_org)?;
|
||||
|
||||
Ok(OrgLeaveResult {
|
||||
actor_organization: "default".to_string(),
|
||||
message: format!(
|
||||
"You left {} and returned to the default organization.",
|
||||
org_name
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disband_org(&self, context: OrgLeaveContext) -> Result<OrgDisbandResult, String> {
|
||||
if context.requester_uid.trim().is_empty() {
|
||||
return Err("A valid player UID is required.".to_string());
|
||||
}
|
||||
if context.org_id.trim().is_empty() || context.org_id.eq_ignore_ascii_case("default") {
|
||||
return Err("Only active player organizations can be disbanded.".to_string());
|
||||
}
|
||||
|
||||
let org = self.get_org(context.org_id.clone())?;
|
||||
if org.owner != context.requester_uid {
|
||||
return Err("Only the organization owner can disband this organization.".to_string());
|
||||
}
|
||||
|
||||
let org_name = org.name.clone();
|
||||
let mut default_org = self.init_org("default".to_string())?;
|
||||
let mut member_results = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
for (member_uid, member) in &org.members {
|
||||
if seen.insert(member_uid.clone()) {
|
||||
default_org
|
||||
.members
|
||||
.insert(member_uid.clone(), member.clone());
|
||||
member_results.push(OrgDisbandMemberResult {
|
||||
uid: member_uid.clone(),
|
||||
requester: member_uid == &context.requester_uid,
|
||||
actor_organization: "default".to_string(),
|
||||
message: if member_uid == &context.requester_uid {
|
||||
format!("Your organization, {}, has been disbanded.", org_name)
|
||||
} else {
|
||||
format!("{} has been disbanded.", org_name)
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if seen.insert(context.requester_uid.clone()) {
|
||||
default_org.members.insert(
|
||||
context.requester_uid.clone(),
|
||||
MemberSummary {
|
||||
uid: context.requester_uid.clone(),
|
||||
name: if context.requester_name.trim().is_empty() {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
context.requester_name
|
||||
},
|
||||
},
|
||||
);
|
||||
member_results.push(OrgDisbandMemberResult {
|
||||
uid: context.requester_uid,
|
||||
requester: true,
|
||||
actor_organization: "default".to_string(),
|
||||
message: format!("Your organization, {}, has been disbanded.", org_name),
|
||||
});
|
||||
}
|
||||
|
||||
self.repository.save(&default_org)?;
|
||||
self.service.delete_org(context.org_id.clone())?;
|
||||
self.repository.delete(&context.org_id)?;
|
||||
|
||||
Ok(OrgDisbandResult {
|
||||
message: format!("{} has been disbanded.", org_name),
|
||||
members: member_results,
|
||||
})
|
||||
}
|
||||
|
||||
fn hydrate_org(&self, id: &str) -> Result<HotOrgRecord, String> {
|
||||
let org = self
|
||||
.service
|
||||
@ -403,3 +842,65 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
||||
Ok(HotOrgRecord::from_parts(org, assets, fleet, members))
|
||||
}
|
||||
}
|
||||
|
||||
fn can_manage_treasury(
|
||||
org: &HotOrgRecord,
|
||||
requester_uid: &str,
|
||||
requester_is_default_org_ceo: bool,
|
||||
) -> bool {
|
||||
org.owner == requester_uid
|
||||
|| ((org.id.eq_ignore_ascii_case("default") || org.owner.eq_ignore_ascii_case("server"))
|
||||
&& requester_is_default_org_ceo)
|
||||
}
|
||||
|
||||
fn resolve_member_uids(org: &HotOrgRecord, requester_uid: Option<&str>) -> Vec<String> {
|
||||
let mut member_uids = org.members.keys().cloned().collect::<Vec<_>>();
|
||||
if let Some(uid) = requester_uid {
|
||||
if !uid.is_empty() && !member_uids.iter().any(|member_uid| member_uid == uid) {
|
||||
member_uids.push(uid.to_string());
|
||||
}
|
||||
}
|
||||
member_uids
|
||||
}
|
||||
|
||||
fn build_org_patch(org: &HotOrgRecord, fields: &[&str]) -> Result<HashMap<String, Value>, String> {
|
||||
let mut patch = HashMap::new();
|
||||
for field in fields {
|
||||
patch.insert((*field).to_string(), current_org_field_value(org, field)?);
|
||||
}
|
||||
Ok(patch)
|
||||
}
|
||||
|
||||
fn current_org_field_value(org: &HotOrgRecord, field: &str) -> Result<Value, String> {
|
||||
match field {
|
||||
"id" => Ok(json!(org.id)),
|
||||
"owner" => Ok(json!(org.owner)),
|
||||
"name" => Ok(json!(org.name)),
|
||||
"funds" => Ok(json!(org.funds)),
|
||||
"reputation" => Ok(json!(org.reputation)),
|
||||
"credit_lines" => serde_json::to_value(&org.credit_lines)
|
||||
.map_err(|error| format!("Failed to serialize org credit lines: {}", error)),
|
||||
"assets" => serde_json::to_value(&org.assets)
|
||||
.map_err(|error| format!("Failed to serialize org assets: {}", error)),
|
||||
"fleet" => serde_json::to_value(&org.fleet)
|
||||
.map_err(|error| format!("Failed to serialize org fleet: {}", error)),
|
||||
"members" => serde_json::to_value(&org.members)
|
||||
.map_err(|error| format!("Failed to serialize org members: {}", error)),
|
||||
_ => Err(format!("Unknown field: {}", field)),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_currency(amount: f64) -> String {
|
||||
let rounded = amount.max(0.0).round() as i64;
|
||||
let digits = rounded.to_string();
|
||||
let mut formatted = String::new();
|
||||
|
||||
for (index, character) in digits.chars().rev().enumerate() {
|
||||
if index > 0 && index % 3 == 0 {
|
||||
formatted.push(',');
|
||||
}
|
||||
formatted.push(character);
|
||||
}
|
||||
|
||||
formatted.chars().rev().collect()
|
||||
}
|
||||
|
||||
685
lib/services/src/store.rs
Normal file
685
lib/services/src/store.rs
Normal file
@ -0,0 +1,685 @@
|
||||
use forge_models::{
|
||||
Bank, BankCheckoutContext, BankMutationResult, EquipmentCategory, HotOrgRecord, Item, Locker,
|
||||
OrgFleetEntry, StoreCheckoutContext, StoreCheckoutResult, StoreGrantedItem,
|
||||
StoreGrantedVehicle, VGarage, VLocker, VehicleCategory,
|
||||
};
|
||||
use forge_repositories::{
|
||||
BankHotRepository, BankRepository, LockerHotRepository, LockerRepository, OrgHotRepository,
|
||||
OrgRepository, VGarageHotRepository, VGarageRepository, VLockerHotRepository,
|
||||
VLockerRepository,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
BankHotStateService, LockerHotStateService, OrgHotStateService, VGarageHotStateService,
|
||||
VLockerHotStateService,
|
||||
};
|
||||
|
||||
pub trait StoreBankBackend {
|
||||
fn get_bank(&self, uid: &str) -> Result<Bank, String>;
|
||||
fn preview_checkout(
|
||||
&self,
|
||||
uid: &str,
|
||||
amount: f64,
|
||||
source: &str,
|
||||
) -> Result<BankMutationResult, String>;
|
||||
fn override_bank(&self, uid: &str, bank: &Bank) -> Result<Bank, String>;
|
||||
}
|
||||
|
||||
pub trait StoreOrgBackend {
|
||||
fn get_org(&self, org_id: &str) -> Result<HotOrgRecord, String>;
|
||||
fn override_org(&self, org_id: &str, org: HotOrgRecord) -> Result<HotOrgRecord, String>;
|
||||
}
|
||||
|
||||
pub trait StoreLockerBackend {
|
||||
fn get_locker(&self, uid: &str) -> Result<Locker, String>;
|
||||
fn override_locker(&self, uid: &str, items: HashMap<String, Item>) -> Result<Locker, String>;
|
||||
}
|
||||
|
||||
pub trait StoreVLockerBackend {
|
||||
fn fetch_locker(&self, uid: &str) -> Result<VLocker, String>;
|
||||
fn override_locker(&self, uid: &str, locker: VLocker) -> Result<VLocker, String>;
|
||||
}
|
||||
|
||||
pub trait StoreVGarageBackend {
|
||||
fn fetch_garage(&self, uid: &str) -> Result<VGarage, String>;
|
||||
fn override_garage(&self, uid: &str, garage: VGarage) -> Result<VGarage, String>;
|
||||
}
|
||||
|
||||
impl<R: BankRepository, H: BankHotRepository> StoreBankBackend for BankHotStateService<R, H> {
|
||||
fn get_bank(&self, uid: &str) -> Result<Bank, String> {
|
||||
BankHotStateService::get_bank(self, uid.to_string())
|
||||
}
|
||||
|
||||
fn preview_checkout(
|
||||
&self,
|
||||
uid: &str,
|
||||
amount: f64,
|
||||
source: &str,
|
||||
) -> Result<BankMutationResult, String> {
|
||||
BankHotStateService::charge_checkout(
|
||||
self,
|
||||
uid.to_string(),
|
||||
amount,
|
||||
BankCheckoutContext {
|
||||
source_field: source.to_string(),
|
||||
commit: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn override_bank(&self, uid: &str, bank: &Bank) -> Result<Bank, String> {
|
||||
let json = serde_json::to_string(bank)
|
||||
.map_err(|error| format!("Invalid bank override JSON: {}", error))?;
|
||||
BankHotStateService::override_bank(self, uid.to_string(), json)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BankRepository, H: BankHotRepository> StoreBankBackend for &BankHotStateService<R, H> {
|
||||
fn get_bank(&self, uid: &str) -> Result<Bank, String> {
|
||||
BankHotStateService::get_bank(self, uid.to_string())
|
||||
}
|
||||
|
||||
fn preview_checkout(
|
||||
&self,
|
||||
uid: &str,
|
||||
amount: f64,
|
||||
source: &str,
|
||||
) -> Result<BankMutationResult, String> {
|
||||
BankHotStateService::charge_checkout(
|
||||
self,
|
||||
uid.to_string(),
|
||||
amount,
|
||||
BankCheckoutContext {
|
||||
source_field: source.to_string(),
|
||||
commit: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn override_bank(&self, uid: &str, bank: &Bank) -> Result<Bank, String> {
|
||||
let json = serde_json::to_string(bank)
|
||||
.map_err(|error| format!("Invalid bank override JSON: {}", error))?;
|
||||
BankHotStateService::override_bank(self, uid.to_string(), json)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: OrgRepository, H: OrgHotRepository> StoreOrgBackend for OrgHotStateService<R, H> {
|
||||
fn get_org(&self, org_id: &str) -> Result<HotOrgRecord, String> {
|
||||
OrgHotStateService::get_org(self, org_id.to_string())
|
||||
}
|
||||
|
||||
fn override_org(&self, org_id: &str, org: HotOrgRecord) -> Result<HotOrgRecord, String> {
|
||||
OrgHotStateService::override_org(self, org_id.to_string(), org)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: OrgRepository, H: OrgHotRepository> StoreOrgBackend for &OrgHotStateService<R, H> {
|
||||
fn get_org(&self, org_id: &str) -> Result<HotOrgRecord, String> {
|
||||
OrgHotStateService::get_org(self, org_id.to_string())
|
||||
}
|
||||
|
||||
fn override_org(&self, org_id: &str, org: HotOrgRecord) -> Result<HotOrgRecord, String> {
|
||||
OrgHotStateService::override_org(self, org_id.to_string(), org)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: LockerRepository, H: LockerHotRepository> StoreLockerBackend
|
||||
for LockerHotStateService<R, H>
|
||||
{
|
||||
fn get_locker(&self, uid: &str) -> Result<Locker, String> {
|
||||
LockerHotStateService::get_locker(self, uid.to_string())
|
||||
}
|
||||
|
||||
fn override_locker(&self, uid: &str, items: HashMap<String, Item>) -> Result<Locker, String> {
|
||||
LockerHotStateService::override_locker(self, uid.to_string(), items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: LockerRepository, H: LockerHotRepository> StoreLockerBackend
|
||||
for &LockerHotStateService<R, H>
|
||||
{
|
||||
fn get_locker(&self, uid: &str) -> Result<Locker, String> {
|
||||
LockerHotStateService::get_locker(self, uid.to_string())
|
||||
}
|
||||
|
||||
fn override_locker(&self, uid: &str, items: HashMap<String, Item>) -> Result<Locker, String> {
|
||||
LockerHotStateService::override_locker(self, uid.to_string(), items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: VLockerRepository, H: VLockerHotRepository> StoreVLockerBackend
|
||||
for VLockerHotStateService<R, H>
|
||||
{
|
||||
fn fetch_locker(&self, uid: &str) -> Result<VLocker, String> {
|
||||
VLockerHotStateService::fetch_locker(self, uid)
|
||||
}
|
||||
|
||||
fn override_locker(&self, uid: &str, locker: VLocker) -> Result<VLocker, String> {
|
||||
VLockerHotStateService::override_locker(self, uid, locker)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: VLockerRepository, H: VLockerHotRepository> StoreVLockerBackend
|
||||
for &VLockerHotStateService<R, H>
|
||||
{
|
||||
fn fetch_locker(&self, uid: &str) -> Result<VLocker, String> {
|
||||
VLockerHotStateService::fetch_locker(self, uid)
|
||||
}
|
||||
|
||||
fn override_locker(&self, uid: &str, locker: VLocker) -> Result<VLocker, String> {
|
||||
VLockerHotStateService::override_locker(self, uid, locker)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: VGarageRepository, H: VGarageHotRepository> StoreVGarageBackend
|
||||
for VGarageHotStateService<R, H>
|
||||
{
|
||||
fn fetch_garage(&self, uid: &str) -> Result<VGarage, String> {
|
||||
VGarageHotStateService::fetch_garage(self, uid)
|
||||
}
|
||||
|
||||
fn override_garage(&self, uid: &str, garage: VGarage) -> Result<VGarage, String> {
|
||||
VGarageHotStateService::override_garage(self, uid, garage)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: VGarageRepository, H: VGarageHotRepository> StoreVGarageBackend
|
||||
for &VGarageHotStateService<R, H>
|
||||
{
|
||||
fn fetch_garage(&self, uid: &str) -> Result<VGarage, String> {
|
||||
VGarageHotStateService::fetch_garage(self, uid)
|
||||
}
|
||||
|
||||
fn override_garage(&self, uid: &str, garage: VGarage) -> Result<VGarage, String> {
|
||||
VGarageHotStateService::override_garage(self, uid, garage)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StoreService<B, O, L, VL, VG> {
|
||||
bank: B,
|
||||
org: O,
|
||||
locker: L,
|
||||
vlocker: VL,
|
||||
vgarage: VG,
|
||||
}
|
||||
|
||||
impl<B, O, L, VL, VG> StoreService<B, O, L, VL, VG> {
|
||||
pub fn new(bank: B, org: O, locker: L, vlocker: VL, vgarage: VG) -> Self {
|
||||
Self {
|
||||
bank,
|
||||
org,
|
||||
locker,
|
||||
vlocker,
|
||||
vgarage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, O, L, VL, VG> StoreService<B, O, L, VL, VG>
|
||||
where
|
||||
B: StoreBankBackend,
|
||||
O: StoreOrgBackend,
|
||||
L: StoreLockerBackend,
|
||||
VL: StoreVLockerBackend,
|
||||
VG: StoreVGarageBackend,
|
||||
{
|
||||
pub fn checkout(&self, context: StoreCheckoutContext) -> Result<StoreCheckoutResult, String> {
|
||||
if context.requester_uid.trim().is_empty() {
|
||||
return Err("A valid requester UID is required.".to_string());
|
||||
}
|
||||
if context.items.is_empty() && context.vehicles.is_empty() {
|
||||
return Err("Add at least one item before checkout.".to_string());
|
||||
}
|
||||
|
||||
let charged_total = checkout_total(&context);
|
||||
if charged_total <= 0.0 {
|
||||
return Err("Checkout total must be greater than zero.".to_string());
|
||||
}
|
||||
|
||||
let requester_uid = context.requester_uid.trim();
|
||||
let payment_method = context.payment_method.trim().to_ascii_lowercase();
|
||||
|
||||
let original_locker = self.locker.get_locker(requester_uid)?;
|
||||
let original_vlocker = self.vlocker.fetch_locker(requester_uid)?;
|
||||
let original_vgarage = self.vgarage.fetch_garage(requester_uid)?;
|
||||
|
||||
let mut next_locker = original_locker.clone();
|
||||
let mut next_vlocker = original_vlocker.clone();
|
||||
let mut next_vgarage = original_vgarage.clone();
|
||||
|
||||
let mut locker_patch = HashMap::new();
|
||||
let mut va_patch = HashMap::new();
|
||||
let mut vgarage_patch = HashMap::new();
|
||||
let mut locker_granted = Vec::new();
|
||||
let mut vehicle_granted = Vec::new();
|
||||
let mut va_categories_changed: Vec<&str> = Vec::new();
|
||||
let mut vgarage_categories_changed: Vec<&str> = Vec::new();
|
||||
|
||||
for item_seed in &context.items {
|
||||
if item_seed.classname.trim().is_empty() || item_seed.quantity == 0 {
|
||||
return Err("Checkout contains an invalid item entry.".to_string());
|
||||
}
|
||||
|
||||
let locker_category = resolve_locker_category(&item_seed.category)?;
|
||||
let arsenal_category = resolve_arsenal_category(&item_seed.category)?;
|
||||
|
||||
let existing_amount = next_locker
|
||||
.items
|
||||
.get(&item_seed.classname)
|
||||
.map(|entry| entry.amount)
|
||||
.unwrap_or(0);
|
||||
let updated_item = Item {
|
||||
category: locker_category.to_string(),
|
||||
classname: item_seed.classname.clone(),
|
||||
amount: existing_amount.saturating_add(item_seed.quantity),
|
||||
};
|
||||
|
||||
next_locker
|
||||
.items
|
||||
.insert(item_seed.classname.clone(), updated_item.clone());
|
||||
locker_patch.insert(
|
||||
item_seed.classname.clone(),
|
||||
serde_json::to_value(&updated_item)
|
||||
.map_err(|error| format!("Failed to serialize locker patch: {}", error))?,
|
||||
);
|
||||
locker_granted.push(StoreGrantedItem {
|
||||
classname: item_seed.classname.clone(),
|
||||
category: locker_category.to_string(),
|
||||
quantity: item_seed.quantity,
|
||||
});
|
||||
|
||||
match arsenal_category {
|
||||
EquipmentCategory::Items => {
|
||||
push_unique(&mut next_vlocker.items, &item_seed.classname);
|
||||
push_unique_str(&mut va_categories_changed, "items");
|
||||
}
|
||||
EquipmentCategory::Weapons => {
|
||||
push_unique(&mut next_vlocker.weapons, &item_seed.classname);
|
||||
push_unique_str(&mut va_categories_changed, "weapons");
|
||||
}
|
||||
EquipmentCategory::Magazines => {
|
||||
push_unique(&mut next_vlocker.magazines, &item_seed.classname);
|
||||
push_unique_str(&mut va_categories_changed, "magazines");
|
||||
}
|
||||
EquipmentCategory::Backpacks => {
|
||||
push_unique(&mut next_vlocker.backpacks, &item_seed.classname);
|
||||
push_unique_str(&mut va_categories_changed, "backpacks");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if next_locker.items.len() > 25 {
|
||||
return Err(
|
||||
"Locker capacity would exceed 25 unique items. Clear space before checkout."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
for category in va_categories_changed {
|
||||
match category {
|
||||
"items" => {
|
||||
va_patch.insert(category.to_string(), json!(next_vlocker.items));
|
||||
}
|
||||
"weapons" => {
|
||||
va_patch.insert(category.to_string(), json!(next_vlocker.weapons));
|
||||
}
|
||||
"magazines" => {
|
||||
va_patch.insert(category.to_string(), json!(next_vlocker.magazines));
|
||||
}
|
||||
"backpacks" => {
|
||||
va_patch.insert(category.to_string(), json!(next_vlocker.backpacks));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for vehicle_seed in &context.vehicles {
|
||||
if vehicle_seed.classname.trim().is_empty() {
|
||||
return Err("Vehicle checkout entry was missing a classname.".to_string());
|
||||
}
|
||||
|
||||
let vehicle_category = resolve_vehicle_category(&vehicle_seed.category)?;
|
||||
match vehicle_category {
|
||||
VehicleCategory::Cars => {
|
||||
push_unique(&mut next_vgarage.cars, &vehicle_seed.classname);
|
||||
push_unique_str(&mut vgarage_categories_changed, "cars");
|
||||
}
|
||||
VehicleCategory::Armor => {
|
||||
push_unique(&mut next_vgarage.armor, &vehicle_seed.classname);
|
||||
push_unique_str(&mut vgarage_categories_changed, "armor");
|
||||
}
|
||||
VehicleCategory::Helis => {
|
||||
push_unique(&mut next_vgarage.helis, &vehicle_seed.classname);
|
||||
push_unique_str(&mut vgarage_categories_changed, "helis");
|
||||
}
|
||||
VehicleCategory::Planes => {
|
||||
push_unique(&mut next_vgarage.planes, &vehicle_seed.classname);
|
||||
push_unique_str(&mut vgarage_categories_changed, "planes");
|
||||
}
|
||||
VehicleCategory::Naval => {
|
||||
push_unique(&mut next_vgarage.naval, &vehicle_seed.classname);
|
||||
push_unique_str(&mut vgarage_categories_changed, "naval");
|
||||
}
|
||||
VehicleCategory::Other => {
|
||||
push_unique(&mut next_vgarage.other, &vehicle_seed.classname);
|
||||
push_unique_str(&mut vgarage_categories_changed, "other");
|
||||
}
|
||||
}
|
||||
|
||||
vehicle_granted.push(StoreGrantedVehicle {
|
||||
classname: vehicle_seed.classname.clone(),
|
||||
category: vehicle_seed.category.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
for category in vgarage_categories_changed {
|
||||
match category {
|
||||
"cars" => {
|
||||
vgarage_patch.insert(category.to_string(), json!(next_vgarage.cars));
|
||||
}
|
||||
"armor" => {
|
||||
vgarage_patch.insert(category.to_string(), json!(next_vgarage.armor));
|
||||
}
|
||||
"helis" => {
|
||||
vgarage_patch.insert(category.to_string(), json!(next_vgarage.helis));
|
||||
}
|
||||
"planes" => {
|
||||
vgarage_patch.insert(category.to_string(), json!(next_vgarage.planes));
|
||||
}
|
||||
"naval" => {
|
||||
vgarage_patch.insert(category.to_string(), json!(next_vgarage.naval));
|
||||
}
|
||||
"other" => {
|
||||
vgarage_patch.insert(category.to_string(), json!(next_vgarage.other));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut bank_patch = HashMap::new();
|
||||
let mut final_bank = None;
|
||||
let mut original_bank = None;
|
||||
|
||||
let mut org_patch = HashMap::new();
|
||||
let mut org_target_uids = Vec::new();
|
||||
let mut final_org = None;
|
||||
let mut original_org = None;
|
||||
|
||||
match payment_method.as_str() {
|
||||
"cash" | "bank" => {
|
||||
original_bank = Some(self.bank.get_bank(requester_uid)?);
|
||||
let preview = self.bank.preview_checkout(
|
||||
requester_uid,
|
||||
charged_total,
|
||||
payment_method.as_str(),
|
||||
)?;
|
||||
bank_patch = preview.patch.clone();
|
||||
final_bank = Some(preview.account);
|
||||
}
|
||||
"org_funds" | "credit_line" => {
|
||||
if context.org_id.trim().is_empty() {
|
||||
return Err("A valid organization is required for this checkout.".to_string());
|
||||
}
|
||||
|
||||
let mut org = self.org.get_org(&context.org_id)?;
|
||||
original_org = Some(org.clone());
|
||||
|
||||
match payment_method.as_str() {
|
||||
"org_funds" => {
|
||||
if !can_manage_treasury(
|
||||
&org,
|
||||
requester_uid,
|
||||
context.requester_is_default_org_ceo,
|
||||
) {
|
||||
return Err(
|
||||
"Only the organization leader or CEO can charge org funds."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if org.funds < charged_total {
|
||||
return Err(
|
||||
"Organization funds cannot cover this checkout.".to_string()
|
||||
);
|
||||
}
|
||||
org.funds -= charged_total;
|
||||
org_patch.insert("funds".to_string(), json!(org.funds));
|
||||
}
|
||||
"credit_line" => {
|
||||
let credit_line =
|
||||
org.credit_lines.get_mut(requester_uid).ok_or_else(|| {
|
||||
"Assigned credit line cannot cover this checkout.".to_string()
|
||||
})?;
|
||||
if credit_line.amount < charged_total {
|
||||
return Err(
|
||||
"Assigned credit line cannot cover this checkout.".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
credit_line.amount -= charged_total;
|
||||
org_patch.insert("credit_lines".to_string(), json!(org.credit_lines));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
if payment_method == "org_funds" && !context.vehicles.is_empty() {
|
||||
add_org_fleet_vehicles(&mut org, &context.vehicles);
|
||||
org_patch.insert("fleet".to_string(), json!(org.fleet));
|
||||
}
|
||||
|
||||
org_target_uids = resolve_member_uids(&org, Some(requester_uid));
|
||||
final_org = Some(org);
|
||||
}
|
||||
_ => return Err("Selected payment source is unsupported.".to_string()),
|
||||
}
|
||||
|
||||
let mut locker_saved = false;
|
||||
let mut vlocker_saved = false;
|
||||
let mut vgarage_saved = false;
|
||||
let mut org_saved = false;
|
||||
|
||||
let commit_result = (|| -> Result<(), String> {
|
||||
if !locker_patch.is_empty() {
|
||||
self.locker
|
||||
.override_locker(requester_uid, next_locker.items.clone())?;
|
||||
locker_saved = true;
|
||||
}
|
||||
|
||||
if !va_patch.is_empty() {
|
||||
self.vlocker
|
||||
.override_locker(requester_uid, next_vlocker.clone())?;
|
||||
vlocker_saved = true;
|
||||
}
|
||||
|
||||
if !vgarage_patch.is_empty() {
|
||||
self.vgarage
|
||||
.override_garage(requester_uid, next_vgarage.clone())?;
|
||||
vgarage_saved = true;
|
||||
}
|
||||
|
||||
if let Some(org) = final_org.clone() {
|
||||
self.org.override_org(&context.org_id, org)?;
|
||||
org_saved = true;
|
||||
}
|
||||
|
||||
if let Some(bank) = final_bank.as_ref() {
|
||||
self.bank.override_bank(requester_uid, bank)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
if let Err(error) = commit_result {
|
||||
if org_saved {
|
||||
if let Some(org) = original_org {
|
||||
let org_id = org.id.clone();
|
||||
let _ = self.org.override_org(&org_id, org);
|
||||
}
|
||||
}
|
||||
if vgarage_saved {
|
||||
let _ = self
|
||||
.vgarage
|
||||
.override_garage(requester_uid, original_vgarage);
|
||||
}
|
||||
if vlocker_saved {
|
||||
let _ = self
|
||||
.vlocker
|
||||
.override_locker(requester_uid, original_vlocker);
|
||||
}
|
||||
if locker_saved {
|
||||
let _ = self
|
||||
.locker
|
||||
.override_locker(requester_uid, original_locker.items);
|
||||
}
|
||||
if let Some(bank) = original_bank {
|
||||
let _ = self.bank.override_bank(requester_uid, &bank);
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Ok(StoreCheckoutResult {
|
||||
charged_total,
|
||||
payment_method,
|
||||
message: format!(
|
||||
"Checkout completed. {} charged, {} locker grant(s), {} vehicle unlock(s).",
|
||||
format_currency(charged_total),
|
||||
locker_granted.len(),
|
||||
vehicle_granted.len()
|
||||
),
|
||||
locker_granted,
|
||||
vehicle_granted,
|
||||
locker_patch,
|
||||
va_patch,
|
||||
vgarage_patch,
|
||||
bank_patch,
|
||||
org_patch,
|
||||
org_target_uids,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn checkout_total(context: &StoreCheckoutContext) -> f64 {
|
||||
let item_total = context
|
||||
.items
|
||||
.iter()
|
||||
.map(|entry| entry.price_value.max(0.0) * f64::from(entry.quantity))
|
||||
.sum::<f64>();
|
||||
let vehicle_total = context
|
||||
.vehicles
|
||||
.iter()
|
||||
.map(|entry| entry.price_value.max(0.0))
|
||||
.sum::<f64>();
|
||||
|
||||
(item_total + vehicle_total).floor()
|
||||
}
|
||||
|
||||
fn resolve_locker_category(category: &str) -> Result<&'static str, String> {
|
||||
match category.trim().to_ascii_lowercase().as_str() {
|
||||
"item" | "attachment" => Ok("item"),
|
||||
"weapon" => Ok("weapon"),
|
||||
"magazine" => Ok("magazine"),
|
||||
"backpack" => Ok("backpack"),
|
||||
other => Err(format!("Store item category '{}' is unsupported.", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_arsenal_category(category: &str) -> Result<EquipmentCategory, String> {
|
||||
match category.trim().to_ascii_lowercase().as_str() {
|
||||
"item" | "attachment" => Ok(EquipmentCategory::Items),
|
||||
"weapon" => Ok(EquipmentCategory::Weapons),
|
||||
"magazine" => Ok(EquipmentCategory::Magazines),
|
||||
"backpack" => Ok(EquipmentCategory::Backpacks),
|
||||
other => Err(format!("Store item category '{}' is unsupported.", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_vehicle_category(category: &str) -> Result<VehicleCategory, String> {
|
||||
match category.trim().to_ascii_lowercase().as_str() {
|
||||
"cars" => Ok(VehicleCategory::Cars),
|
||||
"armor" => Ok(VehicleCategory::Armor),
|
||||
"helis" | "heli" => Ok(VehicleCategory::Helis),
|
||||
"planes" => Ok(VehicleCategory::Planes),
|
||||
"naval" => Ok(VehicleCategory::Naval),
|
||||
"other" => Ok(VehicleCategory::Other),
|
||||
other => Err(format!("Vehicle category '{}' is unsupported.", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_unique(values: &mut Vec<String>, value: &str) {
|
||||
if !values.iter().any(|entry| entry == value) {
|
||||
values.push(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn push_unique_str<'a>(values: &mut Vec<&'a str>, value: &'a str) {
|
||||
if !values.contains(&value) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_manage_treasury(
|
||||
org: &HotOrgRecord,
|
||||
requester_uid: &str,
|
||||
requester_is_default_org_ceo: bool,
|
||||
) -> bool {
|
||||
org.owner == requester_uid
|
||||
|| ((org.id.eq_ignore_ascii_case("default") || org.owner.eq_ignore_ascii_case("server"))
|
||||
&& requester_is_default_org_ceo)
|
||||
}
|
||||
|
||||
fn resolve_member_uids(org: &HotOrgRecord, requester_uid: Option<&str>) -> Vec<String> {
|
||||
let mut member_uids = org.members.keys().cloned().collect::<Vec<_>>();
|
||||
if let Some(uid) = requester_uid {
|
||||
if !uid.is_empty() && !member_uids.iter().any(|member_uid| member_uid == uid) {
|
||||
member_uids.push(uid.to_string());
|
||||
}
|
||||
}
|
||||
member_uids
|
||||
}
|
||||
|
||||
fn add_org_fleet_vehicles(
|
||||
org: &mut HotOrgRecord,
|
||||
vehicles: &[forge_models::StoreCheckoutVehicleSeed],
|
||||
) {
|
||||
let mut fleet_index = org.fleet.len();
|
||||
for vehicle in vehicles {
|
||||
if vehicle.classname.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fleet_type = vehicle.category.trim().to_ascii_lowercase();
|
||||
let mut fleet_key = format!("{}_{}", vehicle.classname, fleet_index);
|
||||
while org.fleet.contains_key(&fleet_key) {
|
||||
fleet_index += 1;
|
||||
fleet_key = format!("{}_{}", vehicle.classname, fleet_index);
|
||||
}
|
||||
|
||||
org.fleet.insert(
|
||||
fleet_key,
|
||||
OrgFleetEntry {
|
||||
classname: vehicle.classname.clone(),
|
||||
name: vehicle.classname.clone(),
|
||||
fleet_type,
|
||||
status: "Ready".to_string(),
|
||||
damage: "0%".to_string(),
|
||||
},
|
||||
);
|
||||
fleet_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn format_currency(amount: f64) -> String {
|
||||
let rounded = amount.max(0.0).round() as i64;
|
||||
let digits = rounded.to_string();
|
||||
let mut formatted = String::new();
|
||||
|
||||
for (index, character) in digits.chars().rev().enumerate() {
|
||||
if index > 0 && index % 3 == 0 {
|
||||
formatted.push(',');
|
||||
}
|
||||
formatted.push(character);
|
||||
}
|
||||
|
||||
format!("${}", formatted.chars().rev().collect::<String>())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user