Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -418,6 +418,11 @@ const menuItems = [
|
||||
label: 'Fiyat Listesi Mail Eşleştirme',
|
||||
to: '/app/order-price-list-mail-mapping',
|
||||
permission: 'system:update'
|
||||
},
|
||||
{
|
||||
label: 'Kullanıcı Fiyat Eşleştirme',
|
||||
to: '/app/order-price-list-user-price-groups',
|
||||
permission: 'system:update'
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<q-item-section>Tumunu Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item v-for="option in priceOptions" :key="option.value" clickable @click="togglePriceOption(option.value)">
|
||||
<q-item v-for="option in allowedPriceOptions" :key="option.value" clickable @click="togglePriceOption(option.value)">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
dense
|
||||
@@ -165,7 +165,7 @@
|
||||
>
|
||||
<div
|
||||
class="top-x-scroll-inner"
|
||||
:style="{ width: `${tableMinWidth}px` }"
|
||||
:style="{ width: `${tableScrollWidth}px` }"
|
||||
/>
|
||||
</div>
|
||||
<q-table
|
||||
@@ -589,18 +589,6 @@
|
||||
<div class="field-row"><span class="k">Kampanya</span><span class="v">{{ productCardData.campaignLabel || '-' }}</span></div>
|
||||
<div class="field-row"><span class="k">Stok</span><span class="v">{{ formatStock(productCardData.stockQty || 0) }}</span></div>
|
||||
|
||||
<div class="product-card-section">
|
||||
<div class="product-card-section-title">Fiyat Bilgileri</div>
|
||||
<div v-if="productCardPriceRows.length" class="price-info-grid">
|
||||
<div v-for="item in productCardPriceRows" :key="item.key" class="price-info-row">
|
||||
<span class="price-label">{{ item.label }}</span>
|
||||
<span class="price-value">{{ item.price || '-' }}</span>
|
||||
<span class="price-campaign">{{ item.campaignPrice || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="product-card-empty-text">Secili fiyat kolonu yok.</div>
|
||||
</div>
|
||||
|
||||
<div class="product-card-section">
|
||||
<div class="product-card-section-title">Beden Stoklari</div>
|
||||
<q-inner-loading :showing="productCardStockLoading">
|
||||
@@ -615,6 +603,25 @@
|
||||
<div v-else-if="!productCardStockLoading" class="product-card-empty-text">Beden stogu bulunamadi.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="product-card-price-panel">
|
||||
<div class="product-card-section product-card-price-section">
|
||||
<div class="product-card-section-title">Fiyat Bilgileri</div>
|
||||
<div class="price-info-header">
|
||||
<span>Fiyat</span>
|
||||
<span>Liste</span>
|
||||
<span>Kampanyali</span>
|
||||
</div>
|
||||
<div v-if="productCardPriceRows.length" class="price-info-grid">
|
||||
<div v-for="item in productCardPriceRows" :key="item.key" :class="['price-info-row', `price-info-row-${item.currency}`, { 'has-campaign-price': item.hasCampaignPrice }]">
|
||||
<span class="price-label">{{ item.label }}</span>
|
||||
<span class="price-value">{{ item.price || '-' }}</span>
|
||||
<span class="price-campaign">{{ item.campaignPrice || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="product-card-empty-text">Secili fiyat kolonu yok.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@@ -664,10 +671,18 @@ import api from 'src/services/api'
|
||||
const PAGE_LIMIT = 250
|
||||
const GUIDANCE_MSG = 'Liste icin filtre secin.'
|
||||
|
||||
const priceOptions = ['USD', 'EUR', 'TRY'].flatMap((cur) => [1, 2, 3, 4, 5, 6].map((lv) => ({
|
||||
const allPriceOptions = ['USD', 'EUR', 'TRY'].flatMap((cur) => [1, 2, 3, 4, 5, 6].map((lv) => ({
|
||||
label: `${cur} ${lv}`,
|
||||
value: `${cur.toLowerCase()}${lv}`
|
||||
})))
|
||||
const allowedPriceGroupValues = ref([])
|
||||
const priceGroupRestricted = ref(false)
|
||||
const allowedPriceOptions = computed(() => {
|
||||
if (!priceGroupRestricted.value) return allPriceOptions
|
||||
const allowed = new Set(allowedPriceGroupValues.value || [])
|
||||
return allPriceOptions.filter((x) => allowed.has(x.value))
|
||||
})
|
||||
const priceOptions = allPriceOptions
|
||||
const campaignPairs = priceOptions.map((x) => ({ base: x.value, derived: `${x.value}Campaign` }))
|
||||
const priceColumnNames = campaignPairs.flatMap((p) => [p.base, p.derived])
|
||||
|
||||
@@ -721,8 +736,10 @@ const productCardPriceRows = computed(() => {
|
||||
.map((option) => ({
|
||||
key: option.value,
|
||||
label: option.label,
|
||||
currency: String(option.value || '').slice(0, 3).toLowerCase(),
|
||||
price: formatPrice(row?.[option.value]),
|
||||
campaignPrice: formatPrice(row?.[`${option.value}Campaign`])
|
||||
campaignPrice: formatPrice(row?.[`${option.value}Campaign`]),
|
||||
hasCampaignPrice: Number(row?.[`${option.value}Campaign`] || 0) > 0
|
||||
}))
|
||||
})
|
||||
const selectedProductCodeSet = computed(() => new Set(selectedProductCodes.value || []))
|
||||
@@ -956,6 +973,20 @@ async function fetchServerFilterOptions (field, q = '') {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMyPriceGroups () {
|
||||
try {
|
||||
const res = await api.get('/order/price-list/my-price-groups')
|
||||
priceGroupRestricted.value = !!res?.data?.restricted
|
||||
allowedPriceGroupValues.value = Array.isArray(res?.data?.price_groups) ? res.data.price_groups : []
|
||||
normalizeSelectedPriceOptions()
|
||||
} catch (err) {
|
||||
console.warn('[order-price-list][ui] price-groups lookup failed', err?.response?.data || err?.message || err)
|
||||
priceGroupRestricted.value = false
|
||||
allowedPriceGroupValues.value = []
|
||||
normalizeSelectedPriceOptions()
|
||||
}
|
||||
}
|
||||
|
||||
function onTopFilterSearchUrunIlkGrubu (val, update) {
|
||||
update(() => {
|
||||
filterSearch.value.urunIlkGrubu = toText(val)
|
||||
@@ -1306,20 +1337,33 @@ function onPageChange (page) {
|
||||
}
|
||||
|
||||
function togglePriceOption (value) {
|
||||
if (!allowedPriceOptions.value.some((x) => x.value === value)) return
|
||||
const set = new Set(selectedPriceOptions.value || [])
|
||||
if (set.has(value)) set.delete(value)
|
||||
else set.add(value)
|
||||
selectedPriceOptions.value = priceOptions.map((x) => x.value).filter((x) => set.has(x))
|
||||
selectedPriceOptions.value = allowedPriceOptions.value.map((x) => x.value).filter((x) => set.has(x))
|
||||
}
|
||||
|
||||
function selectAllPrices () {
|
||||
selectedPriceOptions.value = priceOptions.map((x) => x.value)
|
||||
selectedPriceOptions.value = allowedPriceOptions.value.map((x) => x.value)
|
||||
}
|
||||
|
||||
function clearAllPrices () {
|
||||
selectedPriceOptions.value = []
|
||||
}
|
||||
|
||||
function normalizeSelectedPriceOptions () {
|
||||
const allowedValues = allowedPriceOptions.value.map((x) => x.value)
|
||||
const allowed = new Set(allowedValues)
|
||||
const current = (selectedPriceOptions.value || []).filter((x) => allowed.has(x))
|
||||
if (current.length > 0 || allowedValues.length === 0) {
|
||||
selectedPriceOptions.value = current
|
||||
return
|
||||
}
|
||||
const preferred = ['usd5', 'try5'].filter((x) => allowed.has(x))
|
||||
selectedPriceOptions.value = preferred.length ? preferred : allowedValues.slice(0, 2)
|
||||
}
|
||||
|
||||
function col (name, label, field, width, extra = {}) {
|
||||
return {
|
||||
name,
|
||||
@@ -1402,6 +1446,7 @@ 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 tableStyle = computed(() => ({
|
||||
width: `${tableMinWidth.value}px`,
|
||||
minWidth: `${tableMinWidth.value}px`,
|
||||
@@ -1609,7 +1654,12 @@ watch([tableMinWidth, rows], async () => {
|
||||
bindTableScrollSync()
|
||||
})
|
||||
|
||||
watch(allowedPriceOptions, () => {
|
||||
normalizeSelectedPriceOptions()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
void fetchMyPriceGroups()
|
||||
void fetchServerFilterOptions('urunIlkGrubu', '')
|
||||
void fetchServerFilterOptions('urunAnaGrubu', '')
|
||||
void fetchServerFilterOptions('productCode', '')
|
||||
@@ -1885,23 +1935,23 @@ onMounted(() => {
|
||||
|
||||
.pricing-table :deep(th.usd-col),
|
||||
.pricing-table :deep(td.usd-col) {
|
||||
background: #ecf9f0;
|
||||
color: #178a3e;
|
||||
font-weight: 700;
|
||||
background: #fff;
|
||||
color: #16803a;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.eur-col),
|
||||
.pricing-table :deep(td.eur-col) {
|
||||
background: #fdeeee;
|
||||
color: #c62828;
|
||||
font-weight: 700;
|
||||
background: #fff;
|
||||
color: #b91c1c;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.try-col),
|
||||
.pricing-table :deep(td.try-col) {
|
||||
background: #edf4ff;
|
||||
color: #1e63c6;
|
||||
font-weight: 700;
|
||||
background: #fff;
|
||||
color: #185abc;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.usd-col),
|
||||
@@ -1910,14 +1960,30 @@ onMounted(() => {
|
||||
.pricing-table :deep(td.usd-col),
|
||||
.pricing-table :deep(td.eur-col),
|
||||
.pricing-table :deep(td.try-col) {
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pricing-table :deep(td.campaign-price-col),
|
||||
.pricing-table :deep(th.campaign-price-col) {
|
||||
background: #fff3f1;
|
||||
color: #c62828;
|
||||
font-weight: 800;
|
||||
.pricing-table :deep(th.usd-col.campaign-price-col),
|
||||
.pricing-table :deep(td.usd-col.campaign-price-col) {
|
||||
background: #dff6e7;
|
||||
color: #0f6b2f;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.eur-col.campaign-price-col),
|
||||
.pricing-table :deep(td.eur-col.campaign-price-col) {
|
||||
background: #fde2e2;
|
||||
color: #a61717;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.pricing-table :deep(th.try-col.campaign-price-col),
|
||||
.pricing-table :deep(td.try-col.campaign-price-col) {
|
||||
background: #e2edff;
|
||||
color: #174ea6;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -1989,7 +2055,6 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.campaign-price-text {
|
||||
color: #c62828;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
@@ -2100,7 +2165,7 @@ onMounted(() => {
|
||||
|
||||
.product-card-dialog {
|
||||
--pc-media-h: calc(100vh - 180px);
|
||||
--pc-media-w: min(74vw, 1220px);
|
||||
--pc-media-w: min(28vw, 440px);
|
||||
background: #f9f8f5;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
@@ -2115,10 +2180,10 @@ onMounted(() => {
|
||||
|
||||
.product-card-content {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(360px, 420px) minmax(760px, 1fr);
|
||||
grid-template-columns: minmax(360px, 420px) minmax(360px, 440px) minmax(320px, 420px);
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
justify-content: start;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -2126,6 +2191,7 @@ onMounted(() => {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
height: var(--pc-media-h);
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
@@ -2133,7 +2199,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.product-card-carousel {
|
||||
width: var(--pc-media-w);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
background: linear-gradient(180deg, #f6f1e6 0%, #efe6d3 100%);
|
||||
@@ -2146,7 +2212,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.dialog-image-stage {
|
||||
width: var(--pc-media-w);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@@ -2158,7 +2224,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.dialog-image-empty {
|
||||
width: var(--pc-media-w);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: var(--pc-media-h);
|
||||
border: 1px dashed #c5b28d;
|
||||
@@ -2180,6 +2246,14 @@ onMounted(() => {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.product-card-price-panel {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
min-width: 0;
|
||||
height: var(--pc-media-h);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: grid;
|
||||
grid-template-columns: 150px 1fr;
|
||||
@@ -2220,6 +2294,13 @@ onMounted(() => {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.product-card-price-section {
|
||||
height: 100%;
|
||||
margin-top: 0;
|
||||
overflow: auto;
|
||||
background: linear-gradient(180deg, #fffdf8 0%, #fff7ec 100%);
|
||||
}
|
||||
|
||||
.product-card-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
@@ -2230,20 +2311,36 @@ onMounted(() => {
|
||||
.price-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.price-info-header {
|
||||
display: grid;
|
||||
grid-template-columns: 74px 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
padding: 0 8px;
|
||||
color: #6b5a33;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.price-info-header span:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.price-info-row {
|
||||
display: grid;
|
||||
grid-template-columns: 70px 1fr 1fr;
|
||||
gap: 6px;
|
||||
grid-template-columns: 74px 1fr 1fr;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
padding: 4px 6px;
|
||||
min-height: 34px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #f0e5d2;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
@@ -2253,13 +2350,49 @@ onMounted(() => {
|
||||
|
||||
.price-value,
|
||||
.price-campaign {
|
||||
min-height: 26px;
|
||||
padding: 5px 7px;
|
||||
border-radius: 5px;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 800;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.price-campaign {
|
||||
color: #b13a2b;
|
||||
font-weight: 700;
|
||||
color: #8a8a8a;
|
||||
background: #f4f4f4;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.price-info-row-usd .price-value {
|
||||
color: #16803a;
|
||||
}
|
||||
|
||||
.price-info-row-eur .price-value {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.price-info-row-try .price-value {
|
||||
color: #185abc;
|
||||
}
|
||||
|
||||
.price-info-row-usd.has-campaign-price .price-campaign {
|
||||
background: #dff6e7;
|
||||
color: #0f6b2f;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.price-info-row-eur.has-campaign-price .price-campaign {
|
||||
background: #fde2e2;
|
||||
color: #a61717;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.price-info-row-try.has-campaign-price .price-campaign {
|
||||
background: #e2edff;
|
||||
color: #174ea6;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.size-stock-grid {
|
||||
@@ -2342,9 +2475,12 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.product-card-images,
|
||||
.product-card-fields {
|
||||
.product-card-fields,
|
||||
.product-card-price-panel {
|
||||
grid-column: 1;
|
||||
grid-row: auto;
|
||||
height: auto;
|
||||
min-height: 320px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
163
ui/src/pages/OrderPriceListUserPriceGroupMapping.vue
Normal file
163
ui/src/pages/OrderPriceListUserPriceGroupMapping.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<q-page v-if="canUpdateSystem" class="q-pa-md">
|
||||
<div class="row items-center justify-between q-mb-md">
|
||||
<div>
|
||||
<div class="text-h6">Kullanici Fiyat Grubu Eslestirme</div>
|
||||
<div class="text-caption text-grey-7">Fiyat Listesi ekraninda kullanicinin gorebilecegi fiyat gruplarini belirler.</div>
|
||||
</div>
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="save"
|
||||
label="Degisiklikleri Kaydet"
|
||||
:loading="store.saving"
|
||||
:disable="!hasChanges"
|
||||
@click="saveChanges"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-table
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
row-key="user_id"
|
||||
:loading="store.loading"
|
||||
:rows="store.rows"
|
||||
:columns="columns"
|
||||
:rows-per-page-options="[0]"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
>
|
||||
<template #body-cell-price_groups="props">
|
||||
<q-td :props="props">
|
||||
<q-select
|
||||
:model-value="editableByUser[props.row.user_id] || []"
|
||||
:options="store.priceGroupOptions"
|
||||
option-value="value"
|
||||
option-label="label"
|
||||
emit-value
|
||||
map-options
|
||||
multiple
|
||||
use-chips
|
||||
clearable
|
||||
dense
|
||||
outlined
|
||||
label="Fiyat gruplari"
|
||||
@update:model-value="(val) => updateRowSelection(props.row.user_id, val)"
|
||||
>
|
||||
<template #before-options>
|
||||
<q-item clickable @click="selectAll(props.row.user_id)">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="clearAll(props.row.user_id)">
|
||||
<q-item-section>Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
</template>
|
||||
</q-select>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-page>
|
||||
|
||||
<q-page v-else class="q-pa-md flex flex-center">
|
||||
<div class="text-negative text-subtitle1">
|
||||
Bu module erisim yetkiniz yok.
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
import { useOrderPriceListUserPriceGroupStore } from 'src/stores/orderPriceListUserPriceGroupStore'
|
||||
|
||||
const $q = useQuasar()
|
||||
const store = useOrderPriceListUserPriceGroupStore()
|
||||
const { canUpdate } = usePermission()
|
||||
const canUpdateSystem = canUpdate('system')
|
||||
|
||||
const editableByUser = ref({})
|
||||
const originalByUser = ref({})
|
||||
|
||||
const columns = [
|
||||
{ name: 'username', label: 'Kullanici Kodu', field: 'username', align: 'left', sortable: true },
|
||||
{ name: 'full_name', label: 'Ad Soyad', field: 'full_name', align: 'left', sortable: true },
|
||||
{ name: 'email', label: 'E-Posta', field: 'email', align: 'left' },
|
||||
{ name: 'price_groups', label: 'Gorebilecegi Fiyat Gruplari', field: 'price_groups', align: 'left' }
|
||||
]
|
||||
|
||||
const changedUsers = computed(() => {
|
||||
return (store.rows || [])
|
||||
.map((r) => Number(r.user_id || 0))
|
||||
.filter(Boolean)
|
||||
.filter((id) => !isEqualList(normalizeList(editableByUser.value[id] || []), normalizeList(originalByUser.value[id] || [])))
|
||||
})
|
||||
|
||||
const hasChanges = computed(() => changedUsers.value.length > 0)
|
||||
|
||||
function normalizeList (list) {
|
||||
const allowed = new Set((store.priceGroupOptions || []).map((x) => x.value))
|
||||
return Array.from(new Set((Array.isArray(list) ? list : []).map((x) => String(x).trim()).filter((x) => allowed.has(x)))).sort()
|
||||
}
|
||||
|
||||
function isEqualList (a, b) {
|
||||
if (a.length !== b.length) return false
|
||||
for (let i = 0; i < a.length; i += 1) {
|
||||
if (a[i] !== b[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function initEditableState () {
|
||||
const editable = {}
|
||||
const original = {}
|
||||
for (const row of store.rows || []) {
|
||||
const id = Number(row.user_id || 0)
|
||||
if (!id) continue
|
||||
const selected = normalizeList(row.price_groups || [])
|
||||
editable[id] = [...selected]
|
||||
original[id] = [...selected]
|
||||
}
|
||||
editableByUser.value = editable
|
||||
originalByUser.value = original
|
||||
}
|
||||
|
||||
function updateRowSelection (userId, newValue) {
|
||||
const id = Number(userId || 0)
|
||||
if (!id) return
|
||||
editableByUser.value = { ...editableByUser.value, [id]: normalizeList(newValue) }
|
||||
}
|
||||
|
||||
function selectAll (userId) {
|
||||
updateRowSelection(userId, store.priceGroupOptions.map((x) => x.value))
|
||||
}
|
||||
|
||||
function clearAll (userId) {
|
||||
updateRowSelection(userId, [])
|
||||
}
|
||||
|
||||
async function init () {
|
||||
try {
|
||||
await Promise.all([store.fetchLookups(), store.fetchRows()])
|
||||
initEditableState()
|
||||
} catch (err) {
|
||||
$q.notify({ type: 'negative', message: err?.message || 'Kullanici fiyat grubu eslestirmeleri yuklenemedi' })
|
||||
}
|
||||
}
|
||||
|
||||
async function saveChanges () {
|
||||
if (!hasChanges.value) return
|
||||
try {
|
||||
for (const id of changedUsers.value) {
|
||||
await store.saveUserPriceGroups(id, editableByUser.value[id] || [])
|
||||
}
|
||||
await store.fetchRows()
|
||||
initEditableState()
|
||||
$q.notify({ type: 'positive', message: 'Degisiklikler kaydedildi' })
|
||||
} catch (err) {
|
||||
$q.notify({ type: 'negative', message: err?.message || 'Kayit hatasi' })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => { init() })
|
||||
</script>
|
||||
@@ -277,6 +277,34 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="text-caption text-grey-7 q-mb-xs">Fiyat Listesi Fiyat Gruplari</div>
|
||||
<q-select
|
||||
v-model="form.order_price_list_price_groups"
|
||||
:options="orderPriceListPriceGroupOptions"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
emit-value
|
||||
map-options
|
||||
multiple
|
||||
use-chips
|
||||
clearable
|
||||
dense
|
||||
filled
|
||||
behavior="menu"
|
||||
>
|
||||
<template #before-options>
|
||||
<q-item clickable @click="selectAllOrderPriceGroups">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="clearOrderPriceGroups">
|
||||
<q-item-section>Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@@ -320,6 +348,7 @@ const {
|
||||
departmentOptions,
|
||||
piyasaOptions,
|
||||
nebimUserOptions,
|
||||
orderPriceListPriceGroupOptions,
|
||||
sendingPasswordMail,
|
||||
lastPasswordMailSentAt
|
||||
} = storeToRefs(store)
|
||||
@@ -373,6 +402,16 @@ function clearPiyasalar () {
|
||||
form.value.piyasalar = []
|
||||
}
|
||||
|
||||
function selectAllOrderPriceGroups () {
|
||||
form.value.order_price_list_price_groups = (orderPriceListPriceGroupOptions.value || [])
|
||||
.map((o) => o.value)
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function clearOrderPriceGroups () {
|
||||
form.value.order_price_list_price_groups = []
|
||||
}
|
||||
|
||||
/* ================= LIFECYCLE ================= */
|
||||
watch(
|
||||
() => userId.value,
|
||||
|
||||
@@ -257,6 +257,12 @@ const routes = [
|
||||
component: () => import('../pages/OrderPriceListMailMapping.vue'),
|
||||
meta: { permission: 'system:update' }
|
||||
},
|
||||
{
|
||||
path: 'order-price-list-user-price-groups',
|
||||
name: 'order-price-list-user-price-groups',
|
||||
component: () => import('../pages/OrderPriceListUserPriceGroupMapping.vue'),
|
||||
meta: { permission: 'system:update' }
|
||||
},
|
||||
{
|
||||
path: 'language/translations',
|
||||
name: 'translation-table',
|
||||
|
||||
@@ -26,14 +26,16 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
roles: [],
|
||||
departments: null,
|
||||
piyasalar: [],
|
||||
nebim_users: null
|
||||
nebim_users: null,
|
||||
order_price_list_price_groups: []
|
||||
},
|
||||
|
||||
/* ================= LOOKUPS ================= */
|
||||
roleOptions: [],
|
||||
departmentOptions: [],
|
||||
piyasaOptions: [],
|
||||
nebimUserOptions: []
|
||||
nebimUserOptions: [],
|
||||
orderPriceListPriceGroupOptions: []
|
||||
}),
|
||||
|
||||
actions: {
|
||||
@@ -52,7 +54,8 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
roles: [],
|
||||
departments: null,
|
||||
piyasalar: [],
|
||||
nebim_users: null
|
||||
nebim_users: null,
|
||||
order_price_list_price_groups: []
|
||||
}
|
||||
this.error = null
|
||||
this.hasPassword = false
|
||||
@@ -113,6 +116,7 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
departments: departmentCodes.map(code => ({ code })),
|
||||
|
||||
piyasalar: (this.form.piyasalar || []).map(code => ({ code })),
|
||||
order_price_list_price_groups: this.form.order_price_list_price_groups || [],
|
||||
|
||||
nebim_users: nebimUsernames.map(username => {
|
||||
const opt = (this.nebimUserOptions || []).find(x => x.value === username)
|
||||
@@ -146,6 +150,7 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
this.form.departments = (data.departments || []).map(x => x.code)[0] || null
|
||||
this.form.piyasalar = (data.piyasalar || []).map(x => x.code)
|
||||
this.form.nebim_users = (data.nebim_users || []).map(x => x.username)[0] || null
|
||||
this.form.order_price_list_price_groups = data.order_price_list_price_groups || []
|
||||
|
||||
this.hasPassword = !!data.has_password
|
||||
} catch (e) {
|
||||
@@ -237,17 +242,22 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
===================================================== */
|
||||
async fetchLookups () {
|
||||
// token otomatik
|
||||
const [roles, depts, piyasalar, nebims] = await Promise.all([
|
||||
const [roles, depts, piyasalar, nebims, priceGroups] = await Promise.all([
|
||||
api.get('/lookups/roles'),
|
||||
api.get('/lookups/departments'),
|
||||
api.get('/lookups/piyasalar'),
|
||||
api.get('/lookups/nebim-users')
|
||||
api.get('/lookups/nebim-users'),
|
||||
api.get('/users/order-price-list-price-groups/lookups')
|
||||
])
|
||||
|
||||
this.roleOptions = roles?.data || roles || []
|
||||
this.departmentOptions = depts?.data || depts || []
|
||||
this.piyasaOptions = piyasalar?.data || piyasalar || []
|
||||
this.nebimUserOptions = nebims?.data || nebims || []
|
||||
this.orderPriceListPriceGroupOptions = (priceGroups?.data?.price_groups || []).map(x => ({
|
||||
label: x.label || x.value,
|
||||
value: x.value
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
42
ui/src/stores/orderPriceListUserPriceGroupStore.js
Normal file
42
ui/src/stores/orderPriceListUserPriceGroupStore.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import api from 'src/services/api'
|
||||
|
||||
export const useOrderPriceListUserPriceGroupStore = defineStore('orderPriceListUserPriceGroup', {
|
||||
state: () => ({
|
||||
loading: false,
|
||||
saving: false,
|
||||
rows: [],
|
||||
priceGroupOptions: []
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async fetchLookups () {
|
||||
const res = await api.get('/system/order-price-list-user-price-groups/lookups')
|
||||
this.priceGroupOptions = (res?.data?.price_groups || []).map((x) => ({
|
||||
label: x.label || x.value,
|
||||
value: x.value
|
||||
}))
|
||||
},
|
||||
|
||||
async fetchRows () {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await api.get('/system/order-price-list-user-price-groups')
|
||||
this.rows = Array.isArray(res?.data) ? res.data : []
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async saveUserPriceGroups (userId, priceGroups) {
|
||||
this.saving = true
|
||||
try {
|
||||
await api.put(`/system/order-price-list-user-price-groups/${encodeURIComponent(String(userId))}`, {
|
||||
price_groups: Array.isArray(priceGroups) ? priceGroups : []
|
||||
})
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user