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