Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -501,7 +501,11 @@
|
||||
:class="[props.col.classes, { 'sticky-col': isStickyCol(props.col.name), 'sticky-boundary': isStickyBoundary(props.col.name) }]"
|
||||
:style="getBodyCellStyle(props.col)"
|
||||
>
|
||||
<q-badge v-if="props.row.campaignLabel" color="primary" outline :label="props.row.campaignLabel" class="campaign-badge" />
|
||||
<div class="campaign-cell-content">
|
||||
<span v-if="props.row.campaignLabel" class="campaign-text" :title="props.row.campaignLabel">
|
||||
{{ props.row.campaignLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
@@ -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) : '' }}
|
||||
<span class="cell-text campaign-rate-text" :title="String(props.row.campaignRate ?? '')">
|
||||
{{ props.row.campaignRate ? formatPrice(props.row.campaignRate) : '' }}
|
||||
</span>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
@@ -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]) }}
|
||||
<span :class="['cell-text', 'price-cell-text', { 'campaign-price-text': name.endsWith('Campaign') }]" :title="formatPrice(props.row[name])">
|
||||
{{ formatPrice(props.row[name]) }}
|
||||
</span>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
@@ -580,6 +588,32 @@
|
||||
<div class="field-row"><span class="k">Karisim</span><span class="v">{{ productCardData.karisim || '-' }}</span></div>
|
||||
<div class="field-row"><span class="k">Kampanya</span><span class="v">{{ productCardData.campaignLabel || '-' }}</span></div>
|
||||
<div class="field-row"><span class="k">Stok</span><span class="v">{{ formatStock(productCardData.stockQty || 0) }}</span></div>
|
||||
|
||||
<div class="product-card-section">
|
||||
<div class="product-card-section-title">Fiyat Bilgileri</div>
|
||||
<div v-if="productCardPriceRows.length" class="price-info-grid">
|
||||
<div v-for="item in productCardPriceRows" :key="item.key" class="price-info-row">
|
||||
<span class="price-label">{{ item.label }}</span>
|
||||
<span class="price-value">{{ item.price || '-' }}</span>
|
||||
<span class="price-campaign">{{ item.campaignPrice || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="product-card-empty-text">Secili fiyat kolonu yok.</div>
|
||||
</div>
|
||||
|
||||
<div class="product-card-section">
|
||||
<div class="product-card-section-title">Beden Stoklari</div>
|
||||
<q-inner-loading :showing="productCardStockLoading">
|
||||
<q-spinner size="24px" color="primary" />
|
||||
</q-inner-loading>
|
||||
<div v-if="productCardSizeRows.length" class="size-stock-grid">
|
||||
<div v-for="item in productCardSizeRows" :key="item.size" class="size-stock-cell">
|
||||
<span class="size-label">{{ item.size }}</span>
|
||||
<span class="size-qty">{{ formatStock(item.qty) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!productCardStockLoading" class="product-card-empty-text">Beden stogu bulunamadi.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
@@ -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 '<td></td>'
|
||||
const display = n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
const raw = n.toFixed(2)
|
||||
return `<td style="mso-number-format:'0.00';text-align:right;" x:num="${raw}">${escapeHtml(display)}</td>`
|
||||
}
|
||||
|
||||
function exportExcelCellHtml (row, col) {
|
||||
if (priceColumnNames.includes(col.name)) return excelNumericCell(row[col.field])
|
||||
if (col.name === 'campaignRate') return excelNumericCell(row.campaignRate)
|
||||
return `<td>${escapeHtml(exportCell(row, col))}</td>`
|
||||
}
|
||||
|
||||
function exportVisibleExcel () {
|
||||
const cols = visibleColumns.value.filter((c) => c.name !== 'image')
|
||||
const body = filteredRows.value.map((row) => `<tr>${cols.map((c) => {
|
||||
return `<td>${escapeHtml(exportCell(row, c))}</td>`
|
||||
return exportExcelCellHtml(row, c)
|
||||
}).join('')}</tr>`).join('')
|
||||
const html = `<!doctype html><html><head><meta charset="utf-8"></head><body><table border="1"><thead><tr>${cols.map((c) => `<th>${escapeHtml(c.label)}</th>`).join('')}</tr></thead><tbody>${body}</tbody></table></body></html>`
|
||||
const html = `<!doctype html><html xmlns:x="urn:schemas-microsoft-com:office:excel"><head><meta charset="utf-8"></head><body><table border="1"><thead><tr>${cols.map((c) => `<th>${escapeHtml(c.label)}</th>`).join('')}</tr></thead><tbody>${body}</tbody></table></body></html>`
|
||||
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) => `<tr>${cols.map((c) => {
|
||||
if (c.name === 'image' && row.imageUrl) return `<td><img src="${row.imageUrl}" class="thumb"></td>`
|
||||
return `<td class="${priceColumnNames.includes(c.name) ? 'num' : ''}">${escapeHtml(exportCell(row, c))}</td>`
|
||||
return `<td class="${isExcelNumericColumn(c) ? 'num' : ''}">${escapeHtml(exportCell(row, c))}</td>`
|
||||
}).join('')}</tr>`).join('')
|
||||
const html = `<!doctype html><html><head><meta charset="utf-8"><title>Fiyat Listesi</title><style>
|
||||
@page { size: A3 landscape; margin: 8mm; }
|
||||
@@ -1754,13 +1883,46 @@ onMounted(() => {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.pricing-table :deep(td.center-col) {
|
||||
text-align: center !important;
|
||||
.pricing-table :deep(th.usd-col),
|
||||
.pricing-table :deep(td.usd-col) {
|
||||
background: #ecf9f0;
|
||||
color: #178a3e;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pricing-table :deep(td.center-col .q-badge) {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
.pricing-table :deep(th.eur-col),
|
||||
.pricing-table :deep(td.eur-col) {
|
||||
background: #fdeeee;
|
||||
color: #c62828;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.try-col),
|
||||
.pricing-table :deep(td.try-col) {
|
||||
background: #edf4ff;
|
||||
color: #1e63c6;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.usd-col),
|
||||
.pricing-table :deep(th.eur-col),
|
||||
.pricing-table :deep(th.try-col),
|
||||
.pricing-table :deep(td.usd-col),
|
||||
.pricing-table :deep(td.eur-col),
|
||||
.pricing-table :deep(td.try-col) {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.pricing-table :deep(td.campaign-price-col),
|
||||
.pricing-table :deep(th.campaign-price-col) {
|
||||
background: #fff3f1;
|
||||
color: #c62828;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.pricing-table :deep(td.center-col) {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.pricing-table :deep(td.karisim-wrap-col) {
|
||||
@@ -1784,10 +1946,6 @@ onMounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.order-price-list-table :deep(.campaign-price-col) {
|
||||
background: #f6fbf7;
|
||||
}
|
||||
|
||||
.header-with-filter {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 20px;
|
||||
@@ -1813,10 +1971,55 @@ onMounted(() => {
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.campaign-badge {
|
||||
.cell-text {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.1;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.price-cell-text {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
font-weight: 800;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.campaign-price-text {
|
||||
color: #c62828;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.campaign-cell-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.campaign-text {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
color: #c62828;
|
||||
font-weight: 900;
|
||||
line-height: 1.12;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.campaign-rate-text {
|
||||
width: 100%;
|
||||
color: #c62828;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.header-filter-btn {
|
||||
@@ -2008,6 +2211,94 @@ onMounted(() => {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.product-card-section {
|
||||
position: relative;
|
||||
margin-top: 12px;
|
||||
border: 1px solid #e6dccb;
|
||||
border-radius: 8px;
|
||||
background: #fffdf8;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.product-card-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
color: #5e4a22;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.price-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.price-info-row {
|
||||
display: grid;
|
||||
grid-template-columns: 70px 1fr 1fr;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid #f0e5d2;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-weight: 800;
|
||||
color: #6b5a33;
|
||||
}
|
||||
|
||||
.price-value,
|
||||
.price-campaign {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.price-campaign {
|
||||
color: #b13a2b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.size-stock-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(62px, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.size-stock-cell {
|
||||
min-height: 42px;
|
||||
border: 1px solid #eadfca;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.size-label {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
color: #6b5a33;
|
||||
}
|
||||
|
||||
.size-qty {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
color: #1f1f1f;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.product-card-empty-text {
|
||||
color: #7a6d55;
|
||||
font-size: 12px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.image-fullscreen-dialog {
|
||||
background: #f4f0e2;
|
||||
}
|
||||
|
||||
@@ -1825,10 +1825,20 @@ function getOriginalCellValue (row, field) {
|
||||
return row?.[`__orig_${field}`] ?? row?.[field] ?? 0
|
||||
}
|
||||
|
||||
function exportDecimalValue (value) {
|
||||
const n = parseNumber(value)
|
||||
if (!Number.isFinite(n)) return ''
|
||||
return n.toFixed(2).replace('.', ',')
|
||||
}
|
||||
|
||||
function isExportDecimalField (field) {
|
||||
return editableColumnSet.has(field) || field === 'costPrice' || field === 'basePriceUsd' || field === 'basePriceTry'
|
||||
}
|
||||
|
||||
function exportCellValue (row, field) {
|
||||
if (field === 'stockQty') return formatStock(row?.[field])
|
||||
if (field === 'stockEntryDate' || field === 'lastCostingDate' || field === 'lastPricingDate') return formatDateDisplay(row?.[field])
|
||||
if (editableColumnSet.has(field)) return String(round2(row?.[field] || 0))
|
||||
if (isExportDecimalField(field)) return exportDecimalValue(row?.[field])
|
||||
return String(row?.[field] ?? '').trim()
|
||||
}
|
||||
|
||||
|
||||
@@ -1926,10 +1926,27 @@ function getOriginalCellValue (row, field) {
|
||||
return row?.[`__orig_${field}`] ?? row?.[field] ?? 0
|
||||
}
|
||||
|
||||
function exportDecimalValue (value) {
|
||||
const n = parseNumber(value)
|
||||
if (!Number.isFinite(n)) return ''
|
||||
return n.toFixed(2).replace('.', ',')
|
||||
}
|
||||
|
||||
function isExportDecimalField (field) {
|
||||
return field === 'campaignRate' ||
|
||||
field === 'belowBaseDiff' ||
|
||||
field === 'costPrice' ||
|
||||
field === 'basePriceUsd' ||
|
||||
field === 'basePriceTry' ||
|
||||
/^usd\d$/i.test(field) ||
|
||||
/^eur\d$/i.test(field) ||
|
||||
/^try\d$/i.test(field)
|
||||
}
|
||||
|
||||
function exportCellValue (row, field) {
|
||||
if (field === 'stockQty') return formatStock(row?.[field])
|
||||
if (field === 'stockEntryDate' || field === 'lastCampaignDate') return formatDateDisplay(row?.[field])
|
||||
if (editableColumnSet.has(field)) return String(round2(row?.[field] || 0))
|
||||
if (isExportDecimalField(field)) return exportDecimalValue(row?.[field])
|
||||
return String(row?.[field] ?? '').trim()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user