Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -811,43 +811,27 @@ filtered AS (
|
||||
AND dim1 > 0
|
||||
AND price > 0
|
||||
),
|
||||
latest AS (
|
||||
SELECT DISTINCT ON (s.sdprcgrp_id, s.crn, s.dim1, COALESCE(s.dim3, 0))
|
||||
s.sdprcgrp_id,
|
||||
s.crn,
|
||||
s.dim1,
|
||||
s.dim3,
|
||||
s.prc
|
||||
FROM sdprc s
|
||||
WHERE s.mmitem_id = $2
|
||||
AND (s.sdprcgrp_id, s.crn, s.dim1, COALESCE(s.dim3, 0)) IN (
|
||||
SELECT sdprcgrp_id, currency, dim1, COALESCE(dim3, 0) FROM filtered
|
||||
)
|
||||
ORDER BY s.sdprcgrp_id, s.crn, s.dim1, COALESCE(s.dim3, 0), s.zlins_dttm DESC, s.id DESC
|
||||
),
|
||||
to_insert AS (
|
||||
grouped AS (
|
||||
-- Ensure one row per business key to avoid unique violations under strict constraints (e.g. uq_sdprc_3).
|
||||
SELECT
|
||||
$2::bigint AS mmitem_id,
|
||||
f.sdprcgrp_id,
|
||||
f.currency AS crn,
|
||||
f.dim1,
|
||||
f.dim3,
|
||||
f.price AS prc
|
||||
FROM filtered f
|
||||
LEFT JOIN latest l
|
||||
ON l.sdprcgrp_id = f.sdprcgrp_id
|
||||
AND l.crn = f.currency
|
||||
AND l.dim1 = f.dim1
|
||||
AND ((l.dim3 IS NULL AND f.dim3 IS NULL) OR l.dim3 = f.dim3)
|
||||
WHERE l.prc IS NULL OR l.prc IS DISTINCT FROM f.price
|
||||
sdprcgrp_id,
|
||||
currency AS crn,
|
||||
dim1,
|
||||
dim3,
|
||||
MAX(price) AS prc
|
||||
FROM filtered
|
||||
GROUP BY sdprcgrp_id, currency, dim1, dim3
|
||||
),
|
||||
ins AS (
|
||||
upserted AS (
|
||||
INSERT INTO sdprc (mmitem_id, sdprcgrp_id, crn, dim1, dim3, prc, zlins_dttm)
|
||||
SELECT mmitem_id, sdprcgrp_id, crn, dim1, dim3, prc, now()
|
||||
FROM to_insert
|
||||
SELECT $2::bigint, g.sdprcgrp_id, g.crn, g.dim1, g.dim3, g.prc, now()
|
||||
FROM grouped g
|
||||
ON CONFLICT ON CONSTRAINT uq_sdprc_3
|
||||
DO UPDATE SET prc = EXCLUDED.prc, zlins_dttm = EXCLUDED.zlins_dttm
|
||||
WHERE sdprc.prc IS DISTINCT FROM EXCLUDED.prc
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT COUNT(*)::int FROM ins;
|
||||
SELECT COUNT(*)::int FROM upserted;
|
||||
`
|
||||
var inserted int
|
||||
if err := pgTx.QueryRowContext(ctx, q, raw, mmItemID).Scan(&inserted); err != nil {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<q-page class="q-pa-xs pricing-page">
|
||||
<q-inner-loading :showing="pageBusy">
|
||||
<q-spinner-gears size="52px" color="primary" />
|
||||
</q-inner-loading>
|
||||
|
||||
<div class="top-bar row items-center justify-between q-mb-xs">
|
||||
<div class="text-subtitle1 text-weight-bold">Urun Fiyatlandirma</div>
|
||||
<div class="top-actions">
|
||||
@@ -13,6 +17,7 @@
|
||||
map-options
|
||||
:options="topUrunIlkGrubuOptions"
|
||||
:loading="Boolean(serverFilterLoading.urunIlkGrubu)"
|
||||
:disable="pageBusy"
|
||||
label="Urun Ilk Grubu"
|
||||
style="min-width: 220px"
|
||||
@filter="onTopFilterSearchUrunIlkGrubu"
|
||||
@@ -29,6 +34,7 @@
|
||||
map-options
|
||||
:options="topUrunAnaGrubuOptions"
|
||||
:loading="Boolean(serverFilterLoading.urunAnaGrubu)"
|
||||
:disable="pageBusy"
|
||||
label="Urun Ana Grubu (max 3)"
|
||||
style="min-width: 260px"
|
||||
@filter="onTopFilterSearchUrunAnaGrubu"
|
||||
@@ -38,7 +44,7 @@
|
||||
color="primary"
|
||||
icon="filter_alt"
|
||||
label="Gruplari Getir"
|
||||
:disable="!canFetchByGroup"
|
||||
:disable="pageBusy || !canFetchByGroup"
|
||||
:loading="store.loading"
|
||||
@click="reloadData({ page: 1 })"
|
||||
/>
|
||||
@@ -47,6 +53,7 @@
|
||||
color="grey-7"
|
||||
icon="restart_alt"
|
||||
label="Secimleri Sifirla"
|
||||
:disable="pageBusy"
|
||||
@click="resetGroupSelections"
|
||||
/>
|
||||
</div>
|
||||
@@ -59,6 +66,7 @@
|
||||
color="grey-8"
|
||||
icon="view_sidebar"
|
||||
:label="leftDetailsExpanded ? 'Detaylari Gizle' : 'Detaylari Goster'"
|
||||
:disable="pageBusy"
|
||||
@click="leftDetailsExpanded = !leftDetailsExpanded"
|
||||
/>
|
||||
<q-btn-dropdown dense color="secondary" outline icon="view_module" label="Doviz Gorunumu" :auto-close="false">
|
||||
@@ -75,6 +83,7 @@
|
||||
<q-checkbox
|
||||
:model-value="isCurrencySelected(option.value)"
|
||||
dense
|
||||
:disable="pageBusy"
|
||||
@update:model-value="(val) => toggleCurrency(option.value, val)"
|
||||
@click.stop
|
||||
/>
|
||||
@@ -92,7 +101,7 @@
|
||||
:color="showSelectedOnly ? 'primary' : 'grey-7'"
|
||||
:icon="showSelectedOnly ? 'checklist_rtl' : 'list_alt'"
|
||||
:label="showSelectedOnly ? `Secililer (${selectedRowCount})` : 'Secili Olanlari Getir'"
|
||||
:disable="!showSelectedOnly && selectedRowCount === 0"
|
||||
:disable="pageBusy || (!showSelectedOnly && selectedRowCount === 0)"
|
||||
@click="toggleShowSelectedOnly"
|
||||
/>
|
||||
<q-btn
|
||||
@@ -101,7 +110,7 @@
|
||||
outline
|
||||
icon="calculate"
|
||||
label="Secilileri Hesapla"
|
||||
:disable="selectedRowCount === 0 || bulkCalcLoading"
|
||||
:disable="pageBusy || selectedRowCount === 0 || bulkCalcLoading"
|
||||
:loading="bulkCalcLoading"
|
||||
@click="calculateSelectedRows"
|
||||
/>
|
||||
@@ -110,7 +119,7 @@
|
||||
color="primary"
|
||||
icon="save"
|
||||
:label="saveButtonLabel"
|
||||
:disable="selectedDirtyCount === 0 || saving"
|
||||
:disable="pageBusy || selectedDirtyCount === 0 || saving"
|
||||
:loading="saving"
|
||||
@click="saveSelectedRows"
|
||||
/>
|
||||
@@ -146,6 +155,7 @@
|
||||
:max-pages="8"
|
||||
boundary-links
|
||||
direction-links
|
||||
:disable="pageBusy"
|
||||
@update:model-value="onPageChange"
|
||||
/>
|
||||
<div class="text-caption text-grey-8">
|
||||
@@ -157,9 +167,6 @@
|
||||
</div>
|
||||
|
||||
<div class="table-wrap" :style="{ '--sticky-scroll-comp': `${stickyScrollComp}px` }">
|
||||
<q-inner-loading :showing="saving || bulkCalcLoading">
|
||||
<q-spinner-gears size="46px" color="primary" />
|
||||
</q-inner-loading>
|
||||
<div v-if="showGuidanceOverlay" class="empty-overlay">
|
||||
<div class="empty-overlay-inner">
|
||||
<div class="text-subtitle1 text-weight-bold">Calismaya Baslamak Icin</div>
|
||||
@@ -835,6 +842,9 @@ import api, { download } from 'src/services/api'
|
||||
|
||||
const $q = useQuasar()
|
||||
const store = useProductPricingStore()
|
||||
|
||||
const isReloading = ref(false)
|
||||
const pageBusy = computed(() => Boolean(isReloading.value || store.loading || saving.value || bulkCalcLoading.value || exportAllLoading.value))
|
||||
const PAGE_LIMIT = 250
|
||||
const currentPage = ref(1)
|
||||
let reloadTimer = null
|
||||
@@ -2451,7 +2461,9 @@ async function fetchChunk ({ page = 1, useCache = true } = {}) {
|
||||
}
|
||||
|
||||
async function reloadData ({ page = 1, useCache = true } = {}) {
|
||||
if (isReloading.value) return
|
||||
const startedAt = Date.now()
|
||||
isReloading.value = true
|
||||
console.info('[product-pricing][ui] reload:start', {
|
||||
at: new Date(startedAt).toISOString()
|
||||
})
|
||||
@@ -2469,6 +2481,10 @@ async function reloadData ({ page = 1, useCache = true } = {}) {
|
||||
has_error: Boolean(store.error)
|
||||
})
|
||||
await bindHorizontalScrollSync()
|
||||
// Let the table render before we re-enable actions (prevents double-submits while the UI is still updating).
|
||||
await nextTick()
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
isReloading.value = false
|
||||
}
|
||||
|
||||
// Full "fetch all pages" is intentionally avoided; keep server-side paging for performance.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<q-page class="q-pa-xs pricing-page">
|
||||
<q-inner-loading :showing="pageBusy">
|
||||
<q-spinner-gears size="52px" color="primary" />
|
||||
</q-inner-loading>
|
||||
|
||||
<div class="top-bar row items-center justify-between q-mb-xs">
|
||||
<div class="text-subtitle1 text-weight-bold">Toptan Kampanya Yonetimi</div>
|
||||
<div class="top-actions">
|
||||
@@ -13,6 +17,7 @@
|
||||
map-options
|
||||
:options="topUrunIlkGrubuOptions"
|
||||
:loading="Boolean(serverFilterLoading.urunIlkGrubu)"
|
||||
:disable="pageBusy"
|
||||
label="Urun Ilk Grubu"
|
||||
style="min-width: 220px"
|
||||
@filter="onTopFilterSearchUrunIlkGrubu"
|
||||
@@ -29,6 +34,7 @@
|
||||
map-options
|
||||
:options="topUrunAnaGrubuOptions"
|
||||
:loading="Boolean(serverFilterLoading.urunAnaGrubu)"
|
||||
:disable="pageBusy"
|
||||
label="Urun Ana Grubu (max 3)"
|
||||
style="min-width: 260px"
|
||||
@filter="onTopFilterSearchUrunAnaGrubu"
|
||||
@@ -38,7 +44,7 @@
|
||||
color="primary"
|
||||
icon="filter_alt"
|
||||
label="Gruplari Getir"
|
||||
:disable="!canFetchByGroup"
|
||||
:disable="pageBusy || !canFetchByGroup"
|
||||
:loading="store.loading"
|
||||
@click="reloadData({ page: 1 })"
|
||||
/>
|
||||
@@ -47,6 +53,7 @@
|
||||
color="grey-7"
|
||||
icon="restart_alt"
|
||||
label="Secimleri Sifirla"
|
||||
:disable="pageBusy"
|
||||
@click="resetGroupSelections"
|
||||
/>
|
||||
</div>
|
||||
@@ -59,6 +66,7 @@
|
||||
color="grey-8"
|
||||
icon="view_sidebar"
|
||||
:label="leftDetailsExpanded ? 'Detaylari Gizle' : 'Detaylari Goster'"
|
||||
:disable="pageBusy"
|
||||
@click="leftDetailsExpanded = !leftDetailsExpanded"
|
||||
/>
|
||||
<q-btn-dropdown dense color="secondary" outline icon="view_module" label="Gosterge Fiyat Sec" :auto-close="false">
|
||||
@@ -75,6 +83,7 @@
|
||||
<q-checkbox
|
||||
:model-value="isPriceOptionSelected(option.value)"
|
||||
dense
|
||||
:disable="pageBusy"
|
||||
@update:model-value="(val) => togglePriceOption(option.value, val)"
|
||||
@click.stop
|
||||
/>
|
||||
@@ -106,7 +115,7 @@
|
||||
:color="showSelectedOnly ? 'primary' : 'grey-7'"
|
||||
:icon="showSelectedOnly ? 'checklist_rtl' : 'list_alt'"
|
||||
:label="showSelectedOnly ? `Secililer (${selectedRowCount})` : 'Secili Olanlari Getir'"
|
||||
:disable="!showSelectedOnly && selectedRowCount === 0"
|
||||
:disable="pageBusy || (!showSelectedOnly && selectedRowCount === 0)"
|
||||
@click="toggleShowSelectedOnly"
|
||||
/>
|
||||
<q-btn
|
||||
@@ -114,7 +123,7 @@
|
||||
color="primary"
|
||||
icon="save"
|
||||
:label="saveButtonLabel"
|
||||
:disable="selectedDirtyCount === 0 || saving"
|
||||
:disable="pageBusy || selectedDirtyCount === 0 || saving"
|
||||
:loading="saving"
|
||||
@click="saveSelectedRows"
|
||||
/>
|
||||
@@ -150,6 +159,7 @@
|
||||
:max-pages="8"
|
||||
boundary-links
|
||||
direction-links
|
||||
:disable="pageBusy"
|
||||
@update:model-value="onPageChange"
|
||||
/>
|
||||
<div class="text-caption text-grey-8">
|
||||
@@ -161,9 +171,6 @@
|
||||
</div>
|
||||
|
||||
<div class="table-wrap" :style="{ '--sticky-scroll-comp': `${stickyScrollComp}px` }">
|
||||
<q-inner-loading :showing="saving">
|
||||
<q-spinner-gears size="46px" color="primary" />
|
||||
</q-inner-loading>
|
||||
<div v-if="showGuidanceOverlay" class="empty-overlay">
|
||||
<div class="empty-overlay-inner">
|
||||
<div class="text-subtitle1 text-weight-bold">Calismaya Baslamak Icin</div>
|
||||
@@ -840,6 +847,9 @@ import api, { download } from 'src/services/api'
|
||||
|
||||
const $q = useQuasar()
|
||||
const store = useProductPricingStore()
|
||||
|
||||
const isReloading = ref(false)
|
||||
const pageBusy = computed(() => Boolean(isReloading.value || store.loading || variantLoading.value || saving.value || exportAllLoading.value))
|
||||
// Variant rows explode product rows; keep this smaller than ProductPricing for responsiveness.
|
||||
const PAGE_LIMIT = 50
|
||||
const currentPage = ref(1)
|
||||
@@ -2618,7 +2628,9 @@ async function buildVariantRowsForProductPage (baseProductRows = []) {
|
||||
}
|
||||
|
||||
async function reloadData ({ page = 1, useCache = true } = {}) {
|
||||
if (isReloading.value) return
|
||||
const startedAt = Date.now()
|
||||
isReloading.value = true
|
||||
console.info('[product-pricing][ui] reload:start', {
|
||||
at: new Date(startedAt).toISOString()
|
||||
})
|
||||
@@ -2640,6 +2652,9 @@ async function reloadData ({ page = 1, useCache = true } = {}) {
|
||||
has_error: Boolean(store.error)
|
||||
})
|
||||
await bindHorizontalScrollSync()
|
||||
await nextTick()
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
isReloading.value = false
|
||||
}
|
||||
|
||||
// Full "fetch all pages" is intentionally avoided; keep server-side paging for performance.
|
||||
|
||||
Reference in New Issue
Block a user