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) => `| ${escapeHtml(c.label)} | `).join('')}
${body}
`
+ const html = `${cols.map((c) => `| ${escapeHtml(c.label)} | `).join('')}
${body}
`
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