bug fix reported

This commit is contained in:
Hesabix 2025-05-26 01:49:14 +00:00
parent e269a26d50
commit 17af4bfaa9
8 changed files with 269 additions and 117 deletions

View file

@ -186,9 +186,38 @@ class ReportController extends AbstractController
}
}
$labels = [
'کد حسابداری'
'ردیف',
'کد کالا',
'نام کالا',
'واحد',
'تعداد',
'قیمت واحد',
'قیمت کل',
'تاریخ',
'شماره سند',
'نوع سند',
'خدمات'
];
return new BinaryFileResponse($provider->createExcellFromArray($response));
// اضافه کردن شماره ردیف به داده‌ها
$responseWithRow = [];
foreach ($response as $index => $item) {
$responseWithRow[] = [
'row' => $index + 1,
'code' => $item['code'],
'name' => $item['name'],
'unit' => $item['unit'],
'count' => $item['count'],
'priceOne' => $item['priceOne'],
'priceAll' => $item['priceAll'],
'date' => $item['date'],
'docCode' => $item['docCode'],
'type' => $item['type'],
'khadamat' => $item['khadamat']
];
}
return new BinaryFileResponse($provider->createExcellFromArray($responseWithRow, $labels));
}
#[Route('/api/report/commodity/buysell', name: 'app_report_commodity_buysell')]

View file

@ -1219,14 +1219,16 @@ class SellController extends AbstractController
$itemDiscount = $row->getDiscount() ?? 0;
$itemDiscountType = $row->getDiscountType() ?? 'fixed';
$itemDiscountPercent = $row->getDiscountPercent() ?? 0;
$itemTax = $row->getTax() ?? 0;
// محاسبه تخفیف سطری
if ($itemDiscountType === 'percent') {
$itemDiscount = round(($basePrice * $itemDiscountPercent) / 100);
}
$itemTotal = $basePrice - $itemDiscount;
$totalInvoice += $itemTotal;
// محاسبه قیمت خالص (بدون مالیات)
$netPrice = $basePrice - $itemDiscount;
$totalInvoice += $netPrice;
$items[] = [
'name' => [
@ -1235,13 +1237,13 @@ class SellController extends AbstractController
'code' => $row->getCommodity()->getCode()
],
'count' => $row->getCommdityCount(),
'price' => $row->getCommdityCount() > 0 ? $basePrice / $row->getCommdityCount() : 0,
'price' => $row->getCommdityCount() > 0 ? $netPrice / $row->getCommdityCount() : 0,
'discountPercent' => $itemDiscountPercent,
'discountAmount' => $itemDiscount,
'total' => $itemTotal,
'total' => $netPrice,
'description' => $row->getDes(),
'showPercentDiscount' => $itemDiscountType === 'percent',
'tax' => $row->getTax() ?? 0
'tax' => $itemTax
];
}
}

View file

@ -62,7 +62,7 @@ class CommodityRepository extends ServiceEntityRepository
{
return $this->createQueryBuilder('p')
->where('p.bid = :val')
->andWhere("p.name LIKE :search OR p.barcodes LIKE :search")
->andWhere("p.name LIKE :search OR p.barcodes LIKE :search OR p.code LIKE :search")
->setParameter('val', $bid)
->setParameter('search', '%' . $search . '%')
->setMaxResults($maxResults)

View file

@ -3,10 +3,11 @@
v-model="inputValue"
v-bind="$attrs"
:class="$attrs.class"
type="text"
type="number"
:rules="combinedRules"
:error-messages="errorMessages"
@keypress="restrictToNumbers"
@keydown="restrictToNumbers"
@input="handleInput"
dir="ltr"
dense
:hide-details="$attrs['hide-details'] || 'auto'"
@ -34,6 +35,10 @@ export default {
allowDecimal: {
type: Boolean,
default: false
},
allowNegative: {
type: Boolean,
default: false
}
},
@ -47,7 +52,17 @@ export default {
computed: {
combinedRules() {
return [
v => !v || (this.allowDecimal ? /^\d*\.?\d*$/.test(v.replace(/[^0-9.]/g, '')) : /^\d+$/.test(v.replace(/[^0-9]/g, ''))) || this.$t('numberinput.invalid_number'),
v => {
if (!v && v !== '0') return true // اجازه خالی بودن
const pattern = this.allowDecimal
? this.allowNegative
? /^-?\d*\.?\d*$/
: /^\d*\.?\d*$/
: this.allowNegative
? /^-?\d+$/
: /^\d+$/
return pattern.test(v) || this.$t('numberinput.invalid_number')
},
...this.rules
]
}
@ -57,48 +72,98 @@ export default {
modelValue: {
immediate: true,
handler(newVal) {
if (newVal !== null && newVal !== undefined) {
const cleaned = String(newVal).replace(this.allowDecimal ? /[^0-9.]/g : /[^0-9]/g, '')
this.inputValue = cleaned ? (this.allowDecimal ? cleaned : Number(cleaned).toLocaleString('en-US')) : ''
} else {
if (newVal === null || newVal === undefined) {
this.inputValue = ''
} else {
const cleaned = String(newVal).replace(this.allowDecimal ? /[^0-9.-]/g : /[^0-9-]/g, '')
this.inputValue = cleaned
}
}
},
inputValue(newVal) {
if (newVal === '' || newVal === null || newVal === undefined) {
this.$emit('update:modelValue', 0)
this.$emit('update:modelValue', null)
this.errorMessages = []
return
}
const cleaned = String(newVal).replace(this.allowDecimal ? /[^0-9.-]/g : /[^0-9-]/g, '')
const pattern = this.allowDecimal
? this.allowNegative
? /^-?\d*\.?\d*$/
: /^\d*\.?\d*$/
: this.allowNegative
? /^-?\d+$/
: /^\d+$/
if (pattern.test(cleaned)) {
let numericValue
if (this.allowDecimal) {
numericValue = cleaned === '' || cleaned === '-' ? null : parseFloat(cleaned)
} else {
numericValue = cleaned === '' || cleaned === '-' ? null : parseInt(cleaned, 10)
}
this.$emit('update:modelValue', isNaN(numericValue) ? null : numericValue)
this.errorMessages = []
} else {
const cleaned = String(newVal).replace(this.allowDecimal ? /[^0-9.]/g : /[^0-9]/g, '')
if (this.allowDecimal ? /^\d*\.?\d*$/.test(cleaned) : /^\d+$/.test(cleaned)) {
const numericValue = cleaned ? (this.allowDecimal ? parseFloat(cleaned) : Number(cleaned)) : 0
this.$emit('update:modelValue', numericValue)
this.errorMessages = []
} else {
this.errorMessages = [this.$t('numberinput.invalid_number')]
}
this.errorMessages = [this.$t('numberinput.invalid_number')]
}
}
},
methods: {
restrictToNumbers(event) {
const charCode = event.charCode
const key = event.key
const input = this.inputValue || ''
// اجازه دادن به کلیدهای کنترلی
if (
['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'].includes(key) ||
(event.ctrlKey || event.metaKey)
) {
return
}
if (this.allowDecimal) {
// اجازه ورود اعداد و نقطه اعشاری
if ((charCode < 48 || charCode > 57) && charCode !== 46) {
// اجازه ورود اعداد، ممیز، کاما (برای کیبوردهای محلی) و (در صورت اجازه) منفی
if (!/[0-9.,]/.test(key) && (!this.allowNegative || key !== '-')) {
event.preventDefault()
}
// جلوگیری از ورود بیش از یک نقطه اعشاری
if (charCode === 46 && this.inputValue.includes('.')) {
// جلوگیری از بیش از یک ممیز
if ((key === '.' || key === ',') && input.includes('.')) {
event.preventDefault()
}
// جلوگیری از ممیز در ابتدا یا بعد از منفی
if ((key === '.' || key === ',') && (input === '' || input === '-')) {
event.preventDefault()
}
// جلوگیری از بیش از یک منفی
if (key === '-' && (input.includes('-') || !this.allowNegative)) {
event.preventDefault()
}
// منفی فقط در ابتدا
if (key === '-' && input !== '') {
event.preventDefault()
}
} else {
// فقط اجازه ورود اعداد
if (charCode < 48 || charCode > 57) {
// فقط اعداد و (در صورت اجازه) منفی
if (!/[0-9]/.test(key) && (!this.allowNegative || key !== '-')) {
event.preventDefault()
}
// جلوگیری از بیش از یک منفی
if (key === '-' && (input.includes('-') || !this.allowNegative)) {
event.preventDefault()
}
// منفی فقط در ابتدا
if (key === '-' && input !== '') {
event.preventDefault()
}
}
},
handleInput(event) {
// تبدیل کاما به ممیز برای کیبوردهای محلی
if (this.allowDecimal && event.target.value.includes(',')) {
this.inputValue = event.target.value.replace(',', '.')
}
}
}

View file

@ -194,6 +194,7 @@ const fa_lang = {
repservice_reqs: "درخواست‌ها",
hrm: 'منابع انسانی',
hrm_docs: 'سند حقوق',
buysellByPerson: "گزارش خرید و فروش های اشخاص",
},
time: {
month: "{id} ماه",
@ -524,7 +525,26 @@ const fa_lang = {
description: "توضیحات الزامی است",
person: "انتخاب شخص الزامی است"
}
}
},
buysell_report: {
person: "شخص",
type: "نوع",
date_start: "تاریخ شروع",
date_end: "تاریخ پایان",
search: "جست و جو ...",
goods: "کالا",
services: "خدمات",
unit: "واحد شمارش",
count: "تعداد",
unit_price: "مبلغ فی",
total_price: "مبلغ کل",
cumulative: "تجمعی",
buy: "خرید",
sell: "فروش",
return_buy: "برگشت از خرید",
return_sell: "برگشت از فروش",
all_types: "همه موارد"
},
},
app: {
loading: "در حال بارگذاری...",

View file

@ -1,36 +1,40 @@
<template>
<div class="block block-content-full ">
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
<h3 class="block-title text-primary-dark">
<button @click="$router.back()" type="button" class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
<i class="fa fw-bold fa-arrow-right"></i>
</button>
<i class="fa-solid fa-chart-simple px-2"></i>
گزارش خرید و فروش های اشخاص
</h3>
<div class="block-options">
<div hidden class="dropdown">
<a class="btn btn-sm btn-danger ms-2 dropdown-toggle text-end" href="#" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-file-pdf"></i>
</a>
<ul class="dropdown-menu">
<li><a @click.prevent="print(false)" class="dropdown-item" href="#">انتخاب شدهها</a></li>
<li><a @click.prevent="print(true)" class="dropdown-item" href="#">همه موارد</a></li>
</ul>
</div>
<div class="dropdown">
<a class="btn btn-sm btn-success ms-2 dropdown-toggle text-end" href="#" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-file-excel"></i>
</a>
<ul class="dropdown-menu">
<li><a @click.prevent="excellOutput(false)" class="dropdown-item" href="#">انتخاب شدهها</a></li>
<li><a @click.prevent="excellOutput(true)" class="dropdown-item" href="#">همه موارد</a></li>
</ul>
</div>
</div>
</div>
<v-toolbar color="toolbar" :title="$t('drawer.buysellByPerson')">
<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" />
</template>
</v-tooltip>
</template>
<v-spacer />
<v-menu>
<template v-slot:activator="{ props }">
<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="!itemsSelected.length" class="text-dark" :title="$t('dialog.selected')"
@click="excellOutput(false)">
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-check" />
</template>
</v-list-item>
<v-list-item class="text-dark" :title="$t('dialog.all')" @click="excellOutput(true)">
<template v-slot:prepend>
<v-icon color="indigo-darken-4" icon="mdi-expand-all" />
</template>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar>
<div class="block block-content-full">
<div class="block-content pt-1 pb-3">
<div class="row">
<div class="col-sm-12 col-md-12 m-0 p-0">

View file

@ -67,7 +67,7 @@
<Hcommoditysearch v-model="item.name" density="compact" hide-details class="my-0" style="font-size: 0.8rem;" return-object @update:modelValue="handleCommodityChange(item)"></Hcommoditysearch>
</td>
<td class="text-center px-2">
<Hnumberinput v-model="item.count" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;"></Hnumberinput>
<Hnumberinput v-model="item.count" density="compact" @update:modelValue="recalculateTotals" class="my-0" style="font-size: 0.8rem;" :allow-decimal="true"></Hnumberinput>
</td>
<td class="text-center px-2">
<div class="d-flex align-center justify-center">
@ -145,7 +145,7 @@
</div>
<div class="d-flex justify-space-between mb-2">
<div class="flex-grow-1 mr-2">
<Hnumberinput v-model="item.count" density="compact" label="تعداد" hide-details class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;" @update:modelValue="recalculateTotals"></Hnumberinput>
<Hnumberinput v-model="item.count" density="compact" label="تعداد" hide-details class="my-0" style="font-size: 0.8rem; margin: 0; padding: 0;" @update:modelValue="recalculateTotals" :allow-decimal="true"></Hnumberinput>
</div>
<div class="flex-grow-1">
<div class="d-flex align-center">
@ -906,21 +906,29 @@ export default {
this.totalInvoice = Number(data.totalInvoice);
this.finalTotal = Number(data.finalTotal);
this.items = data.items.map(item => ({
name: {
id: item.name.id,
name: item.name.name,
code: item.name.code
},
count: Number(item.count),
price: Number(item.price),
discountPercent: Number(item.discountPercent),
discountAmount: Number(item.discountAmount),
total: Number(item.total),
description: item.description,
showPercentDiscount: item.showPercentDiscount,
tax: Number(item.tax)
}));
// تبدیل قیمتها به قیمت خالص (بدون مالیات)
this.items = data.items.map(item => {
const basePrice = Number(item.price);
const tax = Number(item.tax);
const netPrice = Math.round(basePrice - tax);
return {
name: {
id: item.name.id,
name: item.name.name,
code: item.name.code,
priceSell: netPrice // قیمت فروش بدون مالیات
},
count: Number(item.count),
price: netPrice, // قیمت واحد بدون مالیات
discountPercent: Number(item.discountPercent),
discountAmount: Number(item.discountAmount),
total: netPrice, // جمع ردیف بدون مالیات
description: item.description,
showPercentDiscount: item.showPercentDiscount,
tax: tax
};
});
// بارگذاری اسناد پرداخت
this.paymentItems = data.payments.map(payment => ({
@ -963,7 +971,12 @@ export default {
shippingCost: this.shippingCost,
showTotalPercentDiscount: this.showTotalPercentDiscount,
items: this.items.map(item => ({
name: { id: item.name.id },
name: {
id: item.name.id,
name: item.name.name,
code: item.name.code,
priceSell: item.price
},
count: item.count,
price: item.price,
discountType: item.showPercentDiscount ? 'percent' : 'fixed',
@ -1063,7 +1076,7 @@ export default {
this.recalculateTotals();
},
handleCommodityChange(item) {
if (item.name && item.name.priceSell !== undefined) {
if (item.name && item.name.priceSell !== undefined && !item.price) {
item.price = Number(item.name.priceSell);
this.recalculateTotals();
}
@ -1075,13 +1088,15 @@ export default {
recalculateTotals() {
let total = 0;
this.items.forEach(item => {
const basePrice = (item.count || 0) * (item.price || 0);
// محاسبه قیمت پایه (قیمت واحد * تعداد)
const basePrice = Math.round((item.count || 0) * (item.price || 0));
// محاسبه تخفیف
let totalDiscount = 0;
if (item.showPercentDiscount) {
totalDiscount = Math.round((basePrice * (item.discountPercent || 0)) / 100);
} else {
totalDiscount = item.discountAmount || 0;
totalDiscount = Math.round(item.discountAmount || 0);
}
if (totalDiscount > basePrice) {
@ -1090,26 +1105,28 @@ export default {
this.showError = true;
}
const itemTotal = basePrice - totalDiscount;
item.total = itemTotal;
total += itemTotal;
});
this.totalInvoice = total;
// محاسبه قیمت خالص (قیمت پایه - تخفیف)
const netPrice = Math.round(basePrice - totalDiscount);
item.total = netPrice;
total += netPrice;
// محاسبه مالیات برای هر آیتم
this.items.forEach(item => {
item.tax = Math.round((item.total * this.taxPercent) / 100);
// محاسبه مالیات برای هر آیتم (بر اساس قیمت خالص)
item.tax = Math.round((netPrice * this.taxPercent) / 100);
});
// جمع کل فاکتور (بدون مالیات)
this.totalInvoice = Math.round(total);
this.totalWithoutTax = Math.round(total);
// محاسبه کل مالیات
this.taxAmount = this.items.reduce((sum, item) => sum + (item.tax || 0), 0);
this.totalWithoutTax = total;
this.taxAmount = Math.round(this.items.reduce((sum, item) => sum + (item.tax || 0), 0));
// محاسبه تخفیف کلی
let calculatedTotalDiscount = 0;
if (this.showTotalPercentDiscount) {
calculatedTotalDiscount = Math.round((total * this.totalDiscountPercent) / 100);
} else {
calculatedTotalDiscount = this.totalDiscount;
calculatedTotalDiscount = Math.round(this.totalDiscount);
}
if (calculatedTotalDiscount > total) {
@ -1118,7 +1135,9 @@ export default {
this.showDiscountError = true;
}
this.finalTotal = total + this.taxAmount - calculatedTotalDiscount + this.shippingCost;
// محاسبه جمع کل نهایی
// جمع کل + مالیات - تخفیف کلی + هزینه حمل
this.finalTotal = Math.round(total + this.taxAmount - calculatedTotalDiscount + this.shippingCost);
this.totalAmount = this.finalTotal;
this.calculatePayments();
},
@ -1341,20 +1360,39 @@ export default {
--v-field-border-opacity: 0.12;
}
:deep(.v-overlay__content) {
z-index: 9999 !important;
/* تنظیم عرض ستون‌های جدول */
:deep(.v-table th:nth-child(3)), /* ستون تعداد */
:deep(.v-table td:nth-child(3)) {
width: 120px;
}
:deep(.v-menu__content) {
z-index: 9999 !important;
:deep(.v-table th:nth-child(4)), /* ستون قیمت */
:deep(.v-table td:nth-child(4)) {
width: 150px;
}
:deep(.v-dialog) {
z-index: 9999 !important;
:deep(.v-table th:nth-child(5)), /* ستون تخفیف */
:deep(.v-table td:nth-child(5)) {
width: 200px;
}
:deep(.v-date-picker) {
z-index: 9999 !important;
/* تنظیم استایل باکس تخفیف */
:deep(.v-table .v-field--variant-outlined.v-field--density-compact) {
max-width: 100%;
}
:deep(.v-table .v-checkbox) {
margin-right: 4px;
}
:deep(.v-table .v-checkbox .v-selection-control) {
margin: 0;
padding: 0;
}
:deep(.v-table .v-checkbox .v-selection-control__wrapper) {
width: 24px;
height: 24px;
}
.settings-section-card {

View file

@ -2,12 +2,11 @@
import { defineComponent } from 'vue';
import axios from 'axios';
import Swal from 'sweetalert2';
import Loading from 'vue-loading-overlay';
import 'vue-loading-overlay/dist/css/index.css';
import CustomEditor from '@/components/Editor.vue';
export default defineComponent({
name: 'mod',
components: { Loading },
components: { CustomEditor },
data: () => ({
loading: true,
id: '',
@ -65,7 +64,7 @@ export default defineComponent({
<v-spacer></v-spacer>
</v-toolbar>
<v-container class="pa-0 ma-0">
<v-card :loading="loading ? 'red' : null" :disabled="loading">
<v-card :loading="loading" :disabled="loading">
<v-card-text class="pa-2">
<v-row>
<v-col cols="12" sm="12" md="6">
@ -80,15 +79,10 @@ export default defineComponent({
</v-col>
<v-col cols="12" sm="12" md="12">
<h3 class="mb-2">{{ $t('app.body') }}</h3>
<v-textarea
<CustomEditor
v-model="body"
:rules="[() => body.length > 0 || $t('validator.required')]"
auto-grow
variant="outlined"
class="font-weight-regular"
style="font-family: 'Vazirmatn FD', sans-serif;"
></v-textarea>
/>
</v-col>
<v-col cols="12" sm="12" md="12">
<v-btn