From ae055a21d2590ed0c68e4060dcefd4c67ab19445 Mon Sep 17 00:00:00 2001 From: Ryan <80364400+Sirryan2002@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:03:50 -0400 Subject: [PATCH] NanoBank Premium: Remote Approval of Departmental Cargo Crates! (#25871) * NanoBank Premium * TGUI prettier * Apply suggestions from code review Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Signed-off-by: Ryan <80364400+Sirryan2002@users.noreply.github.com> * default case for request notice --------- Signed-off-by: Ryan <80364400+Sirryan2002@users.noreply.github.com> Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> --- code/modules/pda/nanobank.dm | 201 +++++++++++++++++- .../tgui/interfaces/pda/pda_nanobank.js | 148 +++++++++++++ tgui/public/tgui.bundle.js | 160 +++++++------- 3 files changed, 427 insertions(+), 82 deletions(-) diff --git a/code/modules/pda/nanobank.dm b/code/modules/pda/nanobank.dm index 106d9503d84..2d992b96851 100644 --- a/code/modules/pda/nanobank.dm +++ b/code/modules/pda/nanobank.dm @@ -1,11 +1,12 @@ #define TRANSFER_REQUEST_MAX 5000 #define TRANSFER_COOLDOWN 5 SECONDS +#define PREMIUM_COST 250 /datum/data/pda/app/nanobank name = "NanoBank" icon = "fas fa-university" notify_icon = "comments" - title = "NanoBank 1.1" + title = "NanoBank 1.2" template = "pda_nanobank" update = PDA_APP_UPDATE_SLOW //we want to avoid iterating through the data lists constantly @@ -16,6 +17,9 @@ ///world time of last transaction, used for cooldowns (to protect against transfer spams or accidental double clicks) var/last_transaction = 0 + /// Has this user purchased NanoBank Premium? + var/premium_version = FALSE + /datum/data/pda/app/nanobank/start() . = ..() reconnect_database() @@ -83,6 +87,68 @@ data["available_accounts"] = get_available_account_data() + data["is_premium"] = premium_version + data["supply_requests"] = list() + + if(premium_version && user_account) + var/list/supply_requests = list() + for(var/datum/supply_order/order as anything in SSeconomy.request_list) + //can this specific order request be approved the the mob user? + var/can_approve = FALSE + //can this specific order request be denied the the mob user? + var/can_deny = FALSE + + // Step 0: Initial checks for denial & CT Approval requirements + if(order.orderedby == user_account.account_name) + can_deny = TRUE //if it's your crate, you can deny it; + + if(isnull(order.ordered_by_department)) + if(order.orderedby != user_account.account_name) + continue // non-department personal order, if it's not yours, you don't get to see it + if(user_account.account_type == ACCOUNT_TYPE_DEPARTMENT) + can_deny = TRUE // if it's a department account, you can deny crates (only it's department crates will show up) + if(order.requires_cargo_approval) + can_approve = FALSE // You cannot remotely approve CT locked transactions :) + + else if(order.requires_head_approval) + // Step 1: Check if this is a department account and assign permission accordingly + if(user_account.account_type == ACCOUNT_TYPE_DEPARTMENT) //set to TRUE initially if account is a Department account + if(order.ordered_by_department.department_account != user_account) + continue // department order, but wrong department, skip this order! + can_approve = TRUE + can_deny = TRUE + + // Step 2: Check through members + var/member_identified = FALSE // is this user in the order's department? + for(var/datum/department_member/member as anything in order.ordered_by_department.members) + if(member.member_account != user_account) + continue + member_identified = TRUE + if(member.can_approve_crates) + can_approve = TRUE + can_deny = TRUE + member_identified = TRUE + break + + // Step 3: If user is not a member of order's department & this is a personal account, skip this order in the UI + if(!member_identified && user_account.account_type == ACCOUNT_TYPE_PERSONAL) + continue // Don't show this order if it's a department order and the user is not part of that department + + var/list/request_data = list( + "ordernum" = order.ordernum, + "supply_type" = order.object.name, + "orderedby" = order.orderedby, + "department" = order.ordered_by_department?.department_name, + "cost" = order.object.get_cost(), + "comment" = order.comment, + "req_cargo_approval" = order.requires_cargo_approval, + "req_head_approval" = order.requires_head_approval, + "can_approve" = can_approve, + "can_deny" = can_deny, + "singleton_id" = initial(order.object?.singleton_group_id) + ) + supply_requests += list(request_data) + data["supply_requests"] = supply_requests return data /datum/data/pda/app/nanobank/ui_act(action, list/params, datum/tgui/ui) @@ -160,6 +226,21 @@ else to_chat(user, "Authentification Failure: incorrect pin.") + if("purchase_premium") + if(premium_version) + return + var/choice = tgui_alert(user, "Purchase NanoBank Premium on this PDA for [PREMIUM_COST] credits?", "Confirm", list("Yes", "No")) + if(choice != "Yes") + return + if(pay_with_account(user, PREMIUM_COST, "NanoBank Premium Purchase", "[pda]", account_database.vendor_account)) + premium_version = TRUE + title += " Premium" // cash money + if(!pda.silent) + playsound(pda, 'sound/machines/ping.ogg', 50, FALSE) + else + if(!pda.silent) + playsound(pda, 'sound/machines/buzz-sigh.ogg', 15, FALSE) + if("toggle_member_approval") for(var/datum/station_department/department as anything in SSjobs.station_departments) if(department.department_account == user_account) @@ -181,7 +262,16 @@ department.auto_approval_cap = clamp(new_amount, 0, 3000) else department.auto_approval_cap = 0 - + if("approve_crate") + var/ordernum = text2num(params["ordernum"]) + if(!ordernum) + return + approve_crate(ordernum, user) + if("deny_crate") + var/ordernum = text2num(params["ordernum"]) + if(!ordernum) + return + deny_crate(ordernum, user) if(play_beep && !pda.silent) playsound(pda, 'sound/machines/terminal_select.ogg', 15, TRUE) @@ -280,6 +370,113 @@ /datum/data/pda/app/nanobank/proc/can_receive() return pda.owner && !hidden + +/datum/data/pda/app/nanobank/proc/approve_crate(order_num, mob/user) + if(!premium_version) + return // cheater cheater pumpkin eater + + for(var/datum/supply_order/order as anything in SSeconomy.request_list) + if(order.ordernum != order_num) + continue + //can this specific order request be approved the the mob user? + var/can_approve = FALSE + + if(order.requires_cargo_approval) + return // You cannot remotely approve CT locked transactions :) + if(order.requires_head_approval) + if(user_account.account_type == ACCOUNT_TYPE_DEPARTMENT) //set to TRUE initially if account is a Department account + if(order.ordered_by_department.department_account != user_account) + return // department order, but wrong department, yeet + can_approve = TRUE + + // Check through members + var/member_identified = FALSE // is this user in the order's department? + for(var/datum/department_member/member as anything in order.ordered_by_department.members) + if(member.member_account != user_account) + continue + member_identified = TRUE + if(member.can_approve_crates) + can_approve = TRUE + break + + // Step 3: If user is not a member of order's department & this is a personal account, skip this order in the UI + if(!member_identified && user_account.account_type == ACCOUNT_TYPE_PERSONAL) + return // user not in this department, get outta here! + + if(!can_approve) + return + + var/datum/supply_packs/pack = order.object + var/datum/money_account/account = order.ordered_by_department.department_account + ///just give the account pin here, its too much work for players to get the department account pin number since approval is access locked anyway + if(attempt_account_authentification(account, user, account.account_pin)) + if(pay_with_account(user, pack.get_cost(), "[pack.name] Crate Purchase", "[src]", account_database.vendor_account)) + if(!pda.silent) + playsound(pda, 'sound/machines/ping.ogg', 50, FALSE) + order.requires_head_approval = FALSE + SSeconomy.process_supply_order(order, TRUE) + pda.investigate_log("| [key_name(user)] has authorized an order for [pack.name]. Remaining Cargo Balance: [SSeconomy.cargo_account.credit_balance].", "cargo") + SSblackbox.record_feedback("tally", "cargo_shuttle_order", 1, pack.name) + else + pda.atom_say("ERROR: Account tied to order cannot pay, auto-denying order") + SSeconomy.request_list -= order + +/datum/data/pda/app/nanobank/proc/deny_crate(order_num, user) + if(!premium_version) + return // cheater cheater pumpkin eater + + for(var/datum/supply_order/order as anything in SSeconomy.request_list) + if(order.ordernum != order_num) + continue + + //can this specific order request be denied by the currently opened NanoBank account? + var/can_deny = FALSE + if(order.orderedby == user_account.account_name) + can_deny = TRUE + else if(order.requires_head_approval) + if(user_account.account_type == ACCOUNT_TYPE_DEPARTMENT) //set to TRUE initially if account is a Department account + if(order.ordered_by_department.department_account != user_account) + return // department order, but wrong department + can_deny = TRUE + + for(var/datum/department_member/member as anything in order.ordered_by_department.members) + if(member.member_account != user_account) + continue + if(member.can_approve_crates) + can_deny = TRUE + break + + if(!can_deny) + return + SSeconomy.request_list -= order + pda.investigate_log("| [key_name(user)] has denied an order for [order.object.name] through the Nanobank app.", "cargo") + +/datum/data/pda/app/nanobank/proc/pay_with_account(user, amount, purpose, transactor, datum/money_account/target) + if(user_account.suspended) + to_chat(user, "Unable to access account: account suspended.") + return FALSE + if(!account_database.charge_account(user_account, amount, purpose, transactor, allow_overdraft = FALSE, supress_log = FALSE)) + to_chat(user, "Unable to complete transaction: account has insufficient credit balance to purchase this.") + return FALSE + account_database.credit_account(target, amount, purpose, transactor, FALSE) + return TRUE + +/datum/data/pda/app/nanobank/proc/attempt_account_authentification(datum/money_account/customer_account, mob/user, pin) + if(customer_account.security_level > ACCOUNT_SECURITY_RESTRICTED) + return FALSE + var/attempt_pin = pin + if(customer_account.security_level != ACCOUNT_SECURITY_ID && !attempt_pin) + //if pin is not given, we'll prompt them here + attempt_pin = tgui_input_number(user, "Enter pin code", "Vendor transaction") + if(!attempt_pin) + return FALSE + var/is_admin = is_admin(user) + if(!account_database.try_authenticate_login(customer_account, attempt_pin, TRUE, FALSE, is_admin)) + to_chat(user, "Unable to access account: incorrect credentials.") + return FALSE + return TRUE + #undef TRANSFER_COOLDOWN #undef TRANSFER_REQUEST_MAX +#undef PREMIUM_COST diff --git a/tgui/packages/tgui/interfaces/pda/pda_nanobank.js b/tgui/packages/tgui/interfaces/pda/pda_nanobank.js index 03ee84cc337..c1387966629 100644 --- a/tgui/packages/tgui/interfaces/pda/pda_nanobank.js +++ b/tgui/packages/tgui/interfaces/pda/pda_nanobank.js @@ -11,6 +11,7 @@ import { Tabs, Table, Divider, + Stack, } from '../../components'; export const pda_nanobank = (props, context) => { @@ -39,6 +40,7 @@ export const pda_nanobank = (props, context) => { const NanoBankNavigation = (properties, context) => { const { data } = useBackend(context); + const { is_premium } = data; const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 1); return ( @@ -55,6 +57,12 @@ const NanoBankNavigation = (properties, context) => { Transaction History + {Boolean(is_premium) && ( + setTabIndex(4)}> + + Supply Orders + + )} ); }; @@ -75,6 +83,8 @@ const NanoBankTabContent = (props, context) => { return ; case 3: return ; + case 4: + return ; default: return "You are somehow on a tab that doesn't exist! Please let a coder know."; } @@ -209,6 +219,7 @@ const AccountActions = (props, context) => { auto_approve, auto_approve_amount, is_department_account, + is_premium, } = data; return ( @@ -246,6 +257,16 @@ const AccountActions = (props, context) => { onClick={() => act('logout')} /> + +