Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-19 22:48:44 +03:00
parent 1054a15547
commit 1fedae041b
2 changed files with 259 additions and 4 deletions

View File

@@ -1446,7 +1446,11 @@ const filteredRows = computed(() => {
return list return list
}) })
const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0)) const tableMinWidth = computed(() => visibleColumns.value.reduce((sum, c) => sum + extractWidth(c.style), 0))
const tableScrollWidth = computed(() => tableMinWidth.value + stickyScrollComp.value + 48) const measuredTableScrollWidth = ref(0)
const tableScrollWidth = computed(() => Math.max(
tableMinWidth.value + stickyScrollComp.value + 48,
measuredTableScrollWidth.value
))
const tableStyle = computed(() => ({ const tableStyle = computed(() => ({
width: `${tableMinWidth.value}px`, width: `${tableMinWidth.value}px`,
minWidth: `${tableMinWidth.value}px`, minWidth: `${tableMinWidth.value}px`,
@@ -1610,8 +1614,22 @@ function getTableMiddleEl () {
return mainTableRef.value?.$el?.querySelector?.('.q-table__middle') || null return mainTableRef.value?.$el?.querySelector?.('.q-table__middle') || null
} }
function updateTableScrollWidth () {
const middle = getTableMiddleEl()
const table = mainTableRef.value?.$el?.querySelector?.('.q-table')
const measured = Math.max(
Number(middle?.scrollWidth || 0),
Number(table?.scrollWidth || 0),
tableMinWidth.value + stickyScrollComp.value + 48
)
if (measured > 0 && measured !== measuredTableScrollWidth.value) {
measuredTableScrollWidth.value = measured
}
}
function onTopScroll () { function onTopScroll () {
if (syncingScroll) return if (syncingScroll) return
updateTableScrollWidth()
const middle = getTableMiddleEl() const middle = getTableMiddleEl()
const top = topScrollRef.value const top = topScrollRef.value
if (!middle || !top) return if (!middle || !top) return
@@ -1622,10 +1640,12 @@ function onTopScroll () {
function bindTableScrollSync () { function bindTableScrollSync () {
const middle = getTableMiddleEl() const middle = getTableMiddleEl()
updateTableScrollWidth()
if (!middle || middle.__orderPriceListScrollBound) return if (!middle || middle.__orderPriceListScrollBound) return
middle.__orderPriceListScrollBound = true middle.__orderPriceListScrollBound = true
middle.addEventListener('scroll', () => { middle.addEventListener('scroll', () => {
if (syncingScroll) return if (syncingScroll) return
updateTableScrollWidth()
const top = topScrollRef.value const top = topScrollRef.value
if (!top) return if (!top) return
syncingScroll = true syncingScroll = true
@@ -1649,9 +1669,10 @@ watch(selectedProductCodes, (list) => {
} }
}) })
watch([tableMinWidth, rows], async () => { watch([tableMinWidth, rows, leftDetailsExpanded, selectedPriceOptions], async () => {
await nextTick() await nextTick()
bindTableScrollSync() bindTableScrollSync()
updateTableScrollWidth()
}) })
watch(allowedPriceOptions, () => { watch(allowedPriceOptions, () => {
@@ -1663,7 +1684,10 @@ onMounted(() => {
void fetchServerFilterOptions('urunIlkGrubu', '') void fetchServerFilterOptions('urunIlkGrubu', '')
void fetchServerFilterOptions('urunAnaGrubu', '') void fetchServerFilterOptions('urunAnaGrubu', '')
void fetchServerFilterOptions('productCode', '') void fetchServerFilterOptions('productCode', '')
void nextTick(bindTableScrollSync) void nextTick(() => {
bindTableScrollSync()
updateTableScrollWidth()
})
}) })
</script> </script>

View File

@@ -834,6 +834,95 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog v-model="priceDeviationDialogOpen" persistent>
<q-card class="pcd-price-deviation-dialog">
<q-card-section class="row items-center justify-between q-pb-sm">
<div>
<div class="text-subtitle1 text-weight-bold">Fiyat Kontrolu (Satinalma Ortalama)</div>
<div class="text-caption text-grey-7">
BAGGI_V3 satinalma gecmisindeki son 10 USD ortalamasindan %10'dan fazla sapan tum satirlar.
</div>
</div>
<q-badge color="primary" outline>{{ priceDeviationRows.length }} satir</q-badge>
</q-card-section>
<q-separator />
<q-card-section class="q-pa-sm">
<div class="row items-center q-gutter-sm q-mb-sm">
<q-checkbox
:model-value="priceDeviationAllSelected"
color="primary"
label="Tumunu sec"
@update:model-value="setAllPriceDeviationRowsSelected"
/>
<q-btn
dense
outline
color="primary"
icon="price_change"
label="Secilileri Ortalama ile Degistir"
:disable="!priceDeviationRows.some(r => r.selected)"
@click="applySelectedPriceDeviationAverages"
/>
</div>
<div class="pcd-price-deviation-table-wrap">
<table class="pcd-price-deviation-table">
<thead>
<tr>
<th>Sec</th>
<th>Kod</th>
<th>Ort USD</th>
<th>Girilen USD</th>
<th>Fark %</th>
<th>Yeni Fiyat</th>
<th>Pr.Br.</th>
</tr>
</thead>
<tbody>
<tr v-for="row in priceDeviationRows" :key="row.key">
<td class="text-center">
<q-checkbox v-model="row.selected" dense color="primary" />
</td>
<td class="text-weight-bold text-no-wrap">{{ row.code }}</td>
<td class="text-right">{{ formatMoney(row.avgUSD) }}</td>
<td class="text-right">{{ formatMoney(row.enteredUSD) }}</td>
<td class="text-right text-weight-bold" :class="row.pct >= 0 ? 'text-negative' : 'text-positive'">
{{ formatSignedPercent(row.pct) }}
</td>
<td>
<q-input
v-model="row.newPrice"
dense
filled
input-class="text-right"
inputmode="decimal"
:disable="!row.selected"
/>
</td>
<td>
<q-select
v-model="row.newCurrency"
dense
filled
emit-value
map-options
options-dense
:options="priceCurrencyOptions"
:disable="!row.selected"
/>
</td>
</tr>
</tbody>
</table>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right" class="q-pa-md">
<q-btn flat color="grey-7" label="Geri Don" @click="cancelPriceDeviationDialog" />
<q-btn color="primary" icon="save" label="Onayla ve Kaydet" @click="confirmPriceDeviationDialog" />
</q-card-actions>
</q-card>
</q-dialog>
<!-- FABRIC copy helper: pick another fabric row and copy Code + Price + Pr.Br into editor --> <!-- FABRIC copy helper: pick another fabric row and copy Code + Price + Pr.Br into editor -->
<q-dialog v-model="fabricCopyDialogOpen"> <q-dialog v-model="fabricCopyDialogOpen">
<q-card style="min-width: min(720px, 92vw);"> <q-card style="min-width: min(720px, 92vw);">
@@ -1043,6 +1132,9 @@ const rowEditorDialogOpen = ref(false)
const rowEditorMode = ref('new') const rowEditorMode = ref('new')
const rowEditorTargetRowKey = ref('') const rowEditorTargetRowKey = ref('')
const rowEditorForm = ref(createRowEditorForm()) const rowEditorForm = ref(createRowEditorForm())
const priceDeviationDialogOpen = ref(false)
const priceDeviationRows = ref([])
const priceDeviationResolver = ref(null)
const fabricCopyDialogOpen = ref(false) const fabricCopyDialogOpen = ref(false)
const fabricCopySelectedKey = ref('') const fabricCopySelectedKey = ref('')
@@ -1292,6 +1384,10 @@ const priceCurrencyOptions = [
{ label: 'GBP', value: 'GBP' } { label: 'GBP', value: 'GBP' }
] ]
const flatDetailRows = computed(() => detailGroups.value.flatMap(grp => Array.isArray(grp?.items) ? grp.items : [])) const flatDetailRows = computed(() => detailGroups.value.flatMap(grp => Array.isArray(grp?.items) ? grp.items : []))
const priceDeviationAllSelected = computed(() => {
const list = Array.isArray(priceDeviationRows.value) ? priceDeviationRows.value : []
return list.length > 0 && list.every((row) => row.selected)
})
const showFabricCopyBtn = computed(() => normalizeGroupName(rowEditorForm.value?.sAciklama3 || '') === 'FABRIC') const showFabricCopyBtn = computed(() => normalizeGroupName(rowEditorForm.value?.sAciklama3 || '') === 'FABRIC')
@@ -4363,6 +4459,12 @@ function round4 (n) {
return Math.round(x * 10000) / 10000 return Math.round(x * 10000) / 10000
} }
function formatSignedPercent (value) {
const n = Number(value || 0)
const sign = n >= 0 ? '+' : ''
return `${sign}${round1(n)}%`
}
function escapeHtml (input) { function escapeHtml (input) {
const s = String(input ?? '') const s = String(input ?? '')
return s return s
@@ -4373,6 +4475,75 @@ function escapeHtml (input) {
.replaceAll("'", '&#39;') .replaceAll("'", '&#39;')
} }
function convertUSDToPriceCurrency (usdPrice, currency) {
const usd = Number(usdPrice || 0)
if (!Number.isFinite(usd) || usd <= 0) return 0
const cur = normalizePriceCurrency(currency) || 'USD'
if (cur === 'USD') return usd
const usdRate = resolveExchangeRateValue('USD')
if (!(usdRate > 0)) return usd
const tryPrice = usd * usdRate
if (cur === 'TRY') return tryPrice
const targetRate = resolveExchangeRateValue(cur)
return targetRate > 0 ? (tryPrice / targetRate) : usd
}
function setAllPriceDeviationRowsSelected (value) {
priceDeviationRows.value = priceDeviationRows.value.map(row => ({
...row,
selected: Boolean(value)
}))
}
function applySelectedPriceDeviationAverages () {
priceDeviationRows.value = priceDeviationRows.value.map(row => {
if (!row.selected) return row
const currency = normalizePriceCurrency(row.newCurrency) || 'USD'
const price = convertUSDToPriceCurrency(row.avgUSD, currency)
return {
...row,
newPrice: normalizeInputPrice(round4(price)),
newCurrency: currency
}
})
}
function applyPriceDeviationRowEdits () {
const selectedRows = priceDeviationRows.value.filter(row => row.selected)
for (const item of selectedRows) {
const target = item.row
if (!target) continue
target.inputPrice = normalizeInputPrice(item.newPrice)
target.inputPricePrBr = normalizePriceCurrency(item.newCurrency) || 'USD'
recalculateDetailRow(target, {
preserveInputs: true,
priceType: 'MAN',
updateState: 'manual',
markChanged: true
})
}
if (selectedRows.length > 0) {
schedulePersistLocalDraft()
triggerUIUpdate()
}
}
function closePriceDeviationDialog (result) {
const resolve = priceDeviationResolver.value
priceDeviationResolver.value = null
priceDeviationDialogOpen.value = false
if (typeof resolve === 'function') resolve(Boolean(result))
}
function cancelPriceDeviationDialog () {
closePriceDeviationDialog(false)
}
function confirmPriceDeviationDialog () {
applyPriceDeviationRowEdits()
closePriceDeviationDialog(true)
}
async function confirmDefaultQtyDeviationIfNeeded () { async function confirmDefaultQtyDeviationIfNeeded () {
// Compare entered qty vs default qty (mk_MaliyetParcaEslestirme_vmiktarlar) per hammadde type. // Compare entered qty vs default qty (mk_MaliyetParcaEslestirme_vmiktarlar) per hammadde type.
// Rule: if deviation > 10% (abs), require user confirmation. // Rule: if deviation > 10% (abs), require user confirmation.
@@ -4548,10 +4719,14 @@ async function confirmBrPriceDeviationIfNeeded () {
const pct = ((enteredUSD - avg.avgUSD) / avg.avgUSD) * 100 const pct = ((enteredUSD - avg.avgUSD) / avg.avgUSD) * 100
if (Math.abs(pct) > 10) { if (Math.abs(pct) > 10) {
outliers.push({ outliers.push({
row: c.row,
key: `${String(c.row?.__rowKey || c.code)}|${outliers.length}`,
code: c.code, code: c.code,
avgUSD: avg.avgUSD, avgUSD: avg.avgUSD,
enteredUSD, enteredUSD,
pct pct,
originalPrice: c.price,
originalCurrency: c.cur
}) })
} }
} }
@@ -4559,6 +4734,17 @@ async function confirmBrPriceDeviationIfNeeded () {
if (outliers.length === 0) return true if (outliers.length === 0) return true
outliers.sort((a, b) => Math.abs(b.pct) - Math.abs(a.pct)) outliers.sort((a, b) => Math.abs(b.pct) - Math.abs(a.pct))
priceDeviationRows.value = outliers.map(x => ({
...x,
selected: true,
newPrice: normalizeInputPrice(x.originalPrice),
newCurrency: normalizePriceCurrency(x.originalCurrency) || 'USD'
}))
return await new Promise(resolve => {
priceDeviationResolver.value = resolve
priceDeviationDialogOpen.value = true
})
const maxRows = 30 const maxRows = 30
const rowsHtml = outliers.slice(0, maxRows).map(x => { const rowsHtml = outliers.slice(0, maxRows).map(x => {
const sign = x.pct >= 0 ? '+' : '' const sign = x.pct >= 0 ? '+' : ''
@@ -5224,6 +5410,51 @@ watch(
opacity: 0.76; opacity: 0.76;
} }
.pcd-price-deviation-dialog {
width: min(1120px, 96vw);
max-width: 96vw;
}
.pcd-price-deviation-table-wrap {
max-height: min(62vh, 620px);
overflow: auto;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
}
.pcd-price-deviation-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: 12px;
}
.pcd-price-deviation-table th {
position: sticky;
top: 0;
z-index: 1;
background: #f4f6f8;
color: #263238;
font-weight: 700;
padding: 6px 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
white-space: nowrap;
}
.pcd-price-deviation-table td {
padding: 5px 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
vertical-align: middle;
}
.pcd-price-deviation-table td:nth-child(6) {
min-width: 120px;
}
.pcd-price-deviation-table td:nth-child(7) {
min-width: 96px;
}
.pcd-row-editor-flag { .pcd-row-editor-flag {
background: color-mix(in srgb, var(--q-secondary) 10%, white); background: color-mix(in srgb, var(--q-secondary) 10%, white);
border: 1px solid color-mix(in srgb, var(--q-secondary) 35%, white); border: 1px solid color-mix(in srgb, var(--q-secondary) 35%, white);