start working on payment ID

This commit is contained in:
Hesabix 2025-08-24 07:37:10 +00:00
parent b7ecafb3a7
commit c8c2bb11d0
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') { if (!empty($search) || $search === '0') {
$search = trim($search); $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%"); ->setParameter('search', "%$search%");
} }
@ -288,6 +288,22 @@ class PersonService
if (isset($params['shenasemeli'])) $person->setShenasemeli($params['shenasemeli']); if (isset($params['shenasemeli'])) $person->setShenasemeli($params['shenasemeli']);
if (isset($params['company'])) $person->setCompany($params['company']); if (isset($params['company'])) $person->setCompany($params['company']);
if (isset($params['tags'])) $person->setTags($params['tags']); 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 (array_key_exists('prelabel', $params)) {
if ($params['prelabel'] != '') { if ($params['prelabel'] != '') {

View file

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

View file

@ -161,6 +161,8 @@ class Person
#[ORM\Column(type: Types::TEXT, nullable: true)] #[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $tags = null; private ?string $tags = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $paymentId = null;
public function __construct() public function __construct()
@ -916,5 +918,16 @@ class Person
return $this; 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') return $this->createQueryBuilder('p')
->where('p.bid = :val') ->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('val', $bid)
->setParameter('search', '%' . $search . '%') ->setParameter('search', '%' . $search . '%')
->setMaxResults($maxResults) ->setMaxResults($maxResults)

View file

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

View file

@ -59,6 +59,10 @@
<span class="info-label">استان:</span> <span class="info-label">استان:</span>
<span class="info-value">{{ person.ostan }}</span> <span class="info-value">{{ person.ostan }}</span>
</div> </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-col>
</v-row> </v-row>
</div> </div>

View file

@ -56,6 +56,12 @@
{{ formatBalance(item.balance) }} {{ formatBalance(item.balance) }}
</v-chip> </v-chip>
</div> </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> </div>
</v-list-item> </v-list-item>
</template> </template>
@ -156,6 +162,12 @@
label="توضیحات" label="توضیحات"
></v-text-field> ></v-text-field>
</v-col> </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-col cols="12">
<v-switch <v-switch
v-model="newPerson.speedAccess" v-model="newPerson.speedAccess"
@ -409,7 +421,8 @@ export default {
code: 0, code: 0,
types: [], types: [],
accounts: [], accounts: [],
speedAccess: false speedAccess: false,
paymentId: ''
} }
}; };
}, },
@ -600,13 +613,19 @@ export default {
this.fetchData(); this.fetchData();
} else if (response.data.result === 2) { } else if (response.data.result === 2) {
this.showMessage('این شخص قبلاً ثبت شده است', 'error'); this.showMessage('این شخص قبلاً ثبت شده است', 'error');
} else if (response.data.result === -4) {
this.showMessage(response.data.error || 'شناسه پرداخت تکراری است', 'error');
} }
} else { } else {
this.showMessage('خطا در ثبت شخص', 'error'); this.showMessage('خطا در ثبت شخص', 'error');
} }
} catch (error) { } catch (error) {
console.error('خطا در ثبت شخص:', 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 { } finally {
this.saving = false; this.saving = false;
} }

View file

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

View file

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

View file

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