diff --git a/svc/routes/product_pricing.go b/svc/routes/product_pricing.go index 0061b90..3078545 100644 --- a/svc/routes/product_pricing.go +++ b/svc/routes/product_pricing.go @@ -545,7 +545,7 @@ func csvEscape(value string) string { } func csvFloat(value float64) string { - return fmt.Sprintf("%.2f", value) + return strings.ReplaceAll(fmt.Sprintf("%.2f", value), ".", ",") } func exportPriceFieldTitle(field string) string { diff --git a/ui/src/pages/OrderPriceList.vue b/ui/src/pages/OrderPriceList.vue index 70c291e..84caea9 100644 --- a/ui/src/pages/OrderPriceList.vue +++ b/ui/src/pages/OrderPriceList.vue @@ -501,7 +501,11 @@ :class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]" :style="getBodyCellStyle(props.col)" > - +
+ + {{ props.row.campaignLabel }} + +
@@ -511,7 +515,9 @@ :class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]" :style="getBodyCellStyle(props.col)" > - {{ props.row.campaignRate ? formatPrice(props.row.campaignRate) : '' }} + + {{ props.row.campaignRate ? formatPrice(props.row.campaignRate) : '' }} + @@ -521,7 +527,9 @@ :class="['text-right', props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]" :style="getBodyCellStyle(props.col)" > - {{ formatPrice(props.row[name]) }} + + {{ formatPrice(props.row[name]) }} + @@ -580,6 +588,32 @@
Karisim{{ productCardData.karisim || '-' }}
Kampanya{{ productCardData.campaignLabel || '-' }}
Stok{{ formatStock(productCardData.stockQty || 0) }}
+ +
+
Fiyat Bilgileri
+
+
+ {{ item.label }} + {{ item.price || '-' }} + {{ item.campaignPrice || '-' }} +
+
+
Secili fiyat kolonu yok.
+
+ +
+
Beden Stoklari
+ + + +
+
+ {{ item.size }} + {{ formatStock(item.qty) }} +
+
+
Beden stogu bulunamadi.
+
@@ -673,11 +707,24 @@ const productCardDialog = ref(false) const productCardData = ref({}) const productCardImages = ref([]) const productCardSlide = ref(0) +const productCardStockLoading = ref(false) +const productCardSizeRows = ref([]) const productImageFullscreenDialog = ref(false) const productImageFullscreenSlide = ref(0) const fullscreenImages = computed(() => productCardImages.value || []) const selectedPriceSet = computed(() => new Set(selectedPriceOptions.value || [])) +const productCardPriceRows = computed(() => { + const row = productCardData.value || {} + return priceOptions + .filter((option) => selectedPriceSet.value.has(option.value)) + .map((option) => ({ + key: option.value, + label: option.label, + price: formatPrice(row?.[option.value]), + campaignPrice: formatPrice(row?.[`${option.value}Campaign`]) + })) +}) const selectedProductCodeSet = computed(() => new Set(selectedProductCodes.value || [])) const selectedCampaignLabelSet = computed(() => new Set(selectedCampaignLabels.value || [])) const selectedVariantCodeSet = computed(() => new Set(selectedVariantCodes.value || [])) @@ -745,6 +792,59 @@ function formatStock (value) { return n.toLocaleString('tr-TR', { maximumFractionDigits: 2 }) } +function parseStockNumber (value) { + if (typeof value === 'number') return Number.isFinite(value) ? value : 0 + const text = String(value ?? '').trim() + if (!text) return 0 + const normalized = text.replace(/\./g, '').replace(',', '.') + const n = Number.parseFloat(normalized) + return Number.isFinite(n) ? n : 0 +} + +function normalizeCardToken (value) { + return String(value ?? '').trim().toUpperCase() +} + +function parseVariantTokens (variantCode) { + const parts = String(variantCode || '').split('-').map((x) => normalizeCardToken(x)).filter(Boolean) + return { + color: parts[0] || '', + dim2: parts.length > 1 ? parts.slice(1).join('-') : '' + } +} + +function stockRowText (row, ...keys) { + for (const key of keys) { + const value = String(row?.[key] ?? '').trim() + if (value) return value + } + return '' +} + +function matchesProductCardVariant (stockRow, cardRow) { + const tokens = parseVariantTokens(cardRow?.variantCodes) + if (!tokens.color && !tokens.dim2) return true + const color = normalizeCardToken(stockRowText(stockRow, 'Renk_Kodu', 'ColorCode', 'colorCode')) + const dim2 = normalizeCardToken(stockRowText(stockRow, 'Yaka', 'ItemDim2Code', 'itemDim2Code', 'Renk2')) + if (tokens.color && color !== tokens.color) return false + if (tokens.dim2 && dim2 !== tokens.dim2) return false + return true +} + +function buildSizeStockRows (stockRows, cardRow) { + const totals = new Map() + for (const item of stockRows || []) { + if (!matchesProductCardVariant(item, cardRow)) continue + const size = stockRowText(item, 'Beden', 'Size', 'ItemDim1Code', 'itemDim1Code') + if (!size) continue + const qty = parseStockNumber(item?.Kullanilabilir_Envanter ?? item?.StockQty ?? item?.qty) + totals.set(size, (totals.get(size) || 0) + qty) + } + return Array.from(totals.entries()) + .map(([size, qty]) => ({ size, qty })) + .sort((a, b) => variantCodeCollator.compare(a.size, b.size)) +} + function mapProductRow (raw, index) { const row = { id: index + 1, @@ -1151,15 +1251,24 @@ async function fetchImageListForRow (row) { async function openProductCard (row) { if (!row) return productCardData.value = { ...row } + productCardSizeRows.value = [] productCardDialog.value = true productCardSlide.value = 0 + productCardStockLoading.value = true try { - const list = await fetchImageListForRow(row) + const [list, stockRes] = await Promise.all([ + fetchImageListForRow(row), + api.get('/product-stock-query', { params: { code: row.productCode }, timeout: 30000 }) + ]) const images = list.map(resolveProductImageUrl).filter(Boolean) if (row.imageUrl && !images.includes(row.imageUrl)) images.unshift(row.imageUrl) productCardImages.value = Array.from(new Set(images)) + productCardSizeRows.value = buildSizeStockRows(Array.isArray(stockRes?.data) ? stockRes.data : [], row) } catch { productCardImages.value = row.imageUrl ? [row.imageUrl] : [] + productCardSizeRows.value = [] + } finally { + productCardStockLoading.value = false } } @@ -1173,6 +1282,8 @@ function openProductImageFullscreen (src) { function onProductCardDialogHide () { productImageFullscreenDialog.value = false + productCardStockLoading.value = false + productCardSizeRows.value = [] } function resetSelections () { @@ -1373,12 +1484,30 @@ function exportCell (row, col) { return toText(row[col.field]) } +function isExcelNumericColumn (col) { + return priceColumnNames.includes(col.name) || col.name === 'campaignRate' +} + +function excelNumericCell (value) { + const n = Number(value) + if (!Number.isFinite(n) || n === 0) return '' + const display = n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + const raw = n.toFixed(2) + return `${escapeHtml(display)}` +} + +function exportExcelCellHtml (row, col) { + if (priceColumnNames.includes(col.name)) return excelNumericCell(row[col.field]) + if (col.name === 'campaignRate') return excelNumericCell(row.campaignRate) + return `${escapeHtml(exportCell(row, col))}` +} + function exportVisibleExcel () { const cols = visibleColumns.value.filter((c) => c.name !== 'image') const body = filteredRows.value.map((row) => `${cols.map((c) => { - return `${escapeHtml(exportCell(row, c))}` + return exportExcelCellHtml(row, c) }).join('')}`).join('') - const html = `${cols.map((c) => ``).join('')}${body}
${escapeHtml(c.label)}
` + const html = `${cols.map((c) => ``).join('')}${body}
${escapeHtml(c.label)}
` const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') @@ -1395,7 +1524,7 @@ function printVisibleRows () { const cols = visibleColumns.value const body = filteredRows.value.map((row) => `${cols.map((c) => { if (c.name === 'image' && row.imageUrl) return `` - return `${escapeHtml(exportCell(row, c))}` + return `${escapeHtml(exportCell(row, c))}` }).join('')}`).join('') const html = `Fiyat Listesi