bug fix in cost controller list

This commit is contained in:
Hesabix 2025-03-26 18:10:38 +00:00
parent dd101c8ee2
commit 65d45e73d1
9 changed files with 746 additions and 629 deletions

View file

@ -205,7 +205,9 @@ class CostController extends AbstractController
// اعمال فیلترها
if (!empty($filters)) {
// جستجوی متنی
if (isset($filters['search'])) {
$searchValue = is_array($filters['search']) ? $filters['search']['value'] : $filters['search'];
$queryBuilder->leftJoin('d.hesabdariRows', 'r')
->leftJoin('r.person', 'p')
->leftJoin('r.ref', 't')
@ -219,18 +221,42 @@ class CostController extends AbstractController
't.name LIKE :search'
)
)
->setParameter('search', "%{$filters['search']}%");
->setParameter('search', "%{$searchValue}%");
}
if (isset($filters['dateFrom'])) {
$queryBuilder->andWhere('d.date >= :dateFrom')
->setParameter('dateFrom', $filters['dateFrom']);
}
if (isset($filters['dateTo'])) {
$queryBuilder->andWhere('d.date <= :dateTo')
// فیلتر زمانی
if (isset($filters['timeFilter'])) {
$today = $jdate->jdate('Y/m/d', time());
switch ($filters['timeFilter']) {
case 'today':
$queryBuilder->andWhere('d.date = :today')
->setParameter('today', $today);
break;
case 'week':
$weekStart = $jdate->jdate('Y/m/d', strtotime('-6 days'));
$queryBuilder->andWhere('d.date BETWEEN :weekStart AND :today')
->setParameter('weekStart', $weekStart)
->setParameter('today', $today);
break;
case 'month':
$monthStart = $jdate->jdate('Y/m/01', time());
$queryBuilder->andWhere('d.date BETWEEN :monthStart AND :today')
->setParameter('monthStart', $monthStart)
->setParameter('today', $today);
break;
case 'custom':
if (isset($filters['dateFrom']) && isset($filters['dateTo'])) {
$queryBuilder->andWhere('d.date BETWEEN :dateFrom AND :dateTo')
->setParameter('dateFrom', $filters['dateFrom'])
->setParameter('dateTo', $filters['dateTo']);
}
break;
case 'all':
default:
// بدون فیلتر زمانی اضافه
break;
}
}
if (isset($filters['amount'])) {
$queryBuilder->andWhere('d.amount = :amount')
@ -239,7 +265,7 @@ class CostController extends AbstractController
}
// اعمال مرتب‌سازی
$sortField = $sort['sortBy'] ?? 'id';
$sortField = is_array($sort['sortBy']) ? ($sort['sortBy']['key'] ?? 'id') : ($sort['sortBy'] ?? 'id');
$sortDirection = ($sort['sortDesc'] ?? true) ? 'DESC' : 'ASC';
$queryBuilder->orderBy("d.$sortField", $sortDirection);
@ -265,7 +291,7 @@ class CostController extends AbstractController
'code' => $doc['code'],
'des' => $doc['des'],
'amount' => $doc['amount'],
'submitter' => $doc['submitter']
'submitter' => $doc['submitter'],
];
// دریافت اطلاعات مرکز هزینه و مبلغ
@ -282,7 +308,7 @@ class CostController extends AbstractController
$item['costCenters'] = array_map(function ($detail) {
return [
'name' => $detail['center_name'],
'amount' => (int) $detail['amount']
'amount' => (int) $detail['amount'],
];
}, $costDetails);
@ -301,7 +327,7 @@ class CostController extends AbstractController
$item['person'] = $personInfo ? [
'id' => $personInfo['id'],
'nikename' => $personInfo['nikename'],
'code' => $personInfo['code']
'code' => $personInfo['code'],
] : null;
$dataTemp[] = $item;
@ -311,7 +337,7 @@ class CostController extends AbstractController
'items' => $dataTemp,
'total' => (int) $totalItems,
'page' => $page,
'limit' => $limit
'limit' => $limit,
]);
}

View file

@ -15,6 +15,7 @@
"@ckeditor/ckeditor5-image": "^36.0.1",
"@ckeditor/ckeditor5-vue": "^4.0.1",
"@ckeditor/vite-plugin-ckeditor5": "^0.1.1",
"@date-io/date-fns-jalali": "^3.2.0",
"@mdi/font": "^7.4.47",
"@syncfusion/ej2-vue-dropdowns": "^21.2.5",
"@vuelidate/core": "^2.0.0",
@ -23,8 +24,11 @@
"animate.css": "^4.1.1",
"apexcharts": "^4.4.0",
"axios": "^1.2.3",
"date-fns": "^4.1.0",
"date-fns-jalali": "^3.2.0-0",
"downloadjs": "^1.4.7",
"file-saver": "^2.0.5",
"jalali-moment": "^3.3.11",
"libphonenumber-js": "^1.10.44",
"lodash": "^4.17.21",
"maska": "^3.0.4",

View file

@ -1,53 +1,95 @@
<script lang="ts">
import {ref, watch, computed, defineComponent} from 'vue'
export default defineComponent({
name:'Hdatepicker',
inheritAttrs: false,
data:()=>{
const self = this;
<template>
<div>
<v-text-field v-model="displayDate" :label="label" prepend-inner-icon="mdi-calendar" persistent-placeholder
class="v-date-input" :rules="rules" @input="updateDateFromInput" @click:prepend="togglePicker"></v-text-field>
<date-picker v-model="displayDate" type="date" format="jYYYY/jMM/jDD" display-format="jYYYY/jMM/jDD"
:min="minDatePersian" :max="maxDatePersian" custom-input=".v-date-input" :input-mode="false"
:editable="pickerActive" @close="pickerActive = false"></date-picker>
</div>
</template>
<script>
import axios from 'axios';
import moment from 'jalali-moment';
export default {
props: {
value: {
type: String,
default: '',
},
label: {
type: String,
default: 'تاریخ',
},
rules: {
type: Array,
default: () => [],
},
},
data() {
return {
isMenuOpen: ref(false),
selectedDate: ref(),
output:''
}
displayDate: '', // تاریخ به فرمت شمسی
pickerActive: false, // کنترل باز شدن تقویم
minDatePersian: '', // تاریخ شروع سال مالی (شمسی برای پکیج)
maxDatePersian: '', // تاریخ پایان سال مالی (شمسی برای پکیج)
};
},
watch: {
selectedDate(){
this.isMenuOpen = false
this.output = this.selectedDate.toLocaleString(
localStorage.getItem('UI_LANG'),
{year: 'numeric', month: 'numeric', day: 'numeric'}
)
displayDate(newVal) {
if (newVal) {
this.$emit('input', newVal); // ارسال تاریخ شمسی به والد
} else {
this.$emit('input', '');
}
},
setup(props, ctx) {
value(newVal) {
if (newVal) {
this.displayDate = newVal;
} else {
this.displayDate = '';
}
},
})
},
async mounted() {
await this.fetchYearData();
if (!this.value && this.displayDate) {
this.$emit('input', this.displayDate);
}
},
methods: {
async fetchYearData() {
axios.get('/api/year/get').then((response) => {
this.minDatePersian = response.data.start; // فرمت YYYY/MM/DD شمسی
this.maxDatePersian = response.data.end; // فرمت YYYY/MM/DD شمسی
this.displayDate = response.data.now; // تاریخ جاری شمسی
});
},
updateDateFromInput(value) {
// بررسی و اعتبارسنجی تاریخ وارد شده توسط کاربر
if (value && moment(value, 'YYYY/MM/DD', 'fa', true).isValid()) {
const parsedDate = moment(value, 'YYYY/MM/DD').locale('fa');
if (
parsedDate.isSameOrAfter(moment(this.minDatePersian, 'YYYY/MM/DD')) &&
parsedDate.isSameOrBefore(moment(this.maxDatePersian, 'YYYY/MM/DD'))
) {
this.displayDate = value;
} else {
this.displayDate = ''; // یا خطا نمایش بدید
}
}
},
togglePicker() {
this.pickerActive = !this.pickerActive; // تغییر وضعیت تقویم
},
},
};
</script>
<template>
<v-menu v-model="isMenuOpen" :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-text-field
v-model="output"
readonly
v-bind="props"
:value="output"
outlined
color="primary"
@input="(v: any) => $emit('input', v)"
></v-text-field>
</template>
<!-- !!! hide-actions prop too !!! -->
<v-date-picker color="indigo" v-model="selectedDate">
<template v-slot:header></template>
</v-date-picker>
</v-menu>
</template>
<style scoped>
/* مطمئن شدن که تقویم فقط با آیکون فعال بشه */
.v-date-input {
position: relative;
}
</style>

View file

@ -1,4 +1,3 @@
import moment from 'moment'
export default {
data() {
return {

View file

@ -13,7 +13,7 @@ if(activeLanguageCode == null || activeLanguageCode == undefined){
activeLanguageCode='fa';
}
const i18n = createI18n({
legacy: false, // Vuetify does not support the legacy mode of vue-i18n
legacy: false,
locale: activeLanguageCode,
fallbackLocale: activeLanguageCode,
messages,

View file

@ -6,13 +6,13 @@ import "./registerServiceWorker";
import { vMaska } from "maska/vue"
import VueApexCharts from "vue3-apexcharts";
import Uploader from 'vue-media-upload';
import DateFnsJalaliAdapter from '@date-io/date-fns-jalali';
import faIR from 'date-fns-jalali/locale/fa-IR';
//pinia
import { createPinia } from 'pinia'
const pinia = createPinia();
import { VDateInput } from 'vuetify/labs/VDateInput'
import CKEditor from '@ckeditor/ckeditor5-vue';
// Import translations for the Persian language.
import '@ckeditor/ckeditor5-build-classic/build/translations/fa';
@ -124,6 +124,12 @@ const vuetify = createVuetify({
},
},
},
date: {
adapter: DateFnsJalaliAdapter,
locale: {
fa:faIR // تنظیم زبان فارسی
},
},
});
// @ts-ignore
@ -146,7 +152,6 @@ import 'vue-select/dist/vue-select.css';
app.component('v-cob', vSelect)
import Hdatepicker from "@/components/forms/Hdatepicker.vue";
import calendarLocalConfig from "@/i18n/calendarLocalConfig";
app.component('h-date-picker', Hdatepicker);
app.use(CKEditor)
app.use(Vue3PersianDatetimePicker, {

View file

@ -647,14 +647,12 @@ export default {
{{ $t('drawer.accounting_docs') }}
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/accounting/list') }}</span>
</v-list-item-title>
<template v-slot:append>
<!--
<template v-slot:append v-if="isPluginActive('accpro') && 1==2">
<v-tooltip :text="$t('dialog.add_new')" location="end">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-plus-box" variant="plain" to="/acc/accounting/mod/" />
</template>
</v-tooltip>
-->
</template>
</v-list-item>
<v-list-item v-if="permissions.accounting" to="/acc/accounting/open_balance">

View file

@ -3,11 +3,16 @@
<v-form @submit.prevent="submitForm">
<v-row>
<v-col cols="12" md="6">
<v-text-field
<Hdatepicker
v-model="form.date"
label="تاریخ (شمسی)"
placeholder="1403/02/28"
:rules="[v => !!v || 'تاریخ الزامی است']"
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="form.des"
label="توضیحات سند"
placeholder="توضیحات مربوط به سند"
></v-text-field>
</v-col>
</v-row>
@ -114,8 +119,12 @@
<script>
import axios from 'axios';
import moment from 'jalali-moment';
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
export default {
components: {
Hdatepicker,
},
props: {
docId: {
type: Number,
@ -125,7 +134,8 @@
data() {
return {
form: {
date: '',
date: '', // تاریخ به فرمت ISO (مثلاً 2025-03-24)
des: '',
rows: [
{ ref: null, refName: '', bd: '0', bs: '0', des: '', selectedAccounts: [] },
],
@ -162,7 +172,9 @@
async fetchDoc() {
try {
const response = await axios.get(`/api/hesabdari/doc/${this.docId}`);
this.form.date = response.data.data.date;
const serverDate = response.data.data.date; // فرض: تاریخ شمسی از سرور
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY-MM-DD');
this.form.des = response.data.data.des || '';
this.form.rows = response.data.data.rows.map(row => ({
ref: row.ref.id,
refName: row.ref.name,
@ -206,7 +218,8 @@
}
const payload = {
date: this.form.date,
date: moment(this.form.date, 'YYYY-MM-DD').locale('fa').format('YYYY/MM/DD'), // ارسال به فرمت شمسی
des: this.form.des,
rows: this.form.rows.map(row => ({
ref: row.ref,
bd: row.bd,

View file

@ -3,36 +3,30 @@
<template v-slot:prepend>
<v-tooltip :text="$t('dialog.back')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text" icon="mdi-arrow-right" />
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
<v-spacer />
<v-slide-group show-arrows>
<v-slide-group-item>
<v-tooltip :text="$t('dialog.add_new')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="mdi-plus" color="primary" to="/acc/costs/mod/" />
</template>
</v-tooltip>
</v-slide-group-item>
<v-slide-group-item>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon=""
color="red"
>
<v-btn v-bind="props" icon="" color="red">
<v-tooltip activator="parent" :text="$t('dialog.export_pdf')" location="bottom" />
<v-icon icon="mdi-file-pdf-box" />
</v-btn>
</template>
<v-list>
<v-list-subheader color="primary">{{ $t('dialog.export_pdf') }}</v-list-subheader>
<v-list-item :disabled="!hasSelected" class="text-dark" :title="$t('dialog.selected')" @click="exportPDF(false)">
<v-list-item :disabled="!hasSelected" class="text-dark" :title="$t('dialog.selected')"
@click="exportPDF(false)">
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-check" />
</template>
@ -44,23 +38,18 @@
</v-list-item>
</v-list>
</v-menu>
</v-slide-group-item>
<v-slide-group-item>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon=""
color="green"
>
<v-btn v-bind="props" icon="" color="green">
<v-tooltip activator="parent" :text="$t('dialog.export_excel')" location="bottom" />
<v-icon icon="mdi-file-excel-box" />
</v-btn>
</template>
<v-list>
<v-list-subheader color="primary">{{ $t('dialog.export_excel') }}</v-list-subheader>
<v-list-item :disabled="!hasSelected" class="text-dark" :title="$t('dialog.selected')" @click="exportExcel(false)">
<v-list-item :disabled="!hasSelected" class="text-dark" :title="$t('dialog.selected')"
@click="exportExcel(false)">
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-check" />
</template>
@ -72,35 +61,16 @@
</v-list-item>
</v-list>
</v-menu>
</v-slide-group-item>
<v-slide-group-item>
<v-tooltip :text="$t('dialog.delete')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon="mdi-trash-can"
color="danger"
@click="deleteGroup"
:disabled="!hasSelected"
/>
<v-btn v-bind="props" icon="mdi-trash-can" color="danger" @click="deleteGroup" :disabled="!hasSelected" />
</template>
</v-tooltip>
</v-slide-group-item>
</v-slide-group>
</v-toolbar>
<v-text-field
:loading="loading"
color="green"
class="mb-0 pt-0 rounded-0"
hide-details="auto"
density="compact"
:placeholder="$t('dialog.search_txt')"
v-model="searchQuery"
type="text"
@input="debouncedSearch"
>
<v-text-field :loading="loading" color="green" class="mb-0 pt-0 rounded-0" hide-details="auto" density="compact"
:placeholder="$t('dialog.search_txt')" v-model="searchQuery" type="text" clearable>
<template v-slot:prepend-inner>
<v-tooltip location="bottom" :text="$t('dialog.search')">
<template v-slot:activator="{ props }">
@ -108,35 +78,40 @@
</template>
</v-tooltip>
</template>
<template v-slot:append-inner>
<v-menu :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-icon v-bind="props" size="sm" color="primary">
<v-icon>mdi-filter</v-icon>
<v-tooltip activator="parent" :text="$t('dialog.filters')" location="bottom" />
</v-icon>
</template>
<v-list>
<v-list-subheader color="primary">
<v-icon>mdi-filter</v-icon>
{{ $t('dialog.filters') }}
</v-list-subheader>
<v-list-item v-for="(filter, index) in timeFilters" :key="index" class="text-dark">
<template v-slot:title>
<v-checkbox v-model="filter.checked" :label="filter.label" @change="applyTimeFilter(filter.value)"
hide-details />
</template>
</v-list-item>
</v-list>
</v-menu>
</template>
</v-text-field>
<v-data-table-server
:headers="headers"
:items="items"
:loading="loading"
:items-length="totalItems"
v-model:options="serverOptions"
@update:options="fetchData"
item-value="code"
class="elevation-1 data-table-wrapper"
:header-props="{ class: 'custom-header' }"
>
<v-data-table-server :headers="headers" :items="items" :loading="loading" :items-length="totalItems"
v-model:options="serverOptions" @update:options="fetchData" item-value="code" class="elevation-1 data-table-wrapper"
:header-props="{ class: 'custom-header' }">
<template #header.checkbox>
<v-checkbox
:model-value="isAllSelected"
@change="toggleSelectAll"
hide-details
density="compact"
/>
<v-checkbox :model-value="isAllSelected" @change="toggleSelectAll" hide-details density="compact" />
</template>
<template #item.checkbox="{ item }">
<v-checkbox
:model-value="selectedItems.has(item.code)"
@change="toggleSelection(item.code)"
hide-details
density="compact"
/>
<v-checkbox :model-value="selectedItems.has(item.code)" @change="toggleSelection(item.code)" hide-details
density="compact" />
</template>
<template #item.operation="{ item }">
@ -163,10 +138,30 @@
</v-list>
</v-menu>
</template>
<template #item.amount="{ item }">
{{ $filters.formatNumber(item.amount) }}
</template>
<template #item.costCenter="{ item }">
{{item.costCenters.map(center => center.name).join(', ') || '—'}}
</template>
</v-data-table-server>
<v-row class="mt-4 pa-4">
<v-col cols="6">
<v-card flat>
<v-card-title>جمع کل هزینهها</v-card-title>
<v-card-text>{{ $filters.formatNumber(totalCost) }}</v-card-text>
</v-card>
</v-col>
<v-col cols="6">
<v-card flat>
<v-card-title>جمع موارد انتخابشده</v-card-title>
<v-card-text>{{ $filters.formatNumber(selectedCost) }}</v-card-text>
</v-card>
</v-col>
</v-row>
</template>
<script setup>
@ -175,8 +170,8 @@ import axios from 'axios';
import Swal from 'sweetalert2';
import { debounce } from 'lodash';
import { getApiUrl } from '/src/hesabixConfig';
import moment from 'jalali-moment';
// تنظیم پایه URL از hesabixConfig
const apiUrl = getApiUrl();
axios.defaults.baseURL = apiUrl;
@ -186,28 +181,26 @@ const items = ref([]);
const selectedItems = ref(new Set());
const totalItems = ref(0);
const searchQuery = ref('');
const timeFilter = ref('all');
// فیلترهای زمانی (بدون بازه دلخواه)
const timeFilters = ref([
{ label: 'امروز', value: 'today', checked: false },
{ label: 'این هفته', value: 'week', checked: false },
{ label: 'این ماه', value: 'month', checked: false },
{ label: 'همه', value: 'all', checked: true },
]);
// تعریف ستونهای جدول
const headers = ref([
{
title: '',
key: 'checkbox',
sortable: false,
width: '50',
align: 'center'
},
{
title: 'ردیف',
key: 'index',
align: 'center',
sortable: false,
width: '70'
},
{ title: '', key: 'checkbox', sortable: false, width: '50', align: 'center' },
{ title: 'ردیف', key: 'index', align: 'center', sortable: false, width: '70' },
{ title: 'عملیات', key: 'operation', align: 'center', sortable: false, width: '100' },
{ title: 'کد', key: 'code', align: 'center', sortable: true },
{ title: 'مرکز هزینه', key: 'costCenter', align: 'center', sortable: false },
{ title: 'مبلغ', key: 'amount', align: 'center', sortable: true },
{ title: 'تاریخ', key: 'date', align: 'center', sortable: true },
{ title: 'شرح', key: 'des', align: 'center', sortable: true },
{ title: 'مبلغ', key: 'amount', align: 'center', sortable: true },
]);
// تنظیمات سرور
@ -218,10 +211,20 @@ const serverOptions = ref({
sortDesc: [],
});
// اضافه کردن computed property برای کنترل وضعیت دکمههای عملیات
// Computed properties
const hasSelected = computed(() => selectedItems.value.size > 0);
const isAllSelected = computed(() => selectedItems.value.size === items.value.length);
const totalCost = computed(() => {
return items.value.reduce((sum, item) => sum + Number(item.amount || 0), 0);
});
const selectedCost = computed(() => {
return items.value
.filter((item) => selectedItems.value.has(item.code))
.reduce((sum, item) => sum + Number(item.amount || 0), 0);
});
// فچ کردن دادهها از سرور
const fetchData = async () => {
try {
@ -231,9 +234,33 @@ const fetchData = async () => {
if (searchQuery.value.trim()) {
filters.search = { value: searchQuery.value.trim() };
}
if (timeFilter.value) {
filters.timeFilter = timeFilter.value;
const sortBy = serverOptions.value.sortBy?.[0] || 'code';
const sortDesc = serverOptions.value.sortDesc?.[0] ?? true;
const today = moment().locale('fa').format('YYYY/MM/DD');
switch (timeFilter.value) {
case 'today':
filters.dateFrom = today;
filters.dateTo = today;
break;
case 'week':
filters.dateFrom = moment().locale('fa').subtract(6, 'days').format('YYYY/MM/DD');
filters.dateTo = today;
break;
case 'month':
filters.dateFrom = moment().locale('fa').startOf('jMonth').format('YYYY/MM/DD');
filters.dateTo = today;
break;
case 'all':
default:
break;
}
}
const sortByArray = Array.isArray(serverOptions.value.sortBy) ? serverOptions.value.sortBy : [];
const sortDescArray = Array.isArray(serverOptions.value.sortDesc) ? serverOptions.value.sortDesc : [];
const sortBy = sortByArray.length > 0 ? sortByArray[0].key : 'code';
const sortDesc = sortDescArray.length > 0 ? sortDescArray[0] : true;
const payload = {
filters,
@ -249,22 +276,20 @@ const fetchData = async () => {
const response = await axios.post('/api/cost/list/search', {
type: 'cost',
...payload
...payload,
});
if (response.data?.items) {
// اضافه کردن شماره ردیف به هر آیتم
const startIndex = (serverOptions.value.page - 1) * serverOptions.value.itemsPerPage;
items.value = response.data.items.map((item, index) => ({
...item,
index: startIndex + index + 1
index: startIndex + index + 1,
}));
totalItems.value = response.data.total; // استفاده از total از پاسخ سرور
totalItems.value = response.data.total;
} else {
items.value = [];
totalItems.value = 0;
}
} catch (error) {
console.error('Error fetching data:', error);
Swal.fire({
@ -280,6 +305,15 @@ const fetchData = async () => {
// دیبونس برای جستجو
const debouncedSearch = debounce(() => fetchData(), 500);
// اعمال فیلتر زمانی
const applyTimeFilter = (value) => {
timeFilters.value.forEach((filter) => {
filter.checked = filter.value === value;
});
timeFilter.value = value;
fetchData();
};
// حذف یک آیتم
const deleteItem = async (code) => {
const result = await Swal.fire({
@ -315,11 +349,8 @@ const deleteItem = async (code) => {
}
};
// تابع toggleSelection را به این صورت تغییر میدهیم
// انتخاب و لغو انتخاب
const toggleSelection = (code) => {
const item = items.value.find(i => i.code === code);
if (!item) return;
if (selectedItems.value.has(code)) {
selectedItems.value.delete(code);
} else {
@ -327,18 +358,15 @@ const toggleSelection = (code) => {
}
};
// تابع toggleSelectAll را به این صورت تغییر میدهیم
const toggleSelectAll = () => {
if (selectedItems.value.size === items.value.length) {
selectedItems.value.clear();
} else {
items.value.forEach(item => {
selectedItems.value.add(item.code);
});
items.value.forEach((item) => selectedItems.value.add(item.code));
}
};
// تغییر توابع export
// خروجی PDF
const exportPDF = async (all = false) => {
try {
loading.value = true;
@ -351,10 +379,9 @@ const exportPDF = async (all = false) => {
return;
}
// ایجاد آرایهای از آیتمهای انتخاب شده
const selectedItemsArray = all
? items.value
: items.value.filter(item => selectedItems.value.has(item.code));
: items.value.filter((item) => selectedItems.value.has(item.code));
const payload = all ? { all: true } : { items: selectedItemsArray };
const response = await axios.post('/api/costs/list/print', payload);
@ -372,6 +399,7 @@ const exportPDF = async (all = false) => {
}
};
// خروجی Excel
const exportExcel = async (all = false) => {
try {
loading.value = true;
@ -384,19 +412,20 @@ const exportExcel = async (all = false) => {
return;
}
// ایجاد آرایهای از آیتمهای انتخاب شده
const selectedItemsArray = all
? items.value
: items.value.filter(item => selectedItems.value.has(item.code));
: items.value.filter((item) => selectedItems.value.has(item.code));
const payload = all ? { all: true } : { items: selectedItemsArray };
const response = await axios.post('/api/costs/list/excel', payload, {
responseType: 'blob'
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
}));
const url = window.URL.createObjectURL(
new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'costs.xlsx');
@ -416,7 +445,7 @@ const exportExcel = async (all = false) => {
}
};
// تغییر تابع deleteGroup
// حذف گروهی
const deleteGroup = async () => {
if (selectedItems.value.size === 0) {
Swal.fire({
@ -439,7 +468,7 @@ const deleteGroup = async () => {
try {
loading.value = true;
const selectedCodes = Array.from(selectedItems.value);
const promises = selectedCodes.map(code =>
const promises = selectedCodes.map((code) =>
axios.post('/api/accounting/remove', { code })
);
@ -466,10 +495,11 @@ const deleteGroup = async () => {
}
};
// اضافه کردن watch برای پاک کردن انتخابها هنگام تغییر صفحه
// Watchers
watch(() => serverOptions.value.page, () => {
selectedItems.value.clear();
});
watch(searchQuery, () => debouncedSearch());
// OnMounted
onMounted(() => {