This commit is contained in:
Gloomy 2025-08-24 07:42:29 +00:00
commit d877be1bb8
12 changed files with 186 additions and 42 deletions

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250824071413 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE person ADD payment_id VARCHAR(255) DEFAULT NULL
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE person DROP payment_id
SQL);
}
}

View file

@ -95,7 +95,7 @@ class PersonService
if (!empty($search) || $search === '0') {
$search = trim($search);
$queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search')
$queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search OR p.paymentId LIKE :search')
->setParameter('search', "%$search%");
}
@ -288,6 +288,22 @@ class PersonService
if (isset($params['shenasemeli'])) $person->setShenasemeli($params['shenasemeli']);
if (isset($params['company'])) $person->setCompany($params['company']);
if (isset($params['tags'])) $person->setTags($params['tags']);
// بررسی منحصر به فرد بودن شناسه پرداخت در کسب و کار
if (isset($params['paymentId']) && !empty(trim($params['paymentId']))) {
$existingPerson = $em->getRepository(\App\Entity\Person::class)->findOneBy([
'paymentId' => trim($params['paymentId']),
'bid' => $acc['bid']
]);
// اگر شخص دیگری با همین شناسه پرداخت وجود دارد و این شخص فعلی نیست
if ($existingPerson && $existingPerson->getId() !== $person->getId()) {
return ['result' => -4, 'error' => 'شناسه پرداخت تکراری است'];
}
$person->setPaymentId(trim($params['paymentId']));
} else {
$person->setPaymentId(null);
}
if (array_key_exists('prelabel', $params)) {
if ($params['prelabel'] != '') {

View file

@ -295,7 +295,8 @@ class PersonsController extends AbstractController
'id' => $person->getId(),
'nikename' => $person->getNikename(),
'code' => $person->getCode(),
'mobile' => $person->getMobile()
'mobile' => $person->getMobile(),
'paymentId' => $person->getPaymentId()
];
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
'person' => $person,
@ -344,7 +345,8 @@ class PersonsController extends AbstractController
'id' => $person->getId(),
'nikename' => $person->getNikename(),
'code' => $person->getCode(),
'mobile' => $person->getMobile()
'mobile' => $person->getMobile(),
'paymentId' => $person->getPaymentId()
];
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
'person' => $person,

View file

@ -161,6 +161,8 @@ class Person
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $tags = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $paymentId = null;
public function __construct()
@ -916,5 +918,16 @@ class Person
return $this;
}
public function getPaymentId(): ?string
{
return $this->paymentId;
}
public function setPaymentId(?string $paymentId): self
{
$this->paymentId = $paymentId;
return $this;
}
}

View file

@ -47,7 +47,7 @@ class PersonRepository extends ServiceEntityRepository
{
return $this->createQueryBuilder('p')
->where('p.bid = :val')
->andWhere("p.nikename LIKE :search OR p.mobile LIKE :search")
->andWhere("p.nikename LIKE :search OR p.mobile LIKE :search OR p.paymentId LIKE :search")
->setParameter('val', $bid)
->setParameter('search', '%' . $search . '%')
->setMaxResults($maxResults)

View file

@ -354,6 +354,7 @@ class Explore
'address' => $person->getAddress(),
'prelabel' => null,
'tags' => $person->getTags(),
'paymentId' => $person->getPaymentId(),
'requireTwoStep' => $person->getBid() ? $person->getBid()->isRequireTwoStepApproval() : false,
];
if ($person->getPrelabel()) {

View file

@ -59,6 +59,10 @@
<span class="info-label">استان:</span>
<span class="info-value">{{ person.ostan }}</span>
</div>
<div class="info-item" v-if="person.paymentId">
<span class="info-label">شناسه پرداخت:</span>
<span class="info-value">{{ person.paymentId }}</span>
</div>
</v-col>
</v-row>
</div>

View file

@ -56,6 +56,12 @@
{{ formatBalance(item.balance) }}
</v-chip>
</div>
<div class="d-flex align-center justify-space-between mt-1" v-if="item.paymentId">
<div class="d-flex align-center">
<v-icon size="small" color="secondary" class="mr-1">mdi-credit-card-outline</v-icon>
<span class="text-caption text-secondary-dark">شناسه پرداخت: {{ item.paymentId }}</span>
</div>
</div>
</div>
</v-list-item>
</template>
@ -156,6 +162,12 @@
label="توضیحات"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="newPerson.paymentId"
label="شناسه پرداخت"
></v-text-field>
</v-col>
<v-col cols="12">
<v-switch
v-model="newPerson.speedAccess"
@ -409,7 +421,8 @@ export default {
code: 0,
types: [],
accounts: [],
speedAccess: false
speedAccess: false,
paymentId: ''
}
};
},
@ -600,13 +613,19 @@ export default {
this.fetchData();
} else if (response.data.result === 2) {
this.showMessage('این شخص قبلاً ثبت شده است', 'error');
} else if (response.data.result === -4) {
this.showMessage(response.data.error || 'شناسه پرداخت تکراری است', 'error');
}
} else {
this.showMessage('خطا در ثبت شخص', 'error');
}
} catch (error) {
console.error('خطا در ثبت شخص:', error);
this.showMessage('خطا در ثبت شخص', 'error');
if (error.response && error.response.data && error.response.data.result === -4) {
this.showMessage(error.response.data.error || 'شناسه پرداخت تکراری است', 'error');
} else {
this.showMessage('خطا در ثبت شخص', 'error');
}
} finally {
this.saving = false;
}

View file

@ -51,6 +51,7 @@ export default defineComponent({
code: 0,
types: [],
accounts: [],
paymentId: '',
},
personPattern: {
nikename: '',
@ -74,6 +75,13 @@ export default defineComponent({
code: 0,
types: [],
accounts: [],
paymentId: '',
},
snackbar: {
show: false,
text: '',
color: 'success',
timeout: 3000
}
}
},
@ -81,6 +89,12 @@ export default defineComponent({
this.loadData();
},
methods: {
showSnackbar(text, color = 'success', timeout = 3000) {
this.snackbar.show = true;
this.snackbar.text = text;
this.snackbar.color = color;
this.snackbar.timeout = timeout;
},
addNewcard() {
this.person.accounts.push({
cardNum: '',
@ -105,29 +119,17 @@ export default defineComponent({
const regex = new RegExp("^(\\+98|0)?9\\d{9}$");
if (!regex.test(this.person.mobile)) {
canSubmit = false;
Swal.fire({
text: 'شماره موبایل وارد شده نامعتبر است.',
icon: 'error',
confirmButtonText: 'قبول'
});
this.showSnackbar('شماره موبایل وارد شده نامعتبر است.', 'error');
}
}
if (this.person.nikename.length === 0) {
canSubmit = false;
Swal.fire({
text: 'نام مستعار الزامی است.',
icon: 'error',
confirmButtonText: 'قبول'
});
this.showSnackbar('نام مستعار الزامی است.', 'error');
}
this.person.accounts.forEach((item) => {
if (item.bank == '') {
canSubmit = false;
Swal.fire({
text: 'بخش حساب‌های بانکی به درستی تکمیل نشده است.لطفا موارد الزامی را وارد کنید.',
icon: 'error',
confirmButtonText: 'قبول'
});
this.showSnackbar('بخش حساب‌های بانکی به درستی تکمیل نشده است.لطفا موارد الزامی را وارد کنید.', 'error');
}
});
if (canSubmit) {
@ -135,21 +137,22 @@ export default defineComponent({
axios.post('/api/person/mod/' + this.person.code, this.person).then((response) => {
this.loading = false;
if (response.data.result == 2) {
Swal.fire({
text: 'قبلا ثبت شده است.',
icon: 'error',
confirmButtonText: 'قبول'
});
this.showSnackbar('قبلا ثبت شده است.', 'error');
}
else if (response.data.result == -4) {
this.showSnackbar(response.data.error || 'شناسه پرداخت تکراری است.', 'error');
}
else {
Swal.fire({
text: 'مشخصات شخص ثبت شد.',
icon: 'success',
confirmButtonText: 'قبول'
}).then(() => {
this.person = this.personPattern
this.dialog = false;
});
this.showSnackbar('مشخصات شخص ثبت شد.', 'success');
this.person = this.personPattern;
this.dialog = false;
}
}).catch((error) => {
this.loading = false;
if (error.response && error.response.data && error.response.data.result === -4) {
this.showSnackbar(error.response.data.error || 'شناسه پرداخت تکراری است.', 'error');
} else {
this.showSnackbar('خطا در ثبت شخص.', 'error');
}
})
}
@ -256,6 +259,12 @@ export default defineComponent({
<label class="form-label">توضیحات</label>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-floating mb-4">
<input v-model="person.paymentId" class="form-control" type="text">
<label class="form-label">شناسه پرداخت</label>
</div>
</div>
</div>
</v-card-text>
</v-card>
@ -445,6 +454,16 @@ export default defineComponent({
</v-row>
</v-card>
</v-dialog>
<!-- Snackbar برای نمایش پیامها -->
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="snackbar.timeout">
{{ snackbar.text }}
<template v-slot:actions>
<v-btn color="white" text @click="snackbar.show = false">
بستن
</v-btn>
</template>
</v-snackbar>
</template>
<style scoped></style>

View file

@ -151,6 +151,13 @@
dense
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="editPersonData.paymentId"
label="شناسه پرداخت"
dense
/>
</v-col>
<v-col cols="12">
</v-col>
@ -229,6 +236,8 @@
selectedPerson.address || '-' }}</span></div>
<div class="text-subtitle-2">{{ $t('pages.person.description') }}: <span class="text-primary">{{
selectedPerson.des || '-' }}</span></div>
<div class="text-subtitle-2">شناسه پرداخت: <span class="text-primary">{{
selectedPerson.paymentId || '-' }}</span></div>
</v-card-text>
</v-card>
</v-col>
@ -352,7 +361,7 @@ export default {
tel: '',
address: '',
des: '',
paymentId: '',
},
debounceTimeout: null, // برای مدیریت debounce
headers: [
@ -437,7 +446,7 @@ export default {
tel: this.selectedPerson.tel || '',
address: this.selectedPerson.address || '',
des: this.selectedPerson.des || '',
paymentId: this.selectedPerson.paymentId || '',
};
const rowsResponse = await axios.post('/api/accounting/rows/search', { type: 'person', id });
@ -463,12 +472,18 @@ export default {
this.editPersonDialog = false;
// بروزرسانی اطلاعات شخص
await this.loadPerson(this.selectedPerson.code);
} else if (response.data.result === -4) {
this.snackbar = { show: true, text: response.data.error || 'شناسه پرداخت تکراری است', color: 'error' };
} else {
this.snackbar = { show: true, text: 'خطا در بروزرسانی اطلاعات شخص', color: 'error' };
}
} catch (error) {
console.error('Save person error:', error);
this.snackbar = { show: true, text: 'خطا در بروزرسانی اطلاعات شخص', color: 'error' };
if (error.response && error.response.data && error.response.data.result === -4) {
this.snackbar = { show: true, text: error.response.data.error || 'شناسه پرداخت تکراری است', color: 'error' };
} else {
this.snackbar = { show: true, text: 'خطا در بروزرسانی اطلاعات شخص', color: 'error' };
}
} finally {
this.saveLoading = false;
}

View file

@ -65,6 +65,10 @@
<v-text-field v-model="person.des" :label="$t('pages.person.description')" dense
prepend-inner-icon="mdi-text" hide-details />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model="person.paymentId" label="شناسه پرداخت" dense
prepend-inner-icon="mdi-credit-card-outline" hide-details />
</v-col>
</v-row>
</v-card-text>
@ -289,6 +293,7 @@ export default {
accounts: [],
prelabel: ref(null),
speedAccess: false,
paymentId: '',
},
snackbar: {
@ -418,6 +423,12 @@ export default {
icon: 'error',
confirmButtonText: this.$t('dialog.confirm')
});
} else if (response.data && response.data.result === -4) {
Swal.fire({
text: response.data.error || 'شناسه پرداخت تکراری است',
icon: 'error',
confirmButtonText: this.$t('dialog.confirm')
});
} else {
Swal.fire({
text: this.$t('pages.person.saved'),
@ -429,11 +440,19 @@ export default {
}
} catch (error) {
this.loading = false;
Swal.fire({
text: this.$t('pages.person.save_error') + error.message,
icon: 'error',
confirmButtonText: this.$t('dialog.confirm')
});
if (error.response && error.response.data && error.response.data.result === -4) {
Swal.fire({
text: error.response.data.error || 'شناسه پرداخت تکراری است',
icon: 'error',
confirmButtonText: this.$t('dialog.confirm')
});
} else {
Swal.fire({
text: this.$t('pages.person.save_error') + error.message,
icon: 'error',
confirmButtonText: this.$t('dialog.confirm')
});
}
}
}
},

View file

@ -255,6 +255,7 @@ const allHeaders = ref([
{ title: 'ایمیل', value: 'email', sortable: true, visible: true, align: 'center' },
{ title: 'وب سایت', value: 'website', sortable: true, visible: true, align: 'center' },
{ title: 'فکس', value: 'fax', sortable: true, visible: true, align: 'center' },
{ title: 'شناسه پرداخت', value: 'paymentId', sortable: true, visible: true, align: 'center' },
]);
// توابع کمکی