This commit is contained in:
sdarbinyan
2026-06-20 15:16:25 +04:00
parent 51445a7341
commit 6410321895
6 changed files with 98 additions and 13 deletions

View File

@@ -68,6 +68,7 @@ export interface ItemDetail {
colour?: string; colour?: string;
size?: string; size?: string;
price: number; price: number;
deliveryPrice?: number;
currency: string; currency: string;
remaining: number; remaining: number;
} }
@@ -80,6 +81,7 @@ export interface Item {
description: string; description: string;
currency: string; currency: string;
price: number; price: number;
deliveryPrice?: number;
discount: number; discount: number;
remainings?: string; remainings?: string;
rating: number; rating: number;

View File

@@ -78,6 +78,12 @@
} @else { } @else {
<span class="current-price">{{ item.price }} {{ item.currency }}</span> <span class="current-price">{{ item.price }} {{ item.currency }}</span>
} }
@if (item.deliveryPrice != null) {
<span class="delivery-price">
{{ 'cart.deliveryLabel' | translate }}: {{ item.deliveryPrice | number:'1.2-2' }} {{ item.currency }}
</span>
}
</div> </div>
<div class="quantity-controls"> <div class="quantity-controls">
@@ -116,14 +122,16 @@
<span class="value">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span> <span class="value">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
</div> </div>
@if (hasDeliveryPrice()) {
<div class="summary-row delivery"> <div class="summary-row delivery">
<span>{{ 'cart.deliveryLabel' | translate }}</span> <span>{{ 'cart.deliveryLabel' | translate }}</span>
<span>0 {{ currentCurrency }}</span> <span class="value">{{ totalDeliveryPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
</div> </div>
}
<div class="summary-row total"> <div class="summary-row total">
<span>{{ 'cart.toPay' | translate }}</span> <span>{{ 'cart.toPay' | translate }}</span>
<span class="total-price">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span> <span class="total-price">{{ totalWithDelivery() | number:'1.2-2' }} {{ currentCurrency }}</span>
</div> </div>
<div class="terms-agreement"> <div class="terms-agreement">

View File

@@ -555,6 +555,12 @@
font-weight: 700; font-weight: 700;
color: #497671; color: #497671;
} }
.delivery-price {
font-size: 0.85rem;
font-weight: 500;
color: #697777;
}
} }
// Dexar quantity controls // Dexar quantity controls
@@ -838,7 +844,7 @@
color: #6b7280; color: #6b7280;
&.delivery { &.delivery {
display: none; // Hide delivery in Novo display: flex;
} }
&.total { &.total {
@@ -1830,6 +1836,7 @@
margin: 0; margin: 0;
line-height: 1.5; line-height: 1.5;
display: -webkit-box; display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;

View File

@@ -26,6 +26,9 @@ export class CartComponent implements OnDestroy {
items; items;
itemCount; itemCount;
totalPrice; totalPrice;
totalDeliveryPrice;
totalWithDelivery;
hasDeliveryPrice;
termsAccepted = false; termsAccepted = false;
isnovo = environment.theme === 'novo'; isnovo = environment.theme === 'novo';
@@ -68,6 +71,9 @@ export class CartComponent implements OnDestroy {
this.items = this.cartService.items; this.items = this.cartService.items;
this.itemCount = this.cartService.itemCount; this.itemCount = this.cartService.itemCount;
this.totalPrice = this.cartService.totalPrice; this.totalPrice = this.cartService.totalPrice;
this.totalDeliveryPrice = this.cartService.totalDeliveryPrice;
this.totalWithDelivery = this.cartService.totalWithDelivery;
this.hasDeliveryPrice = this.cartService.hasDeliveryPrice;
} }
requestLogin(): void { requestLogin(): void {
@@ -195,7 +201,7 @@ export class CartComponent implements OnDestroy {
createPayment(): void { createPayment(): void {
const orderId = this.generateOrderId(); const orderId = this.generateOrderId();
const paymentPayload = { const paymentPayload = {
amount: Number(this.totalPrice()), amount: Number(this.totalWithDelivery()),
currency: 'RUB' as const, currency: 'RUB' as const,
siteuserID: this.getPaymentUserId(), siteuserID: this.getPaymentUserId(),
siteorderID: orderId, siteorderID: orderId,

View File

@@ -86,6 +86,18 @@ export class ApiService {
return c.startsWith('0x') ? '#' + c.slice(2) : c; return c.startsWith('0x') ? '#' + c.slice(2) : c;
} }
private normalizeOptionalNumber(value: unknown): number | undefined {
if (value === null || value === undefined || value === '') {
return undefined;
}
const normalized = typeof value === 'number'
? value
: Number(String(value).replace(',', '.'));
return Number.isFinite(normalized) ? normalized : undefined;
}
/** Resolve relative image URLs (e.g. ./images/x.webp) against site origin */ /** Resolve relative image URLs (e.g. ./images/x.webp) against site origin */
private resolveImageUrl(url: string): string { private resolveImageUrl(url: string): string {
if (!url) return ''; if (!url) return '';
@@ -102,6 +114,13 @@ export class ApiService {
private normalizeItem(raw: any): Item { private normalizeItem(raw: any): Item {
const { partnerID, ...rest } = raw; const { partnerID, ...rest } = raw;
const item: Item = { ...rest }; const item: Item = { ...rest };
const topLevelDeliveryPrice = this.normalizeOptionalNumber(
raw.deliveryPrice ?? raw.delivery_price ?? raw.deliveryprice
);
if (topLevelDeliveryPrice !== undefined) {
item.deliveryPrice = topLevelDeliveryPrice;
}
// Extract price/currency/remaining/colour/size from itemDetails[] // Extract price/currency/remaining/colour/size from itemDetails[]
// Note: Go struct tag is "itemdetails" but actual API may send "itemDetails" // Note: Go struct tag is "itemdetails" but actual API may send "itemDetails"
@@ -112,11 +131,23 @@ export class ApiService {
...d, ...d,
colour: this.normalizeColor(d.colour || d.color || ''), colour: this.normalizeColor(d.colour || d.color || ''),
color: undefined, color: undefined,
deliveryPrice: this.normalizeOptionalNumber(
d.deliveryPrice ?? d.delivery_price ?? d.deliveryprice
),
})); }));
if (item.price == null || item.price === 0) item.price = detail.price; if (item.price == null || item.price === 0) item.price = detail.price;
if (!item.currency) item.currency = detail.currency; if (!item.currency) item.currency = detail.currency;
if (!item.colour) item.colour = this.normalizeColor(detail.colour || detail.color || ''); if (!item.colour) item.colour = this.normalizeColor(detail.colour || detail.color || '');
if (!item.size) item.size = detail.size || ''; if (!item.size) item.size = detail.size || '';
if (item.deliveryPrice == null) {
const detailDeliveryPrice = this.normalizeOptionalNumber(
detail.deliveryPrice ?? detail.delivery_price ?? detail.deliveryprice
);
if (detailDeliveryPrice !== undefined) {
item.deliveryPrice = detailDeliveryPrice;
}
}
// Use remaining from detail for stock level // Use remaining from detail for stock level
if (raw.remaining == null && detail.remaining != null) { if (raw.remaining == null && detail.remaining != null) {
(raw as any).remaining = detail.remaining; (raw as any).remaining = detail.remaining;

View File

@@ -28,6 +28,17 @@ export class CartService {
return total + (getDiscountedPrice(item) * item.quantity); return total + (getDiscountedPrice(item) * item.quantity);
}, 0); }, 0);
}); });
totalDeliveryPrice = computed(() => {
const items = this.cartItems();
if (!Array.isArray(items)) return 0;
return items.reduce((total, item) => total + ((item.deliveryPrice ?? 0) * item.quantity), 0);
});
totalWithDelivery = computed(() => this.totalPrice() + this.totalDeliveryPrice());
hasDeliveryPrice = computed(() => {
const items = this.cartItems();
if (!Array.isArray(items)) return false;
return items.some(item => item.deliveryPrice !== undefined && item.deliveryPrice !== null);
});
constructor(private apiService: ApiService) { constructor(private apiService: ApiService) {
this.loadCart(); this.loadCart();
@@ -41,6 +52,29 @@ export class CartService {
}); });
} }
private normalizeOptionalNumber(value: unknown): number | undefined {
if (value === null || value === undefined || value === '') {
return undefined;
}
const normalized = typeof value === 'number'
? value
: Number(String(value).replace(',', '.'));
return Number.isFinite(normalized) ? normalized : undefined;
}
private normalizeCartItem(item: CartItem): CartItem {
const { deliveryPrice, ...rest } = item;
const normalizedDeliveryPrice = this.normalizeOptionalNumber(deliveryPrice);
return {
...rest,
quantity: item.quantity || 1,
...(normalizedDeliveryPrice !== undefined ? { deliveryPrice: normalizedDeliveryPrice } : {}),
};
}
private saveToStorage(items: CartItem[]): void { private saveToStorage(items: CartItem[]): void {
const data = JSON.stringify(items); const data = JSON.stringify(items);
@@ -90,10 +124,7 @@ export class CartService {
try { try {
const items = JSON.parse(json); const items = JSON.parse(json);
if (Array.isArray(items)) { if (Array.isArray(items)) {
this.cartItems.set(items.map(item => ({ this.cartItems.set(items.map(item => this.normalizeCartItem(item)));
...item,
quantity: item.quantity || 1
})));
return true; return true;
} }
} catch (err) { } catch (err) {
@@ -118,14 +149,14 @@ export class CartService {
this.addingItems.add(itemID); this.addingItems.add(itemID);
this.apiService.getItem(itemID).subscribe({ this.apiService.getItem(itemID).subscribe({
next: (item) => { next: (item) => {
const cartItem: CartItem = { const cartItem = this.normalizeCartItem({
...item, ...item,
quantity, quantity,
...(variant?.colour != null && { colour: variant.colour }), ...(variant?.colour != null && { colour: variant.colour }),
...(variant?.size != null && { size: variant.size }), ...(variant?.size != null && { size: variant.size }),
...(variant?.price != null && { price: variant.price }), ...(variant?.price != null && { price: variant.price }),
...(variant?.currency != null && { currency: variant.currency }), ...(variant?.currency != null && { currency: variant.currency }),
}; });
this.cartItems.set([...this.cartItems(), cartItem]); this.cartItems.set([...this.cartItems(), cartItem]);
this.addingItems.delete(itemID); this.addingItems.delete(itemID);
}, },