Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-06 10:56:53 +03:00
parent 9097b5af2d
commit 9e534e9a34
18 changed files with 2204 additions and 2015 deletions

View File

@@ -0,0 +1,200 @@
<template>
<q-page v-if="canReadFinance" class="q-pa-md">
<div class="row q-col-gutter-sm items-end q-mb-md">
<div class="col-12 col-sm-4 col-md-3">
<q-input v-model="accountCode" filled dense label="Cari Kod" clearable />
</div>
<div class="col-12 col-sm-4 col-md-2">
<q-input v-model="endDate" filled dense label="Son Tarih" readonly>
<template #append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="endDate" mask="YYYY-MM-DD" locale="tr-TR" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<div class="col-12 col-sm-4 col-md-3">
<q-select
v-model="parislemler"
:options="parislemOptions"
emit-value
map-options
multiple
filled
dense
label="Parasal İşlem Tipi"
/>
</div>
<div class="col-auto">
<q-btn color="primary" icon="filter_alt" label="Filtrele" :loading="store.loading" @click="load" />
</div>
</div>
<div class="text-caption text-grey-7 q-mb-sm">
Son Tarih bazlı fatura/ödeme karşılaştırma ekranı
</div>
<q-table
:rows="store.masterRows"
:columns="masterColumns"
row-key="group_key"
flat
bordered
dense
:loading="store.loading"
:rows-per-page-options="[25, 50, 100, 0]"
>
<template #body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
size="sm"
dense
flat
round
:icon="expandedMaster[props.row.group_key] ? 'remove' : 'add'"
@click="toggleMaster(props.row.group_key)"
/>
</q-td>
<q-td key="cari8" :props="props">{{ props.row.cari8 }}</q-td>
<q-td key="cari_detay" :props="props">{{ props.row.cari_detay }}</q-td>
<q-td key="acik_kalem_tutari_usd" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_tutari_usd) }}</q-td>
<q-td key="acik_kalem_tutari_try" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_tutari_try) }}</q-td>
<q-td key="acik_kalem_ort_vade_gun" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_ort_vade_gun) }}</q-td>
<q-td key="acik_kalem_ort_belge_gun" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_ort_belge_gun) }}</q-td>
</q-tr>
<q-tr v-if="expandedMaster[props.row.group_key]">
<q-td colspan="100%" class="bg-grey-1">
<q-table
:rows="store.getCurrenciesByMaster(props.row.group_key)"
:columns="currencyColumns"
row-key="group_key"
flat
dense
hide-bottom
>
<template #body="cprops">
<q-tr :props="cprops">
<q-td auto-width>
<q-btn
size="sm"
dense
flat
round
:icon="expandedCurrency[cprops.row.group_key] ? 'remove' : 'add'"
@click="toggleCurrency(cprops.row.group_key)"
/>
</q-td>
<q-td key="doviz_cinsi" :props="cprops">{{ cprops.row.doviz_cinsi }}</q-td>
<q-td key="acik_kalem_tutari" :props="cprops" class="text-right">{{ fmt(cprops.row.acik_kalem_tutari) }}</q-td>
<q-td key="acik_kalem_usd" :props="cprops" class="text-right">{{ fmt(cprops.row.acik_kalem_usd) }}</q-td>
<q-td key="acik_kalem_try" :props="cprops" class="text-right">{{ fmt(cprops.row.acik_kalem_try) }}</q-td>
<q-td key="ort_gun" :props="cprops" class="text-right">{{ fmt(cprops.row.ort_gun) }}</q-td>
<q-td key="ort_belge_gun" :props="cprops" class="text-right">{{ fmt(cprops.row.ort_belge_gun) }}</q-td>
</q-tr>
<q-tr v-if="expandedCurrency[cprops.row.group_key]">
<q-td colspan="100%" class="bg-white">
<q-table
:rows="store.getDetailsByCurrency(cprops.row.group_key)"
:columns="detailColumns"
row-key="detail_key"
flat
dense
hide-bottom
/>
</q-td>
</q-tr>
</template>
</q-table>
</q-td>
</q-tr>
</template>
</q-table>
</q-page>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { useStatementAgingStore } from 'src/stores/statementAgingStore'
import { usePermission } from 'src/composables/usePermission'
const { canRead } = usePermission()
const canReadFinance = canRead('finance')
const store = useStatementAgingStore()
const accountCode = ref('')
const endDate = ref(new Date().toISOString().slice(0, 10))
const parislemler = ref(['2'])
const parislemOptions = [
{ label: '1-2 hesap', value: '2' },
{ label: '1-3 hesap', value: '3' }
]
const expandedMaster = reactive({})
const expandedCurrency = reactive({})
const masterColumns = [
{ name: 'expand', label: '', field: 'expand' },
{ name: 'cari8', label: 'Cari', field: 'cari8', align: 'left' },
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
{ name: 'acik_kalem_tutari_usd', label: 'Açık Kalem USD', field: 'acik_kalem_tutari_usd', align: 'right' },
{ name: 'acik_kalem_tutari_try', label: 'Açık Kalem TRY', field: 'acik_kalem_tutari_try', align: 'right' },
{ name: 'acik_kalem_ort_vade_gun', label: 'Ort Vade Gün', field: 'acik_kalem_ort_vade_gun', align: 'right' },
{ name: 'acik_kalem_ort_belge_gun', label: 'Ort Belge Gün', field: 'acik_kalem_ort_belge_gun', align: 'right' }
]
const currencyColumns = [
{ name: 'expand', label: '', field: 'expand' },
{ name: 'doviz_cinsi', label: 'Döviz', field: 'doviz_cinsi', align: 'left' },
{ name: 'acik_kalem_tutari', label: 'Açık Kalem Tutar', field: 'acik_kalem_tutari', align: 'right' },
{ name: 'acik_kalem_usd', label: 'Açık Kalem USD', field: 'acik_kalem_usd', align: 'right' },
{ name: 'acik_kalem_try', label: 'Açık Kalem TRY', field: 'acik_kalem_try', align: 'right' },
{ name: 'ort_gun', label: 'Ort Gün', field: 'ort_gun', align: 'right' },
{ name: 'ort_belge_gun', label: 'Ort Belge Gün', field: 'ort_belge_gun', align: 'right' }
]
const detailColumns = [
{ name: 'fatura_cari', label: 'Fatura Cari', field: 'fatura_cari', align: 'left' },
{ name: 'odeme_cari', label: 'Ödeme Cari', field: 'odeme_cari', align: 'left' },
{ name: 'fatura_ref', label: 'Fatura Ref', field: 'fatura_ref', align: 'left' },
{ name: 'odeme_ref', label: 'Ödeme Ref', field: 'odeme_ref', align: 'left' },
{ name: 'fatura_tarihi', label: 'Fatura Tarihi', field: 'fatura_tarihi', align: 'left' },
{ name: 'odeme_tarihi', label: 'Ödeme Tarihi', field: 'odeme_tarihi', align: 'left' },
{ name: 'eslesen_tutar', label: 'Eşleşen Tutar', field: 'eslesen_tutar', align: 'right' },
{ name: 'usd_tutar', label: 'USD Tutar', field: 'usd_tutar', align: 'right' },
{ name: 'try_tutar', label: 'TRY Tutar', field: 'try_tutar', align: 'right' },
{ name: 'gun_sayisi', label: 'Gün Sayısı', field: 'gun_sayisi', align: 'right' },
{ name: 'gun_sayisi_docdate', label: 'Belge Gün', field: 'gun_sayisi_docdate', align: 'right' },
{ name: 'gun_kur', label: 'Gün Kur', field: 'gun_kur', align: 'right' },
{ name: 'aciklama', label: 'Açıklama', field: 'aciklama', align: 'left' }
]
function fmt(v) {
const n = Number(v)
if (!Number.isFinite(n)) return v ?? ''
return n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
function toggleMaster(key) {
expandedMaster[key] = !expandedMaster[key]
}
function toggleCurrency(key) {
expandedCurrency[key] = !expandedCurrency[key]
}
async function load() {
await store.load({
accountcode: accountCode.value || '',
enddate: endDate.value || '',
parislemler: parislemler.value
})
}
load()
</script>

View File

@@ -1,454 +1,129 @@
<template>
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
<div class="filter-sticky" :class="{ collapsed: filtersCollapsed }">
<div class="top-actions row q-col-gutter-sm items-end q-mb-sm" :class="{ 'single-line': filtersCollapsed }">
<div class="col-12 col-sm-6 col-md-2">
<q-input
v-model="store.filters.selectedDate"
label="Tarih (Bugün)"
<template>
<q-page v-if="canReadFinance" class="q-px-md q-pb-md q-pt-xs page-col statement-page">
<q-slide-transition>
<div v-show="!filtersCollapsed" class="local-filter-bar compact-filter q-pa-sm q-mb-xs">
<div class="row q-col-gutter-sm items-end">
<div class="col-12 col-md-5">
<q-select
v-model="selectedCari"
:options="filteredOptions"
label="Cari kod / isim"
filled
dense
readonly
disable
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>
<div class="col-12 col-sm-6 col-md-2">
<q-input v-model="dateTo" label="Son tarih" filled dense 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 class="col-12 col-sm-6 col-md-3">
<q-toggle
v-model="store.filters.excludeZeroBalance12"
<q-select
v-model="selectedMonType"
:options="monetaryTypeOptions"
label="Parasal İşlem Tipi"
emit-value
map-options
filled
dense
label="1_2 Bakiyesi Sıfır Olanları Alma"
@update:model-value="onToggle12Changed"
/>
</div>
<div class="col-12 col-sm-6 col-md-3">
<q-toggle
v-model="store.filters.excludeZeroBalance13"
dense
label="1_3 Bakiyesi Sıfır Olanları Alma"
@update:model-value="onToggle13Changed"
/>
</div>
<div class="col-auto">
<q-btn
color="primary"
icon="download"
label="Verileri Yenile"
:loading="store.loading"
@click="store.fetchBalances()"
/>
<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="onReset"
/>
<q-btn flat color="grey-8" icon="restart_alt" label="Sıfırla" @click="resetFilters" />
</div>
</div>
</div>
</q-slide-transition>
<q-slide-transition>
<div v-show="!filtersCollapsed" class="filters-panel q-pa-sm q-mb-md">
<div class="row q-col-gutter-sm">
<div class="col-12 col-sm-6 col-md-4">
<q-input
v-model="store.filters.cariSearch"
filled
dense
label="Cari Kodu / Cari Adı"
/>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.cariIlkGrup"
:options="store.cariIlkGrupOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Cari İlk Grup"
:display-value="selectionLabel(store.filters.cariIlkGrup, 'Cari İlk Grup')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('cariIlkGrup', store.cariIlkGrupOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('cariIlkGrup')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.piyasa"
:options="store.piyasaOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Piyasa"
:display-value="selectionLabel(store.filters.piyasa, 'Piyasa')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('piyasa', store.piyasaOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('piyasa')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.temsilci"
:options="store.temsilciOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Temsilci"
:display-value="selectionLabel(store.filters.temsilci, 'Temsilci')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('temsilci', store.temsilciOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('temsilci')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.riskDurumu"
:options="store.riskDurumuOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Risk Durumu"
:display-value="selectionLabel(store.filters.riskDurumu, 'Risk Durumu')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('riskDurumu', store.riskDurumuOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('riskDurumu')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.islemTipi"
:options="islemTipiOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="İşlem Tipi"
:display-value="selectionLabel(store.filters.islemTipi, 'İşlem Tipi')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('islemTipi', islemTipiOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('islemTipi')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.ulke"
:options="store.ulkeOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Ülke"
:display-value="selectionLabel(store.filters.ulke, 'Ülke')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('ulke', store.ulkeOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('ulke')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.il"
:options="store.ilOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="İl"
:display-value="selectionLabel(store.filters.il, 'İl')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('il', store.ilOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('il')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.ilce"
:options="store.ilceOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="İlçe"
:display-value="selectionLabel(store.filters.ilce, 'İlçe')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('ilce', store.ilceOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('ilce')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
</div>
</div>
</q-slide-transition>
<q-banner v-if="store.error" class="bg-red-1 text-negative q-mb-md rounded-borders">
{{ store.error }}
</q-banner>
<q-banner v-if="!store.hasFetched && !store.loading" class="bg-blue-1 text-primary q-mb-md rounded-borders">
Veriler bugünün cache tablosundan okunur. Verileri Yenile ile tekrar yükleyebilirsiniz.
</q-banner>
</div>
<div class="table-area">
<div class="sticky-bar row justify-between items-center q-pa-sm bg-grey-1">
<div />
<div class="row items-center q-gutter-sm">
<q-btn
flat
color="secondary"
icon="list"
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
@click="toggleAllDetails"
/>
<q-btn-dropdown
v-if="canExportFinance"
flat
color="red"
icon="picture_as_pdf"
label="Yazdır"
>
<q-list style="min-width: 240px">
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(true)">
<q-item-section class="text-primary">
Detaylı Cari Yaşlandırmalı Bakiye Listesi Yazdır
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(false)">
<q-item-section class="text-secondary">
Detaysız Cari Yaşlandırmalı Bakiye Listesi Yazdır
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn
v-if="canExportFinance"
flat
color="green-8"
icon="table_view"
label="Excel"
@click="downloadAgingBalanceExcel"
/>
<q-btn
flat
color="primary"
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
@click="toggleFiltersCollapsed"
/>
</div>
<div class="table-scroll">
<div class="sticky-bar row justify-end items-center q-pa-sm bg-grey-1 q-gutter-sm">
<q-btn
flat
color="primary"
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
@click="toggleFiltersCollapsed"
/>
<q-btn-dropdown
v-if="canExportFinance"
flat
color="red"
icon="picture_as_pdf"
label="Yazdır"
>
<q-list style="min-width: 240px">
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(true)">
<q-item-section class="text-primary">
Detaylı Cari Yaşlandırmalı Bakiye Listesi Yazdır
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(false)">
<q-item-section class="text-secondary">
Detaysız Cari Yaşlandırmalı Bakiye Listesi Yazdır
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn
v-if="canExportFinance"
flat
color="green-8"
icon="table_view"
label="Excel"
@click="downloadAgingBalanceExcel"
/>
<q-btn
flat
color="secondary"
icon="list"
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
@click="toggleAllDetails"
/>
</div>
<q-table
title="Cari Yaşlandırmalı Cari Bakiye Listesi"
:rows="store.summaryRows"
:columns="summaryColumns"
class="sticky-table statement-table"
title="Cari Yaşlandırmalı Ekstre"
:rows="agingStore.masterRows"
:columns="masterColumns"
row-key="group_key"
:loading="store.loading"
flat
bordered
dense
wrap-cells
separator="cell"
hide-bottom
wrap-cells
:rows-per-page-options="[0]"
:pagination="{ rowsPerPage: 0 }"
:loading="agingStore.loading"
:table-style="{ tableLayout: 'fixed', width: '100%' }"
class="balance-table"
>
<template #header="props">
<q-tr :props="props" class="header-row">
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
<q-tr class="totals-row">
<q-th
v-for="col in props.cols"
:key="`tot-${col.name}`"
:class="col.align === 'right' ? 'text-right' : ''"
>
{{ totalCellValue(col.name) }}
</q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props" class="sub-header-row">
<q-tr :props="props" class="master-row">
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<q-btn
v-if="col.name === 'expand'"
@@ -456,287 +131,301 @@
flat
round
size="sm"
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
@click="toggleGroup(props.row.group_key)"
:icon="masterExpanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
@click="toggleMaster(props.row.group_key)"
/>
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
</span>
<span v-else-if="col.name === 'prbr_1_3'" class="text-right block prbr-cell">
{{ formatCurrencyMap(props.row.bakiye_1_3_map) }}
</span>
<span v-else-if="staticMoneyFields.includes(col.name)" class="text-center block">
<span
v-else-if="masterNumericCols.includes(col.name)"
:class="['block', masterCenteredCols.includes(col.name) ? 'text-center' : 'text-right']"
>
{{ formatAmount(props.row[col.field]) }}
</span>
<span v-else-if="dayFields.includes(col.name)" class="text-center block">
{{ formatDay(props.row[col.field]) }}
</span>
<span v-else>{{ props.row[col.field] || '-' }}</span>
<span v-else>{{ props.row[col.field] ?? '-' }}</span>
</q-td>
</q-tr>
<q-tr v-if="expanded[props.row.group_key]" class="detail-host-row">
<q-td colspan="100%">
<div class="detail-wrap">
<q-table
:rows="store.getDetailsByGroup(props.row.group_key)"
:columns="detailColumns"
row-key="cari_kodu"
dense
flat
bordered
hide-bottom
:table-style="{ tableLayout: 'fixed', width: '100%' }"
class="detail-table"
<q-tr v-if="masterExpanded[props.row.group_key]" class="master-sub-row">
<q-td colspan="100%" class="q-pa-none">
<div class="currency-groups">
<div class="currency-level-head">
<div class="cgh-cell cgh-expand"></div>
<div class="cgh-cell cgh-code">Döviz</div>
<div class="cgh-cell cgh-num">Satır</div>
<div class="cgh-cell cgh-num">Toplam Tutar</div>
<div class="cgh-cell cgh-num">Toplam USD</div>
<div class="cgh-cell cgh-num">Normal</div>
<div class="cgh-cell cgh-num">ık Kalem</div>
<div class="cgh-cell cgh-center">Ort. Gün</div>
<div class="cgh-cell cgh-center">Ort. Gün (DocDate)</div>
</div>
<div
v-for="currRow in agingStore.getCurrenciesByMaster(props.row.group_key)"
:key="currRow.group_key"
class="currency-group"
>
<template #body-cell-prbr_1_2="scope">
<q-td :props="scope" class="text-right prbr-cell">
{{ formatRowPrBr(scope.row, '1_2') }}
</q-td>
</template>
<template #body-cell-prbr_1_3="scope">
<q-td :props="scope" class="text-right prbr-cell">
{{ formatRowPrBr(scope.row, '1_3') }}
</q-td>
</template>
<template #body-cell-vade_gun="scope">
<q-td :props="scope" class="text-center">
{{ formatDay(scope.row.vade_gun) }}
</q-td>
</template>
<template #body-cell-vade_belge_tarihi_gun="scope">
<q-td :props="scope" class="text-center">
{{ formatDay(scope.row.vade_belge_tarihi_gun) }}
</q-td>
</template>
</q-table>
<div class="currency-group-header">
<div class="cgh-cell cgh-expand">
<q-btn
dense
flat
round
size="sm"
:icon="currencyExpanded[currRow.group_key] ? 'expand_less' : 'expand_more'"
@click="toggleCurrency(currRow.group_key)"
/>
</div>
<div class="cgh-cell cgh-code">{{ currRow.doc_currency_code }}</div>
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.satir_sayisi, 0) }}</div>
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.toplam_tutar) }}</div>
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.toplam_usd) }}</div>
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.normal_tutar) }}</div>
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_tutar) }}</div>
<div class="cgh-cell cgh-center">{{ formatAmount(currRow.ortalama_gun, 0) }}</div>
<div class="cgh-cell cgh-center">{{ formatAmount(currRow.ortalama_gun_docdate, 0) }}</div>
</div>
<div v-if="currencyExpanded[currRow.group_key]" class="detail-host-row">
<q-table
:rows="agingStore.getDetailsByCurrency(currRow.group_key)"
:columns="detailColumns"
row-key="detail_key"
flat
dense
bordered
hide-bottom
:rows-per-page-options="[0]"
:pagination="{ rowsPerPage: 0 }"
class="detail-subtable"
:table-style="{ minWidth: '1500px' }"
>
<template #body-cell-eslesen_tutar="d">
<q-td :props="d" class="text-right">{{ formatAmount(d.row.eslesen_tutar) }}</q-td>
</template>
<template #body-cell-usd_tutar="d">
<q-td :props="d" class="text-right">{{ formatAmount(d.row.usd_tutar) }}</q-td>
</template>
<template #body-cell-gun_sayisi="d">
<q-td :props="d" class="text-center">{{ formatAmount(d.row.gun_sayisi, 0) }}</q-td>
</template>
<template #body-cell-gun_sayisi_docdate="d">
<q-td :props="d" class="text-center">{{ formatAmount(d.row.gun_sayisi_docdate, 0) }}</q-td>
</template>
</q-table>
</div>
</div>
</div>
</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 modüle erişim yetkiniz yok.
</div>
<div class="text-negative text-subtitle1">Bu modüle erişim yetkiniz yok.</div>
</q-page>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onMounted, ref } from 'vue'
import { useQuasar } from 'quasar'
import { onMounted } from 'vue'
import { useAccountAgingBalanceStore } from 'src/stores/accountAgingBalanceStore'
import dayjs from 'dayjs'
import { usePermission } from 'src/composables/usePermission'
import { useAccountStore } from 'src/stores/accountStore'
import { useStatementAgingStore } from 'src/stores/statementAgingStore'
import { download, extractApiErrorDetail } from 'src/services/api'
const store = useAccountAgingBalanceStore()
const expanded = ref({})
const allDetailsOpen = ref(false)
const filtersCollapsed = ref(false)
const $q = useQuasar()
const { canRead, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance')
const islemTipiOptions = [
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' },
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
const $q = useQuasar()
const accountStore = useAccountStore()
const agingStore = useStatementAgingStore()
const selectedCari = ref(null)
const filteredOptions = ref([])
const dateTo = ref(dayjs().format('YYYY-MM-DD'))
const masterExpanded = ref({})
const currencyExpanded = ref({})
const allDetailsOpen = ref(false)
const filtersCollapsed = ref(false)
const monetaryTypeOptions = [
{ label: '1-2 hesap', value: ['1', '2'] },
{ label: '1-3 r hesap', value: ['1', '3'] }
]
const selectedMonType = ref(monetaryTypeOptions[0].value)
const masterColumns = [
{ name: 'expand', label: '', field: 'expand', align: 'center' },
{ name: 'cari8', label: 'Ana Cari Kod', field: 'cari8', align: 'left', sortable: true },
{ name: 'cari_detay', label: 'Ana Cari Detay', field: 'cari_detay', align: 'left', sortable: true },
{ name: 'satir_sayisi', label: 'Satır', field: 'satir_sayisi', align: 'right', sortable: true },
{ name: 'toplam_usd', label: 'Toplam USD', field: 'toplam_usd', align: 'right', sortable: true },
{ name: 'normal_usd', label: 'Normal USD', field: 'normal_usd', align: 'right', sortable: true },
{ name: 'acik_kalem_usd', label: 'Açık Kalem USD', field: 'acik_kalem_usd', align: 'right', sortable: true },
{ name: 'ortalama_gun', label: 'Ort. Gün', field: 'ortalama_gun', align: 'center', sortable: true },
{ name: 'ortalama_gun_docdate', label: 'Ort. Gün (DocDate)', field: 'ortalama_gun_docdate', align: 'center', sortable: true }
]
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
const dayFields = ['vade_gun', 'vade_belge_tarihi_gun']
const detailColumns = [
{ name: 'fatura_cari', label: 'Fatura Cari', field: 'fatura_cari', align: 'left' },
{ name: 'odeme_cari', label: 'Ödeme Cari', field: 'odeme_cari', align: 'left' },
{ name: 'fatura_ref', label: 'Fatura Ref', field: 'fatura_ref', align: 'left' },
{ name: 'odeme_ref', label: 'Ödeme Ref', field: 'odeme_ref', align: 'left' },
{ name: 'fatura_tarihi', label: 'Fatura Tarihi', field: 'fatura_tarihi', align: 'left' },
{ name: 'odeme_tarihi', label: 'Ödeme Vade', field: 'odeme_tarihi', align: 'left' },
{ name: 'odeme_doc_date', label: 'Ödeme DocDate', field: 'odeme_doc_date', align: 'left' },
{ name: 'eslesen_tutar', label: 'Eşleşen Tutar', field: 'eslesen_tutar', align: 'right' },
{ name: 'usd_tutar', label: 'USD Tutar', field: 'usd_tutar', align: 'right' },
{ name: 'gun_sayisi', label: 'Gün', field: 'gun_sayisi', align: 'center' },
{ name: 'gun_sayisi_docdate', label: 'Gün (DocDate)', field: 'gun_sayisi_docdate', align: 'center' },
{ name: 'aciklama', label: 'Açıklama', field: 'aciklama', align: 'left' },
{ name: 'doc_currency_code', label: 'Döviz', field: 'doc_currency_code', align: 'left' }
]
function toNumericSortValue (value) {
if (typeof value === 'number') {
return Number.isFinite(value) ? value : 0
}
const masterNumericCols = ['satir_sayisi', 'toplam_usd', 'normal_usd', 'acik_kalem_usd', 'ortalama_gun', 'ortalama_gun_docdate']
const masterCenteredCols = ['ortalama_gun', 'ortalama_gun_docdate']
const s = String(value ?? '').trim()
if (!s) return 0
function normalizeText(str) {
return (str || '')
.toString()
.toLocaleLowerCase('tr-TR')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim()
}
const hasComma = s.includes(',')
const hasDot = s.includes('.')
function filterCari(val, update) {
const needle = normalizeText(val)
let normalized = s.replace(/\s+/g, '')
if (hasComma && hasDot) {
const lastComma = normalized.lastIndexOf(',')
const lastDot = normalized.lastIndexOf('.')
if (lastComma > lastDot) {
normalized = normalized.replace(/\./g, '').replace(',', '.')
} else {
normalized = normalized.replace(/,/g, '')
update(() => {
if (!needle) {
filteredOptions.value = accountStore.accountOptions
return
}
} else if (hasComma) {
normalized = normalized.replace(/\./g, '').replace(',', '.')
}
const n = Number.parseFloat(normalized)
return Number.isFinite(n) ? n : 0
}
function sortTextTr (a, b) {
return String(a ?? '').localeCompare(String(b ?? ''), 'tr', { sensitivity: 'base' })
}
const metricDefs = {
prbr_1_2: { name: 'prbr_1_2', label: '1_2 Bakiye\nPr.Br', field: 'prbr_1_2', align: 'right', sortable: false },
prbr_1_3: { name: 'prbr_1_3', label: '1_3 Bakiye\nPr.Br', field: 'prbr_1_3', align: 'right', sortable: false },
usd_1_2: { name: 'usd_bakiye_1_2', label: '1_2 USD_BAKIYE', field: 'usd_bakiye_1_2', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
try_1_2: { name: 'tl_bakiye_1_2', label: '1_2 TRY_BAKIYE', field: 'tl_bakiye_1_2', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
usd_1_3: { name: 'usd_bakiye_1_3', label: '1_3 USD_BAKIYE', field: 'usd_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
}
const selectedMetricKeys = computed(() => {
const selected = store.filters.islemTipi || []
if (!selected.length) return [...Object.keys(metricDefs)]
return selected.filter((k) => k in metricDefs)
})
const summaryColumns = computed(() => ([
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left', sortable: true, sort: sortTextTr },
...selectedMetricKeys.value.map((k) => metricDefs[k]),
{ name: 'vade_gun', label: 'Vade Gun', field: 'vade_gun', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
{ name: 'vade_belge_tarihi_gun', label: 'Belge Tarihi Gun', field: 'vade_belge_tarihi_gun', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
]))
const liveTotals = computed(() => {
return store.filteredRows.reduce((acc, row) => {
acc.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
acc.tl_bakiye_1_2 += Number(row.tl_bakiye_1_2) || 0
acc.usd_bakiye_1_3 += Number(row.usd_bakiye_1_3) || 0
acc.tl_bakiye_1_3 += Number(row.tl_bakiye_1_3) || 0
const vadeGun = Number(row.vade_gun) || 0
const vadeBelge = Number(row.vade_belge_tarihi_gun) || 0
if (vadeGun !== 0 || vadeBelge !== 0) {
acc.vade_count += 1
acc.vade_gun_sum += vadeGun
acc.vade_belge_sum += vadeBelge
}
return acc
}, {
usd_bakiye_1_2: 0,
tl_bakiye_1_2: 0,
usd_bakiye_1_3: 0,
tl_bakiye_1_3: 0,
vade_gun_sum: 0,
vade_belge_sum: 0,
vade_count: 0
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()
filteredOptions.value = accountStore.accountOptions
})
const detailColumns = computed(() => [
{ name: 'cari_kodu', label: 'Cari Kodu', field: 'cari_kodu', align: 'left' },
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
{ name: 'sirket', label: 'Şirket', field: 'sirket', align: 'left' },
{ name: 'sirket_detay', label: 'Şirket Detayı', field: 'sirket_detay', align: 'left' },
{ name: 'muhasebe_kodu', label: 'Muhasebe Kodu', field: 'muhasebe_kodu', align: 'left' },
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left' },
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left' },
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left' },
{ name: 'ozellik05', label: 'Ülke', field: 'ozellik05', align: 'left' },
{ name: 'il', label: 'İl', field: 'il', align: 'left' },
{ name: 'ilce', label: 'İlçe', field: 'ilce', align: 'left' },
{ name: 'cari_doviz', label: 'Döviz', field: 'cari_doviz', align: 'left' },
...selectedMetricKeys.value.map((k) => metricDefs[k]),
{ name: 'vade_gun', label: 'Vade Gun', field: 'vade_gun', align: 'center' },
{ name: 'vade_belge_tarihi_gun', label: 'Belge Tarihi Gun', field: 'vade_belge_tarihi_gun', align: 'center' }
])
function onReset () {
store.resetFilters()
store.fetchBalances()
}
function onToggle12Changed (val) {
if (val) {
store.filters.excludeZeroBalance13 = false
}
}
function onToggle13Changed (val) {
if (val) {
store.filters.excludeZeroBalance12 = false
}
}
function toggleFiltersCollapsed () {
filtersCollapsed.value = !filtersCollapsed.value
}
function toggleGroup (key) {
expanded.value[key] = !expanded.value[key]
if (!expanded.value[key]) {
allDetailsOpen.value = false
return
}
allDetailsOpen.value =
store.summaryRows.length > 0 &&
store.summaryRows.every(r => expanded.value[r.group_key])
}
function toggleAllDetails () {
allDetailsOpen.value = !allDetailsOpen.value
if (allDetailsOpen.value) {
const next = {}
for (const row of store.summaryRows) {
next[row.group_key] = true
}
expanded.value = next
return
}
expanded.value = {}
}
async function downloadAgingBalancePDF (detailed) {
if (!canExportFinance.value) {
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
return
}
if (!store.hasFetched) {
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
async function onFilterClick() {
if (!selectedCari.value || !dateTo.value) {
$q.notify({
type: 'warning',
message: 'Lütfen cari ve son tarih seçiniz.',
position: 'top-right'
})
return
}
try {
const params = {
cari_search: String(store.filters.cariSearch || '').trim(),
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
piyasa: (store.filters.piyasa || []).join(','),
temsilci: (store.filters.temsilci || []).join(','),
risk_durumu: (store.filters.riskDurumu || []).join(','),
islem_tipi: (store.filters.islemTipi || []).join(','),
ulke: (store.filters.ulke || []).join(','),
il: (store.filters.il || []).join(','),
ilce: (store.filters.ilce || []).join(','),
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0',
detailed: detailed ? '1' : '0'
}
await agingStore.load({
accountcode: selectedCari.value,
enddate: dateTo.value,
parislemler: selectedMonType.value
})
const blob = await download('/finance/account-aging-statement/export-pdf', params)
const m = {}
const c = {}
for (const row of agingStore.masterRows) {
m[row.group_key] = true
for (const cr of agingStore.getCurrenciesByMaster(row.group_key)) {
c[cr.group_key] = true
}
}
masterExpanded.value = m
currencyExpanded.value = c
allDetailsOpen.value = agingStore.masterRows.length > 0
} catch (err) {
const msg = await extractApiErrorDetail(err)
$q.notify({
type: 'negative',
message: msg || 'Veriler yüklenemedi',
position: 'top-right'
})
}
}
function resetFilters() {
selectedCari.value = null
dateTo.value = dayjs().format('YYYY-MM-DD')
selectedMonType.value = monetaryTypeOptions[0].value
masterExpanded.value = {}
currencyExpanded.value = {}
allDetailsOpen.value = false
agingStore.reset()
}
function toggleMaster(key) {
masterExpanded.value[key] = !masterExpanded.value[key]
}
function toggleCurrency(key) {
currencyExpanded.value[key] = !currencyExpanded.value[key]
}
function toggleAllDetails() {
allDetailsOpen.value = !allDetailsOpen.value
if (!allDetailsOpen.value) {
masterExpanded.value = {}
currencyExpanded.value = {}
return
}
const m = {}
const c = {}
for (const row of agingStore.masterRows) {
m[row.group_key] = true
for (const cr of agingStore.getCurrenciesByMaster(row.group_key)) {
c[cr.group_key] = true
}
}
masterExpanded.value = m
currencyExpanded.value = c
}
function toggleFiltersCollapsed() {
filtersCollapsed.value = !filtersCollapsed.value
}
function buildExportParams(detailed = false) {
return {
accountcode: String(selectedCari.value || '').trim(),
cari_search: String(selectedCari.value || '').trim(),
enddate: dateTo.value,
selected_date: dateTo.value,
parislemler: selectedMonType.value,
detailed: detailed ? '1' : '0',
exclude_zero_12: '0',
exclude_zero_13: '0'
}
}
async function downloadAgingBalancePDF(detailed) {
if (!canExportFinance.value) {
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
return
}
if (!selectedCari.value || !dateTo.value) {
$q.notify({ type: 'warning', message: 'Önce cari ve son tarih seçiniz.', position: 'top-right' })
return
}
try {
const blob = await download('/finance/account-aging-statement/export-pdf', buildExportParams(detailed))
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
window.open(pdfUrl, '_blank')
} catch (err) {
@@ -749,37 +438,19 @@ async function downloadAgingBalancePDF (detailed) {
}
}
async function downloadAgingBalanceExcel () {
async function downloadAgingBalanceExcel() {
if (!canExportFinance.value) {
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
return
}
if (!store.hasFetched) {
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
if (!selectedCari.value || !dateTo.value) {
$q.notify({ type: 'warning', message: 'Önce cari ve son tarih seçiniz.', position: 'top-right' })
return
}
try {
const params = {
cari_search: String(store.filters.cariSearch || '').trim(),
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
piyasa: (store.filters.piyasa || []).join(','),
temsilci: (store.filters.temsilci || []).join(','),
risk_durumu: (store.filters.riskDurumu || []).join(','),
islem_tipi: (store.filters.islemTipi || []).join(','),
ulke: (store.filters.ulke || []).join(','),
il: (store.filters.il || []).join(','),
ilce: (store.filters.ilce || []).join(','),
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0'
}
const file = await download('/finance/account-aging-statement/export-excel', params)
const blob = new Blob(
[file],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
)
const file = await download('/finance/account-aging-statement/export-excel', buildExportParams(false))
const blob = new Blob([file], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
@@ -798,261 +469,224 @@ async function downloadAgingBalanceExcel () {
}
}
function formatAmount (value) {
function formatAmount(value, fraction = 2) {
const n = Number(value || 0)
return new Intl.NumberFormat('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
minimumFractionDigits: fraction,
maximumFractionDigits: fraction
}).format(n)
}
function formatDay (value) {
const n = Number(value || 0)
return new Intl.NumberFormat('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n)
}
function selectionLabel (arr, label) {
const count = Array.isArray(arr) ? arr.length : 0
if (count === 0) return `Tümü (${label})`
if (count === 1) return '1 seçim'
return `${count} seçim`
}
function totalCellValue (colName) {
if (colName === 'expand') return 'Toplam'
if (colName === 'piyasa') return '-'
if (colName === 'temsilci') return '-'
if (colName === 'risk_durumu') return '-'
if (colName === 'prbr_1_2') return formatCurrencyMap(totalByCurrency('1_2'))
if (colName === 'prbr_1_3') return formatCurrencyMap(totalByCurrency('1_3'))
if (colName === 'usd_bakiye_1_2') return formatAmount(liveTotals.value.usd_bakiye_1_2)
if (colName === 'tl_bakiye_1_2') return formatAmount(liveTotals.value.tl_bakiye_1_2)
if (colName === 'usd_bakiye_1_3') return formatAmount(liveTotals.value.usd_bakiye_1_3)
if (colName === 'tl_bakiye_1_3') return formatAmount(liveTotals.value.tl_bakiye_1_3)
if (colName === 'vade_gun') return formatDay(liveTotals.value.vade_count > 0 ? liveTotals.value.vade_gun_sum / liveTotals.value.vade_count : 0)
if (colName === 'vade_belge_tarihi_gun') return formatDay(liveTotals.value.vade_count > 0 ? liveTotals.value.vade_belge_sum / liveTotals.value.vade_count : 0)
return '-'
}
function totalByCurrency (tip) {
const key = tip === '1_2' ? 'bakiye_1_2_map' : 'bakiye_1_3_map'
const out = {}
for (const r of store.summaryRows) {
const m = r[key] || {}
for (const [curr, val] of Object.entries(m)) {
out[curr] = (Number(out[curr]) || 0) + (Number(val) || 0)
}
}
return out
}
function formatCurrencyMap (mapObj) {
const entries = Object.entries(mapObj || {})
.filter(([, amount]) => Number(amount) !== 0)
.sort((a, b) => a[0].localeCompare(b[0], 'en'))
if (!entries.length) return '-'
return entries
.map(([curr, amount]) => `${curr}: ${formatAmount(amount)}`)
.join('\n')
}
function formatRowPrBr (row, tip) {
const curr = String(row.cari_doviz || '').trim().toUpperCase() || 'N/A'
const amount = tip === '1_2'
? (Number(row.bakiye_1_2) || 0)
: (Number(row.bakiye_1_3) || 0)
if (amount === 0) return '-'
return `${curr} ${formatAmount(amount)}`
}
onMounted(async () => {
store.filters.selectedDate = new Date().toISOString().slice(0, 10)
await store.fetchBalances()
})
</script>
<style scoped>
.page-layout {
height: calc(100vh - 110px);
.statement-page {
height: calc(100vh - 56px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.filter-sticky {
position: sticky;
top: 0;
z-index: 20;
background: #fff;
padding-bottom: 6px;
.table-scroll {
flex: 1;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
.filter-sticky.collapsed {
padding-bottom: 0;
}
.top-actions.single-line {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
padding-bottom: 4px;
}
.top-actions.single-line > [class*='col-'],
.top-actions.single-line > .col-auto {
flex: 0 0 auto;
min-width: 220px;
}
.top-actions.single-line > .col-auto {
min-width: auto;
}
.filters-panel {
.compact-filter {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 8px;
background: #fafafa;
}
.compact-select :deep(.q-field__control) {
min-height: 40px;
}
.compact-select :deep(.q-field__native),
.compact-select :deep(.q-field__input) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.table-area {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
.statement-page .local-filter-bar {
position: sticky;
top: 0;
z-index: 40;
}
.sticky-bar {
position: sticky;
top: 0;
z-index: 9;
z-index: 30;
flex: 0 0 auto;
background: var(--q-secondary);
color: #fff;
border: 1px solid rgba(0, 0, 0, 0.08);
}
.balance-table {
.statement-table {
flex: 1;
min-height: 0;
}
.balance-table :deep(.q-table__container) {
.statement-table :deep(.q-table__container) {
height: 100%;
display: flex;
flex-direction: column;
}
.balance-table :deep(.q-table__top) {
position: sticky;
top: 0;
z-index: 6;
background: #fff;
.statement-table :deep(.q-table__top) {
flex: 0 0 auto;
position: static;
}
.balance-table :deep(.q-table__middle) {
.statement-table :deep(.q-table__middle) {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
overflow: auto !important;
max-height: none !important;
}
.balance-table :deep(.header-row th) {
.statement-table :deep(.header-row th) {
position: sticky;
top: 0;
z-index: 8;
z-index: 30;
background: var(--q-primary);
color: #fff;
font-weight: 600;
font-family: 'Roboto', sans-serif;
border-bottom: 1px solid rgba(255, 255, 255, 0.22);
box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.2), 0 1px 0 rgba(0, 0, 0, 0.2);
}
.balance-table :deep(.totals-row th) {
position: sticky;
top: 38px;
z-index: 7;
background: var(--q-secondary);
color: var(--q-dark);
font-weight: 700;
font-family: 'Roboto', sans-serif;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
.detail-table :deep(.q-table__middle) {
max-height: 320px;
}
.balance-table :deep(.sub-header-row td) {
background: #fff;
.statement-table :deep(.master-row td) {
background: color-mix(in srgb, var(--q-secondary) 12%, white);
border-bottom: 2px solid rgba(0, 0, 0, 0.18);
font-weight: 600;
}
.balance-table :deep(.sub-header-row td:first-child) {
.statement-table :deep(.master-row td:first-child) {
border-left: 3px solid var(--q-primary);
}
.balance-table :deep(.detail-host-row td) {
background: #f7f7f7;
border-bottom: 10px solid #fff;
padding-top: 10px;
padding-bottom: 12px;
.statement-table :deep(.master-sub-row td) {
background: #f4f6fb;
border-bottom: 8px solid #fff;
vertical-align: top;
padding: 0 !important;
}
.detail-wrap {
border: 1px solid rgba(0, 0, 0, 0.14);
border-left: 4px solid var(--q-secondary);
border-radius: 6px;
background: #fff;
.currency-groups {
padding: 6px;
background: #f8faff;
}
.balance-table :deep(.header-row th) {
white-space: pre-line;
line-height: 1.15;
.currency-group {
border-left: 4px solid var(--q-secondary);
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-right: 1px solid rgba(0, 0, 0, 0.08);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
margin-bottom: 8px;
background: #fff;
}
.prbr-cell {
white-space: pre-line;
word-break: break-word;
line-height: 1.25;
.currency-level-head {
position: sticky;
top: 36px;
z-index: 26;
display: grid;
grid-template-columns: 48px 100px 80px 1.2fr 1.2fr 1.1fr 1.1fr 110px 140px;
align-items: center;
gap: 0;
background: var(--q-secondary);
color: #fff;
border: 1px solid rgba(0, 0, 0, 0.15);
margin-bottom: 6px;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
.balance-table :deep(th),
.balance-table :deep(td),
.detail-table :deep(th),
.detail-table :deep(td) {
white-space: normal !important;
word-break: break-word;
overflow-wrap: anywhere;
.currency-group-header {
position: sticky;
top: 72px;
z-index: 24;
display: grid;
grid-template-columns: 48px 100px 80px 1.2fr 1.2fr 1.1fr 1.1fr 110px 140px;
align-items: center;
gap: 0;
background: #4c5f7a;
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
}
.cgh-cell {
padding: 6px 8px;
border-right: 1px solid rgba(255, 255, 255, 0.2);
font-weight: 600;
font-size: 11px;
line-height: 1.2;
padding: 4px 6px !important;
}
.balance-table :deep(.q-table__table),
.detail-table :deep(.q-table__table) {
width: 100% !important;
.cgh-cell:last-child {
border-right: none;
}
.detail-table :deep(.q-table__middle) {
overflow-x: hidden;
.cgh-num {
text-align: right;
}
.cgh-center {
text-align: center;
}
.cgh-expand {
text-align: center;
}
.detail-host-row :deep(td) {
background: #fdfdfd;
padding: 6px !important;
}
.detail-subtable {
border-left: 4px solid var(--q-primary);
border-top: 1px solid rgba(0, 0, 0, 0.08);
border-right: 1px solid rgba(0, 0, 0, 0.08);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
background: #fff;
}
.statement-table :deep(th),
.statement-table :deep(td),
.detail-subtable :deep(th),
.detail-subtable :deep(td) {
padding: 3px 6px !important;
font-size: 11px !important;
line-height: 1.2 !important;
}
.detail-subtable :deep(.q-table__top) {
position: static;
}
.detail-subtable :deep(thead th) {
position: sticky;
top: 108px;
z-index: 22;
background: #1f3b5b;
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
.detail-subtable :deep(td[data-col="gun_sayisi"]),
.detail-subtable :deep(td[data-col="gun_sayisi_docdate"]),
.statement-table :deep(td[data-col="ortalama_gun"]),
.statement-table :deep(td[data-col="ortalama_gun_docdate"]) {
text-align: center !important;
}
@media (max-width: 1366px) {
.statement-table :deep(th),
.statement-table :deep(td),
.detail-subtable :deep(th),
.detail-subtable :deep(td) {
font-size: 10px !important;
padding: 2px 4px !important;
}
.currency-group-header {
grid-template-columns: 44px 90px 70px 1.1fr 1.1fr 1fr 1fr 90px 120px;
}
}
</style>

View File

@@ -0,0 +1,1148 @@
Logo
Konular
Değişiklik İstekleri
Dönüm noktaları
Keşfet
M_Kececi
/
bssapp
Kod
Konular
Değişiklik İstekleri
İşlemler
Paketler
Projeler
Sürüm
Wiki
Aktivite
Ayarlar
Dosyalar
AccountAgingStatement.vue
ActivityLogs.vue
ChangePassword.vue
CustomerBalanceList.vue
Dashboard.vue
ErrorNotFound.vue
FirstPasswordChange.vue
MainPage.vue
MainPanel.vue
MePassword.vue
OrderBulkClose.vue
OrderEntry.vue
OrderGateway.vue
OrderList.vue
OrderPdf.vue
OrderProductionUpdate.vue
OrderProductionUpdateList.vue
PermissionMatrix.vue
ProductStockByAttributes.vue
ProductStockQuery.vue
ProductionWorker.vue
ProductionWorkerGateway.vue
ResetPassword.vue
RoleDepartmentPermissionGateway.vue
RoleDepartmentPermissionList.vue
RoleDepartmentPermissionPage.vue
StatementHeaderReport.vue
StatementReport.vue
TestMail.vue
UserDetail.vue
UserGateway.vue
UserList.vue
UserPermissionPage.vue
UserSync.vue
dummydata.vue
statementofaccount.vue
App.vue
-.editorconfig
.editorconfig
.env.development
.env.production
.gitignore
.npmrc
README.md
babel.config.js
index.html
jsconfig.json
package-lock.json
package.json
postcss.config.js
quasar.config.js
quasar.config.js.temporary.compiled.1772696562168.mjs
.gitignore
readme.md
bssapp
/
ui
/
src
/
pages
/
AccountAgingStatement.vue
M_Kececi
ce31aff645
Merge remote-tracking branch 'origin/master'
3 gün önce
1059 satır
35 KiB
Vue
<template>
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
<div class="filter-sticky" :class="{ collapsed: filtersCollapsed }">
<div class="top-actions row q-col-gutter-sm items-end q-mb-sm" :class="{ 'single-line': filtersCollapsed }">
<div class="col-12 col-sm-6 col-md-2">
<q-input
v-model="store.filters.selectedDate"
label="Tarih (Bugün)"
filled
dense
readonly
disable
/>
</div>
<div class="col-12 col-sm-6 col-md-3">
<q-toggle
v-model="store.filters.excludeZeroBalance12"
dense
label="1_2 Bakiyesi Sıfır Olanları Alma"
@update:model-value="onToggle12Changed"
/>
</div>
<div class="col-12 col-sm-6 col-md-3">
<q-toggle
v-model="store.filters.excludeZeroBalance13"
dense
label="1_3 Bakiyesi Sıfır Olanları Alma"
@update:model-value="onToggle13Changed"
/>
</div>
<div class="col-auto">
<q-btn
color="primary"
icon="download"
label="Verileri Yenile"
:loading="store.loading"
@click="store.fetchBalances()"
/>
</div>
<div class="col-auto">
<q-btn
flat
color="grey-8"
icon="restart_alt"
label="Sıfırla"
@click="onReset"
/>
</div>
</div>
<q-slide-transition>
<div v-show="!filtersCollapsed" class="filters-panel q-pa-sm q-mb-md">
<div class="row q-col-gutter-sm">
<div class="col-12 col-sm-6 col-md-4">
<q-input
v-model="store.filters.cariSearch"
filled
dense
label="Cari Kodu / Cari Adı"
/>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.cariIlkGrup"
:options="store.cariIlkGrupOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Cari İlk Grup"
:display-value="selectionLabel(store.filters.cariIlkGrup, 'Cari İlk Grup')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('cariIlkGrup', store.cariIlkGrupOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('cariIlkGrup')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.piyasa"
:options="store.piyasaOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Piyasa"
:display-value="selectionLabel(store.filters.piyasa, 'Piyasa')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('piyasa', store.piyasaOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('piyasa')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.temsilci"
:options="store.temsilciOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Temsilci"
:display-value="selectionLabel(store.filters.temsilci, 'Temsilci')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('temsilci', store.temsilciOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('temsilci')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.riskDurumu"
:options="store.riskDurumuOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Risk Durumu"
:display-value="selectionLabel(store.filters.riskDurumu, 'Risk Durumu')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('riskDurumu', store.riskDurumuOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('riskDurumu')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.islemTipi"
:options="islemTipiOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="İşlem Tipi"
:display-value="selectionLabel(store.filters.islemTipi, 'İşlem Tipi')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('islemTipi', islemTipiOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('islemTipi')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.ulke"
:options="store.ulkeOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="Ülke"
:display-value="selectionLabel(store.filters.ulke, 'Ülke')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('ulke', store.ulkeOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('ulke')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.il"
:options="store.ilOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="İl"
:display-value="selectionLabel(store.filters.il, 'İl')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('il', store.ilOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('il')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-select
v-model="store.filters.ilce"
:options="store.ilceOptions"
multiple
emit-value
map-options
filled
dense
options-dense
class="compact-select"
label="İlçe"
:display-value="selectionLabel(store.filters.ilce, 'İlçe')"
>
<template #before-options>
<q-item clickable dense @click.stop="store.selectAll('ilce', store.ilceOptions)">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable dense @click.stop="store.clearAll('ilce')">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-checkbox :model-value="scope.selected" />
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.label }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
</div>
</div>
</div>
</q-slide-transition>
<q-banner v-if="store.error" class="bg-red-1 text-negative q-mb-md rounded-borders">
{{ store.error }}
</q-banner>
<q-banner v-if="!store.hasFetched && !store.loading" class="bg-blue-1 text-primary q-mb-md rounded-borders">
Veriler bugünün cache tablosundan okunur. Verileri Yenile ile tekrar yükleyebilirsiniz.
</q-banner>
</div>
<div class="table-area">
<div class="sticky-bar row justify-between items-center q-pa-sm bg-grey-1">
<div />
<div class="row items-center q-gutter-sm">
<q-btn
flat
color="secondary"
icon="list"
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
@click="toggleAllDetails"
/>
<q-btn-dropdown
v-if="canExportFinance"
flat
color="red"
icon="picture_as_pdf"
label="Yazdır"
>
<q-list style="min-width: 240px">
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(true)">
<q-item-section class="text-primary">
Detaylı Cari Yaşlandırmalı Bakiye Listesi Yazdır
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(false)">
<q-item-section class="text-secondary">
Detaysız Cari Yaşlandırmalı Bakiye Listesi Yazdır
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn
v-if="canExportFinance"
flat
color="green-8"
icon="table_view"
label="Excel"
@click="downloadAgingBalanceExcel"
/>
<q-btn
flat
color="primary"
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
@click="toggleFiltersCollapsed"
/>
</div>
</div>
<q-table
title="Cari Yaşlandırmalı Cari Bakiye Listesi"
:rows="store.summaryRows"
:columns="summaryColumns"
row-key="group_key"
:loading="store.loading"
flat
bordered
dense
wrap-cells
separator="cell"
hide-bottom
:rows-per-page-options="[0]"
:pagination="{ rowsPerPage: 0 }"
:table-style="{ tableLayout: 'fixed', width: '100%' }"
class="balance-table"
>
<template #header="props">
<q-tr :props="props" class="header-row">
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
<q-tr class="totals-row">
<q-th
v-for="col in props.cols"
:key="`tot-${col.name}`"
:class="col.align === 'right' ? 'text-right' : ''"
>
{{ totalCellValue(col.name) }}
</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props" class="sub-header-row">
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<q-btn
v-if="col.name === 'expand'"
dense
flat
round
size="sm"
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
@click="toggleGroup(props.row.group_key)"
/>
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
</span>
<span v-else-if="col.name === 'prbr_1_3'" class="text-right block prbr-cell">
{{ formatCurrencyMap(props.row.bakiye_1_3_map) }}
</span>
<span v-else-if="staticMoneyFields.includes(col.name)" class="text-center block">
{{ formatAmount(props.row[col.field]) }}
</span>
<span v-else-if="dayFields.includes(col.name)" class="text-center block">
{{ formatDay(props.row[col.field]) }}
</span>
<span v-else>{{ props.row[col.field] || '-' }}</span>
</q-td>
</q-tr>
<q-tr v-if="expanded[props.row.group_key]" class="detail-host-row">
<q-td colspan="100%">
<div class="detail-wrap">
<q-table
:rows="store.getDetailsByGroup(props.row.group_key)"
:columns="detailColumns"
row-key="cari_kodu"
dense
flat
bordered
hide-bottom
:table-style="{ tableLayout: 'fixed', width: '100%' }"
class="detail-table"
>
<template #body-cell-prbr_1_2="scope">
<q-td :props="scope" class="text-right prbr-cell">
{{ formatRowPrBr(scope.row, '1_2') }}
</q-td>
</template>
<template #body-cell-prbr_1_3="scope">
<q-td :props="scope" class="text-right prbr-cell">
{{ formatRowPrBr(scope.row, '1_3') }}
</q-td>
</template>
<template #body-cell-vade_gun="scope">
<q-td :props="scope" class="text-center">
{{ formatDay(scope.row.vade_gun) }}
</q-td>
</template>
<template #body-cell-vade_belge_tarihi_gun="scope">
<q-td :props="scope" class="text-center">
{{ formatDay(scope.row.vade_belge_tarihi_gun) }}
</q-td>
</template>
</q-table>
</div>
</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 modüle erişim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useQuasar } from 'quasar'
import { onMounted } from 'vue'
import { useAccountAgingBalanceStore } from 'src/stores/accountAgingBalanceStore'
import { usePermission } from 'src/composables/usePermission'
import { download, extractApiErrorDetail } from 'src/services/api'
const store = useAccountAgingBalanceStore()
const expanded = ref({})
const allDetailsOpen = ref(false)
const filtersCollapsed = ref(false)
const $q = useQuasar()
const { canRead, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance')
const islemTipiOptions = [
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' },
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
]
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
const dayFields = ['vade_gun', 'vade_belge_tarihi_gun']
function toNumericSortValue (value) {
if (typeof value === 'number') {
return Number.isFinite(value) ? value : 0
}
const s = String(value ?? '').trim()
if (!s) return 0
const hasComma = s.includes(',')
const hasDot = s.includes('.')
let normalized = s.replace(/\s+/g, '')
if (hasComma && hasDot) {
const lastComma = normalized.lastIndexOf(',')
const lastDot = normalized.lastIndexOf('.')
if (lastComma > lastDot) {
normalized = normalized.replace(/\./g, '').replace(',', '.')
} else {
normalized = normalized.replace(/,/g, '')
}
} else if (hasComma) {
normalized = normalized.replace(/\./g, '').replace(',', '.')
}
const n = Number.parseFloat(normalized)
return Number.isFinite(n) ? n : 0
}
function sortTextTr (a, b) {
return String(a ?? '').localeCompare(String(b ?? ''), 'tr', { sensitivity: 'base' })
}
const metricDefs = {
prbr_1_2: { name: 'prbr_1_2', label: '1_2 Bakiye\nPr.Br', field: 'prbr_1_2', align: 'right', sortable: false },
prbr_1_3: { name: 'prbr_1_3', label: '1_3 Bakiye\nPr.Br', field: 'prbr_1_3', align: 'right', sortable: false },
usd_1_2: { name: 'usd_bakiye_1_2', label: '1_2 USD_BAKIYE', field: 'usd_bakiye_1_2', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
try_1_2: { name: 'tl_bakiye_1_2', label: '1_2 TRY_BAKIYE', field: 'tl_bakiye_1_2', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
usd_1_3: { name: 'usd_bakiye_1_3', label: '1_3 USD_BAKIYE', field: 'usd_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
}
const selectedMetricKeys = computed(() => {
const selected = store.filters.islemTipi || []
if (!selected.length) return [...Object.keys(metricDefs)]
return selected.filter((k) => k in metricDefs)
})
const summaryColumns = computed(() => ([
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left', sortable: true, sort: sortTextTr },
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left', sortable: true, sort: sortTextTr },
...selectedMetricKeys.value.map((k) => metricDefs[k]),
{ name: 'vade_gun', label: 'Vade Gun', field: 'vade_gun', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
{ name: 'vade_belge_tarihi_gun', label: 'Belge Tarihi Gun', field: 'vade_belge_tarihi_gun', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
]))
const liveTotals = computed(() => {
return store.filteredRows.reduce((acc, row) => {
acc.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
acc.tl_bakiye_1_2 += Number(row.tl_bakiye_1_2) || 0
acc.usd_bakiye_1_3 += Number(row.usd_bakiye_1_3) || 0
acc.tl_bakiye_1_3 += Number(row.tl_bakiye_1_3) || 0
const vadeGun = Number(row.vade_gun) || 0
const vadeBelge = Number(row.vade_belge_tarihi_gun) || 0
if (vadeGun !== 0 || vadeBelge !== 0) {
acc.vade_count += 1
acc.vade_gun_sum += vadeGun
acc.vade_belge_sum += vadeBelge
}
return acc
}, {
usd_bakiye_1_2: 0,
tl_bakiye_1_2: 0,
usd_bakiye_1_3: 0,
tl_bakiye_1_3: 0,
vade_gun_sum: 0,
vade_belge_sum: 0,
vade_count: 0
})
})
const detailColumns = computed(() => [
{ name: 'cari_kodu', label: 'Cari Kodu', field: 'cari_kodu', align: 'left' },
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
{ name: 'sirket', label: 'Şirket', field: 'sirket', align: 'left' },
{ name: 'sirket_detay', label: 'Şirket Detayı', field: 'sirket_detay', align: 'left' },
{ name: 'muhasebe_kodu', label: 'Muhasebe Kodu', field: 'muhasebe_kodu', align: 'left' },
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left' },
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left' },
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left' },
{ name: 'ozellik05', label: 'Ülke', field: 'ozellik05', align: 'left' },
{ name: 'il', label: 'İl', field: 'il', align: 'left' },
{ name: 'ilce', label: 'İlçe', field: 'ilce', align: 'left' },
{ name: 'cari_doviz', label: 'Döviz', field: 'cari_doviz', align: 'left' },
...selectedMetricKeys.value.map((k) => metricDefs[k]),
{ name: 'vade_gun', label: 'Vade Gun', field: 'vade_gun', align: 'center' },
{ name: 'vade_belge_tarihi_gun', label: 'Belge Tarihi Gun', field: 'vade_belge_tarihi_gun', align: 'center' }
])
function onReset () {
store.resetFilters()
store.fetchBalances()
}
function onToggle12Changed (val) {
if (val) {
store.filters.excludeZeroBalance13 = false
}
}
function onToggle13Changed (val) {
if (val) {
store.filters.excludeZeroBalance12 = false
}
}
function toggleFiltersCollapsed () {
filtersCollapsed.value = !filtersCollapsed.value
}
function toggleGroup (key) {
expanded.value[key] = !expanded.value[key]
if (!expanded.value[key]) {
allDetailsOpen.value = false
return
}
allDetailsOpen.value =
store.summaryRows.length > 0 &&
store.summaryRows.every(r => expanded.value[r.group_key])
}
function toggleAllDetails () {
allDetailsOpen.value = !allDetailsOpen.value
if (allDetailsOpen.value) {
const next = {}
for (const row of store.summaryRows) {
next[row.group_key] = true
}
expanded.value = next
return
}
expanded.value = {}
}
async function downloadAgingBalancePDF (detailed) {
if (!canExportFinance.value) {
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
return
}
if (!store.hasFetched) {
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
return
}
try {
const params = {
cari_search: String(store.filters.cariSearch || '').trim(),
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
piyasa: (store.filters.piyasa || []).join(','),
temsilci: (store.filters.temsilci || []).join(','),
risk_durumu: (store.filters.riskDurumu || []).join(','),
islem_tipi: (store.filters.islemTipi || []).join(','),
ulke: (store.filters.ulke || []).join(','),
il: (store.filters.il || []).join(','),
ilce: (store.filters.ilce || []).join(','),
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0',
detailed: detailed ? '1' : '0'
}
const blob = await download('/finance/account-aging-statement/export-pdf', params)
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
window.open(pdfUrl, '_blank')
} catch (err) {
const detail = await extractApiErrorDetail(err?.original || err)
$q.notify({
type: 'negative',
message: detail || 'PDF oluşturulamadı',
position: 'top-right'
})
}
}
async function downloadAgingBalanceExcel () {
if (!canExportFinance.value) {
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
return
}
if (!store.hasFetched) {
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
return
}
try {
const params = {
cari_search: String(store.filters.cariSearch || '').trim(),
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
piyasa: (store.filters.piyasa || []).join(','),
temsilci: (store.filters.temsilci || []).join(','),
risk_durumu: (store.filters.riskDurumu || []).join(','),
islem_tipi: (store.filters.islemTipi || []).join(','),
ulke: (store.filters.ulke || []).join(','),
il: (store.filters.il || []).join(','),
ilce: (store.filters.ilce || []).join(','),
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0'
}
const file = await download('/finance/account-aging-statement/export-excel', params)
const blob = new Blob(
[file],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
)
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'cari_yaslandirmali_bakiye_listesi.xlsx'
document.body.appendChild(a)
a.click()
a.remove()
window.URL.revokeObjectURL(url)
} catch (err) {
const detail = await extractApiErrorDetail(err?.original || err)
$q.notify({
type: 'negative',
message: detail || 'Excel oluşturulamadı',
position: 'top-right'
})
}
}
function formatAmount (value) {
const n = Number(value || 0)
return new Intl.NumberFormat('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n)
}
function formatDay (value) {
const n = Number(value || 0)
return new Intl.NumberFormat('tr-TR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(n)
}
function selectionLabel (arr, label) {
const count = Array.isArray(arr) ? arr.length : 0
if (count === 0) return `Tümü (${label})`
if (count === 1) return '1 seçim'
return `${count} seçim`
}
function totalCellValue (colName) {
if (colName === 'expand') return 'Toplam'
if (colName === 'piyasa') return '-'
if (colName === 'temsilci') return '-'
if (colName === 'risk_durumu') return '-'
if (colName === 'prbr_1_2') return formatCurrencyMap(totalByCurrency('1_2'))
if (colName === 'prbr_1_3') return formatCurrencyMap(totalByCurrency('1_3'))
if (colName === 'usd_bakiye_1_2') return formatAmount(liveTotals.value.usd_bakiye_1_2)
if (colName === 'tl_bakiye_1_2') return formatAmount(liveTotals.value.tl_bakiye_1_2)
if (colName === 'usd_bakiye_1_3') return formatAmount(liveTotals.value.usd_bakiye_1_3)
if (colName === 'tl_bakiye_1_3') return formatAmount(liveTotals.value.tl_bakiye_1_3)
if (colName === 'vade_gun') return formatDay(liveTotals.value.vade_count > 0 ? liveTotals.value.vade_gun_sum / liveTotals.value.vade_count : 0)
if (colName === 'vade_belge_tarihi_gun') return formatDay(liveTotals.value.vade_count > 0 ? liveTotals.value.vade_belge_sum / liveTotals.value.vade_count : 0)
return '-'
}
function totalByCurrency (tip) {
const key = tip === '1_2' ? 'bakiye_1_2_map' : 'bakiye_1_3_map'
const out = {}
for (const r of store.summaryRows) {
const m = r[key] || {}
for (const [curr, val] of Object.entries(m)) {
out[curr] = (Number(out[curr]) || 0) + (Number(val) || 0)
}
}
return out
}
function formatCurrencyMap (mapObj) {
const entries = Object.entries(mapObj || {})
.filter(([, amount]) => Number(amount) !== 0)
.sort((a, b) => a[0].localeCompare(b[0], 'en'))
if (!entries.length) return '-'
return entries
.map(([curr, amount]) => `${curr}: ${formatAmount(amount)}`)
.join('\n')
}
function formatRowPrBr (row, tip) {
const curr = String(row.cari_doviz || '').trim().toUpperCase() || 'N/A'
const amount = tip === '1_2'
? (Number(row.bakiye_1_2) || 0)
: (Number(row.bakiye_1_3) || 0)
if (amount === 0) return '-'
return `${curr} ${formatAmount(amount)}`
}
onMounted(async () => {
store.filters.selectedDate = new Date().toISOString().slice(0, 10)
await store.fetchBalances()
})
</script>
<style scoped>
.page-layout {
height: calc(100vh - 110px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.filter-sticky {
position: sticky;
top: 0;
z-index: 20;
background: #fff;
padding-bottom: 6px;
}
.filter-sticky.collapsed {
padding-bottom: 0;
}
.top-actions.single-line {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
padding-bottom: 4px;
}
.top-actions.single-line > [class*='col-'],
.top-actions.single-line > .col-auto {
flex: 0 0 auto;
min-width: 220px;
}
.top-actions.single-line > .col-auto {
min-width: auto;
}
.filters-panel {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 8px;
background: #fafafa;
}
.compact-select :deep(.q-field__control) {
min-height: 40px;
}
.compact-select :deep(.q-field__native),
.compact-select :deep(.q-field__input) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.table-area {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.sticky-bar {
position: sticky;
top: 0;
z-index: 9;
}
.balance-table {
flex: 1;
min-height: 0;
}
.balance-table :deep(.q-table__container) {
height: 100%;
display: flex;
flex-direction: column;
}
.balance-table :deep(.q-table__top) {
position: sticky;
top: 0;
z-index: 6;
background: #fff;
}
.balance-table :deep(.q-table__middle) {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
.balance-table :deep(.header-row th) {
position: sticky;
top: 0;
z-index: 8;
background: var(--q-primary);
color: #fff;
font-weight: 600;
font-family: 'Roboto', sans-serif;
}
.balance-table :deep(.totals-row th) {
position: sticky;
top: 38px;
z-index: 7;
background: var(--q-secondary);
color: var(--q-dark);
font-weight: 700;
font-family: 'Roboto', sans-serif;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
.detail-table :deep(.q-table__middle) {
max-height: 320px;
}
.balance-table :deep(.sub-header-row td) {
background: #fff;
border-bottom: 2px solid rgba(0, 0, 0, 0.18);
font-weight: 600;
}
.balance-table :deep(.sub-header-row td:first-child) {
border-left: 3px solid var(--q-primary);
}
.balance-table :deep(.detail-host-row td) {
background: #f7f7f7;
border-bottom: 10px solid #fff;
padding-top: 10px;
padding-bottom: 12px;
}
.detail-wrap {
border: 1px solid rgba(0, 0, 0, 0.14);
border-left: 4px solid var(--q-secondary);
border-radius: 6px;
background: #fff;
padding: 6px;
}
.balance-table :deep(.header-row th) {
white-space: pre-line;
line-height: 1.15;
}
.prbr-cell {
white-space: pre-line;
word-break: break-word;
line-height: 1.25;
}
.balance-table :deep(th),
.balance-table :deep(td),
.detail-table :deep(th),
.detail-table :deep(td) {
white-space: normal !important;
word-break: break-word;
overflow-wrap: anywhere;
font-size: 11px;
line-height: 1.2;
padding: 4px 6px !important;
}
.balance-table :deep(.q-table__table),
.detail-table :deep(.q-table__table) {
width: 100% !important;
}
.detail-table :deep(.q-table__middle) {
overflow-x: hidden;
}
</style>

View File

@@ -2,96 +2,104 @@
<q-page v-if="canReadFinance" class="q-px-md q-pb-md page-col statement-page">
<q-slide-transition>
<div
v-show="!filtersCollapsed"
class="local-filter-bar compact-filter q-pa-sm q-mb-xs"
>
<div class="row q-col-gutter-sm items-end">
<div class="col-12 col-md-4">
<q-select
v-model="selectedCari"
:options="filteredOptions"
label="Cari kod / isim"
filled
dense
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>
<div class="col-12 col-sm-6 col-md-2">
<q-input
v-model="dateFrom"
label="Tarih aralığı - başlangıç"
filled
dense
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 col-md-2">
<q-input
v-model="dateTo"
label="Tarih aralığı - bitiş"
filled
dense
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 class="col-12 col-sm-6 col-md-2">
<q-select
v-model="selectedMonType"
:options="monetaryTypeOptions"
label="Parasal İşlem Tipi"
emit-value
map-options
filled
dense
/>
</div>
<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
v-show="!filtersCollapsed"
class="local-filter-bar compact-filter q-pa-sm q-mb-xs"
>
<div class="row q-col-gutter-sm items-end">
<div class="col-12 col-md-4">
<q-select
v-model="selectedCari"
:options="filteredOptions"
label="Cari kod / isim"
filled
dense
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>
<div class="col-12 col-sm-6 col-md-2">
<q-input
v-model="dateFrom"
label="Tarih aralığı - başlangıç"
filled
dense
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 col-md-2">
<q-input
v-model="dateTo"
label="Tarih aralığı - bitiş"
filled
dense
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 class="col-12 col-sm-6 col-md-2">
<q-select
v-model="selectedMonType"
:options="monetaryTypeOptions"
label="Parasal İşlem Tipi"
emit-value
map-options
filled
dense
/>
</div>
<div class="col-12 col-sm-6 col-md-2">
<q-toggle
v-model="excludeOpening"
label="Devir bakiyesiz listele"
color="primary"
dense
/>
</div>
<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>
</div>
</q-slide-transition>
<!-- Tablo Alanı -->
@@ -358,6 +366,7 @@ const monetaryTypeOptions = [
{ label: '1-3 r hesap', value: ['1', '3'] }
]
const selectedMonType = ref(monetaryTypeOptions[0].value)
const excludeOpening = ref(false)
/* Expand kontrolü */
const expandedRows = ref({})
@@ -404,7 +413,8 @@ async function onFilterClick() {
enddate: dateTo.value,
accountcode: selectedCari.value,
langcode: 'TR',
parislemler: selectedMonType.value
parislemler: selectedMonType.value,
excludeopening: excludeOpening.value
})
await detailStore.loadDetails({
@@ -454,6 +464,7 @@ function resetFilters() {
dateFrom.value = ''
dateTo.value = ''
selectedMonType.value = monetaryTypeOptions[0].value
excludeOpening.value = false
statementheaderStore.headers = []
detailStore.reset()
}
@@ -715,4 +726,3 @@ async function CurrheadDownload() {
}
}
</style>