This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

View 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ı /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] Storedan 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>