ilk
This commit is contained in:
521
ui/src/pages/statementofaccount.vue
Normal file
521
ui/src/pages/statementofaccount.vue
Normal file
@@ -0,0 +1,521 @@
|
||||
<template>
|
||||
<q-page class="q-pa-md page-col">
|
||||
|
||||
<!-- 🔹 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"/>
|
||||
</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" />
|
||||
</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
|
||||
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"
|
||||
title="Hareketler"
|
||||
:rows="statementheaderStore.groupedRows"
|
||||
:columns="columns"
|
||||
:visible-columns="visibleColumns"
|
||||
:row-key="row => row.OrderHeaderID + '_' + row.OrderNumber"
|
||||
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
:rows-per-page-options="[0]"
|
||||
:loading="statementheaderStore.loading"
|
||||
:table-style="{ tableLayout: 'auto', minWidth: '1600px' }"
|
||||
>
|
||||
<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>
|
||||
</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, canWrite, canUpdate } = usePermission()
|
||||
|
||||
const canReadOrder = canRead('order')
|
||||
const canWriteOrder = canWrite('order')
|
||||
const canUpdateOrder = canUpdate('order')
|
||||
|
||||
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'))
|
||||
|
||||
/* 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
|
||||
}
|
||||
|
||||
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() {
|
||||
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
|
||||
}
|
||||
|
||||
// ✅ 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() {
|
||||
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
|
||||
}
|
||||
|
||||
// ✅ 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>
|
||||
Reference in New Issue
Block a user