From e8d8aafd97afd75b8936619658877386b4747576 Mon Sep 17 00:00:00 2001 From: Priec Date: Sat, 27 Jun 2026 14:01:30 +0200 Subject: [PATCH] dodacia adresa --- assets/i18n/en/main.ftl | 6 +- assets/i18n/sk/main.ftl | 4 +- assets/views/account/order_detail.html | 19 ++++- assets/views/account/profile.html | 6 +- assets/views/admin/orders/show.html | 4 + assets/views/shop/checkout.html | 73 ++++++++++++++++--- assets/views/shop/order_confirmed.html | 15 ++++ migration/src/lib.rs | 4 +- ...20260627_000001_order_residence_address.rs | 22 ++++++ src/controllers/checkout.rs | 66 ++++++++++++----- src/models/_entities/orders.rs | 4 + src/models/orders.rs | 8 ++ src/views/checkout.rs | 4 + 13 files changed, 195 insertions(+), 40 deletions(-) create mode 100644 migration/src/m20260627_000001_order_residence_address.rs diff --git a/assets/i18n/en/main.ftl b/assets/i18n/en/main.ftl index e45905d..5b237c1 100644 --- a/assets/i18n/en/main.ftl +++ b/assets/i18n/en/main.ftl @@ -350,7 +350,9 @@ cart-update = Update cart-continue = Continue shopping checkout-title = Checkout checkout-contact = Contact details -checkout-shipping = Shipping address +checkout-shipping = Delivery address +checkout-residence-address = Residence address +checkout-delivery-same = Delivery address is the same as residence address checkout-email = Email checkout-name = Full name checkout-phone = Phone @@ -365,7 +367,7 @@ country-de = Germany country-pl = Poland country-hu = Hungary checkout-note = Order note -checkout-save-profile = Save this address to my profile +checkout-save-profile = Save residence address to my profile account-type = Account type account-personal = Individual account-company = Company diff --git a/assets/i18n/sk/main.ftl b/assets/i18n/sk/main.ftl index 1b20751..3af0294 100644 --- a/assets/i18n/sk/main.ftl +++ b/assets/i18n/sk/main.ftl @@ -351,6 +351,8 @@ cart-continue = Pokračovať v nákupe checkout-title = Pokladňa checkout-contact = Kontaktné údaje checkout-shipping = Dodacia adresa +checkout-residence-address = Adresa bydliska +checkout-delivery-same = Dodacia adresa je rovnaká ako adresa bydliska checkout-email = E-mail checkout-name = Meno a priezvisko checkout-phone = Telefón @@ -365,7 +367,7 @@ country-de = Nemecko country-pl = Poľsko country-hu = Maďarsko checkout-note = Poznámka k objednávke -checkout-save-profile = Uložiť túto adresu do môjho profilu +checkout-save-profile = Uložiť adresu bydliska do môjho profilu account-type = Typ účtu account-personal = Súkromná osoba account-company = Firma diff --git a/assets/views/account/order_detail.html b/assets/views/account/order_detail.html index a31dd13..e773e6e 100644 --- a/assets/views/account/order_detail.html +++ b/assets/views/account/order_detail.html @@ -55,10 +55,21 @@ {% endif %}
-

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

-

{{ order.customer_name }}

- {% if order.address %}

{{ order.address }}

{% endif %} -

{{ order.zip }} {{ order.city }}{% if order.country %}, {{ order.country }}{% endif %}

+
+ {% if order.residence_address %} +
+

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

+

{{ order.residence_address }}

+

{{ order.residence_zip }} {{ order.residence_city }}{% if order.residence_country %}, {{ order.residence_country }}{% endif %}

+
+ {% endif %} +
+

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

+

{{ order.customer_name }}

+ {% if order.address %}

{{ order.address }}

{% endif %} +

{{ order.zip }} {{ order.city }}{% if order.country %}, {{ order.country }}{% endif %}

+
+
{% if order.payment_method == "bank_transfer" and order.status == "pending" %} diff --git a/assets/views/account/profile.html b/assets/views/account/profile.html index 65e16b5..9c9d3ab 100644 --- a/assets/views/account/profile.html +++ b/assets/views/account/profile.html @@ -107,7 +107,7 @@
- {{ t(key="checkout-shipping", lang=lang | default(value='sk')) }} + {{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }} {{ self::field(label=t(key="checkout-address", lang=lang | default(value='sk')), value=address) }}
{{ self::field(label=t(key="checkout-city", lang=lang | default(value='sk')), value=city) }} @@ -211,9 +211,9 @@
- +
- {{ t(key="checkout-shipping", lang=lang | default(value='sk')) }} + {{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}
{{ ui::input(name="address", id="address", value=address | default(value=''), autocomplete="street-address") }} diff --git a/assets/views/admin/orders/show.html b/assets/views/admin/orders/show.html index 40cedc8..71059ae 100644 --- a/assets/views/admin/orders/show.html +++ b/assets/views/admin/orders/show.html @@ -69,6 +69,10 @@ {% if order.vat_id %}

{{ t(key="company-icdph", lang=lang | default(value='sk')) }}: {{ order.vat_id }}

{% endif %}
{% endif %} +
+

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

+

{% if order.residence_address %}{{ order.residence_address }}
{{ order.residence_zip }} {{ order.residence_city }}
{{ order.residence_country }}{% else %}{{ t(key="profile-not-set", lang=lang | default(value='sk')) }}{% endif %}

+

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

{{ order.address }}
{{ order.zip }} {{ order.city }}
{{ order.country }}

diff --git a/assets/views/shop/checkout.html b/assets/views/shop/checkout.html index 27754b0..5578651 100644 --- a/assets/views/shop/checkout.html +++ b/assets/views/shop/checkout.html @@ -12,6 +12,7 @@ x-data="{ paymentMethod: '', accountType: '{{ prefill_account_type | default(value='personal') }}', + deliverySame: false, carrier: '', carrierPrice: 0, requiresPoint: false, @@ -128,26 +129,26 @@
- +
- {{ t(key="checkout-shipping", lang=lang | default(value='sk')) }} + {{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}
- - {{ ui::input(name="address", id="address", value=prefill_address | default(value=''), required=true, autocomplete="street-address") }} + + {{ ui::input(name="residence_address", id="residence_address", value=prefill_residence_address | default(value=''), required=true, autocomplete="billing street-address") }}
- - {{ ui::input(name="city", id="city", value=prefill_city | default(value=''), required=true, autocomplete="address-level2") }} + + {{ ui::input(name="residence_city", id="residence_city", value=prefill_residence_city | default(value=''), required=true, autocomplete="billing address-level2") }}
- - {{ ui::input(name="zip", id="zip", value=prefill_zip | default(value=''), required=true, autocomplete="postal-code") }} + + {{ ui::input(name="residence_zip", id="residence_zip", value=prefill_residence_zip | default(value=''), required=true, autocomplete="billing postal-code") }}
- +
!this.country || o.v.toLowerCase().includes(this.country.toLowerCase())) } }"> - + +
    + +
+
+
+
+
+ + {{ ui::checkbox(name="delivery_same_as_residence", id="delivery_same_as_residence", label=t(key="checkout-delivery-same", lang=lang | default(value='sk')), attrs='x-model="deliverySame"') }} + + +
+ {{ t(key="checkout-shipping", lang=lang | default(value='sk')) }} +
+ + {{ ui::input(name="address", id="address", autocomplete="shipping street-address", attrs=':required="!deliverySame"') }} +
+
+
+ + {{ ui::input(name="city", id="city", autocomplete="shipping address-level2", attrs=':required="!deliverySame"') }} +
+
+ + {{ ui::input(name="zip", id="zip", autocomplete="shipping postal-code", attrs=':required="!deliverySame"') }} +
+
+ +
+
+
+ {% if order.residence_address %} +
+

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

+

{{ order.residence_address }}

+

{{ order.residence_zip }} {{ order.residence_city }}{% if order.residence_country %}, {{ order.residence_country }}{% endif %}

+
+ {% endif %} +
+

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

+ {% if order.address %}

{{ order.address }}

{% endif %} +

{{ order.zip }} {{ order.city }}{% if order.country %}, {{ order.country }}{% endif %}

+
+
+ {% if order.payment_method == "bank_transfer" %}

{{ t(key="payment-bank-instructions", lang=lang | default(value='sk')) }}

diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 150971c..d8eb988 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -50,6 +50,7 @@ mod m20260623_000002_strip_html_from_product_search; mod m20260623_000003_drop_currency; mod m20260623_000004_currencies; mod m20260625_000001_add_avatar_to_users; +mod m20260627_000001_order_residence_address; pub struct Migrator; #[async_trait::async_trait] @@ -104,7 +105,8 @@ impl MigratorTrait for Migrator { Box::new(m20260623_000003_drop_currency::Migration), Box::new(m20260623_000004_currencies::Migration), Box::new(m20260625_000001_add_avatar_to_users::Migration), + Box::new(m20260627_000001_order_residence_address::Migration), // inject-above (do not remove this comment) ] } -} \ No newline at end of file +} diff --git a/migration/src/m20260627_000001_order_residence_address.rs b/migration/src/m20260627_000001_order_residence_address.rs new file mode 100644 index 0000000..6099d1d --- /dev/null +++ b/migration/src/m20260627_000001_order_residence_address.rs @@ -0,0 +1,22 @@ +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> { + add_column(m, "orders", "residence_address", ColType::StringNull).await?; + add_column(m, "orders", "residence_city", ColType::StringNull).await?; + add_column(m, "orders", "residence_zip", ColType::StringNull).await?; + add_column(m, "orders", "residence_country", ColType::StringNull).await + } + + async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> { + remove_column(m, "orders", "residence_country").await?; + remove_column(m, "orders", "residence_zip").await?; + remove_column(m, "orders", "residence_city").await?; + remove_column(m, "orders", "residence_address").await + } +} diff --git a/src/controllers/checkout.rs b/src/controllers/checkout.rs index 5be217b..52895f1 100644 --- a/src/controllers/checkout.rs +++ b/src/controllers/checkout.rs @@ -35,10 +35,15 @@ struct CheckoutForm { company_id: Option, tax_id: Option, vat_id: Option, - address: String, - city: String, - zip: String, - country: String, + residence_address: String, + residence_city: String, + residence_zip: String, + residence_country: String, + delivery_same_as_residence: Option, + address: Option, + city: Option, + zip: Option, + country: Option, note: Option, payment_method: String, carrier_code: String, @@ -110,7 +115,7 @@ async fn checkout_page( let p = |get: fn(&customer_profiles::Model) -> Option| { profile.as_ref().and_then(get) }; - // Whether the customer already has a shipping address on file. When they do, + // Whether the customer already has a residence address on file. When they do, // the "save this address to my profile" opt-in is pointless (the profile was // filled in advance), so it's hidden and the existing profile is left alone. let profile_filled = profile @@ -147,10 +152,10 @@ async fn checkout_page( "prefill_vat_id": p(|x| x.vat_id.clone()), "prefill_phone_prefix": p(|x| x.phone_prefix.clone()), "prefill_phone": p(|x| x.phone.clone()), - "prefill_address": p(|x| x.address.clone()), - "prefill_city": p(|x| x.city.clone()), - "prefill_zip": p(|x| x.zip.clone()), - "prefill_country": p(|x| x.country.clone()), + "prefill_residence_address": p(|x| x.address.clone()), + "prefill_residence_city": p(|x| x.city.clone()), + "prefill_residence_zip": p(|x| x.zip.clone()), + "prefill_residence_country": p(|x| x.country.clone()), "lang": current_lang(&jar), }), ) @@ -177,16 +182,37 @@ async fn place_order( None => number.clone(), }; - // Contact and shipping-address fields are mandatory (also enforced in the + // Contact and residence-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 require_opt = |value: Option<&str>, field: &str| -> Result { + value + .and_then(trimmed) + .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")?; + let residence_address = require(&form.residence_address, "residence address")?; + let residence_city = require(&form.residence_city, "residence city")?; + let residence_zip = require(&form.residence_zip, "residence zip")?; + let residence_country = require(&form.residence_country, "residence country")?; + let same_address = form.delivery_same_as_residence.is_some(); + let (address, city, zip, country) = if same_address { + ( + residence_address.clone(), + residence_city.clone(), + residence_zip.clone(), + residence_country.clone(), + ) + } else { + ( + require_opt(form.address.as_deref(), "delivery address")?, + require_opt(form.city.as_deref(), "delivery city")?, + require_opt(form.zip.as_deref(), "delivery zip")?, + require_opt(form.country.as_deref(), "delivery country")?, + ) + }; // The account type is fixed for a logged-in customer (taken from their // account, never the form); a guest picks it on the form. Admins are treated @@ -246,10 +272,10 @@ async fn place_order( vat_id: vat_id.clone(), phone_prefix: trimmed(&form.phone_prefix), phone: Some(number.clone()), - address: Some(address.clone()), - city: Some(city.clone()), - zip: Some(zip.clone()), - country: Some(country.clone()), + address: Some(residence_address.clone()), + city: Some(residence_city.clone()), + zip: Some(residence_zip.clone()), + country: Some(residence_country.clone()), }; // Resolve the account that will own this order. A logged-in customer always @@ -318,6 +344,10 @@ async fn place_order( company_id, tax_id, vat_id, + residence_address: Some(residence_address), + residence_city: Some(residence_city), + residence_zip: Some(residence_zip), + residence_country: Some(residence_country), address: Some(address), city: Some(city), zip: Some(zip), diff --git a/src/models/_entities/orders.rs b/src/models/_entities/orders.rs index 531dd08..42545e4 100644 --- a/src/models/_entities/orders.rs +++ b/src/models/_entities/orders.rs @@ -38,6 +38,10 @@ pub struct Model { pub tax_id: Option, pub vat_id: Option, pub user_id: Option, + pub residence_address: Option, + pub residence_city: Option, + pub residence_zip: Option, + pub residence_country: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/models/orders.rs b/src/models/orders.rs index d821e95..d934e16 100644 --- a/src/models/orders.rs +++ b/src/models/orders.rs @@ -24,6 +24,10 @@ pub struct Checkout { pub company_id: Option, pub tax_id: Option, pub vat_id: Option, + pub residence_address: Option, + pub residence_city: Option, + pub residence_zip: Option, + pub residence_country: Option, pub address: Option, pub city: Option, pub zip: Option, @@ -102,6 +106,10 @@ pub async fn place( company_id: Set(details.company_id), tax_id: Set(details.tax_id), vat_id: Set(details.vat_id), + residence_address: Set(details.residence_address), + residence_city: Set(details.residence_city), + residence_zip: Set(details.residence_zip), + residence_country: Set(details.residence_country), address: Set(details.address), city: Set(details.city), zip: Set(details.zip), diff --git a/src/views/checkout.rs b/src/views/checkout.rs index b135fe8..00d5a8d 100644 --- a/src/views/checkout.rs +++ b/src/views/checkout.rs @@ -40,6 +40,10 @@ pub fn detail(order: &orders::Model, bank_iban: &str, bank_account_name: &str) - "subtotal": format_price(order.total_cents - order.shipping_cents), "shipping": format_price(order.shipping_cents), "total": format_price(order.total_cents), + "residence_address": order.residence_address, + "residence_city": order.residence_city, + "residence_zip": order.residence_zip, + "residence_country": order.residence_country, "address": order.address, "city": order.city, "zip": order.zip,