Files
Aurora.3/code/datums/cargo.dm
naut 55f4e12614 Codeside Cargo Refactor (#20030)
**NOTE TO SYSADMINS: See "SQL Details" section below for information on
SQL modifications.**

Moves the data containing cargo items (i.e. the ones you order from ops
and get in the cargo shuttle) from the online database to the codebase.
Everything from suppliers to categories to individual items is now
code-side and editable by developers/contributors.

Refactors cargo items to use `singletons` instead of `datums` for
`cargo_supplier`, `cargo_category`, and `cargo_item`. Multiple-instnace
things like cargo_orders, etc. still use `datums`.

Fixed a bunch of strange discrepancies in categories, suppliers, and
pricing for various cargo items. I did a little bit, but it's exhausting
to go through all of it right now.

Clicking the 'Details' button on the Cargo Order app now actually gives
you details instead of bluescreening. Also added some UI elements to the
Cargo Order app - Cargo Control and Delivery remain untouched.

Overhauled the Cargo Order console TGUI window. It now has tabs on the
left, displays restricted access, supplier information, and boasts
search functionality.

### SQL Details
<details>
<summary>SQL Details [Click to Expand]</summary>

The following SQL tables should be deleted or deprecated from the server
database, as they are no longer in use:

- `ss13_cargo_items`
- `ss13_cargo_categories`
- `ss13_cargo_suppliers`

The included migration file, `V011__codeside_cargo`, creates a new table
`ss13_cargo_item_orderlog` to the DB. This **replaces**
`ss13_cargo_orderlog_items`. Because of this,
`ss13_cargo_orderlog_items` is deprecated and should either be deleted
or locked & preserved for logging purposes.

</details>

## Screenshots


![image](https://github.com/user-attachments/assets/79129923-1fb6-4cee-ac8d-5505a52270a4)

![image](https://github.com/user-attachments/assets/a323be35-8ce6-4ec4-98f7-ee701d0931a3)

![image](https://github.com/user-attachments/assets/5ddb02c5-152f-4715-b2da-20903fa11c93)

![image](https://github.com/user-attachments/assets/420e45b0-6a9f-4420-beb8-a2c8423a5be4)

![image](https://github.com/user-attachments/assets/114f4755-ee51-41e6-8670-07aacc5326ae)

---------

Signed-off-by: naut <55491249+nauticall@users.noreply.github.com>
Co-authored-by: Fluffy <65877598+FluffyGhoster@users.noreply.github.com>
2025-03-04 18:18:00 +00:00

476 lines
16 KiB
Plaintext

//NOTE: Cargo orders, categories and suppliers have been moved to singletons (10/11/2024).
/datum/cargo_order
var/list/items = list() //List of cargo_items in the order
var/order_id = 0 //ID of the order
var/price = 0 //Total price of the order
var/item_id = 1 //Current id of the item in the order
var/item_num = 0 //Number of items in the container - Used to limit the items in the crate
var/status = "basket" //Status of the order: basket - Adding items, submitted - Submitted to cargo, approved - Order sent to suppliers, rejected - Order has been denied, shipped - Has been shipped to the station, delivered - Order has been delivered
var/container_type = "" //Type of the container for the order - cate, box
var/list/required_access = list() //Access required to unlock the crate
var/can_add_items = 1 //If new items can be added to the order
var/ordered_by = null //Person that ordered the items
var/ordered_by_id = null //Character ID of the person that ordered the items
var/authorized_by = null //Person that authorized the order
var/authorized_by_id = null //Character ID of the person that authorized the items
var/received_by = null //Person the order has been delivered to by cargo
var/received_by_id = null //Character ID of the person that received the items
var/paid_by = null //Person that has paid for the order
var/paid_by_id = null //Character ID of the Person that paid for the items
var/time_submitted = null //Time the order has been sent to cargo
var/time_approved = null //Time the order has been approved by cargo
var/time_shipped = null //Time the order has been shipped to the station
var/time_delivered = null //Time the order has been delivered
var/time_paid = null //The the order has been paid
var/tracking_code = null //Use this code with the order ID to get details about the order
var/reason = null //Reason for the order
//Gets the tracking code for the order. Generates one if it does not exist already
/datum/cargo_order/proc/get_tracking_code()
if(!tracking_code)
tracking_code = rand(1000,9999)
return tracking_code
// Returns a list of the items in the order - Formatted as list to be json_encoded
/datum/cargo_order/proc/get_item_list()
var/list/item_list = list()
for (var/datum/cargo_order_item/coi in items)
item_list.Add(list(coi.get_list()))
return item_list
// 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)
return object_list
// Gets a list of the order data - Formatted as list to be json_encoded
/datum/cargo_order/proc/get_list()
var/list/data = list()
data["order_id"] = order_id
data["tracking_code"] = get_tracking_code()
data["price"] = get_value(2)
data["price_customer"] = get_value(0)
data["price_cargo"] = get_value(1)
data["status"] = get_order_status(0)
data["status_pretty"] = get_order_status(1)
data["status_payment"] = get_payment_status(1)
data["ordered_by"] = ordered_by
data["authorized_by"] = authorized_by
data["received_by"] = received_by
data["paid_by"] = paid_by
data["needs_payment"] = paid_by != null ? 0:1
data["time_submitted"] = time_submitted
data["time_approved"] = time_approved
data["time_shipped"] = time_shipped
data["time_delivered"] = time_delivered
data["time_paid"] = time_paid
data["items"] = get_item_list()
data["shipment_cost"] = get_shipment_cost()
data["reason"] = reason
return data
//Adds a item to a order. Returns a status message
/datum/cargo_order/proc/add_item(var/datum/cargo_order_item/coi)
if(!coi)
return "Unable to add item - Internal Error"
//Check if the container type of the added item matches the one of the current order
if(container_type == "")
container_type = coi.ci.container_type
else if (container_type != coi.ci.container_type)
return "Unable to add item - Different container required" //You can only add items with the same container type to the order
//Check if more items can be added to the order
if(!can_add_items)
return "Unable to add item - Order can not contain more items"
if(!coi.ci.groupable) //The item is not groupable with other items and needs to be ordered on its own
if(get_item_count() > 0) // There are already other items in the order. -> Reject it
return "Unable to add item - Item can not be grouped and must be ordered separately"
else //No item in the order. Add it and set can_add_items to 0
can_add_items = 0
//Check if there is space in the container
if(item_num + coi.ci.amount > get_container_storage())
return "Unable to add item - Container full"
//Set item id of coi and increment item_id of co
coi.item_id = item_id
item_id += 1
//Increment the item number
item_num += coi.ci.amount
//Add coi to ordered item
items.Add(coi)
price += coi.price
//Update the access requirement of the order
if(coi.ci.access)
required_access.Add(coi.ci.access)
return "Item successfully added"
//Removes a item from a order - Returns a status message
/datum/cargo_order/proc/remove_item(var/remove_item_id)
//Find the item with the specified id in the items list
for (var/datum/cargo_order_item/coi in items)
if(coi)
if(coi.item_id == remove_item_id)
items.Remove(coi)
price -= coi.price
item_num -= coi.ci.amount
qdel(coi)
return "Item removed successfully"
return "Unable to remove item - Internal Error 608"
// Returns the value of the order
/datum/cargo_order/proc/get_value(var/type=0)
//Type specifies if the price cargo has to pay or the price the customer has to be should be returned
// 0 - Price the customer has to pay
// 1 - Price cargo has to pay
// 2 - Just the value of the items in the crate
switch(type)
if(0)
//The price of the contents of the crate + the price for the crate + the handling fee + the shipment fee
return price + SScargo.get_cratefee() + SScargo.get_handlingfee() + get_shipment_cost()
if(1)
//The price of the contents of the crate + the price of the crate + the shipment fee
return price + SScargo.get_cratefee() + get_shipment_cost()
if(2)
//Just the value of the items in the crate
return price
else
//As a fallback, return the value of the items
return price
// Returns the number of items in the order
/datum/cargo_order/proc/get_item_count()
return items.len
// Returns the type of the required container for the order
/datum/cargo_order/proc/get_container_type()
switch(container_type)
if(CARGO_CONTAINER_CRATE)
if(required_access.len > 0)
return /obj/structure/closet/crate/secure
else
return /obj/structure/closet/crate
if(CARGO_CONTAINER_BOX)
if(required_access.len > 0)
return /obj/structure/closet/crate/secure/large
else
return /obj/structure/largecrate
if(CARGO_CONTAINER_FREEZER)
return /obj/structure/closet/crate/freezer
if(CARGO_CONTAINER_BODYBAG)
return /obj/structure/closet/body_bag
else
LOG_DEBUG("Cargo: Tried to get container type for invalid container [container_type]")
return /obj/structure/largecrate
// Returns the number of items that can be stored in the container
/datum/cargo_order/proc/get_container_storage()
switch(container_type)
if(CARGO_CONTAINER_CRATE)
return 40 //Thats the default storage capacity of a crate
if(CARGO_CONTAINER_FREEZER)
return 40
if(CARGO_CONTAINER_BOX)
return 5 //You can fit 5 larger items into a box
else
LOG_DEBUG("Cargo: Tried to get storage size for invalid container [container_type]")
return 0 //Something went wrong
// Gets the list of the suppliers involved in the order
/datum/cargo_order/proc/get_supplier_list()
var/list/supplier_list = list()
for(var/datum/cargo_order_item/coi in items)
supplier_list[coi.ci.supplier] = coi.ci.supplier
return supplier_list
// Gets the shipment cost for the order
/datum/cargo_order/proc/get_shipment_cost()
var/list/supplier_list = get_supplier_list()
var/cost = 0
for(var/supplier in supplier_list)
var/singleton/cargo_supplier/cs = SScargo.cargo_suppliers[supplier]
if(cs)
cost += cs.shuttle_price
return cost
// Gets the order status
/datum/cargo_order/proc/get_order_status(var/pretty=1)
if(pretty)
switch(status)
if("submitted")
return "Submitted to Cargo"
if("approved")
return "Approved"
if("rejected")
return "Rejected"
if("shipped")
return "Shipped to the [station_name(TRUE)]"
if("delivered")
return "Delivered"
else
return "Unknown Status"
else
return status
//Returns the payment status
/datum/cargo_order/proc/get_payment_status(var/pretty=1)
if(pretty)
if(paid_by != null)
return "Paid by [paid_by]"
else
return "Unpaid"
else
if(paid_by != null)
return 1
else
return 0
// Returns a Invoice for the Order
/datum/cargo_order/proc/get_report_invoice()
var/list/order_data = list()
var/invoice_type = "Preliminary"
if(status == "delivered" && paid_by)
invoice_type = "Final"
order_data += "<h4>[invoice_type] Invoice #[order_id]</h4>"
order_data += "<hr>"
//List the personnel involved in the order
order_data += "<u>Ordered by:</u> [ordered_by]<br>"
order_data += "<u>Submitted at:</u> [time_submitted]<br>"
if(authorized_by)
order_data += "<u>Authorized by: </u> [authorized_by]<br>"
order_data += "<u>Approved at: </u> [time_approved]<br>"
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>Delivered at:</u> [time_delivered]<br>"
order_data += "<hr>"
order_data += "<u>Order ID:</u> [order_id]<br>"
order_data += "<u>Tracking Code:</u> [get_tracking_code()]<br>"
order_data += "<u>Order Status:</u> [get_order_status(1)]<br>"
order_data += "<u>Payment Status:</u> [get_payment_status(1)]<br>"
if(paid_by)
order_data += "<u>Paid at:</u> [time_paid]<br>"
order_data += "<hr>"
if(required_access.len)
order_data += "<u>Required Access:</u><br>"
order_data += "<ul>"
for(var/A in required_access)
order_data += "<li>[get_access_desc(A)]</li>"
order_data += "</ul>"
order_data += "<hr>"
order_data += "<u>Order Fees:</u><br>"
order_data += "<ul>"
for(var/item in get_item_list())
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 += "</ul>"
return order_data.Join("")
// Returns a Delivery Order for the Crate
/datum/cargo_order/proc/get_report_delivery_order()
var/list/order_data = list()
order_data += "<h4>Delivery Order #[order_id]</h4>"
order_data += "<hr>"
order_data += "<u>Submitted at:</u> [time_submitted]<br>"
order_data += "<u>Shipped at:</u> [time_shipped]<br>"
order_data += "<u>Contents:</u><br>"
order_data += "<ul>"
for(var/object in get_object_list())
order_data += "<li>[object]</li>"
order_data += "</ul>"
return order_data.Join("")
//Marks a order as submitted
/datum/cargo_order/proc/set_submitted(var/user_name, var/user_id, var/order_reason)
if(user_id <= 0)
user_id = null
ordered_by = user_name
ordered_by_id = user_id
reason = order_reason
status = "submitted"
time_submitted = worldtime2text()
order_id = SScargo.get_next_order_id()
SScargo.all_orders.Add(src)
return 1
//Marks a order as approved - Returns a status message
/datum/cargo_order/proc/set_approved(var/user_name, var/user_id)
if(user_id <= 0)
user_id = null
if(status == "submitted")
status = "approved"
time_approved = worldtime2text()
authorized_by = user_name
authorized_by_id = user_id
return "The order has been approved"
else
return "The order could not be approved - Invalid Status"
//Marks a order as rejected - Returns a status message
/datum/cargo_order/proc/set_rejected()
if(status == "submitted")
status = "rejected"
time_approved = worldtime2text()
return "The order has been rejected"
else
return "The order could not be rejected - Invalid Status"
//Marks a order as shipped
/datum/cargo_order/proc/set_shipped()
status = "shipped"
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)
if(user_id <= 0)
user_id = null
if(status == "shipped")
status = "delivered"
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"
//Mark a order as paid
/datum/cargo_order/proc/set_paid(var/user_name, var/user_id)
if(user_id <= 0)
user_id = null
time_paid = worldtime2text()
paid_by = user_name
paid_by_id = user_id
return "The order has been paid for"
/// A cargo order item. Part of a category. Specifies the item, the supplier and the price of the item
/datum/cargo_order_item
var/singleton/cargo_item/ci //Item that has been ordered
var/price //Price of the item with the given supplier
var/item_id //Item id in the order
//TODO-CARGO: Maybe add the option to set a fake item for traitors here -> So that cargo cant see what they are really ordering
//Calculate Price
/datum/cargo_order_item/proc/calculate_price()
price = ci.adjusted_price
// Gets a list of the cargo order item - to be json encoded
/datum/cargo_order_item/proc/get_list()
var/list/data = list()
data["name"] = ci.name
data["supplier_data"] = ci.supplier_data
data["amount"] = ci.amount
data["price"] = price
data["item_id"] = item_id
return data
/datum/cargo_order_item/Destroy()
ci = null
return ..()
/*
A shipment sent to the station. Contains multiple orders
*/
/datum/cargo_shipment
var/list/orders = list() //List of orders in that shipment
var/shipment_num //Number of the shipment
var/shipment_cost_sell = 0 //The amount of money cargo got for the shipment
var/shipment_cost_purchase = 0 //The amount of money cargo paid for the shipment
var/shipment_invoice = null//The invoice for the shipment (detailing the expenses ,credits received and charges)
var/shuttle_fee //The shuttle fee at the time of calling it
var/shuttle_time //The time the shuttle took to get to the station
var/shuttle_called_by //The person that called the shuttle
var/shuttle_recalled_by //The person that recalled the shuttle
var/completed = 0
var/message = null //Message from central
/datum/cargo_shipment/proc/get_list(var/shipment_completion = 1)
if(shipment_completion != completed)
return null
else
var/list/data = list()
data["shipment_num"] = shipment_num
data["shipment_cost_sell"] = shipment_cost_sell
data["shipment_cost_purchase"] = shipment_cost_purchase
data["shipment_invoice"] = shipment_invoice
data["shuttle_fee"] = shuttle_fee
data["shuttle_time"] = shuttle_time
data["shuttle_called_by"] = shuttle_called_by
data["shuttle_recalled_by"] = shuttle_recalled_by
data["invoice"] = get_invoice()
return data
// Generates the invoice at the time of shipping
/datum/cargo_shipment/proc/generate_invoice()
var/list/invoice_data = list()
invoice_data += "<h1>Shipping Invoice</h1>"
invoice_data += "<p>Shipment Data</p>"
invoice_data += "<table>"
invoice_data += "<tr>"
invoice_data += "<td>Shipment Number:</td>"
invoice_data += "<td>[shipment_num]</td>"
invoice_data += "</tr>"
invoice_data += "<tr>"
invoice_data += "<td>Shipment Income:</td>"
invoice_data += "<td>[shipment_cost_sell]</td>"
invoice_data += "</tr>"
invoice_data += "<tr>"
invoice_data += "<td>Shipment Expense:</td>"
invoice_data += "<td>[shipment_cost_purchase]</td>"
invoice_data += "</tr>"
invoice_data += "</table>"
invoice_data += "<p>Shuttle Data</p>"
invoice_data += "<table>"
invoice_data += "<tr>"
invoice_data += "<td>Shuttle Fee::</td>"
invoice_data += "<td>[shuttle_fee]</td>"
invoice_data += "</tr>"
invoice_data += "<tr>"
invoice_data += "<td>Shuttle Time:</td>"
invoice_data += "<td>[shuttle_time]</td>"
invoice_data += "</tr>"
invoice_data += "<tr>"
invoice_data += "<td>Shuttle Called By:</td>"
invoice_data += "<td>[shuttle_called_by]</td>"
invoice_data += "</tr>"
invoice_data += "<tr>"
invoice_data += "<td>Shuttle Recalled By:</td>"
invoice_data += "<td>[shuttle_recalled_by]</td>"
invoice_data += "</tr>"
invoice_data += "</table>"
invoice_data += "<p>Central Command Message:</p>"
invoice_data += "<pre>[nl2br(message)]</pre>"
shipment_invoice = invoice_data.Join("")
completed = 1
return shipment_invoice
// Returns the invoice. Generates it if it does not exist
/datum/cargo_shipment/proc/get_invoice()
if(!shipment_invoice)
if(completed == 1)
generate_invoice()
else
return "Invoice Unavailable. Shipment not completed."
return shipment_invoice