Files
bssapp/ui/src/pages/OrderBulkClose.vue

297 lines
7.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>