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"
        v-model:pagination="summaryPagination"
        row-key="group_key"
        :loading="store.loading"
        flat
        bordered
        dense
        wrap-cells
        separator="cell"
        hide-bottom
        :rows-per-page-options="[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 summaryPagination = ref({
  page: 1,
  rowsPerPage: 0,
  sortBy: 'ana_cari_kodu',
  descending: false
})
const $q = useQuasar()

const { canRead, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance')

const islemTipiOptions = [
  { label: '1_2', value: '1_2' },
  { label: '1_3', value: '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 || [])
    .map(v => String(v).trim())
    .filter(Boolean)

  if (selected.length === 0) {
    return [...Object.keys(metricDefs)]
  }

  const show12 = selected.includes('1_2')
  const show13 = selected.includes('1_3')

  return Object.keys(metricDefs).filter((key) => {
    if (!show12 && !show13) return true
    if (key.endsWith('_1_2')) return show12
    if (key.endsWith('_1_3')) return show13
    return true
  })
})

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',
      sort_by: String(summaryPagination.value?.sortBy || ''),
      sort_desc: summaryPagination.value?.descending ? '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>


