Files
bssapp/ui/src/pages/OrderList.vue
MEHMETKECECI 03d6c61587 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	ui/src/pages/OrderList.vue
2026-02-13 15:17:23 +03:00

565 lines
15 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="canReadOrder" class="ol-page">
<div class="ol-filter-bar">
<div class="ol-filter-row">
<q-input
v-model="store.filters.search"
class="ol-filter-input ol-search"
dense
filled
debounce="300"
clearable
label="Arama (Sipariş No / Cari / Açıklama)"
>
<template #append>
<q-icon name="search" />
</template>
</q-input>
<q-input
v-model="store.filters.CurrAccCode"
class="ol-filter-input"
dense
filled
clearable
label="Cari Kodu"
/>
<q-input
v-model="store.filters.OrderDate"
class="ol-filter-input"
dense
filled
type="date"
label="Sipariş Tarihi"
/>
<div class="ol-filter-actions">
<q-btn
label="Temizle"
icon="clear"
color="grey-7"
flat
:disable="store.loading"
@click="clearFilters"
>
<q-tooltip>Tüm filtreleri temizle</q-tooltip>
</q-btn>
<q-btn
label="Yenile"
color="primary"
icon="refresh"
:loading="store.loading"
@click="store.fetchOrders"
/>
<q-btn
label="Excel'e Aktar"
icon="download"
color="primary"
outline
:disable="store.loading || store.filteredOrders.length === 0"
@click="exportExcel"
/>
</div>
<div class="ol-filter-total">
<div class="ol-total-line">
<span class="ol-total-label">Toplam USD:</span>
<strong class="ol-total-value">
{{ store.totalVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}
</strong>
</div>
<div class="ol-total-line">
<span class="ol-total-label">Paketlenen USD:</span>
<strong class="ol-total-value">
{{ store.totalPackedVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}
</strong>
</div>
<div class="ol-total-line">
<span class="ol-total-label">Paketlenme %:</span>
<strong class="ol-total-value" :class="packRateClass(store.packedVisibleRatePct)">
{{ store.packedVisibleRatePct.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}
</strong>
</div>
</div>
</div>
</div>
<q-table
title="Mevcut Siparişler"
class="ol-table"
flat
bordered
dense
separator="cell"
row-key="OrderHeaderID"
:rows="store.filteredOrders"
:columns="columns"
:loading="store.loading"
no-data-label="Sipariş bulunamadı"
:rows-per-page-options="[0]"
hide-bottom
>
<template #body-cell-IsCreditableConfirmed="props">
<q-td :props="props" class="text-center q-gutter-sm">
<q-btn
icon="picture_as_pdf"
color="red"
flat
round
dense
@click="printPDF(props.row)"
>
<q-tooltip>Siparişi PDF olarak </q-tooltip>
</q-btn>
<q-icon
:name="props.row.IsCreditableConfirmed ? 'check_circle' : 'cancel'"
:color="props.row.IsCreditableConfirmed ? 'green' : 'red'"
size="20px"
>
<q-tooltip>
{{ props.row.IsCreditableConfirmed ? 'Onaylı' : 'Onaysız' }}
</q-tooltip>
</q-icon>
</q-td>
</template>
<template #body-cell-OrderDate="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.OrderDate) }}
</q-td>
</template>
<template #body-cell-CreditableConfirmedDate="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.CreditableConfirmedDate) }}
</q-td>
</template>
<template #body-cell-PackedAmount="props">
<q-td :props="props" class="text-right text-weight-medium">
{{ Number(props.row.PackedAmount || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
{{ props.row.DocCurrencyCode }}
</q-td>
</template>
<template #body-cell-PackedUSD="props">
<q-td :props="props" class="text-right text-weight-medium">
{{ Number(props.row.PackedUSD || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }} USD
</q-td>
</template>
<template #body-cell-PackedRatePct="props">
<q-td
:props="props"
class="text-right text-weight-bold ol-pack-rate-cell"
:class="packRateClass(props.row.PackedRatePct)"
>
{{ Number(props.row.PackedRatePct || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }} %
</q-td>
</template>
<template #body-cell-CurrAccDescription="props">
<q-td :props="props" class="ol-col-cari">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-MusteriTemsilcisi="props">
<q-td :props="props" class="ol-col-short">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-Piyasa="props">
<q-td :props="props" class="ol-col-short">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-Description="props">
<q-td :props="props" class="ol-col-desc">
<div class="ol-col-multiline">{{ props.value }}</div>
<q-tooltip v-if="props.value">
{{ props.value }}
</q-tooltip>
</q-td>
</template>
<template #body-cell-select="props">
<q-td :props="props" class="text-center">
<q-btn
icon="open_in_new"
color="primary"
flat
round
dense
@click="selectOrder(props.row)"
>
<q-tooltip>Siparişi </q-tooltip>
</q-btn>
</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 { onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useQuasar } from 'quasar'
import { useOrderListStore } from 'src/stores/OrdernewListStore'
import { useAuthStore } from 'src/stores/authStore'
import { usePermission } from 'src/composables/usePermission'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
const router = useRouter()
const $q = useQuasar()
const store = useOrderListStore()
let searchTimer = null
watch(
() => store.filters.search,
() => {
clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
store.fetchOrders()
}, 400)
}
)
function exportExcel () {
const auth = useAuthStore()
if (!auth?.token) {
$q.notify({
type: 'negative',
message: 'Oturum bulunamadı',
position: 'top-right'
})
return
}
const params = new URLSearchParams({
search: store.filters.search || '',
CurrAccCode: store.filters.CurrAccCode || '',
OrderDate: store.filters.OrderDate || ''
})
const url = `http://localhost:8080/api/orders/export?${params.toString()}`
fetch(url, {
headers: {
Authorization: `Bearer ${auth.token}`
}
})
.then(res => res.blob())
.then(blob => {
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = 'siparis_listesi.xlsx'
link.click()
})
}
function formatDate (s) {
if (!s) return ''
const [y, m, d] = String(s).split('-')
if (!y || !m || !d) return s
return `${d}.${m}.${y}`
}
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'
}
const columns = [
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false },
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true, style: 'min-width:108px;white-space:nowrap', headerStyle: 'min-width:108px;white-space:nowrap' },
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'center', sortable: true, style: 'min-width:82px;white-space:nowrap', headerStyle: 'min-width:82px;white-space:nowrap' },
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true, style: 'min-width:82px;white-space:nowrap', headerStyle: 'min-width:82px;white-space:nowrap' },
{ name: 'CurrAccDescription', label: 'Cari Adı', field: 'CurrAccDescription', align: 'left', sortable: true, classes: 'ol-col-cari', headerClasses: 'ol-col-cari', style: 'width:160px;max-width:160px', headerStyle: 'width:160px;max-width:160px' },
{ name: 'MusteriTemsilcisi', label: 'Temsilci', field: 'MusteriTemsilcisi', align: 'left', sortable: true, classes: 'ol-col-short', headerClasses: 'ol-col-short', style: 'width:88px;max-width:88px', headerStyle: 'width:88px;max-width:88px' },
{ name: 'Piyasa', label: 'Piyasa', field: 'Piyasa', align: 'left', sortable: true, classes: 'ol-col-short', headerClasses: 'ol-col-short', style: 'width:72px;max-width:72px', headerStyle: 'width:72px;max-width:72px' },
{ name: 'CreditableConfirmedDate', label: 'Onay', field: 'CreditableConfirmedDate', align: 'center', sortable: true, style: 'min-width:86px;white-space:nowrap', headerStyle: 'min-width:86px;white-space:nowrap' },
{ name: 'DocCurrencyCode', label: 'PB', field: 'DocCurrencyCode', align: 'center', sortable: true, style: 'min-width:46px;white-space:nowrap', headerStyle: 'min-width:46px;white-space:nowrap' },
{
name: 'TotalAmount',
label: 'Tutar',
field: 'TotalAmount',
align: 'right',
sortable: true,
style: 'min-width:120px;white-space:nowrap',
headerStyle: 'min-width:120px;white-space:nowrap',
format: (val, row) => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' ' + row.DocCurrencyCode
},
{
name: 'TotalAmountUSD',
label: 'Tutar (USD)',
field: 'TotalAmountUSD',
align: 'right',
sortable: true,
style: 'min-width:120px;white-space:nowrap',
headerStyle: 'min-width:120px;white-space:nowrap',
format: val => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' USD'
},
{
name: 'PackedAmount',
label: 'Paketlenen',
field: 'PackedAmount',
align: 'right',
sortable: true,
style: 'min-width:120px;white-space:nowrap',
headerStyle: 'min-width:120px;white-space:nowrap',
format: (val, row) => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' ' + row.DocCurrencyCode
},
{
name: 'PackedUSD',
label: 'Paketlenen (USD)',
field: 'PackedUSD',
align: 'right',
sortable: true,
style: 'min-width:120px;white-space:nowrap',
headerStyle: 'min-width:120px;white-space:nowrap',
format: val => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' USD'
},
{
name: 'PackedRatePct',
label: 'Paketlenme %',
field: 'PackedRatePct',
align: 'right',
sortable: true,
classes: 'ol-pack-rate-cell',
headerClasses: 'ol-pack-rate-cell',
style: 'min-width:96px;white-space:nowrap',
headerStyle: 'min-width:96px;white-space:nowrap',
format: val => Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' %'
},
{ name: 'IsCreditableConfirmed', label: 'Durum', field: 'IsCreditableConfirmed', align: 'center', sortable: true },
{ name: 'Description', label: 'Açıklama', field: 'Description', align: 'left', sortable: false, classes: 'ol-col-desc', headerClasses: 'ol-col-desc', style: 'width:160px;max-width:160px', headerStyle: 'width:160px;max-width:160px' },
{ name: 'pdf', label: 'PDF', field: 'pdf', align: 'center', sortable: false }
]
function selectOrder (row) {
if (!row?.OrderHeaderID) {
$q.notify({ type: 'warning', message: 'OrderHeaderID bulunamadı' })
return
}
router.push({
name: 'order-edit',
params: { orderHeaderID: row.OrderHeaderID },
query: { mode: 'edit' }
})
}
async function printPDF (row) {
if (!row?.OrderHeaderID) return
const token = useAuthStore().token
const url = `http://localhost:8080/api/order/pdf/${row.OrderHeaderID}`
try {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` }
})
if (!res.ok) throw new Error()
const blob = await res.blob()
window.open(URL.createObjectURL(blob), '_blank')
} catch {
$q.notify({ type: 'negative', message: 'PDF yüklenemedi' })
}
}
function clearFilters () {
store.filters.search = ''
store.filters.CurrAccCode = ''
store.filters.OrderDate = ''
store.fetchOrders()
$q.notify({
type: 'info',
message: 'Filtreler temizlendi',
position: 'top-right'
})
}
onMounted(() => {
store.fetchOrders()
})
</script>
<style scoped>
.ol-page {
padding: 10px;
}
.ol-filter-bar {
margin-bottom: 8px;
}
.ol-filter-row {
display: flex;
flex-wrap: nowrap;
gap: 10px;
align-items: center;
}
.ol-filter-input {
min-width: 118px;
width: 136px;
flex: 0 0 136px;
}
.ol-search {
min-width: 240px;
max-width: 420px;
flex: 1 1 360px;
}
.ol-filter-actions {
display: flex;
gap: 8px;
flex-wrap: nowrap;
flex: 0 0 auto;
}
.ol-filter-total {
margin-left: auto;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
gap: 2px;
min-width: 250px;
line-height: 1.2;
flex: 0 0 auto;
align-self: center;
}
.ol-total-line {
display: flex;
align-items: baseline;
gap: 8px;
white-space: nowrap;
}
.ol-total-label {
font-size: 12px;
color: #4b5563;
}
.ol-total-value {
font-size: 13px;
}
.ol-table :deep(.q-table thead th) {
font-size: 11px;
padding: 4px 6px;
white-space: nowrap;
}
.ol-table :deep(.q-table tbody td) {
font-size: 11px;
padding: 3px 6px;
}
.ol-col-multiline {
display: block;
white-space: normal !important;
word-break: break-word;
line-height: 1.15;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
max-height: 2.35em;
}
.ol-col-cari {
max-width: 160px;
}
.ol-col-short {
max-width: 88px;
}
.ol-col-desc {
max-width: 160px;
}
.ol-pack-rate-cell {
font-weight: 700;
}
.pack-rate-danger {
color: #c62828;
}
.pack-rate-warn {
color: #8a6d00;
}
.pack-rate-ok {
color: #1f7a4f;
}
@media (max-width: 1440px) {
.ol-filter-row {
flex-wrap: wrap;
align-items: flex-start;
}
.ol-filter-actions {
flex-wrap: wrap;
}
.ol-filter-input {
flex: 1 1 140px;
}
.ol-filter-total {
min-width: 100%;
margin-left: 0;
align-items: flex-start;
margin-top: 6px;
}
.ol-total-line {
width: 100%;
justify-content: space-between;
}
}
</style>