Merge remote-tracking branch 'origin/master'
This commit is contained in:
83
ui/quasar.config.js.temporary.compiled.1770999604634.mjs
Normal file
83
ui/quasar.config.js.temporary.compiled.1770999604634.mjs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||||
|
* 1. DO NOT edit this file directly as it won't do anything.
|
||||||
|
* 2. EDIT the original quasar.config file INSTEAD.
|
||||||
|
* 3. DO NOT git commit this file. It should be ignored.
|
||||||
|
*
|
||||||
|
* This file is still here because there was an error in
|
||||||
|
* the original quasar.config file and this allows you to
|
||||||
|
* investigate the Node.js stack error.
|
||||||
|
*
|
||||||
|
* After you fix the original file, this file will be
|
||||||
|
* deleted automatically.
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
// quasar.config.js
|
||||||
|
import { defineConfig } from "@quasar/app-webpack/wrappers";
|
||||||
|
var quasar_config_default = defineConfig(() => {
|
||||||
|
return {
|
||||||
|
// ✅ UYGULAMA KİMLİĞİ (WEB'DE GÖRÜNEN İSİM)
|
||||||
|
productName: "Baggi BSS",
|
||||||
|
productDescription: "Baggi Tekstil Business Support System",
|
||||||
|
// 🔹 Boot dosyaları
|
||||||
|
boot: ["axios", "dayjs"],
|
||||||
|
// 🔹 Global CSS
|
||||||
|
css: ["app.css"],
|
||||||
|
// 🔹 Ekstra icon/font setleri
|
||||||
|
extras: [
|
||||||
|
"roboto-font",
|
||||||
|
"material-icons"
|
||||||
|
],
|
||||||
|
// 🔹 Derleme Ayarları
|
||||||
|
build: {
|
||||||
|
vueRouterMode: "hash",
|
||||||
|
env: {
|
||||||
|
VITE_API_BASE_URL: "http://localhost:8080/api"
|
||||||
|
},
|
||||||
|
esbuildTarget: {
|
||||||
|
browser: ["es2022", "firefox115", "chrome115", "safari14"],
|
||||||
|
node: "node20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 🔹 Geliştirme Sunucusu
|
||||||
|
devServer: {
|
||||||
|
server: { type: "http" },
|
||||||
|
port: 9e3,
|
||||||
|
open: true
|
||||||
|
},
|
||||||
|
// 🔹 Quasar Framework ayarları
|
||||||
|
framework: {
|
||||||
|
config: {
|
||||||
|
notify: { position: "top", timeout: 2500 }
|
||||||
|
},
|
||||||
|
lang: "tr",
|
||||||
|
plugins: ["Loading", "Dialog", "Notify"]
|
||||||
|
},
|
||||||
|
animations: [],
|
||||||
|
ssr: {
|
||||||
|
prodPort: 3e3,
|
||||||
|
middlewares: ["render"],
|
||||||
|
pwa: false
|
||||||
|
},
|
||||||
|
pwa: {
|
||||||
|
workboxMode: "GenerateSW"
|
||||||
|
},
|
||||||
|
capacitor: {
|
||||||
|
hideSplashscreen: true
|
||||||
|
},
|
||||||
|
electron: {
|
||||||
|
preloadScripts: ["electron-preload"],
|
||||||
|
inspectPort: 5858,
|
||||||
|
bundler: "packager",
|
||||||
|
builder: { appId: "baggisowtfaresystem" }
|
||||||
|
},
|
||||||
|
bex: {
|
||||||
|
extraScripts: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export {
|
||||||
|
quasar_config_default as default
|
||||||
|
};
|
||||||
296
ui/src/pages/OrderBulkClose.vue
Normal file
296
ui/src/pages/OrderBulkClose.vue
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<template>
|
||||||
|
<q-page v-if="canUpdateOrder" class="bulk-close-page">
|
||||||
|
<div class="bulk-filter-bar">
|
||||||
|
<div class="bulk-filter-row">
|
||||||
|
<q-input
|
||||||
|
v-model="store.search"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
clearable
|
||||||
|
class="bulk-search"
|
||||||
|
label="Arama (Sipariş No / Cari / Açıklama)"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<div class="bulk-filter-actions">
|
||||||
|
<q-btn
|
||||||
|
label="Yenile"
|
||||||
|
icon="refresh"
|
||||||
|
color="primary"
|
||||||
|
:loading="store.loading"
|
||||||
|
@click="store.fetchOrders"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="Seçimi Temizle"
|
||||||
|
icon="clear"
|
||||||
|
flat
|
||||||
|
color="grey-7"
|
||||||
|
:disable="store.closing || !store.selectedCount"
|
||||||
|
@click="store.clearSelection"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="Seçilenleri Toplu Kapat"
|
||||||
|
icon="task_alt"
|
||||||
|
color="negative"
|
||||||
|
:loading="store.closing"
|
||||||
|
:disable="store.loading || store.closing || !store.selectedCount"
|
||||||
|
@click="onBulkClose"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bulk-summary">
|
||||||
|
<div>Toplam: <strong>{{ store.totalCount }}</strong></div>
|
||||||
|
<div>Seçilen: <strong>{{ store.selectedCount }}</strong></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-table
|
||||||
|
class="bulk-table"
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
dense
|
||||||
|
row-key="OrderNumber"
|
||||||
|
:rows="store.orders"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="store.loading"
|
||||||
|
no-data-label="Kapatmaya uygun sipariş bulunamadı"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
hide-bottom
|
||||||
|
>
|
||||||
|
<template #header-cell-select="props">
|
||||||
|
<q-th :props="props" class="text-center">
|
||||||
|
<q-checkbox
|
||||||
|
:model-value="allSelected"
|
||||||
|
:disable="!store.totalCount || store.closing"
|
||||||
|
@update:model-value="toggleSelectAll"
|
||||||
|
/>
|
||||||
|
</q-th>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-select="props">
|
||||||
|
<q-td :props="props" class="text-center">
|
||||||
|
<q-checkbox
|
||||||
|
:model-value="store.isSelected(props.row.OrderNumber)"
|
||||||
|
:disable="store.closing"
|
||||||
|
@update:model-value="(v) => store.setSelected(props.row.OrderNumber, v)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-OrderDate="props">
|
||||||
|
<q-td :props="props">{{ formatDate(props.row.OrderDate) }}</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-PackedRatePct="props">
|
||||||
|
<q-td
|
||||||
|
:props="props"
|
||||||
|
class="text-right text-weight-bold"
|
||||||
|
:class="packRateClass(props.row.PackedRatePct)"
|
||||||
|
>
|
||||||
|
{{ formatPct(props.row.PackedRatePct) }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-TotalAmountUSD="props">
|
||||||
|
<q-td :props="props" class="text-right">
|
||||||
|
{{ formatMoney(props.row.TotalAmountUSD) }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-PackedUSD="props">
|
||||||
|
<q-td :props="props" class="text-right">
|
||||||
|
{{ formatMoney(props.row.PackedUSD) }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
|
||||||
|
{{ store.error }}
|
||||||
|
</q-banner>
|
||||||
|
</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, watch } from 'vue'
|
||||||
|
import { Dialog, useQuasar } from 'quasar'
|
||||||
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
import { useOrderBulkCloseStore } from 'src/stores/orderBulkCloseStore'
|
||||||
|
|
||||||
|
const $q = useQuasar()
|
||||||
|
const store = useOrderBulkCloseStore()
|
||||||
|
const { canUpdate } = usePermission()
|
||||||
|
const canUpdateOrder = canUpdate('order')
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: 'select', label: '', field: 'select', align: 'center' },
|
||||||
|
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true },
|
||||||
|
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'left', sortable: true },
|
||||||
|
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true },
|
||||||
|
{ name: 'CurrAccDescription', label: 'Cari Adı', field: 'CurrAccDescription', align: 'left', sortable: true },
|
||||||
|
{ name: 'DocCurrencyCode', label: 'PB', field: 'DocCurrencyCode', align: 'center', sortable: true },
|
||||||
|
{ name: 'TotalAmountUSD', label: 'Toplam USD', field: 'TotalAmountUSD', align: 'right', sortable: true },
|
||||||
|
{ name: 'PackedUSD', label: 'Paket USD', field: 'PackedUSD', align: 'right', sortable: true },
|
||||||
|
{ name: 'PackedRatePct', label: 'Paket %', field: 'PackedRatePct', align: 'right', sortable: true },
|
||||||
|
{ name: 'Description', label: 'Açıklama', field: 'Description', align: 'left' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const allSelected = computed(() => {
|
||||||
|
if (!store.totalCount) return false
|
||||||
|
return store.orders.every((o) => store.isSelected(o.OrderNumber))
|
||||||
|
})
|
||||||
|
|
||||||
|
let searchTimer = null
|
||||||
|
watch(
|
||||||
|
() => store.search,
|
||||||
|
() => {
|
||||||
|
clearTimeout(searchTimer)
|
||||||
|
searchTimer = setTimeout(() => {
|
||||||
|
if (canUpdateOrder.value) {
|
||||||
|
store.fetchOrders()
|
||||||
|
}
|
||||||
|
}, 350)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function toggleSelectAll (checked) {
|
||||||
|
const orderNumbers = store.orders.map((o) => o.OrderNumber)
|
||||||
|
store.toggleSelectMany(orderNumbers, checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate (value) {
|
||||||
|
if (!value) return ''
|
||||||
|
const [y, m, d] = String(value).split('-')
|
||||||
|
if (!y || !m || !d) return value
|
||||||
|
return `${d}.${m}.${y}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPct (value) {
|
||||||
|
return `${Number(value || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} %`
|
||||||
|
}
|
||||||
|
|
||||||
|
function packRateClass (value) {
|
||||||
|
const pct = Number(value || 0)
|
||||||
|
if (pct <= 50) return 'pack-rate-danger'
|
||||||
|
if (pct < 100) return 'pack-rate-warn'
|
||||||
|
return 'pack-rate-ok'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoney (value) {
|
||||||
|
return Number(value || 0).toLocaleString('tr-TR', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBulkClose () {
|
||||||
|
if (!store.selectedCount) return
|
||||||
|
|
||||||
|
Dialog.create({
|
||||||
|
title: 'Toplu Kapatma',
|
||||||
|
message: `${store.selectedCount} sipariş kapatılacak. Onaylıyor musunuz?`,
|
||||||
|
cancel: true,
|
||||||
|
persistent: true,
|
||||||
|
ok: { color: 'negative', label: 'Kapat' }
|
||||||
|
}).onOk(async () => {
|
||||||
|
try {
|
||||||
|
const { affected } = await store.closeSelectedOrders()
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: `${affected} sipariş başarıyla kapatıldı.`,
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
await store.fetchOrders()
|
||||||
|
} catch {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: store.error || 'Toplu kapatma başarısız',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (canUpdateOrder.value) {
|
||||||
|
store.fetchOrders()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bulk-close-page {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-filter-bar {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-filter-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-search {
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 520px;
|
||||||
|
flex: 1 1 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-summary {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
min-width: 140px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-table :deep(.q-table thead th) {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-table :deep(.q-table tbody td) {
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-rate-danger {
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-rate-warn {
|
||||||
|
color: #8a6d00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-rate-ok {
|
||||||
|
color: #1f7a4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.bulk-summary {
|
||||||
|
margin-left: 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
ui/src/pages/RoleDepartmentPermissionGateway.vue
Normal file
55
ui/src/pages/RoleDepartmentPermissionGateway.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<q-page v-if="canUpdateUser" class="perm-gateway flex flex-center column">
|
||||||
|
<div class="text-h5 text-primary q-mb-xl">
|
||||||
|
Rol + Departman Yetkileri
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row q-gutter-lg q-mt-md">
|
||||||
|
<q-btn
|
||||||
|
color="secondary"
|
||||||
|
icon="folder_open"
|
||||||
|
label="MEVCUT YETKİLERİ GÖSTER"
|
||||||
|
@click="goList"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
icon="add_circle"
|
||||||
|
label="YETKİ EKLE / GÜNCELLE"
|
||||||
|
@click="goEditor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
|
||||||
|
<q-page v-else class="q-pa-md flex flex-center">
|
||||||
|
<div class="text-negative text-subtitle1">
|
||||||
|
Bu modüle erişim yetkiniz yok.
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { canUpdate } = usePermission()
|
||||||
|
const canUpdateUser = canUpdate('user')
|
||||||
|
|
||||||
|
function goList () {
|
||||||
|
router.push({ name: 'role-dept-permissions-list' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function goEditor () {
|
||||||
|
router.push({
|
||||||
|
name: 'role-dept-permissions-editor',
|
||||||
|
query: { mode: 'new' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.perm-gateway {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
591
ui/src/pages/RoleDepartmentPermissionList.vue
Normal file
591
ui/src/pages/RoleDepartmentPermissionList.vue
Normal file
@@ -0,0 +1,591 @@
|
|||||||
|
<template>
|
||||||
|
<q-page v-if="canUpdateUser" class="rdp-list-page">
|
||||||
|
<div class="rdp-filter-bar">
|
||||||
|
<div class="rdp-filter-row">
|
||||||
|
<q-input
|
||||||
|
v-model="store.filters.search"
|
||||||
|
class="rdp-filter-input rdp-search"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
clearable
|
||||||
|
debounce="300"
|
||||||
|
label="Arama (Rol / Departman)"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<div class="rdp-filter-actions">
|
||||||
|
<q-btn
|
||||||
|
label="Temizle"
|
||||||
|
icon="clear"
|
||||||
|
color="grey-7"
|
||||||
|
flat
|
||||||
|
:disable="store.loading"
|
||||||
|
@click="clearFilters"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="Yenile"
|
||||||
|
icon="refresh"
|
||||||
|
color="primary"
|
||||||
|
:loading="store.loading"
|
||||||
|
@click="store.fetchRows"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rdp-config-menus">
|
||||||
|
<q-btn-dropdown
|
||||||
|
color="secondary"
|
||||||
|
outline
|
||||||
|
icon="view_module"
|
||||||
|
label="Modüller"
|
||||||
|
:auto-close="false"
|
||||||
|
>
|
||||||
|
<q-list dense class="rdp-menu-list">
|
||||||
|
<q-item clickable @click="selectAllModules">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item
|
||||||
|
v-for="m in store.modules"
|
||||||
|
:key="m.value"
|
||||||
|
clickable
|
||||||
|
@click="onModuleRowClick(m.value)"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
:model-value="isModuleSelected(m.value)"
|
||||||
|
dense
|
||||||
|
@update:model-value="(v) => toggleModule(m.value, v)"
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ m.label }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
|
||||||
|
<q-btn-dropdown
|
||||||
|
color="secondary"
|
||||||
|
outline
|
||||||
|
icon="tune"
|
||||||
|
:label="`Aksiyonlar (${activeModuleLabel})`"
|
||||||
|
:disable="!activeModuleCode"
|
||||||
|
:auto-close="false"
|
||||||
|
>
|
||||||
|
<q-list dense class="rdp-menu-list">
|
||||||
|
<q-item clickable @click="selectAllActionsForActive">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item
|
||||||
|
v-for="a in actionsForActiveModule"
|
||||||
|
:key="`${activeModuleCode}:${a}`"
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
:model-value="isActionSelected(activeModuleCode, a)"
|
||||||
|
dense
|
||||||
|
@update:model-value="(v) => toggleAction(activeModuleCode, a, v)"
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>{{ actionLabel(a) }}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rdp-summary">
|
||||||
|
<span>Toplam Kayıt: <strong>{{ store.totalCount }}</strong></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-table
|
||||||
|
title="Rol + Departman Yetki Setleri"
|
||||||
|
class="rdp-table"
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
dense
|
||||||
|
row-key="row_key"
|
||||||
|
:rows="tableRows"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="store.loading"
|
||||||
|
no-data-label="Kayıt bulunamadı"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
hide-bottom
|
||||||
|
>
|
||||||
|
<template #body-cell="props">
|
||||||
|
<q-td :props="props" :class="props.col.classes">
|
||||||
|
<template v-if="props.col.name === 'open'">
|
||||||
|
<div class="text-center">
|
||||||
|
<q-btn
|
||||||
|
icon="open_in_new"
|
||||||
|
color="primary"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
dense
|
||||||
|
@click="openEditor(props.row)"
|
||||||
|
>
|
||||||
|
<q-tooltip>Yetki setini aç</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="isPermissionColumn(props.col.name)">
|
||||||
|
<div class="text-center">
|
||||||
|
<q-checkbox
|
||||||
|
:model-value="Boolean(props.value)"
|
||||||
|
disable
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
{{ props.value }}
|
||||||
|
</template>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
|
||||||
|
Hata: {{ store.error }}
|
||||||
|
</q-banner>
|
||||||
|
</q-page>
|
||||||
|
|
||||||
|
<q-page v-else class="q-pa-md flex flex-center">
|
||||||
|
<div class="text-negative text-subtitle1">
|
||||||
|
Bu modüle erişim yetkiniz yok.
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
import { useRoleDeptPermissionListStore } from 'src/stores/RoleDeptPermissionListStore'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const $q = useQuasar()
|
||||||
|
const store = useRoleDeptPermissionListStore()
|
||||||
|
const { canUpdate } = usePermission()
|
||||||
|
const canUpdateUser = canUpdate('user')
|
||||||
|
|
||||||
|
const selectedModules = ref([])
|
||||||
|
const selectedActionsByModule = ref({})
|
||||||
|
const activeModuleCode = ref('')
|
||||||
|
|
||||||
|
const actionLabelMap = {
|
||||||
|
update: 'Güncelleme',
|
||||||
|
view: 'Görüntüleme',
|
||||||
|
insert: 'Ekleme',
|
||||||
|
export: 'Çıktı',
|
||||||
|
write: 'Yazma',
|
||||||
|
read: 'Okuma',
|
||||||
|
delete: 'Silme',
|
||||||
|
login: 'Giriş',
|
||||||
|
refresh: 'Yenileme',
|
||||||
|
'user.update': 'Kullanıcı Güncelle'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixedColumns = [
|
||||||
|
{
|
||||||
|
name: 'open',
|
||||||
|
label: '',
|
||||||
|
field: 'open',
|
||||||
|
align: 'center',
|
||||||
|
sortable: false,
|
||||||
|
classes: 'freeze-col freeze-1',
|
||||||
|
headerClasses: 'freeze-col freeze-1',
|
||||||
|
style: 'width:56px; min-width:56px; max-width:56px',
|
||||||
|
headerStyle: 'width:56px; min-width:56px; max-width:56px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'role_title',
|
||||||
|
label: 'Rol',
|
||||||
|
field: 'role_title',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
classes: 'freeze-col freeze-2',
|
||||||
|
headerClasses: 'freeze-col freeze-2',
|
||||||
|
style: 'width:220px; min-width:220px; max-width:220px',
|
||||||
|
headerStyle: 'width:220px; min-width:220px; max-width:220px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'department_title',
|
||||||
|
label: 'Departman',
|
||||||
|
field: 'department_title',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
classes: 'freeze-col freeze-3',
|
||||||
|
headerClasses: 'freeze-col freeze-3',
|
||||||
|
style: 'width:220px; min-width:220px; max-width:220px',
|
||||||
|
headerStyle: 'width:220px; min-width:220px; max-width:220px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'department_code',
|
||||||
|
label: 'Departman Kodu',
|
||||||
|
field: 'department_code',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
style: 'width:140px; min-width:140px; max-width:140px',
|
||||||
|
headerStyle: 'width:140px; min-width:140px; max-width:140px'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const moduleLabelMap = computed(() => {
|
||||||
|
const map = {}
|
||||||
|
;(store.modules || []).forEach((m) => {
|
||||||
|
map[m.value] = m.label || m.value
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
const actionsByModule = computed(() => {
|
||||||
|
const map = {}
|
||||||
|
;(store.moduleActions || []).forEach((x) => {
|
||||||
|
if (!map[x.module_code]) map[x.module_code] = []
|
||||||
|
if (!map[x.module_code].includes(x.action)) {
|
||||||
|
map[x.module_code].push(x.action)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.keys(map).forEach((k) => map[k].sort())
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeModuleLabel = computed(() => {
|
||||||
|
if (!activeModuleCode.value) return 'Seçim'
|
||||||
|
return moduleLabelMap.value[activeModuleCode.value] || activeModuleCode.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const actionsForActiveModule = computed(() => {
|
||||||
|
if (!activeModuleCode.value) return []
|
||||||
|
return actionsByModule.value[activeModuleCode.value] || []
|
||||||
|
})
|
||||||
|
|
||||||
|
function actionLabel (action) {
|
||||||
|
const key = String(action || '').toLowerCase().trim()
|
||||||
|
return actionLabelMap[key] || key
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncSelections () {
|
||||||
|
const availableModules = (store.modules || []).map((m) => m.value)
|
||||||
|
if (!availableModules.length) {
|
||||||
|
selectedModules.value = []
|
||||||
|
selectedActionsByModule.value = {}
|
||||||
|
activeModuleCode.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = selectedModules.value.filter((m) => availableModules.includes(m))
|
||||||
|
selectedModules.value = selected.length ? selected : [...availableModules]
|
||||||
|
|
||||||
|
if (!selectedModules.value.includes(activeModuleCode.value)) {
|
||||||
|
activeModuleCode.value = selectedModules.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = {}
|
||||||
|
selectedModules.value.forEach((m) => {
|
||||||
|
const allActions = actionsByModule.value[m] || []
|
||||||
|
const prev = selectedActionsByModule.value[m] || []
|
||||||
|
const filtered = prev.filter((a) => allActions.includes(a))
|
||||||
|
next[m] = filtered.length ? filtered : [...allActions]
|
||||||
|
})
|
||||||
|
selectedActionsByModule.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [store.modules, store.moduleActions],
|
||||||
|
() => {
|
||||||
|
syncSelections()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function isModuleSelected (moduleCode) {
|
||||||
|
return selectedModules.value.includes(moduleCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleModule (moduleCode, checked) {
|
||||||
|
const set = new Set(selectedModules.value)
|
||||||
|
if (checked) {
|
||||||
|
set.add(moduleCode)
|
||||||
|
} else {
|
||||||
|
set.delete(moduleCode)
|
||||||
|
}
|
||||||
|
selectedModules.value = [...set]
|
||||||
|
if (!selectedModules.value.length) {
|
||||||
|
selectedModules.value = [moduleCode]
|
||||||
|
}
|
||||||
|
if (!selectedModules.value.includes(activeModuleCode.value)) {
|
||||||
|
activeModuleCode.value = selectedModules.value[0]
|
||||||
|
}
|
||||||
|
syncSelections()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onModuleRowClick (moduleCode) {
|
||||||
|
activeModuleCode.value = moduleCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllModules () {
|
||||||
|
selectedModules.value = (store.modules || []).map((m) => m.value)
|
||||||
|
syncSelections()
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionSelected (moduleCode, action) {
|
||||||
|
return (selectedActionsByModule.value[moduleCode] || []).includes(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAction (moduleCode, action, checked) {
|
||||||
|
const current = new Set(selectedActionsByModule.value[moduleCode] || [])
|
||||||
|
if (checked) {
|
||||||
|
current.add(action)
|
||||||
|
} else {
|
||||||
|
current.delete(action)
|
||||||
|
}
|
||||||
|
if (current.size === 0) {
|
||||||
|
current.add(action)
|
||||||
|
}
|
||||||
|
selectedActionsByModule.value = {
|
||||||
|
...selectedActionsByModule.value,
|
||||||
|
[moduleCode]: [...current]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllActionsForActive () {
|
||||||
|
if (!activeModuleCode.value) return
|
||||||
|
selectedActionsByModule.value = {
|
||||||
|
...selectedActionsByModule.value,
|
||||||
|
[activeModuleCode.value]: [...actionsForActiveModule.value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionColumns = computed(() => {
|
||||||
|
const cols = []
|
||||||
|
selectedModules.value.forEach((m) => {
|
||||||
|
const actions = selectedActionsByModule.value[m] || []
|
||||||
|
actions.forEach((a) => {
|
||||||
|
const key = `${m}|${a}`
|
||||||
|
cols.push({
|
||||||
|
name: `perm_${key}`,
|
||||||
|
label: `${moduleLabelMap.value[m] || m}\n${actionLabel(a)}`,
|
||||||
|
field: (row) => Boolean(row.module_flags?.[key]),
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
style: 'width:150px; min-width:150px; max-width:150px',
|
||||||
|
headerStyle: 'width:150px; min-width:150px; max-width:150px; white-space:pre-line; line-height:1.15'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return cols
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
...fixedColumns,
|
||||||
|
...permissionColumns.value
|
||||||
|
])
|
||||||
|
|
||||||
|
const tableRows = computed(() =>
|
||||||
|
(store.rows || []).map((r) => ({
|
||||||
|
...r,
|
||||||
|
row_key: `${r.role_id}:${r.department_code}`
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
function isPermissionColumn (name) {
|
||||||
|
return String(name || '').startsWith('perm_')
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchTimer = null
|
||||||
|
watch(
|
||||||
|
() => store.filters.search,
|
||||||
|
() => {
|
||||||
|
clearTimeout(searchTimer)
|
||||||
|
searchTimer = setTimeout(() => {
|
||||||
|
if (canUpdateUser.value) {
|
||||||
|
store.fetchRows()
|
||||||
|
}
|
||||||
|
}, 350)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function openEditor (row) {
|
||||||
|
if (!row?.role_id || !row?.department_code) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Kayıt bilgisi eksik' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
name: 'role-dept-permissions-editor',
|
||||||
|
query: {
|
||||||
|
mode: 'edit',
|
||||||
|
roleId: String(row.role_id),
|
||||||
|
deptCode: String(row.department_code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilters () {
|
||||||
|
store.filters.search = ''
|
||||||
|
store.fetchRows()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (canUpdateUser.value) {
|
||||||
|
await store.fetchRows()
|
||||||
|
syncSelections()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rdp-list-page {
|
||||||
|
--rdp-header-h: 56px;
|
||||||
|
--rdp-filter-h: 96px;
|
||||||
|
height: calc(100vh - var(--rdp-header-h));
|
||||||
|
overflow: auto;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-filter-bar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 600;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||||
|
min-height: var(--rdp-filter-h);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-filter-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-filter-input {
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-search {
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 520px;
|
||||||
|
flex: 1 1 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-config-menus {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-menu-list {
|
||||||
|
min-width: 260px;
|
||||||
|
max-height: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-summary {
|
||||||
|
margin-left: auto;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4b5563;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.q-table__middle) {
|
||||||
|
overflow: visible !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.q-table thead th) {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--rdp-filter-h);
|
||||||
|
z-index: 500;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.q-table tbody td) {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.q-checkbox__inner) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.freeze-col) {
|
||||||
|
position: sticky;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 510;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(thead .freeze-col) {
|
||||||
|
z-index: 520;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.freeze-1) {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.freeze-2) {
|
||||||
|
left: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table :deep(.freeze-3) {
|
||||||
|
left: 276px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1400px) {
|
||||||
|
.rdp-filter-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-filter-actions {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-config-menus {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-summary {
|
||||||
|
min-width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
93
ui/src/stores/RoleDeptPermissionListStore.js
Normal file
93
ui/src/stores/RoleDeptPermissionListStore.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import api from 'src/services/api'
|
||||||
|
|
||||||
|
let lastRequestId = 0
|
||||||
|
|
||||||
|
export const useRoleDeptPermissionListStore = defineStore('roleDeptPermissionList', {
|
||||||
|
state: () => ({
|
||||||
|
modules: [],
|
||||||
|
moduleActions: [],
|
||||||
|
rows: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
filters: {
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
totalCount (state) {
|
||||||
|
return state.rows.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async fetchRows () {
|
||||||
|
const rid = ++lastRequestId
|
||||||
|
this.loading = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const search = String(this.filters.search || '').trim()
|
||||||
|
const params = {}
|
||||||
|
if (search) params.search = search
|
||||||
|
|
||||||
|
const res = await api.get('/role-dept-permissions/list', { params })
|
||||||
|
|
||||||
|
if (rid !== lastRequestId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = res?.data || {}
|
||||||
|
this.modules = Array.isArray(payload?.modules)
|
||||||
|
? payload.modules.map((m) => ({
|
||||||
|
value: String(m.value || '').toLowerCase().trim(),
|
||||||
|
label: String(m.label || '')
|
||||||
|
})).filter((m) => m.value)
|
||||||
|
: []
|
||||||
|
|
||||||
|
this.moduleActions = Array.isArray(payload?.module_actions)
|
||||||
|
? payload.module_actions.map((a) => ({
|
||||||
|
module_code: String(a.module_code || '').toLowerCase().trim(),
|
||||||
|
action: String(a.action || '').toLowerCase().trim()
|
||||||
|
})).filter((a) => a.module_code && a.action)
|
||||||
|
: []
|
||||||
|
|
||||||
|
const rawRows = Array.isArray(payload?.rows)
|
||||||
|
? payload.rows
|
||||||
|
: Array.isArray(res?.data) ? res.data : []
|
||||||
|
|
||||||
|
this.rows = rawRows.map((r) => {
|
||||||
|
const rawFlags = (r?.module_flags && typeof r.module_flags === 'object')
|
||||||
|
? r.module_flags
|
||||||
|
: {}
|
||||||
|
const flags = {}
|
||||||
|
Object.keys(rawFlags).forEach((k) => {
|
||||||
|
flags[String(k).toLowerCase().trim()] = Boolean(rawFlags[k])
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
role_id: Number(r.role_id || 0),
|
||||||
|
role_title: r.role_title || '',
|
||||||
|
department_code: r.department_code || '',
|
||||||
|
department_title: r.department_title || '',
|
||||||
|
module_flags: flags
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
if (rid !== lastRequestId) return
|
||||||
|
this.modules = []
|
||||||
|
this.moduleActions = []
|
||||||
|
this.rows = []
|
||||||
|
this.error =
|
||||||
|
err?.response?.data ||
|
||||||
|
err?.message ||
|
||||||
|
'Yetki listesi alınamadı'
|
||||||
|
} finally {
|
||||||
|
if (rid === lastRequestId) {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
127
ui/src/stores/orderBulkCloseStore.js
Normal file
127
ui/src/stores/orderBulkCloseStore.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import api from 'src/services/api'
|
||||||
|
|
||||||
|
export const useOrderBulkCloseStore = defineStore('orderBulkClose', {
|
||||||
|
state: () => ({
|
||||||
|
orders: [],
|
||||||
|
selectedOrderNumbers: [],
|
||||||
|
search: '',
|
||||||
|
loading: false,
|
||||||
|
closing: false,
|
||||||
|
error: null
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
selectedCount (state) {
|
||||||
|
return state.selectedOrderNumbers.length
|
||||||
|
},
|
||||||
|
|
||||||
|
totalCount (state) {
|
||||||
|
return state.orders.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
isSelected (orderNumber) {
|
||||||
|
return this.selectedOrderNumbers.includes(orderNumber)
|
||||||
|
},
|
||||||
|
|
||||||
|
setSelected (orderNumber, selected) {
|
||||||
|
if (!orderNumber) return
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
if (!this.selectedOrderNumbers.includes(orderNumber)) {
|
||||||
|
this.selectedOrderNumbers.push(orderNumber)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedOrderNumbers = this.selectedOrderNumbers.filter(
|
||||||
|
(n) => n !== orderNumber
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleSelectMany (orderNumbers, selected) {
|
||||||
|
const clean = Array.from(
|
||||||
|
new Set((orderNumbers || []).filter(Boolean))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
const current = new Set(this.selectedOrderNumbers)
|
||||||
|
clean.forEach((n) => current.add(n))
|
||||||
|
this.selectedOrderNumbers = Array.from(current)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const block = new Set(clean)
|
||||||
|
this.selectedOrderNumbers = this.selectedOrderNumbers.filter(
|
||||||
|
(n) => !block.has(n)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSelection () {
|
||||||
|
this.selectedOrderNumbers = []
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchOrders () {
|
||||||
|
this.loading = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const search = String(this.search || '').trim()
|
||||||
|
const params = {}
|
||||||
|
if (search) params.search = search
|
||||||
|
|
||||||
|
const res = await api.get('/orders/close-ready', { params })
|
||||||
|
this.orders = Array.isArray(res?.data) ? res.data : []
|
||||||
|
|
||||||
|
// new list disinda kalan secimleri temizle
|
||||||
|
const available = new Set(this.orders.map((o) => o.OrderNumber))
|
||||||
|
this.selectedOrderNumbers = this.selectedOrderNumbers.filter(
|
||||||
|
(n) => available.has(n)
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
this.orders = []
|
||||||
|
this.error =
|
||||||
|
err?.response?.data ||
|
||||||
|
err?.message ||
|
||||||
|
'Siparişler alınamadı'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async closeSelectedOrders () {
|
||||||
|
if (!this.selectedOrderNumbers.length) {
|
||||||
|
return { affected: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closing = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
order_numbers: this.selectedOrderNumbers
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.post('/orders/bulk-close', payload)
|
||||||
|
const affected = Number(res?.data?.affected || 0)
|
||||||
|
|
||||||
|
// local optimizasyon: secilenleri listeden dus
|
||||||
|
const selected = new Set(this.selectedOrderNumbers)
|
||||||
|
this.orders = this.orders.filter((o) => !selected.has(o.OrderNumber))
|
||||||
|
this.selectedOrderNumbers = []
|
||||||
|
|
||||||
|
return { affected }
|
||||||
|
} catch (err) {
|
||||||
|
this.error =
|
||||||
|
err?.response?.data ||
|
||||||
|
err?.message ||
|
||||||
|
'Toplu kapatma başarısız'
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
this.closing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user