Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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("'", ''')
|
.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 () {
|
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user