This commit is contained in:
sdarbinyan
2026-06-21 23:13:01 +04:00
parent 1b2a5af2be
commit 3b802b7c7b
13 changed files with 510 additions and 33 deletions

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, timer } from 'rxjs';
import { map, retry } from 'rxjs/operators';
import { Category, Item, Subcategory } from '../models';
import { Category, DeliveryOption, Item, Subcategory } from '../models';
import { environment } from '../../environments/environment';
export interface QrCreateRequest {
@@ -41,7 +41,7 @@ export interface CartPaymentRequest {
siteorderID: string;
redirectUrl: string;
telegramUsername: string;
items: Array<{ itemID: number; price: number; name: string }>;
items: Array<{ itemID: number; price: number; name: string; quantity?: number; delivery?: DeliveryOption }>;
}
export interface QrDynamicStatusResponse {
@@ -98,6 +98,89 @@ export class ApiService {
return Number.isFinite(normalized) ? normalized : undefined;
}
private normalizeOptionalString(value: unknown): string {
if (typeof value === 'string') {
return value.trim();
}
if (typeof value === 'number' || typeof value === 'bigint') {
return String(value);
}
return '';
}
private asRecord(value: unknown): Record<string, unknown> | null {
return value !== null && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
: null;
}
private normalizeDeliveryOption(value: unknown): DeliveryOption | null {
const source = this.asRecord(value);
if (!source) {
return null;
}
const deliveryPlace = this.normalizeOptionalString(
source['deliveryPlace'] ?? source['delivery_place'] ?? source['deliveryplace']
);
const deliveryTime = this.normalizeOptionalString(
source['deliveryTime'] ?? source['delivery_time'] ?? source['deliverytime']
);
const deliveryPrice = this.normalizeOptionalNumber(
source['deliveryPrice'] ?? source['delivery_price'] ?? source['deliveryprice']
);
if (deliveryPrice === undefined && !deliveryPlace && !deliveryTime) {
return null;
}
return {
deliveryPrice: deliveryPrice ?? 0,
deliveryPlace,
deliveryTime,
};
}
private normalizeDeliveryData(
raw: any,
legacyDeliveryPrice?: number
): { options: DeliveryOption[]; isDigital: boolean; requiresSelection: boolean } {
const rawDelivery = raw.delivery ?? raw.deliveries;
if (typeof rawDelivery === 'string' && rawDelivery.trim().toLowerCase() === 'digital') {
return { options: [], isDigital: true, requiresSelection: false };
}
const deliveryCandidates = Array.isArray(rawDelivery)
? rawDelivery
: rawDelivery != null
? [rawDelivery]
: [];
const options = deliveryCandidates
.map(candidate => this.normalizeDeliveryOption(candidate))
.filter((option): option is DeliveryOption => option !== null);
if (options.length > 0) {
return { options, isDigital: false, requiresSelection: rawDelivery != null };
}
if (legacyDeliveryPrice !== undefined) {
return {
options: [{ deliveryPrice: legacyDeliveryPrice, deliveryPlace: '', deliveryTime: '' }],
isDigital: false,
requiresSelection: false,
};
}
if (rawDelivery != null) {
return { options: [], isDigital: true, requiresSelection: false };
}
return { options: [], isDigital: false, requiresSelection: false };
}
/** Resolve relative image URLs (e.g. ./images/x.webp) against site origin */
private resolveImageUrl(url: string): string {
if (!url) return '';
@@ -114,14 +197,10 @@ export class ApiService {
private normalizeItem(raw: any): Item {
const { partnerID, ...rest } = raw;
const item: Item = { ...rest };
const topLevelDeliveryPrice = this.normalizeOptionalNumber(
let legacyDeliveryPrice = this.normalizeOptionalNumber(
raw.deliveryPrice ?? raw.delivery_price ?? raw.deliveryprice
);
if (topLevelDeliveryPrice !== undefined) {
item.deliveryPrice = topLevelDeliveryPrice;
}
// Extract price/currency/remaining/colour/size from itemDetails[]
// Note: Go struct tag is "itemdetails" but actual API may send "itemDetails"
const details = raw.itemDetails || raw.itemdetails;
@@ -139,20 +218,25 @@ export class ApiService {
if (!item.currency) item.currency = detail.currency;
if (!item.colour) item.colour = this.normalizeColor(detail.colour || detail.color || '');
if (!item.size) item.size = detail.size || '';
if (item.deliveryPrice == null) {
const detailDeliveryPrice = this.normalizeOptionalNumber(
if (legacyDeliveryPrice === undefined) {
legacyDeliveryPrice = this.normalizeOptionalNumber(
detail.deliveryPrice ?? detail.delivery_price ?? detail.deliveryprice
);
if (detailDeliveryPrice !== undefined) {
item.deliveryPrice = detailDeliveryPrice;
}
}
// Use remaining from detail for stock level
if (raw.remaining == null && detail.remaining != null) {
(raw as any).remaining = detail.remaining;
}
}
const deliveryData = this.normalizeDeliveryData(raw, legacyDeliveryPrice);
if (deliveryData.options.length > 0) {
item.deliveryOptions = deliveryData.options;
item.deliveryMode = 'selectable';
item.deliverySelectionRequired = deliveryData.requiresSelection;
} else if (deliveryData.isDigital) {
item.deliveryMode = 'digital';
item.deliverySelectionRequired = false;
}
// Map backOffice string id → legacy numeric itemID
if (raw.id != null && raw.itemID == null) {
@@ -481,8 +565,9 @@ export class ApiService {
submitPurchaseEmail(emailData: {
email: string;
phone?: string;
telegramUserId: string | null;
items: Array<{ itemID: number; name: string; price: number; currency: string }>;
items: Array<{ itemID: number; name: string; price: number; currency: string; quantity?: number; delivery?: DeliveryOption }>;
}): Observable<{ message: string }> {
return this.http.post<{ message: string }>(`${this.baseUrl}/purchase-email`, emailData);
}