diff --git a/ui/src/pages/OrderPriceList.vue b/ui/src/pages/OrderPriceList.vue index aa5d123..7831887 100644 --- a/ui/src/pages/OrderPriceList.vue +++ b/ui/src/pages/OrderPriceList.vue @@ -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() + }) }) diff --git a/ui/src/pages/ProductionProductCostingHasCostDetail.vue b/ui/src/pages/ProductionProductCostingHasCostDetail.vue index 1fdbfa9..60b4bc3 100644 --- a/ui/src/pages/ProductionProductCostingHasCostDetail.vue +++ b/ui/src/pages/ProductionProductCostingHasCostDetail.vue @@ -834,6 +834,95 @@ + + + +
+
Fiyat Kontrolu (Satinalma Ortalama)
+
+ BAGGI_V3 satinalma gecmisindeki son 10 USD ortalamasindan %10'dan fazla sapan tum satirlar. +
+
+ {{ priceDeviationRows.length }} satir +
+ + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
SecKodOrt USDGirilen USDFark %Yeni FiyatPr.Br.
+ + {{ row.code }}{{ formatMoney(row.avgUSD) }}{{ formatMoney(row.enteredUSD) }} + {{ formatSignedPercent(row.pct) }} + + + + +
+
+
+ + + + + +
+
+ @@ -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);