Operations delivery app fixes and improvements (#21720)

A small bundle of fixes with what is displayed, as mentioned in the
changelog.

Notable changes:
- It is now possible to charge department accounts directly using it.
The one doing it is logged in the transaction log for that account so
command can still go and check it. This was a feature I have been
requested repeatedly to make by operations member, to help encourage
people to buy more things for their department. Only a hangar tech
should be able to change the account that is charged as well.
- The delivery and pay buttons are now separate in the delivery app,
permitting someone to pay without having it delivered, or have it
delivered without paying. This also fixes a bug that paying before it
gets shipped to the Horizon could get an order permanently stuck as
being shipped & paid but not delivered.

<img width="575" height="700" alt="image"
src="https://github.com/user-attachments/assets/f14c9c0d-39fb-41b1-b3e9-aa479ccc0804"
/>
<img width="575" height="700" alt="image"
src="https://github.com/user-attachments/assets/7e33b8ef-caff-4e11-9e02-7c0ab3b4dba9"
/>
<img width="452" height="532" alt="image"
src="https://github.com/user-attachments/assets/413d8097-1379-432a-91ec-cd7ed13f0832"
/>
<img width="452" height="532" alt="image"
src="https://github.com/user-attachments/assets/8b73ddc0-3dfa-4503-bfcc-972a32687f97"
/>
<img width="575" height="700" alt="image"
src="https://github.com/user-attachments/assets/93663ba7-e443-45f4-8b87-cc04de35faf7"
/>
<img width="452" height="532" alt="image"
src="https://github.com/user-attachments/assets/8d4a1ba7-fbd5-40c9-a5ef-592c2067074e"
/>
This commit is contained in:
Casper3667
2026-02-01 23:29:38 +00:00
committed by GitHub
parent a802b48e2a
commit 306206f542
7 changed files with 201 additions and 118 deletions
+6 -9
View File
@@ -42,9 +42,8 @@
// Returns a list of all the objects in the order - Formatted as a list to be json_encoded
/datum/cargo_order/proc/get_object_list()
var/list/object_list = list()
for (var/datum/cargo_order_item/coi in items)
for(var/atom/object in coi.ci.items)
object_list.Add(object.name)
for(var/item in get_item_list())
object_list.Add(item["name"])
return object_list
// Gets a list of the order data - Formatted as list to be json_encoded
@@ -253,7 +252,7 @@
if(time_shipped)
order_data += "<u>Shipped at:</u> [time_shipped]<br>"
if(received_by)
order_data += "<u>Received by:</u [received_by]><br>"
order_data += "<u>Received by:</u> [received_by]<br>"
order_data += "<u>Delivered at:</u> [time_delivered]<br>"
order_data += "<hr>"
order_data += "<u>Order ID:</u> [order_id]<br>"
@@ -276,7 +275,7 @@
order_data += "<li>[item["name"]]: [item["price"]]</li>"
order_data += "<li>Crate Fee: [SScargo.get_cratefee()]</li>"
order_data += "<li>Handling Fee: [SScargo.get_handlingfee()]</li>"
order_data += "<li>Shuttle Fee: [get_shipment_cost()]</li>"
order_data += "<li>Supplier Fee: [get_shipment_cost()]</li>"
order_data += "</ul>"
return order_data.Join("")
@@ -338,7 +337,7 @@
time_shipped = worldtime2text()
//Marks a order as delivered - Returns a status message
/datum/cargo_order/proc/set_delivered(var/user_name, var/user_id, var/paid=0)
/datum/cargo_order/proc/set_delivered(var/user_name, var/user_id)
if(user_id <= 0)
user_id = null
if(status == "shipped")
@@ -346,8 +345,6 @@
time_delivered = worldtime2text()
received_by = user_name
received_by_id = user_id
if(paid)
set_paid(user_name, user_id)
return "The order has been delivered"
else
return "The order could not be delivered - Invalid Status"
@@ -443,7 +440,7 @@
invoice_data += "<p>Shuttle Data</p>"
invoice_data += "<table>"
invoice_data += "<tr>"
invoice_data += "<td>Shuttle Fee::</td>"
invoice_data += "<td>Supplier fee::</td>"
invoice_data += "<td>[shuttle_fee]</td>"
invoice_data += "</tr>"
invoice_data += "<tr>"
+2 -2
View File
@@ -291,7 +291,7 @@
/obj/structure/cash_register/commissary/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "QuikPay", "Idris Quik-Pay Register", 400, 400)
ui = new(user, src, "QuikPay", "Idris Quik-Pay Register", 550, 550)
ui.open()
/obj/structure/cash_register/commissary/ui_data(var/mob/user)
@@ -349,7 +349,7 @@
if(L["name"] == params["buying"])
L["amount"]++
return TRUE
buying += list(list("name" = params["buying"], "amount" = params["amount"]))
buying += list(list("name" = params["buying"], "amount" = params["amount"], "price" = items_to_price[params["buying"]]))
if("removal")
var/index = 0
+2 -2
View File
@@ -117,7 +117,7 @@
/obj/item/quikpay/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "QuikPay", "Idris Quik-Pay", 400, 400)
ui = new(user, src, "QuikPay", "Idris Quik-Pay", 550, 550)
ui.open()
/obj/item/quikpay/ui_data(var/mob/user)
@@ -176,7 +176,7 @@
if(L["name"] == params["buying"])
L["amount"]++
return TRUE
buying += list(list("name" = params["buying"], "amount" = params["amount"]))
buying += list(list("name" = params["buying"], "amount" = params["amount"], "price" = items_to_price[params["buying"]]))
if("removal")
var/index = 0
@@ -16,6 +16,7 @@
var/list/order_details = list() //Order Details for the order
var/datum/cargo_order/co
var/mod_mode = TRUE //If it can be used to pay for orders
var/destinationact = "Personal"
/datum/computer_file/program/civilian/cargodelivery/ui_data(mob/user)
var/list/data = initial_data()
@@ -46,6 +47,8 @@
//Pass the status message along
data["status_message"] = status_message
data["paying_account"] = destinationact
return data
/datum/computer_file/program/civilian/cargodelivery/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
@@ -55,18 +58,35 @@
var/obj/item/card/id/I = usr.GetIdCard()
//Check if we want to deliver or pay
//If we are at the status shipped, then only the confirm delivery and pay button should be shown (deliver)
//If the status is not shipped, then only show the pay button as we can not confirm that it has been paid so far
//Everyone can pay / confirm delivery
if(action == "deliver")
order_details = co.get_list()
//Check if its already delivered
if(order_details["status"] == "delivered" && !order_details["needs_payment"])
status_message = "Unable to Deliver - Order has already been delivered and paid for."
if(computer && computer.card_slot && computer.network_card)
var/obj/item/card/id/id_card
if(computer.card_slot?.stored_card)
id_card = computer.card_slot.stored_card
if(!id_card?.registered_name)
status_message = "Card Error: Invalid ID Card in Card Reader"
return TRUE
order_details = co.get_list()
//Check if its already delivered
if(order_details["status"] == "delivered")
status_message = "Unable to Deliver - Order has already been delivered."
return TRUE
//If a payment is not needed and we are at the status shipped, then confirm the delivery
else if(order_details["status"] == "shipped")
playsound(computer, 'sound/machines/chime.ogg', 50, TRUE)
status_message = co.set_delivered(GetNameAndAssignmentFromId(id_card), usr.character_id)
order_details = co.get_list()
else
status_message = "The order is not yet shipped."
return TRUE
else
status_message = "Unable to process - Network Card or Card Reader Missing"
return TRUE
if(action == "pay")
if(computer && computer.card_slot && computer.network_card)
var/using_id = FALSE
var/obj/item/card/id/id_card
@@ -77,6 +97,13 @@
using_id = FALSE
status_message = "Card Error: Invalid ID Card in Card Reader"
order_details = co.get_list()
//Check if its already paid for
if(!order_details["needs_payment"])
status_message = "Unable to pay - Order has already been paid for."
return TRUE
var/obj/item/spacecash/ewallet/charge_card
if(!using_id)
if(isliving(usr))
@@ -85,14 +112,18 @@
if(!istype(charge_card))
return TRUE
//Check if a payment is required
if(order_details["needs_payment"])
var/transaction_amount = order_details["price_customer"]
var/transaction_purpose = "Cargo Order #[order_details["order_id"]]"
var/transaction_terminal = "Modular Computer #[computer.network_card.identification_id]"
if(using_id)
var/status = SSeconomy.transfer_money(id_card.associated_account_number, SScargo.supply_account.account_number,transaction_purpose,transaction_terminal,transaction_amount,null,usr)
var/status
if(destinationact == "Personal")
status = SSeconomy.transfer_money(id_card.associated_account_number, SScargo.supply_account.account_number,transaction_purpose,transaction_terminal,transaction_amount,null,usr)
else
transaction_purpose = "Cargo Order #[order_details["order_id"]] by [id_card.registered_name]"
status = SSeconomy.transfer_money(SSeconomy.get_department_account(destinationact)?.account_number, SScargo.supply_account.account_number,transaction_purpose,transaction_terminal,transaction_amount,null,usr)
if(status)
status_message = status
return TRUE
@@ -106,22 +137,10 @@
charge_card.worth -= transaction_amount
playsound(computer, 'sound/machines/chime.ogg', 50, TRUE)
//Check if we have delivered it aswell or only paid
if(order_details["status"] == "shipped")
status_message = co.set_delivered(GetNameAndAssignmentFromId(I), usr.character_id, 1)
else
status_message = co.set_paid(GetNameAndAssignmentFromId(I), usr.character_id)
status_message = co.set_paid(GetNameAndAssignmentFromId(id_card), usr.character_id)
order_details = co.get_list()
else
//If a payment is not needed and we are at the status shipped, then confirm the delivery
if(order_details["status"] == "shipped")
playsound(computer, 'sound/machines/chime.ogg', 50, TRUE)
status_message = co.set_delivered(GetNameAndAssignmentFromId(I), usr.character_id, 0)
order_details = co.get_list()
else
status_message = "Unable to process - Network Card or Cardreader Missing"
status_message = "Unable to process - Network Card or Card Reader Missing"
return TRUE
@@ -147,3 +166,11 @@
else
page = "overview_main" //fall back to overview_main if a unknown page has been supplied
return TRUE
if(action == "accountselect")
var/list/choices = assoc_to_keys(SSeconomy.department_accounts)
choices += "Personal"
var/dest = tgui_input_list(usr, "What account would you like to select?", "Destination Account", choices)
if(!dest)
return FALSE
destinationact = dest
return TRUE
@@ -0,0 +1,12 @@
author: TheGreyWolf
delete-after: True
changes:
- rscadd: "Added the ability to charge department accounts directly for operation orders through the operations delivery app, without command presence required."
- rscadd: "Separated the delivery and the pay buttons in the operations delivery app."
- bugfix: "Fixed the display of who accepted a delivery order when printing an invoice."
- bugfix: "Prices shown on the delivery app are now correct."
- bugfix: "The paper spawning in crates from the cargo elevator now properly show the supposed contents."
- rscadd: "The quikpay ui was updated."
+33 -23
View File
@@ -15,6 +15,7 @@ export type CargoData = {
order_details: Order;
page: string;
status_message: string;
paying_account: string;
};
type Order = {
@@ -160,7 +161,7 @@ export const Overview = (props, context) => {
: 'Unauthorised'}
</LabeledList.Item>
<LabeledList.Item label="Price">
{data.order_details.price}.toFixed(2)
{data.order_details.price.toFixed(2)}
</LabeledList.Item>
<LabeledList.Item label="Operations Expense">
{data.order_details.price_cargo.toFixed(2)}
@@ -168,9 +169,6 @@ export const Overview = (props, context) => {
<LabeledList.Item label="Personal Expense">
{data.order_details.price_customer.toFixed(2)}
</LabeledList.Item>
<LabeledList.Item label="Personal Expense">
{data.order_details.price_customer.toFixed(2)}
</LabeledList.Item>
<LabeledList.Item label="Ordered At">
{data.order_details.time_submitted}
</LabeledList.Item>
@@ -223,29 +221,41 @@ export const Payment = (props, context) => {
<Section
title={'Payment: Order No. ' + data.order_details.order_id}
buttons={
<Button
content={
data.order_details.status === 'shipped'
? 'Confirm Delivery and Pay'
: data.order_details.status === 'delivered'
? 'Delivered Already'
: data.order_details.needs_payment
? 'Pay'
: 'Paid but not Shipped'
}
disabled={
!data.order_details.needs_payment ||
data.order_details.status === 'delivered'
}
icon="check"
color="green"
onClick={() => act('deliver', { deliver: 'true' })}
/>
<>
<Button
content="Account"
color="good"
onClick={() => act('accountselect')}
/>
<Button
content={
data.order_details.status === 'shipped'
? 'Confirm Delivery'
: data.order_details.status === 'delivered'
? 'Delivered Already'
: 'Not Shipped'
}
disabled={data.order_details.status !== 'shipped'}
icon="check"
color="green"
onClick={() => act('deliver', { deliver: 'true' })}
/>
<Button
content="Pay"
disabled={!data.order_details.needs_payment}
icon="credit-card"
color="green"
onClick={() => act('pay', { deliver: 'true' })}
/>
</>
}
>
<LabeledList>
<LabeledList.Item label="Price">
{data.order_details.price.toFixed(2)}
{data.order_details.price_customer.toFixed(2)}
</LabeledList.Item>
<LabeledList.Item label="Paying Account">
{data.paying_account}
</LabeledList.Item>
<LabeledList.Item label="Customer">
{data.order_details.ordered_by}
+95 -58
View File
@@ -6,6 +6,9 @@ import {
Input,
NumberInput,
Section,
Flex,
Table,
Box,
} from '../components';
import { Window } from '../layouts';
@@ -27,6 +30,7 @@ type Item = {
type ItemBuy = {
name: string;
amount: number;
price: number;
};
export const QuikPay = (props, context) => {
@@ -39,12 +43,6 @@ export const QuikPay = (props, context) => {
title="Ordering"
buttons={
<>
<Button
content={data.editmode ? 'Unlocked' : 'Locked'}
color={data.editmode ? 'bad' : ''}
icon={data.editmode ? 'lock-open' : 'lock'}
onClick={() => act('locking')}
/>
{data.editmode ? (
<Button
content="Select Account"
@@ -53,6 +51,12 @@ export const QuikPay = (props, context) => {
onClick={() => act('accountselect')}
/>
) : null}
<Button
content={data.editmode ? 'Unlocked' : 'Locked'}
color={data.editmode ? 'bad' : ''}
icon={data.editmode ? 'lock-open' : 'lock'}
onClick={() => act('locking')}
/>
</>
}
>
@@ -69,55 +73,73 @@ export const ItemWindow = (props, context) => {
return (
<Section>
<LabeledList>
{data.items.map((item) => (
<LabeledList.Item key={item.name} label={item.name}>
{item.price.toFixed(2)} &nbsp;
<Button
content="Buy"
icon="calendar"
onClick={() => act('buy', { buying: item.name, amount: 1 })}
/>
{data.editmode ? (
<Flex direction="row">
<Flex.Item grow={1}>
<Section title="For sale" fill>
<Table>
{data.items.map((item) => (
<Table.Row key={item.name}>
<Table.Cell>{item.name}</Table.Cell>
<Table.Cell align="right">
{item.price.toFixed(2)} &nbsp;
</Table.Cell>
<Table.Cell>
{!data.editmode ? (
<Button
content="Buy"
icon="calendar"
onClick={() =>
act('buy', { buying: item.name, amount: 1 })
}
/>
) : (
<>
&nbsp;
<Button
content="Delete"
icon="trash"
color="bad"
onClick={() => act('remove', { removing: item.name })}
/>
</>
)}
</Table.Cell>
</Table.Row>
))}
</Table>
</Section>
</Flex.Item>
<Box width="1px" backgroundColor="rgba(255, 255, 255, 0.15)" mx={2} />
<Flex.Item grow={1}>
<Section
fill
title="Cart"
buttons={
<>
&nbsp;
{' '}
<Button
content="Delete"
content="Clear"
icon="trash"
color="bad"
onClick={() => act('remove', { removing: item.name })}
onClick={() => act('clear')}
/>
<Button
content="Confirm"
icon="check"
color={data.sum ? 'good' : ''}
onClick={() => act('confirm')}
/>
</>
) : null}
</LabeledList.Item>
))}
</LabeledList>
<Section
title="Cart"
buttons={
<>
{' '}
<Button
content="Clear"
icon="trash"
color="bad"
onClick={() => act('clear')}
/>
<Button
content="Confirm"
icon="check"
color={data.sum ? 'good' : ''}
onClick={() => act('confirm')}
/>
</>
}
>
{data.buying.length < 1 ? (
'Your shopping cart is empty.'
) : (
<CartWindow />
)}
</Section>
}
>
{data.buying.length < 1 ? (
'Your shopping cart is empty.'
) : (
<CartWindow />
)}
</Section>
</Flex.Item>
</Flex>
</Section>
);
};
@@ -148,17 +170,32 @@ export const CartWindow = (props, context) => {
<Section>
<LabeledList>
{data.buying.map((item) => (
<LabeledList.Item key={item.name} label={item.name}>
x{item.amount}&nbsp;
<Button
content="Remove"
icon="times"
onClick={() => act('removal', { removal: item.name })}
/>
</LabeledList.Item>
<Table.Row key={item.name}>
<Table.Cell>&nbsp;{item.name}</Table.Cell>
<Table.Cell>x{item.amount}</Table.Cell>
<Table.Cell align="right">
<Flex justify="flex-end">
<Flex.Item>{(item.price * item.amount).toFixed(2)}</Flex.Item>
<Flex.Item>
&nbsp;
<Button
icon="trash"
onClick={() => act('removal', { removal: item.name })}
/>
&nbsp;
</Flex.Item>
</Flex>
</Table.Cell>
</Table.Row>
))}
</LabeledList>
{data.sum ? 'Please swipe your ID to pay.' : ''}
{data.sum ? (
<Section color="average">
The total is {data.sum.toFixed(2)}.
<br />
Please swipe your id to pay.
</Section>
) : null}
</Section>
);
};