dodacia adresa

This commit is contained in:
Priec
2026-06-27 14:01:30 +02:00
parent 5001e46866
commit e8d8aafd97
13 changed files with 195 additions and 40 deletions

View File

@@ -350,7 +350,9 @@ cart-update = Update
cart-continue = Continue shopping cart-continue = Continue shopping
checkout-title = Checkout checkout-title = Checkout
checkout-contact = Contact details 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-email = Email
checkout-name = Full name checkout-name = Full name
checkout-phone = Phone checkout-phone = Phone
@@ -365,7 +367,7 @@ country-de = Germany
country-pl = Poland country-pl = Poland
country-hu = Hungary country-hu = Hungary
checkout-note = Order note 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-type = Account type
account-personal = Individual account-personal = Individual
account-company = Company account-company = Company

View File

@@ -351,6 +351,8 @@ cart-continue = Pokračovať v nákupe
checkout-title = Pokladňa checkout-title = Pokladňa
checkout-contact = Kontaktné údaje checkout-contact = Kontaktné údaje
checkout-shipping = Dodacia adresa checkout-shipping = Dodacia adresa
checkout-residence-address = Adresa bydliska
checkout-delivery-same = Dodacia adresa je rovnaká ako adresa bydliska
checkout-email = E-mail checkout-email = E-mail
checkout-name = Meno a priezvisko checkout-name = Meno a priezvisko
checkout-phone = Telefón checkout-phone = Telefón
@@ -365,7 +367,7 @@ country-de = Nemecko
country-pl = Poľsko country-pl = Poľsko
country-hu = Maďarsko country-hu = Maďarsko
checkout-note = Poznámka k objednávke 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-type = Typ účtu
account-personal = Súkromná osoba account-personal = Súkromná osoba
account-company = Firma account-company = Firma

View File

@@ -55,10 +55,21 @@
{% endif %} {% endif %}
<div class="rounded-radius border border-outline bg-surface p-6 text-sm dark:border-outline-dark dark:bg-surface-dark-alt"> <div class="rounded-radius border border-outline bg-surface p-6 text-sm dark:border-outline-dark dark:bg-surface-dark-alt">
<h2 class="mb-2 font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</h2> <div class="space-y-4">
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.customer_name }}</p> {% if order.residence_address %}
{% if order.address %}<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.address }}</p>{% endif %} <div>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.zip }} {{ order.city }}{% if order.country %}, {{ order.country }}{% endif %}</p> <h2 class="mb-2 font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}</h2>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.residence_address }}</p>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.residence_zip }} {{ order.residence_city }}{% if order.residence_country %}, {{ order.residence_country }}{% endif %}</p>
</div>
{% endif %}
<div>
<h2 class="mb-2 font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</h2>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.customer_name }}</p>
{% if order.address %}<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.address }}</p>{% endif %}
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.zip }} {{ order.city }}{% if order.country %}, {{ order.country }}{% endif %}</p>
</div>
</div>
</div> </div>
{% if order.payment_method == "bank_transfer" and order.status == "pending" %} {% if order.payment_method == "bank_transfer" and order.status == "pending" %}

View File

@@ -107,7 +107,7 @@
</fieldset> </fieldset>
<fieldset class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt"> <fieldset class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</legend> <legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}</legend>
{{ self::field(label=t(key="checkout-address", lang=lang | default(value='sk')), value=address) }} {{ self::field(label=t(key="checkout-address", lang=lang | default(value='sk')), value=address) }}
<div class="grid gap-4 sm:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-3">
{{ self::field(label=t(key="checkout-city", lang=lang | default(value='sk')), value=city) }} {{ self::field(label=t(key="checkout-city", lang=lang | default(value='sk')), value=city) }}
@@ -211,9 +211,9 @@
</div> </div>
</fieldset> </fieldset>
<!-- default shipping address --> <!-- residence address -->
<fieldset class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt"> <fieldset class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</legend> <legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}</legend>
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="address" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-address", lang=lang | default(value='sk')) }}</label> <label for="address" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-address", lang=lang | default(value='sk')) }}</label>
{{ ui::input(name="address", id="address", value=address | default(value=''), autocomplete="street-address") }} {{ ui::input(name="address", id="address", value=address | default(value=''), autocomplete="street-address") }}

View File

@@ -69,6 +69,10 @@
{% if order.vat_id %}<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ t(key="company-icdph", lang=lang | default(value='sk')) }}: {{ order.vat_id }}</p>{% endif %} {% if order.vat_id %}<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ t(key="company-icdph", lang=lang | default(value='sk')) }}: {{ order.vat_id }}</p>{% endif %}
</div> </div>
{% endif %} {% endif %}
<div>
<p class="text-xs uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">{{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}</p>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{% if order.residence_address %}{{ order.residence_address }}<br>{{ order.residence_zip }} {{ order.residence_city }}<br>{{ order.residence_country }}{% else %}{{ t(key="profile-not-set", lang=lang | default(value='sk')) }}{% endif %}</p>
</div>
<div> <div>
<p class="text-xs uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</p> <p class="text-xs uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</p>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.address }}<br>{{ order.zip }} {{ order.city }}<br>{{ order.country }}</p> <p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.address }}<br>{{ order.zip }} {{ order.city }}<br>{{ order.country }}</p>

View File

@@ -12,6 +12,7 @@
x-data="{ x-data="{
paymentMethod: '', paymentMethod: '',
accountType: '{{ prefill_account_type | default(value='personal') }}', accountType: '{{ prefill_account_type | default(value='personal') }}',
deliverySame: false,
carrier: '', carrier: '',
carrierPrice: 0, carrierPrice: 0,
requiresPoint: false, requiresPoint: false,
@@ -128,26 +129,26 @@
</div> </div>
</fieldset> </fieldset>
<!-- shipping address --> <!-- residence address -->
<fieldset class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt"> <fieldset class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</legend> <legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}</legend>
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="address" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-address", lang=lang | default(value='sk')) }}{{ ui::req() }}</label> <label for="residence_address" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-address", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
{{ 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") }}
</div> </div>
<div class="grid gap-4 sm:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-3">
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="city" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-city", lang=lang | default(value='sk')) }}{{ ui::req() }}</label> <label for="residence_city" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-city", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
{{ 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") }}
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="zip" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-zip", lang=lang | default(value='sk')) }}{{ ui::req() }}</label> <label for="residence_zip" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-zip", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
{{ 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") }}
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
<label for="country" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-country", lang=lang | default(value='sk')) }}{{ ui::req() }}</label> <label for="residence_country" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-country", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
<div class="relative" @click.outside="countryOpen = false" <div class="relative" @click.outside="countryOpen = false"
x-data="{ countryOpen: false, country: '{{ prefill_country | default(value=t(key='country-sk', lang=lang | default(value='sk'))) }}', opts: [ x-data="{ countryOpen: false, country: '{{ prefill_residence_country | default(value=t(key='country-sk', lang=lang | default(value='sk'))) }}', opts: [
{ v: '{{ t(key='country-sk', lang=lang | default(value='sk')) }}', l: '🇸🇰 {{ t(key='country-sk', lang=lang | default(value='sk')) }}' }, { v: '{{ t(key='country-sk', lang=lang | default(value='sk')) }}', l: '🇸🇰 {{ t(key='country-sk', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-cz', lang=lang | default(value='sk')) }}', l: '🇨🇿 {{ t(key='country-cz', lang=lang | default(value='sk')) }}' }, { v: '{{ t(key='country-cz', lang=lang | default(value='sk')) }}', l: '🇨🇿 {{ t(key='country-cz', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-at', lang=lang | default(value='sk')) }}', l: '🇦🇹 {{ t(key='country-at', lang=lang | default(value='sk')) }}' }, { v: '{{ t(key='country-at', lang=lang | default(value='sk')) }}', l: '🇦🇹 {{ t(key='country-at', lang=lang | default(value='sk')) }}' },
@@ -155,7 +156,57 @@
{ v: '{{ t(key='country-pl', lang=lang | default(value='sk')) }}', l: '🇵🇱 {{ t(key='country-pl', lang=lang | default(value='sk')) }}' }, { v: '{{ t(key='country-pl', lang=lang | default(value='sk')) }}', l: '🇵🇱 {{ t(key='country-pl', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-hu', lang=lang | default(value='sk')) }}', l: '🇭🇺 {{ t(key='country-hu', lang=lang | default(value='sk')) }}' } { v: '{{ t(key='country-hu', lang=lang | default(value='sk')) }}', l: '🇭🇺 {{ t(key='country-hu', lang=lang | default(value='sk')) }}' }
], get filtered() { return this.opts.filter(o => !this.country || o.v.toLowerCase().includes(this.country.toLowerCase())) } }"> ], get filtered() { return this.opts.filter(o => !this.country || o.v.toLowerCase().includes(this.country.toLowerCase())) } }">
<input id="country" name="country" type="text" x-model="country" required @focus="countryOpen = true" @input="countryOpen = true" <input id="residence_country" name="residence_country" type="text" x-model="country" required @focus="countryOpen = true" @input="countryOpen = true"
class="w-full rounded-radius border border-outline bg-surface py-2 pl-3 pr-8 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
<button type="button" tabindex="-1" @click="countryOpen = !countryOpen"
class="absolute inset-y-0 right-0 flex w-8 items-center justify-center text-on-surface/60 dark:text-on-surface-dark/60">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"
class="size-4 transition-transform" :class="countryOpen && 'rotate-180'">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</button>
<ul x-show="countryOpen" x-cloak x-transition
class="absolute z-20 mt-1 max-h-56 w-full overflow-auto rounded-radius border border-outline bg-surface p-1 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt">
<template x-for="o in filtered" :key="o.v">
<li><button type="button" @click="country = o.v; countryOpen = false" x-text="o.l"
class="block w-full rounded-radius px-3 py-1.5 text-left text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark"></button></li>
</template>
</ul>
</div>
</div>
</div>
</fieldset>
{{ 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"') }}
<!-- delivery address -->
<fieldset x-show="!deliverySame" x-cloak class="space-y-4 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</legend>
<div class="space-y-1.5">
<label for="address" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-address", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
{{ ui::input(name="address", id="address", autocomplete="shipping street-address", attrs=':required="!deliverySame"') }}
</div>
<div class="grid gap-4 sm:grid-cols-3">
<div class="space-y-1.5">
<label for="city" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-city", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
{{ ui::input(name="city", id="city", autocomplete="shipping address-level2", attrs=':required="!deliverySame"') }}
</div>
<div class="space-y-1.5">
<label for="zip" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-zip", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
{{ ui::input(name="zip", id="zip", autocomplete="shipping postal-code", attrs=':required="!deliverySame"') }}
</div>
<div class="space-y-1.5">
<label for="country" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-country", lang=lang | default(value='sk')) }}{{ ui::req() }}</label>
<div class="relative" @click.outside="countryOpen = false"
x-data="{ countryOpen: false, country: '{{ t(key='country-sk', lang=lang | default(value='sk')) }}', opts: [
{ v: '{{ t(key='country-sk', lang=lang | default(value='sk')) }}', l: '🇸🇰 {{ t(key='country-sk', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-cz', lang=lang | default(value='sk')) }}', l: '🇨🇿 {{ t(key='country-cz', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-at', lang=lang | default(value='sk')) }}', l: '🇦🇹 {{ t(key='country-at', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-de', lang=lang | default(value='sk')) }}', l: '🇩🇪 {{ t(key='country-de', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-pl', lang=lang | default(value='sk')) }}', l: '🇵🇱 {{ t(key='country-pl', lang=lang | default(value='sk')) }}' },
{ v: '{{ t(key='country-hu', lang=lang | default(value='sk')) }}', l: '🇭🇺 {{ t(key='country-hu', lang=lang | default(value='sk')) }}' }
], get filtered() { return this.opts.filter(o => !this.country || o.v.toLowerCase().includes(this.country.toLowerCase())) } }">
<input id="country" name="country" type="text" x-model="country" :required="!deliverySame" @focus="countryOpen = true" @input="countryOpen = true"
class="w-full rounded-radius border border-outline bg-surface py-2 pl-3 pr-8 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark"> class="w-full rounded-radius border border-outline bg-surface py-2 pl-3 pr-8 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
<button type="button" tabindex="-1" @click="countryOpen = !countryOpen" <button type="button" tabindex="-1" @click="countryOpen = !countryOpen"
class="absolute inset-y-0 right-0 flex w-8 items-center justify-center text-on-surface/60 dark:text-on-surface-dark/60"> class="absolute inset-y-0 right-0 flex w-8 items-center justify-center text-on-surface/60 dark:text-on-surface-dark/60">

View File

@@ -45,6 +45,21 @@
</div> </div>
</div> </div>
<div class="grid gap-4 text-sm sm:grid-cols-2">
{% if order.residence_address %}
<div class="rounded-radius border border-outline bg-surface p-4 dark:border-outline-dark dark:bg-surface-dark-alt">
<h2 class="mb-2 font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-residence-address", lang=lang | default(value='sk')) }}</h2>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.residence_address }}</p>
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.residence_zip }} {{ order.residence_city }}{% if order.residence_country %}, {{ order.residence_country }}{% endif %}</p>
</div>
{% endif %}
<div class="rounded-radius border border-outline bg-surface p-4 dark:border-outline-dark dark:bg-surface-dark-alt">
<h2 class="mb-2 font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-shipping", lang=lang | default(value='sk')) }}</h2>
{% if order.address %}<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.address }}</p>{% endif %}
<p class="text-on-surface/80 dark:text-on-surface-dark/80">{{ order.zip }} {{ order.city }}{% if order.country %}, {{ order.country }}{% endif %}</p>
</div>
</div>
{% if order.payment_method == "bank_transfer" %} {% if order.payment_method == "bank_transfer" %}
<div class="space-y-2 rounded-radius border border-primary/40 bg-primary/5 p-6 text-sm dark:border-primary-dark/40"> <div class="space-y-2 rounded-radius border border-primary/40 bg-primary/5 p-6 text-sm dark:border-primary-dark/40">
<p class="font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="payment-bank-instructions", lang=lang | default(value='sk')) }}</p> <p class="font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="payment-bank-instructions", lang=lang | default(value='sk')) }}</p>

View File

@@ -50,6 +50,7 @@ mod m20260623_000002_strip_html_from_product_search;
mod m20260623_000003_drop_currency; mod m20260623_000003_drop_currency;
mod m20260623_000004_currencies; mod m20260623_000004_currencies;
mod m20260625_000001_add_avatar_to_users; mod m20260625_000001_add_avatar_to_users;
mod m20260627_000001_order_residence_address;
pub struct Migrator; pub struct Migrator;
#[async_trait::async_trait] #[async_trait::async_trait]
@@ -104,6 +105,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260623_000003_drop_currency::Migration), Box::new(m20260623_000003_drop_currency::Migration),
Box::new(m20260623_000004_currencies::Migration), Box::new(m20260623_000004_currencies::Migration),
Box::new(m20260625_000001_add_avatar_to_users::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) // inject-above (do not remove this comment)
] ]
} }

View File

@@ -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
}
}

View File

@@ -35,10 +35,15 @@ struct CheckoutForm {
company_id: Option<String>, company_id: Option<String>,
tax_id: Option<String>, tax_id: Option<String>,
vat_id: Option<String>, vat_id: Option<String>,
address: String, residence_address: String,
city: String, residence_city: String,
zip: String, residence_zip: String,
country: String, residence_country: String,
delivery_same_as_residence: Option<String>,
address: Option<String>,
city: Option<String>,
zip: Option<String>,
country: Option<String>,
note: Option<String>, note: Option<String>,
payment_method: String, payment_method: String,
carrier_code: String, carrier_code: String,
@@ -110,7 +115,7 @@ async fn checkout_page(
let p = |get: fn(&customer_profiles::Model) -> Option<String>| { let p = |get: fn(&customer_profiles::Model) -> Option<String>| {
profile.as_ref().and_then(get) 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 // 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. // filled in advance), so it's hidden and the existing profile is left alone.
let profile_filled = profile let profile_filled = profile
@@ -147,10 +152,10 @@ async fn checkout_page(
"prefill_vat_id": p(|x| x.vat_id.clone()), "prefill_vat_id": p(|x| x.vat_id.clone()),
"prefill_phone_prefix": p(|x| x.phone_prefix.clone()), "prefill_phone_prefix": p(|x| x.phone_prefix.clone()),
"prefill_phone": p(|x| x.phone.clone()), "prefill_phone": p(|x| x.phone.clone()),
"prefill_address": p(|x| x.address.clone()), "prefill_residence_address": p(|x| x.address.clone()),
"prefill_city": p(|x| x.city.clone()), "prefill_residence_city": p(|x| x.city.clone()),
"prefill_zip": p(|x| x.zip.clone()), "prefill_residence_zip": p(|x| x.zip.clone()),
"prefill_country": p(|x| x.country.clone()), "prefill_residence_country": p(|x| x.country.clone()),
"lang": current_lang(&jar), "lang": current_lang(&jar),
}), }),
) )
@@ -177,16 +182,37 @@ async fn place_order(
None => number.clone(), 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`). // browser via `required`).
let require = |value: &str, field: &str| -> Result<String> { let require = |value: &str, field: &str| -> Result<String> {
trimmed(value).ok_or_else(|| Error::BadRequest(format!("{field} is required"))) trimmed(value).ok_or_else(|| Error::BadRequest(format!("{field} is required")))
}; };
let require_opt = |value: Option<&str>, field: &str| -> Result<String> {
value
.and_then(trimmed)
.ok_or_else(|| Error::BadRequest(format!("{field} is required")))
};
let customer_name = require(&form.customer_name, "name")?; let customer_name = require(&form.customer_name, "name")?;
let address = require(&form.address, "address")?; let residence_address = require(&form.residence_address, "residence address")?;
let city = require(&form.city, "city")?; let residence_city = require(&form.residence_city, "residence city")?;
let zip = require(&form.zip, "zip")?; let residence_zip = require(&form.residence_zip, "residence zip")?;
let country = require(&form.country, "country")?; 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 // 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 // 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(), vat_id: vat_id.clone(),
phone_prefix: trimmed(&form.phone_prefix), phone_prefix: trimmed(&form.phone_prefix),
phone: Some(number.clone()), phone: Some(number.clone()),
address: Some(address.clone()), address: Some(residence_address.clone()),
city: Some(city.clone()), city: Some(residence_city.clone()),
zip: Some(zip.clone()), zip: Some(residence_zip.clone()),
country: Some(country.clone()), country: Some(residence_country.clone()),
}; };
// Resolve the account that will own this order. A logged-in customer always // Resolve the account that will own this order. A logged-in customer always
@@ -318,6 +344,10 @@ async fn place_order(
company_id, company_id,
tax_id, tax_id,
vat_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), address: Some(address),
city: Some(city), city: Some(city),
zip: Some(zip), zip: Some(zip),

View File

@@ -38,6 +38,10 @@ pub struct Model {
pub tax_id: Option<String>, pub tax_id: Option<String>,
pub vat_id: Option<String>, pub vat_id: Option<String>,
pub user_id: Option<i32>, pub user_id: Option<i32>,
pub residence_address: Option<String>,
pub residence_city: Option<String>,
pub residence_zip: Option<String>,
pub residence_country: Option<String>,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -24,6 +24,10 @@ pub struct Checkout {
pub company_id: Option<String>, pub company_id: Option<String>,
pub tax_id: Option<String>, pub tax_id: Option<String>,
pub vat_id: Option<String>, pub vat_id: Option<String>,
pub residence_address: Option<String>,
pub residence_city: Option<String>,
pub residence_zip: Option<String>,
pub residence_country: Option<String>,
pub address: Option<String>, pub address: Option<String>,
pub city: Option<String>, pub city: Option<String>,
pub zip: Option<String>, pub zip: Option<String>,
@@ -102,6 +106,10 @@ pub async fn place(
company_id: Set(details.company_id), company_id: Set(details.company_id),
tax_id: Set(details.tax_id), tax_id: Set(details.tax_id),
vat_id: Set(details.vat_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), address: Set(details.address),
city: Set(details.city), city: Set(details.city),
zip: Set(details.zip), zip: Set(details.zip),

View File

@@ -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), "subtotal": format_price(order.total_cents - order.shipping_cents),
"shipping": format_price(order.shipping_cents), "shipping": format_price(order.shipping_cents),
"total": format_price(order.total_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, "address": order.address,
"city": order.city, "city": order.city,
"zip": order.zip, "zip": order.zip,