297 lines
7.6 KiB
Vue
297 lines
7.6 KiB
Vue
<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>
|