Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1446,7 +1446,11 @@ const filteredRows = computed(() => {
|
||||
return list
|
||||
})
|
||||
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(() => ({
|
||||
width: `${tableMinWidth.value}px`,
|
||||
minWidth: `${tableMinWidth.value}px`,
|
||||
@@ -1610,8 +1614,22 @@ function getTableMiddleEl () {
|
||||
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 () {
|
||||
if (syncingScroll) return
|
||||
updateTableScrollWidth()
|
||||
const middle = getTableMiddleEl()
|
||||
const top = topScrollRef.value
|
||||
if (!middle || !top) return
|
||||
@@ -1622,10 +1640,12 @@ function onTopScroll () {
|
||||
|
||||
function bindTableScrollSync () {
|
||||
const middle = getTableMiddleEl()
|
||||
updateTableScrollWidth()
|
||||
if (!middle || middle.__orderPriceListScrollBound) return
|
||||
middle.__orderPriceListScrollBound = true
|
||||
middle.addEventListener('scroll', () => {
|
||||
if (syncingScroll) return
|
||||
updateTableScrollWidth()
|
||||
const top = topScrollRef.value
|
||||
if (!top) return
|
||||
syncingScroll = true
|
||||
@@ -1649,9 +1669,10 @@ watch(selectedProductCodes, (list) => {
|
||||
}
|
||||
})
|
||||
|
||||
watch([tableMinWidth, rows], async () => {
|
||||
watch([tableMinWidth, rows, leftDetailsExpanded, selectedPriceOptions], async () => {
|
||||
await nextTick()
|
||||
bindTableScrollSync()
|
||||
updateTableScrollWidth()
|
||||
})
|
||||
|
||||
watch(allowedPriceOptions, () => {
|
||||
@@ -1663,7 +1684,10 @@ onMounted(() => {
|
||||
void fetchServerFilterOptions('urunIlkGrubu', '')
|
||||
void fetchServerFilterOptions('urunAnaGrubu', '')
|
||||
void fetchServerFilterOptions('productCode', '')
|
||||
void nextTick(bindTableScrollSync)
|
||||
void nextTick(() => {
|
||||
bindTableScrollSync()
|
||||
updateTableScrollWidth()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -834,6 +834,95 @@
|
||||
</q-card>
|
||||
</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 -->
|
||||
<q-dialog v-model="fabricCopyDialogOpen">
|
||||
<q-card style="min-width: min(720px, 92vw);">
|
||||
@@ -1043,6 +1132,9 @@ const rowEditorDialogOpen = ref(false)
|
||||
const rowEditorMode = ref('new')
|
||||
const rowEditorTargetRowKey = ref('')
|
||||
const rowEditorForm = ref(createRowEditorForm())
|
||||
const priceDeviationDialogOpen = ref(false)
|
||||
const priceDeviationRows = ref([])
|
||||
const priceDeviationResolver = ref(null)
|
||||
const fabricCopyDialogOpen = ref(false)
|
||||
const fabricCopySelectedKey = ref('')
|
||||
|
||||
@@ -1292,6 +1384,10 @@ const priceCurrencyOptions = [
|
||||
{ label: 'GBP', value: 'GBP' }
|
||||
]
|
||||
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')
|
||||
|
||||
@@ -4363,6 +4459,12 @@ function round4 (n) {
|
||||
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) {
|
||||
const s = String(input ?? '')
|
||||
return s
|
||||
@@ -4373,6 +4475,75 @@ function escapeHtml (input) {
|
||||
.replaceAll("'", ''')
|
||||
}
|
||||
|
||||
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 () {
|
||||
// Compare entered qty vs default qty (mk_MaliyetParcaEslestirme_vmiktarlar) per hammadde type.
|
||||
// Rule: if deviation > 10% (abs), require user confirmation.
|
||||
@@ -4548,10 +4719,14 @@ async function confirmBrPriceDeviationIfNeeded () {
|
||||
const pct = ((enteredUSD - avg.avgUSD) / avg.avgUSD) * 100
|
||||
if (Math.abs(pct) > 10) {
|
||||
outliers.push({
|
||||
row: c.row,
|
||||
key: `${String(c.row?.__rowKey || c.code)}|${outliers.length}`,
|
||||
code: c.code,
|
||||
avgUSD: avg.avgUSD,
|
||||
enteredUSD,
|
||||
pct
|
||||
pct,
|
||||
originalPrice: c.price,
|
||||
originalCurrency: c.cur
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4559,6 +4734,17 @@ async function confirmBrPriceDeviationIfNeeded () {
|
||||
if (outliers.length === 0) return true
|
||||
|
||||
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 rowsHtml = outliers.slice(0, maxRows).map(x => {
|
||||
const sign = x.pct >= 0 ? '+' : ''
|
||||
@@ -5224,6 +5410,51 @@ watch(
|
||||
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 {
|
||||
background: color-mix(in srgb, var(--q-secondary) 10%, white);
|
||||
border: 1px solid color-mix(in srgb, var(--q-secondary) 35%, white);
|
||||
|
||||
Reference in New Issue
Block a user