692 lines
19 KiB
Vue
692 lines
19 KiB
Vue
<template>
|
||
<q-page v-if="canReadFinance" class="q-pa-md page-col statement-page">
|
||
|
||
<!-- 🔹 Cari Kod / İsim (sabit) -->
|
||
<div class="filter-sticky">
|
||
<q-select
|
||
v-model="selectedCari"
|
||
:options="filteredOptions"
|
||
label="Cari kod / isim"
|
||
filled
|
||
clearable
|
||
use-input
|
||
input-debounce="300"
|
||
@filter="filterCari"
|
||
emit-value
|
||
map-options
|
||
:loading="accountStore.loading"
|
||
option-value="value"
|
||
option-label="label"
|
||
behavior="menu"
|
||
:keep-selected="true"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 🔹 Filtre Alanı -->
|
||
<div class="filter-collapsible">
|
||
<div class="row items-center justify-between q-pa-sm bg-grey-2">
|
||
<div class="text-subtitle1">Filtreler</div>
|
||
<q-btn
|
||
dense flat round
|
||
:icon="filtersOpen ? 'expand_less' : 'expand_more'"
|
||
@click="filtersOpen = !filtersOpen"
|
||
/>
|
||
</div>
|
||
|
||
<q-slide-transition>
|
||
<div v-show="filtersOpen" class="q-pa-md bg-grey-1">
|
||
|
||
<!-- Tarih Aralığı -->
|
||
<div class="row q-col-gutter-sm q-mb-md">
|
||
<div class="col-12 col-sm-6">
|
||
<q-input
|
||
v-model="dateFrom"
|
||
label="Tarih aralığı - başlangıç"
|
||
filled clearable readonly
|
||
>
|
||
<template #append>
|
||
<q-icon name="event" class="cursor-pointer">
|
||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||
<q-date
|
||
v-model="dateFrom"
|
||
mask="YYYY-MM-DD"
|
||
locale="tr-TR"
|
||
:options="isValidFromDate"
|
||
/>
|
||
</q-popup-proxy>
|
||
</q-icon>
|
||
</template>
|
||
</q-input>
|
||
</div>
|
||
|
||
<div class="col-12 col-sm-6">
|
||
<q-input
|
||
v-model="dateTo"
|
||
label="Tarih aralığı - bitiş"
|
||
filled clearable readonly
|
||
>
|
||
<template #append>
|
||
<q-icon name="event" class="cursor-pointer">
|
||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||
<q-date
|
||
v-model="dateTo"
|
||
mask="YYYY-MM-DD"
|
||
locale="tr-TR"
|
||
:options="isValidToDate"
|
||
/>
|
||
</q-popup-proxy>
|
||
</q-icon>
|
||
</template>
|
||
</q-input>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Parasal İşlem Tipi -->
|
||
<q-select
|
||
v-model="selectedMonType"
|
||
:options="monetaryTypeOptions"
|
||
label="Parasal İşlem Tipi"
|
||
emit-value
|
||
map-options
|
||
filled
|
||
class="q-mb-md"
|
||
/>
|
||
|
||
<!-- Filtre / Sıfırla Butonları -->
|
||
<div class="row q-col-gutter-md items-center">
|
||
<div class="col-auto">
|
||
<q-btn
|
||
color="primary"
|
||
icon="filter_alt"
|
||
label="Filtrele"
|
||
@click="onFilterClick"
|
||
/>
|
||
</div>
|
||
<div class="col-auto">
|
||
<q-btn
|
||
flat
|
||
color="grey-8"
|
||
icon="restart_alt"
|
||
label="Sıfırla"
|
||
@click="resetFilters"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</q-slide-transition>
|
||
</div>
|
||
|
||
<!-- 🔹 Tablo Alanı -->
|
||
<div class="table-scroll">
|
||
|
||
<!-- Toggle butonları (sticky üst bar) -->
|
||
<div class="sticky-bar row justify-between items-center q-pa-sm bg-grey-1">
|
||
|
||
<!-- Sol buton: CARİ BİLGİ DETAY göster/gizle -->
|
||
<q-btn
|
||
flat
|
||
color="primary"
|
||
icon="view_column"
|
||
:label="showLeftCols ? 'CARİ BİLGİ DETAY Gizle' : 'CARİ BİLGİ DETAY Sütunu Göster'"
|
||
@click="toggleLeftCols"
|
||
/>
|
||
|
||
<!-- Sağ taraftaki buton grubu -->
|
||
<div class="row items-center q-gutter-sm">
|
||
|
||
<!-- Tüm detayları aç/kapat -->
|
||
<q-btn
|
||
flat
|
||
color="secondary"
|
||
icon="list"
|
||
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
||
@click="toggleAllDetails"
|
||
/>
|
||
|
||
<!-- ✅ PDF Yazdır Dropdown -->
|
||
<q-btn-dropdown
|
||
v-if="canExportFinance"
|
||
flat
|
||
color="red"
|
||
icon="picture_as_pdf"
|
||
label="Yazdır"
|
||
>
|
||
<q-list style="min-width: 200px">
|
||
<!-- 1. Seçenek -->
|
||
<q-item clickable v-close-popup @click="handleDownload" >
|
||
<q-item-section class="text-primary">
|
||
Detaylı Cari Ekstre Yazdır
|
||
</q-item-section>
|
||
</q-item>
|
||
|
||
<!-- 2. Seçenek -->
|
||
<q-item clickable v-close-popup @click="CurrheadDownload">
|
||
<q-item-section class="text-secondary">
|
||
Cari Hesap Ekstresi Yazdır
|
||
</q-item-section>
|
||
</q-item>
|
||
</q-list>
|
||
</q-btn-dropdown>
|
||
|
||
</div> <!-- sağdaki row kapandı -->
|
||
</div> <!-- sticky-bar kapandı -->
|
||
|
||
<!-- Ana Tablo -->
|
||
<q-table
|
||
class="sticky-table statement-table"
|
||
title="Hareketler"
|
||
:rows="statementheaderStore.groupedRows"
|
||
:columns="columns"
|
||
:visible-columns="visibleColumns"
|
||
:row-key="rowKeyFn"
|
||
|
||
flat
|
||
bordered
|
||
dense
|
||
hide-bottom
|
||
wrap-cells
|
||
:rows-per-page-options="[0]"
|
||
:loading="statementheaderStore.loading"
|
||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||
>
|
||
<template #body="props">
|
||
|
||
<!-- Grup başlığı satırı -->
|
||
<q-tr
|
||
v-if="props.row._type === 'group'"
|
||
class="group-row bg-grey-3 text-weight-bold"
|
||
>
|
||
<q-td colspan="100%" class="q-pa-sm">
|
||
<div class="row items-center justify-between">
|
||
<div class="row items-center">
|
||
<q-btn
|
||
dense flat round
|
||
:icon="statementheaderStore.groupOpen[props.row.para_birimi] ? 'expand_less' : 'expand_more'"
|
||
class="q-mr-sm"
|
||
@click="statementheaderStore.toggleGroup(props.row.para_birimi)"
|
||
/>
|
||
<span>Para Birimi: {{ props.row.para_birimi }}</span>
|
||
</div>
|
||
<div class="row items-center q-gutter-md text-right">
|
||
<div>Bakiye: {{ formatAmount(props.row.sonBakiye) }}</div>
|
||
</div>
|
||
</div>
|
||
</q-td>
|
||
</q-tr>
|
||
|
||
<!-- Normal data satırı -->
|
||
<q-tr
|
||
v-else-if="props.row._type === 'data'"
|
||
:props="props"
|
||
class="main-row"
|
||
>
|
||
<q-td
|
||
v-for="col in props.cols"
|
||
:key="col.name"
|
||
:props="props"
|
||
@click="col.name === 'belge_no' ? toggleRowDetails(props.row) : null"
|
||
:class="[
|
||
'cursor-pointer',
|
||
col.name === 'aciklama' ? 'resizable-cell' : '',
|
||
col.name === 'belge_no' ? 'text-primary text-bold' : ''
|
||
]"
|
||
>
|
||
<span v-if="['borc','alacak','bakiye'].includes(col.name)">
|
||
{{ formatAmount(props.row[col.field]) }}
|
||
</span>
|
||
|
||
<div v-else-if="col.name === 'aciklama'" class="resizable-cell-content">
|
||
{{ props.row[col.field] ?? '' }}
|
||
</div>
|
||
|
||
<span v-else>
|
||
{{ props.row[col.field] ?? '' }}
|
||
</span>
|
||
</q-td>
|
||
</q-tr>
|
||
|
||
<!-- Detay tablosu -->
|
||
<q-tr
|
||
v-if="props.row._type === 'data' && expandedRows[props.row.belge_no]"
|
||
class="sub-row"
|
||
>
|
||
<q-td colspan="100%">
|
||
<q-table
|
||
:rows="detailStore.getDetailsByBelge(props.row.belge_no)"
|
||
:columns="detailColumns(props.row.belge_no)"
|
||
row-key="Urun_Kodu"
|
||
flat
|
||
dense
|
||
bordered
|
||
hide-bottom
|
||
no-data-label="Detay bulunamadı"
|
||
class="custom-subtable"
|
||
:loading="detailStore.loading"
|
||
:table-style="{ minWidth: '1200px' }"
|
||
/>
|
||
</q-td>
|
||
</q-tr>
|
||
|
||
</template>
|
||
</q-table>
|
||
</div>
|
||
</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 { ref, onMounted, computed, watch } from 'vue'
|
||
import { useQuasar } from 'quasar'
|
||
import { useAccountStore } from 'src/stores/accountStore'
|
||
import { useStatementheaderStore } from 'src/stores/statementheaderStore'
|
||
import { useStatementdetailStore } from 'src/stores/statementdetailStore'
|
||
import { useDownloadstpdfStore } from 'src/stores/downloadstpdfStore'
|
||
import dayjs from 'dayjs'
|
||
import { usePermission } from 'src/composables/usePermission'
|
||
|
||
const { canRead, canExport } = usePermission()
|
||
const canReadFinance = canRead('finance')
|
||
const canExportFinance = canExport('finance')
|
||
|
||
const $q = useQuasar()
|
||
|
||
const accountStore = useAccountStore()
|
||
const statementheaderStore = useStatementheaderStore()
|
||
const detailStore = useStatementdetailStore()
|
||
const downloadstpdfStore = useDownloadstpdfStore()
|
||
|
||
/* Cari seçimi */
|
||
const selectedCari = ref(null)
|
||
const filteredOptions = ref([])
|
||
|
||
function filterCari (val, update) {
|
||
const needle = normalizeText(val)
|
||
|
||
update(() => {
|
||
if (!needle) {
|
||
filteredOptions.value = accountStore.accountOptions
|
||
return
|
||
}
|
||
|
||
filteredOptions.value =
|
||
accountStore.accountOptions.filter(o => {
|
||
|
||
const label = normalizeText(o.label)
|
||
const value = normalizeText(o.value)
|
||
|
||
return (
|
||
label.includes(needle) ||
|
||
value.includes(needle)
|
||
)
|
||
})
|
||
})
|
||
}
|
||
|
||
|
||
onMounted(async () => {
|
||
await accountStore.fetchAccounts()
|
||
console.log("ACCOUNTS LEN:", accountStore.accounts?.length)
|
||
console.log("OPTIONS LEN:", accountStore.accountOptions?.length)
|
||
console.log("FIRST 5:", accountStore.accountOptions?.slice(0,5))
|
||
filteredOptions.value = accountStore.accountOptions
|
||
|
||
|
||
// ✅ Backend erişimi için global fonksiyon
|
||
window.toggleAllDetails = toggleAllDetails
|
||
})
|
||
|
||
/* Tarih aralığı */
|
||
const dateFrom = ref(dayjs().startOf('year').format('YYYY-MM-DD'))
|
||
const dateTo = ref(dayjs().format('YYYY-MM-DD'))
|
||
|
||
function isValidFromDate (date) {
|
||
if (!dateTo.value) return true
|
||
return !dayjs(date).isAfter(dayjs(dateTo.value), 'day')
|
||
}
|
||
|
||
function isValidToDate (date) {
|
||
if (!dateFrom.value) return true
|
||
return !dayjs(date).isBefore(dayjs(dateFrom.value), 'day')
|
||
}
|
||
|
||
function hasInvalidDateRange () {
|
||
if (!dateFrom.value || !dateTo.value) return false
|
||
return dayjs(dateFrom.value).isAfter(dayjs(dateTo.value), 'day')
|
||
}
|
||
|
||
function notifyInvalidDateRange () {
|
||
$q.notify({
|
||
type: 'warning',
|
||
message: '⚠️ Başlangıç tarihi bitiş tarihinden sonra olamaz.',
|
||
position: 'top-right'
|
||
})
|
||
}
|
||
|
||
/* Parasal İşlem Tipi */
|
||
const monetaryTypeOptions = [
|
||
{ label: '1-2 hesap', value: ['1', '2'] },
|
||
{ label: '1-3 r hesap', value: ['1', '3'] }
|
||
]
|
||
const selectedMonType = ref(monetaryTypeOptions[0].value)
|
||
|
||
/* Expand kontrolü */
|
||
const expandedRows = ref({})
|
||
const allDetailsOpen = ref(false)
|
||
|
||
/* Kolonları dinamik üretelim */
|
||
function buildColumns(data) {
|
||
if (!data || data.length === 0) return []
|
||
return Object.keys(data[0]).map(key => ({
|
||
name: key,
|
||
label: key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
||
field: key,
|
||
align: typeof data[0][key] === 'number' ? 'right' : 'left',
|
||
sortable: true
|
||
}))
|
||
}
|
||
|
||
const columns = computed(() => buildColumns(statementheaderStore.headers))
|
||
|
||
function detailColumns(belgeNo) {
|
||
const details = detailStore.getDetailsByBelge(belgeNo)
|
||
return buildColumns(details)
|
||
}
|
||
|
||
/* Filtrele */
|
||
async function onFilterClick() {
|
||
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {
|
||
$q.notify({
|
||
type: 'warning',
|
||
message: '⚠️ Lütfen cari ve tarih aralığını seçiniz.',
|
||
position: 'top-right'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (hasInvalidDateRange()) {
|
||
notifyInvalidDateRange()
|
||
return
|
||
}
|
||
|
||
await statementheaderStore.loadStatements({
|
||
startdate: dateFrom.value,
|
||
enddate: dateTo.value,
|
||
accountcode: selectedCari.value,
|
||
langcode: 'TR',
|
||
parislemler: selectedMonType.value
|
||
})
|
||
|
||
await detailStore.loadDetails({
|
||
accountCode: selectedCari.value,
|
||
startDate: dateFrom.value,
|
||
endDate: dateTo.value
|
||
})
|
||
}
|
||
|
||
/* Grup satırları için özel rowKey */
|
||
const rowKeyFn = (row) =>
|
||
row._type === 'group' ? `grp-${row.para_birimi}` : row.belge_no
|
||
|
||
/* Detay açma sadece expand kontrolü */
|
||
function toggleRowDetails(row) {
|
||
if (row._type === 'group') return
|
||
expandedRows.value[row.belge_no] = !expandedRows.value[row.belge_no]
|
||
}
|
||
|
||
/* 🔹 Tüm detayları aç/kapat */
|
||
function toggleAllDetails() {
|
||
allDetailsOpen.value = !allDetailsOpen.value
|
||
if (allDetailsOpen.value) {
|
||
for (const row of statementheaderStore.headers) {
|
||
if (row.belge_no) {
|
||
expandedRows.value[row.belge_no] = true
|
||
}
|
||
}
|
||
} else {
|
||
expandedRows.value = {}
|
||
}
|
||
}
|
||
function normalizeText (str) {
|
||
return (str || '')
|
||
.toString()
|
||
.toLocaleLowerCase('tr-TR') // 🔥 Türkçe uyumlu
|
||
.normalize('NFD') // aksan temizleme
|
||
.replace(/[\u0300-\u036f]/g, '')
|
||
.trim()
|
||
}
|
||
/* Reset */
|
||
function resetFilters() {
|
||
selectedCari.value = null
|
||
dateFrom.value = ''
|
||
dateTo.value = ''
|
||
selectedMonType.value = monetaryTypeOptions[0].value
|
||
statementheaderStore.headers = []
|
||
detailStore.reset()
|
||
}
|
||
|
||
/* Format */
|
||
function formatAmount(n) {
|
||
if (n == null || isNaN(n)) return '0,00'
|
||
return new Intl.NumberFormat('tr-TR', {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2
|
||
}).format(n)
|
||
}
|
||
|
||
const filtersOpen = ref(true)
|
||
|
||
/* 🔹 Kolon gizle/göster */
|
||
const visibleColumns = ref([])
|
||
const showLeftCols = ref(true)
|
||
|
||
watch(columns, (cols) => {
|
||
if (cols.length > 0 && visibleColumns.value.length === 0) {
|
||
visibleColumns.value = cols.map(c => c.name)
|
||
}
|
||
})
|
||
|
||
function toggleLeftCols() {
|
||
if (showLeftCols.value) {
|
||
visibleColumns.value = columns.value.map((c, i) =>
|
||
i < 3 ? null : c.name
|
||
).filter(Boolean)
|
||
} else {
|
||
visibleColumns.value = columns.value.map(c => c.name)
|
||
}
|
||
showLeftCols.value = !showLeftCols.value
|
||
}
|
||
|
||
/* 🔹 PDF İndirme Butonuna bağla */
|
||
async function handleDownload() {
|
||
if (!canExportFinance.value) {
|
||
$q.notify({
|
||
type: 'negative',
|
||
message: 'PDF export yetkiniz yok',
|
||
position: 'top-right'
|
||
})
|
||
return
|
||
}
|
||
|
||
console.log("▶️ [DEBUG] handleDownload:", selectedCari.value, dateFrom.value, dateTo.value)
|
||
|
||
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {
|
||
$q.notify({
|
||
type: 'warning',
|
||
message: '⚠️ Cari ve tarih aralığını seçmeden PDF alınamaz!',
|
||
position: 'top-right'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (hasInvalidDateRange()) {
|
||
notifyInvalidDateRange()
|
||
return
|
||
}
|
||
|
||
// ✅ Seçilen parasal işlem tipini gönder
|
||
const result = await downloadstpdfStore.downloadPDF(
|
||
selectedCari.value, // accountCode
|
||
dateFrom.value, // startDate
|
||
dateTo.value, // endDate
|
||
selectedMonType.value // <-- eklendi (['1','2'] veya ['1','3'])
|
||
)
|
||
|
||
console.log("📤 [DEBUG] Store’dan gelen result:", result)
|
||
|
||
$q.notify({
|
||
type: result.ok ? 'positive' : 'negative',
|
||
message: result.message,
|
||
position: 'top-right'
|
||
})
|
||
}/* 🔹 Cari Hesap Ekstresi (2. seçenek) */
|
||
import { useDownloadstHeadStore } from 'src/stores/downloadstHeadStore'
|
||
|
||
const downloadstHeadStore = useDownloadstHeadStore()
|
||
|
||
async function CurrheadDownload() {
|
||
if (!canExportFinance.value) {
|
||
$q.notify({
|
||
type: 'negative',
|
||
message: 'PDF export yetkiniz yok',
|
||
position: 'top-right'
|
||
})
|
||
return
|
||
}
|
||
|
||
console.log("▶️ [DEBUG] CurrheadDownload:", selectedCari.value, dateFrom.value, dateTo.value)
|
||
|
||
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {
|
||
$q.notify({
|
||
type: 'warning',
|
||
message: '⚠️ Cari ve tarih aralığını seçmeden PDF alınamaz!',
|
||
position: 'top-right'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (hasInvalidDateRange()) {
|
||
notifyInvalidDateRange()
|
||
return
|
||
}
|
||
|
||
// ✅ Yeni store fonksiyonu doğru şekilde çağrılıyor
|
||
const result = await downloadstHeadStore.handlestHeadDownload(
|
||
selectedCari.value, // accountCode
|
||
dateFrom.value, // startDate
|
||
dateTo.value, // endDate
|
||
selectedMonType.value // parasal işlem tipi (parislemler)
|
||
)
|
||
|
||
console.log("📤 [DEBUG] CurrheadDownloadresult:", result)
|
||
|
||
$q.notify({
|
||
type: result.ok ? 'positive' : 'negative',
|
||
message: result.message,
|
||
position: 'top-right'
|
||
})
|
||
}
|
||
|
||
</script>
|
||
|
||
<style scoped>
|
||
.statement-page {
|
||
height: calc(100vh - 56px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sticky-bar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 20;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.statement-table {
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
|
||
.statement-table :deep(.q-table__container) {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.statement-table :deep(.q-table__top) {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.statement-table :deep(.q-table__middle) {
|
||
flex: 1 1 auto;
|
||
min-height: 0;
|
||
overflow: auto !important;
|
||
max-height: none !important;
|
||
}
|
||
|
||
.statement-table :deep(thead th) {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
background: #fff;
|
||
}
|
||
|
||
.statement-table :deep(th),
|
||
.statement-table :deep(td) {
|
||
padding: 3px 6px !important;
|
||
font-size: 11px !important;
|
||
line-height: 1.2 !important;
|
||
}
|
||
|
||
.statement-table :deep(td) {
|
||
white-space: nowrap !important;
|
||
overflow: hidden !important;
|
||
text-overflow: ellipsis !important;
|
||
max-width: 120px;
|
||
}
|
||
|
||
.statement-table :deep(td[data-col="aciklama"]),
|
||
.statement-table :deep(th[data-col="aciklama"]) {
|
||
max-width: 220px;
|
||
}
|
||
|
||
.statement-table :deep(.resizable-cell-content) {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
white-space: normal !important;
|
||
}
|
||
|
||
@media (max-width: 1366px) {
|
||
.statement-table :deep(th),
|
||
.statement-table :deep(td) {
|
||
font-size: 10px !important;
|
||
padding: 2px 4px !important;
|
||
}
|
||
|
||
.statement-table :deep(td) {
|
||
max-width: 100px;
|
||
}
|
||
|
||
.statement-table :deep(td[data-col="aciklama"]),
|
||
.statement-table :deep(th[data-col="aciklama"]) {
|
||
max-width: 180px;
|
||
}
|
||
}
|
||
</style>
|