From d18bdeaf6e8a24b53abe7b403e2d918be7c4bec2 Mon Sep 17 00:00:00 2001 From: Priec Date: Wed, 17 Jun 2026 18:02:46 +0200 Subject: [PATCH] phone number + country --- assets/i18n/en/main.ftl | 7 ++++ assets/i18n/sk/main.ftl | 7 ++++ assets/views/admin/orders/show.html | 1 + assets/views/shop/checkout.html | 28 +++++++++++++++- migration/src/lib.rs | 2 ++ .../m20260617_000003_add_phone_to_orders.rs | 17 ++++++++++ src/controllers/admin_orders.rs | 1 + src/controllers/checkout.rs | 32 ++++++++++++++++--- src/integrations/mod.rs | 1 + src/integrations/packeta.rs | 2 ++ src/models/_entities/orders.rs | 1 + src/models/orders.rs | 2 ++ src/views/checkout.rs | 1 + 13 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 migration/src/m20260617_000003_add_phone_to_orders.rs diff --git a/assets/i18n/en/main.ftl b/assets/i18n/en/main.ftl index 03d1e73..bb8c7cd 100644 --- a/assets/i18n/en/main.ftl +++ b/assets/i18n/en/main.ftl @@ -222,10 +222,17 @@ checkout-contact = Contact details checkout-shipping = Shipping address checkout-email = Email checkout-name = Full name +checkout-phone = Phone checkout-address = Address checkout-city = City checkout-zip = Postal code checkout-country = Country +country-sk = Slovakia +country-cz = Czechia +country-at = Austria +country-de = Germany +country-pl = Poland +country-hu = Hungary checkout-note = Order note checkout-place-order = Place order checkout-summary = Order summary diff --git a/assets/i18n/sk/main.ftl b/assets/i18n/sk/main.ftl index bd4f87b..0f6d666 100644 --- a/assets/i18n/sk/main.ftl +++ b/assets/i18n/sk/main.ftl @@ -222,10 +222,17 @@ checkout-contact = Kontaktné údaje checkout-shipping = Dodacia adresa checkout-email = E-mail checkout-name = Meno a priezvisko +checkout-phone = Telefón checkout-address = Adresa checkout-city = Mesto checkout-zip = PSČ checkout-country = Krajina +country-sk = Slovensko +country-cz = Česko +country-at = Rakúsko +country-de = Nemecko +country-pl = Poľsko +country-hu = Maďarsko checkout-note = Poznámka k objednávke checkout-place-order = Odoslať objednávku checkout-summary = Súhrn objednávky diff --git a/assets/views/admin/orders/show.html b/assets/views/admin/orders/show.html index e60327d..ba153b2 100644 --- a/assets/views/admin/orders/show.html +++ b/assets/views/admin/orders/show.html @@ -51,6 +51,7 @@

{{ t(key="order-customer", lang=lang | default(value='sk')) }}

{{ order.customer_name }}

{{ order.email }}

+ {% if order.phone %}

{{ order.phone }}

{% endif %}

{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}

diff --git a/assets/views/shop/checkout.html b/assets/views/shop/checkout.html index e718923..e38f9d5 100644 --- a/assets/views/shop/checkout.html +++ b/assets/views/shop/checkout.html @@ -43,6 +43,25 @@
+
+ +
+ + +
+
@@ -66,8 +85,15 @@
- + + + + + + +
diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 797109f..d84281f 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -29,6 +29,7 @@ mod m20260616_150812_add_shipping_fields_to_orders; mod m20260616_160000_add_parent_to_categories; mod m20260617_000001_add_carrier_to_shipping_methods; mod m20260617_000002_add_shipment_to_orders; +mod m20260617_000003_add_phone_to_orders; pub struct Migrator; #[async_trait::async_trait] @@ -62,6 +63,7 @@ impl MigratorTrait for Migrator { Box::new(m20260616_160000_add_parent_to_categories::Migration), Box::new(m20260617_000001_add_carrier_to_shipping_methods::Migration), Box::new(m20260617_000002_add_shipment_to_orders::Migration), + Box::new(m20260617_000003_add_phone_to_orders::Migration), // inject-above (do not remove this comment) ] } diff --git a/migration/src/m20260617_000003_add_phone_to_orders.rs b/migration/src/m20260617_000003_add_phone_to_orders.rs new file mode 100644 index 0000000..072699e --- /dev/null +++ b/migration/src/m20260617_000003_add_phone_to_orders.rs @@ -0,0 +1,17 @@ +use loco_rs::schema::*; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> { + // Customer contact phone, also passed to carriers for pickup SMS. + add_column(m, "orders", "phone", ColType::StringNull).await + } + + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + remove_column(m, "orders", "phone").await + } +} diff --git a/src/controllers/admin_orders.rs b/src/controllers/admin_orders.rs index bddeb74..d60390f 100644 --- a/src/controllers/admin_orders.rs +++ b/src/controllers/admin_orders.rs @@ -178,6 +178,7 @@ async fn ship( order_number: &order.order_number, recipient_name: recipient, email: &order.email, + phone: order.phone.as_deref(), address: order.address.as_deref(), city: order.city.as_deref(), zip: order.zip.as_deref(), diff --git a/src/controllers/checkout.rs b/src/controllers/checkout.rs index eb72b53..9375273 100644 --- a/src/controllers/checkout.rs +++ b/src/controllers/checkout.rs @@ -21,6 +21,8 @@ const PAYMENT_METHODS: [&str; 2] = ["cod", "bank_transfer"]; #[derive(Debug, Deserialize)] struct CheckoutForm { email: String, + phone_prefix: String, + phone: String, customer_name: String, address: String, city: String, @@ -111,6 +113,25 @@ async fn place_order( } let email = trimmed(&form.email).ok_or_else(|| Error::BadRequest("email is required".to_string()))?; + // Combine the dialling-code prefix with the local number into one E.164-ish + // value (e.g. "+421 900123456"). + let number = + trimmed(&form.phone).ok_or_else(|| Error::BadRequest("phone is required".to_string()))?; + let phone = match trimmed(&form.phone_prefix) { + Some(prefix) => format!("{prefix} {number}"), + None => number, + }; + + // Contact and shipping-address fields are mandatory (also enforced in the + // browser via `required`). + let require = |value: &str, field: &str| -> Result { + trimmed(value).ok_or_else(|| Error::BadRequest(format!("{field} is required"))) + }; + let customer_name = require(&form.customer_name, "name")?; + let address = require(&form.address, "address")?; + let city = require(&form.city, "city")?; + let zip = require(&form.zip, "zip")?; + let country = require(&form.country, "country")?; if !PAYMENT_METHODS.contains(&form.payment_method.as_str()) { return Err(Error::BadRequest("invalid payment method".to_string())); @@ -141,11 +162,12 @@ async fn place_order( &valid, orders::Checkout { email, - customer_name: trimmed(&form.customer_name), - address: trimmed(&form.address), - city: trimmed(&form.city), - zip: trimmed(&form.zip), - country: trimmed(&form.country), + phone, + customer_name: Some(customer_name), + address: Some(address), + city: Some(city), + zip: Some(zip), + country: Some(country), note: form.note.as_deref().and_then(trimmed), payment_method: form.payment_method, method, diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs index 4d20173..c0bdb36 100644 --- a/src/integrations/mod.rs +++ b/src/integrations/mod.rs @@ -19,6 +19,7 @@ pub struct ShipmentRequest<'a> { pub order_number: &'a str, pub recipient_name: &'a str, pub email: &'a str, + pub phone: Option<&'a str>, pub address: Option<&'a str>, pub city: Option<&'a str>, pub zip: Option<&'a str>, diff --git a/src/integrations/packeta.rs b/src/integrations/packeta.rs index 21ea1b6..ec495d1 100644 --- a/src/integrations/packeta.rs +++ b/src/integrations/packeta.rs @@ -60,6 +60,7 @@ pub async fn create_shipment(ctx: &AppContext, req: ShipmentRequest<'_>) -> Resu {}\ -\ {}\ + {}\ {}\ {:.2}\ {:.2}\ @@ -72,6 +73,7 @@ pub async fn create_shipment(ctx: &AppContext, req: ShipmentRequest<'_>) -> Resu xml_escape(req.order_number), xml_escape(req.recipient_name), xml_escape(req.email), + xml_escape(req.phone.unwrap_or("")), xml_escape(address_id), value, cod, diff --git a/src/models/_entities/orders.rs b/src/models/_entities/orders.rs index 2cabdd9..fe113d2 100644 --- a/src/models/_entities/orders.rs +++ b/src/models/_entities/orders.rs @@ -13,6 +13,7 @@ pub struct Model { #[sea_orm(unique)] pub order_number: String, pub email: String, + pub phone: Option, pub customer_name: Option, pub status: String, pub total_cents: i64, diff --git a/src/models/orders.rs b/src/models/orders.rs index fbfcf77..a49768a 100644 --- a/src/models/orders.rs +++ b/src/models/orders.rs @@ -12,6 +12,7 @@ pub type Orders = Entity; /// database inside [`place`] so the customer cannot influence what they pay. pub struct Checkout { pub email: String, + pub phone: String, pub customer_name: Option, pub address: Option, pub city: Option, @@ -64,6 +65,7 @@ pub async fn place(ctx: &AppContext, items: &[(i32, i32)], details: Checkout) -> let order = ActiveModel { order_number: Set(generate_order_number()), email: Set(details.email), + phone: Set(Some(details.phone)), customer_name: Set(details.customer_name), status: Set("pending".to_string()), total_cents: Set(subtotal + details.method.price_cents), diff --git a/src/views/checkout.rs b/src/views/checkout.rs index b9a7750..015d7ee 100644 --- a/src/views/checkout.rs +++ b/src/views/checkout.rs @@ -28,6 +28,7 @@ pub fn detail(order: &orders::Model, bank_iban: &str, bank_account_name: &str) - "id": order.id, "order_number": order.order_number, "email": order.email, + "phone": order.phone, "customer_name": order.customer_name, "status": order.status, "subtotal": format_price(order.total_cents - order.shipping_cents),