some bug fix and login with email
This commit is contained in:
parent
9ad415a23b
commit
8d528f70c0
|
|
@ -8,7 +8,6 @@ security:
|
||||||
app_user_provider:
|
app_user_provider:
|
||||||
entity:
|
entity:
|
||||||
class: App\Entity\User
|
class: App\Entity\User
|
||||||
property: mobile
|
|
||||||
firewalls:
|
firewalls:
|
||||||
dev:
|
dev:
|
||||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
|
|
||||||
|
|
@ -1164,6 +1164,7 @@ class SellController extends AbstractController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
// سیاست جدید: در ویرایش، شماره فاکتور تغییر نمیکند (نادیده گرفتن هر ورودی مربوط به شماره)
|
||||||
} else {
|
} else {
|
||||||
// ایجاد فاکتور جدید
|
// ایجاد فاکتور جدید
|
||||||
$doc = new HesabdariDoc();
|
$doc = new HesabdariDoc();
|
||||||
|
|
@ -1173,7 +1174,36 @@ class SellController extends AbstractController
|
||||||
$doc->setType('sell');
|
$doc->setType('sell');
|
||||||
$doc->setSubmitter($this->getUser());
|
$doc->setSubmitter($this->getUser());
|
||||||
$doc->setMoney($acc['money']);
|
$doc->setMoney($acc['money']);
|
||||||
|
// انتخاب شماره فاکتور بر اساس حالت شمارهگذاری
|
||||||
|
$numberingMode = $params['numberingMode'] ?? 'auto';
|
||||||
|
if ($numberingMode === 'custom') {
|
||||||
|
$customNumber = isset($params['customNumber']) ? trim((string)$params['customNumber']) : '';
|
||||||
|
if ($customNumber === '') {
|
||||||
|
return $this->json([
|
||||||
|
'result' => 0,
|
||||||
|
'message' => 'شماره سفارشی ارسال نشده است'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
if (!preg_match('/^[A-Za-z0-9_-]{1,30}$/', $customNumber)) {
|
||||||
|
return $this->json([
|
||||||
|
'result' => 0,
|
||||||
|
'message' => 'فرمت شماره فاکتور نامعتبر است'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
$exist = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||||
|
'bid' => $acc['bid'],
|
||||||
|
'code' => $customNumber
|
||||||
|
]);
|
||||||
|
if ($exist) {
|
||||||
|
return $this->json([
|
||||||
|
'result' => 0,
|
||||||
|
'message' => 'این شماره فاکتور قبلاً استفاده شده است'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
$doc->setCode($customNumber);
|
||||||
|
} else {
|
||||||
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||||
|
}
|
||||||
if ($TwoStepApproval) {
|
if ($TwoStepApproval) {
|
||||||
$doc->setIsPreview(true);
|
$doc->setIsPreview(true);
|
||||||
$doc->setIsApproved(false);
|
$doc->setIsApproved(false);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||||
|
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends ServiceEntityRepository<User>
|
* @extends ServiceEntityRepository<User>
|
||||||
|
|
@ -17,13 +18,53 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||||
* @method User[] findAll()
|
* @method User[] findAll()
|
||||||
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||||
*/
|
*/
|
||||||
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
|
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface, UserLoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
{
|
{
|
||||||
parent::__construct($registry, User::class);
|
parent::__construct($registry, User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadUserByIdentifier(string $identifier): ?User
|
||||||
|
{
|
||||||
|
$identifier = trim($identifier);
|
||||||
|
|
||||||
|
$qb = $this->createQueryBuilder('u');
|
||||||
|
|
||||||
|
if (str_contains($identifier, '@')) {
|
||||||
|
$email = mb_strtolower($identifier);
|
||||||
|
return $qb
|
||||||
|
->andWhere('LOWER(u.email) = :email')
|
||||||
|
->setParameter('email', $email)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
$mobile = $this->normalizeMobile($identifier);
|
||||||
|
$emailFromIdentifier = mb_strtolower($identifier);
|
||||||
|
|
||||||
|
return $qb
|
||||||
|
->andWhere('u.mobile = :mobile OR LOWER(u.email) = :email')
|
||||||
|
->setParameter('mobile', $mobile)
|
||||||
|
->setParameter('email', $emailFromIdentifier)
|
||||||
|
->getQuery()
|
||||||
|
->getOneOrNullResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeMobile(string $value): string
|
||||||
|
{
|
||||||
|
$value = $this->convertPersianToEnglish($value);
|
||||||
|
$value = preg_replace('/\s+/', '', $value ?? '');
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertPersianToEnglish(string $input): string
|
||||||
|
{
|
||||||
|
$persianNumbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||||||
|
$englishNumbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
return str_replace($persianNumbers, $englishNumbers, $input);
|
||||||
|
}
|
||||||
|
|
||||||
public function save(User $entity, bool $flush = false): void
|
public function save(User $entity, bool $flush = false): void
|
||||||
{
|
{
|
||||||
$this->getEntityManager()->persist($entity);
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ const en_lang = {
|
||||||
password_sended: "کلمه عبور جدید به شماره تلفن شما ارسال شد.",
|
password_sended: "کلمه عبور جدید به شماره تلفن شما ارسال شد.",
|
||||||
mobile_placeholder:"مثلا 09121234567",
|
mobile_placeholder:"مثلا 09121234567",
|
||||||
mobile:"Mobile number",
|
mobile:"Mobile number",
|
||||||
|
email_or_mobile:"Email or Mobile",
|
||||||
|
email_or_mobile_placeholder:"Enter your email or mobile number",
|
||||||
send_new_password: "Send new password",
|
send_new_password: "Send new password",
|
||||||
editNumber: "Edit Number",
|
editNumber: "Edit Number",
|
||||||
resendCodeLabel: "Send Again",
|
resendCodeLabel: "Send Again",
|
||||||
|
|
|
||||||
|
|
@ -683,6 +683,8 @@ const fa_lang = {
|
||||||
password_sended: "کلمه عبور جدید به شماره تلفن شما ارسال شد.",
|
password_sended: "کلمه عبور جدید به شماره تلفن شما ارسال شد.",
|
||||||
mobile_placeholder: "مثلا 09121234567",
|
mobile_placeholder: "مثلا 09121234567",
|
||||||
mobile: "تلفن همراه",
|
mobile: "تلفن همراه",
|
||||||
|
email_or_mobile: "ایمیل یا شماره موبایل",
|
||||||
|
email_or_mobile_placeholder: "ایمیل یا شماره موبایل خود را وارد کنید",
|
||||||
send_new_password: "ارسال کلمه عبور جدید",
|
send_new_password: "ارسال کلمه عبور جدید",
|
||||||
editNumber: "ویرایش شماره",
|
editNumber: "ویرایش شماره",
|
||||||
resendCodeLabel: "ارسال مجدد",
|
resendCodeLabel: "ارسال مجدد",
|
||||||
|
|
|
||||||
|
|
@ -305,8 +305,6 @@ export default {
|
||||||
).then((response) => {
|
).then((response) => {
|
||||||
this.items = response.data;
|
this.items = response.data;
|
||||||
this.items.forEach((item) => {
|
this.items.forEach((item) => {
|
||||||
item.bs = this.$filters.formatNumber(item.bs)
|
|
||||||
item.bd = this.$filters.formatNumber(item.bd)
|
|
||||||
item.accounts = []; // Initialize accounts array
|
item.accounts = []; // Initialize accounts array
|
||||||
})
|
})
|
||||||
this.loadCounterpartAccounts(id);
|
this.loadCounterpartAccounts(id);
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,40 @@
|
||||||
<Hpersonsearch v-model="customer" label="خریدار" :rules="[v => !!v || 'خریدار الزامی است']" required></Hpersonsearch>
|
<Hpersonsearch v-model="customer" label="خریدار" :rules="[v => !!v || 'خریدار الزامی است']" required></Hpersonsearch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-row class="mb-2" v-if="isNewInvoice">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-radio-group v-model="numberingMode" inline density="compact">
|
||||||
|
<v-label class="mb-2 d-inline-block">روش شمارهگذاری</v-label>
|
||||||
|
<v-radio label="خودکار" value="auto"></v-radio>
|
||||||
|
<v-radio label="سفارشی" value="custom"></v-radio>
|
||||||
|
</v-radio-group>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
v-if="numberingMode === 'custom'"
|
||||||
|
v-model="customNumber"
|
||||||
|
label="شماره فاکتور (سفارشی)"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
:counter="30"
|
||||||
|
:rules="[
|
||||||
|
v => !!v || 'شماره سفارشی الزامی است',
|
||||||
|
v => /^[A-Za-z0-9_-]{1,30}$/.test(v || '') || 'فقط حروف/اعداد/خط تیره/زیرخط، حداکثر ۳۰ کاراکتر'
|
||||||
|
]"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row class="mb-2" v-else>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="invoiceNumber"
|
||||||
|
label="شماره فاکتور"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
readonly
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
<v-text-field v-model="invoiceDescription" label="توضیحات فاکتور" density="compact" hide-details class="mb-4">
|
<v-text-field v-model="invoiceDescription" label="توضیحات فاکتور" density="compact" hide-details class="mb-4">
|
||||||
<template v-slot:prepend-inner>
|
<template v-slot:prepend-inner>
|
||||||
<mostdes v-model="invoiceDescription" :submitData="{ id: null, des: invoiceDescription }" type="sell" label=""></mostdes>
|
<mostdes v-model="invoiceDescription" :submitData="{ id: null, des: invoiceDescription }" type="sell" label=""></mostdes>
|
||||||
|
|
@ -624,6 +658,9 @@ export default {
|
||||||
const hasChanges = ref(false);
|
const hasChanges = ref(false);
|
||||||
const isNewInvoice = ref(true);
|
const isNewInvoice = ref(true);
|
||||||
const isInitializing = ref(true);
|
const isInitializing = ref(true);
|
||||||
|
const numberingMode = ref('auto');
|
||||||
|
const customNumber = ref('');
|
||||||
|
const invoiceNumber = ref('');
|
||||||
|
|
||||||
// بارگذاری تنظیمات از لوکال استوریج
|
// بارگذاری تنظیمات از لوکال استوریج
|
||||||
const loadSettings = () => {
|
const loadSettings = () => {
|
||||||
|
|
@ -710,7 +747,10 @@ export default {
|
||||||
showDraftDialog,
|
showDraftDialog,
|
||||||
hasChanges,
|
hasChanges,
|
||||||
isNewInvoice,
|
isNewInvoice,
|
||||||
isInitializing
|
isInitializing,
|
||||||
|
numberingMode,
|
||||||
|
customNumber,
|
||||||
|
invoiceNumber
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -893,6 +933,14 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isNewInvoice && this.numberingMode === 'custom') {
|
||||||
|
if (!this.customNumber || this.customNumber.trim() === '') {
|
||||||
|
this.validationErrors.push('شماره سفارشی الزامی است');
|
||||||
|
} else if (!/^[A-Za-z0-9_-]{1,30}$/.test(this.customNumber)) {
|
||||||
|
this.validationErrors.push('شماره سفارشی فقط حروف/اعداد/خط تیره/زیرخط و حداکثر ۳۰ کاراکتر');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.validationErrors.length > 0) {
|
if (this.validationErrors.length > 0) {
|
||||||
this.showValidationErrors = true;
|
this.showValidationErrors = true;
|
||||||
}
|
}
|
||||||
|
|
@ -913,6 +961,14 @@ export default {
|
||||||
this.invoiceDate = data.date;
|
this.invoiceDate = data.date;
|
||||||
this.customer = data.person.id;
|
this.customer = data.person.id;
|
||||||
this.invoiceDescription = data.des;
|
this.invoiceDescription = data.des;
|
||||||
|
// مقدار شماره فاکتور برای نمایش فقطخواندنی (code یا در نبود آن id)
|
||||||
|
{
|
||||||
|
let codeField = (data.code !== undefined && data.code !== null) ? data.code : (data.id !== undefined && data.id !== null ? data.id : '');
|
||||||
|
if (codeField === '' || codeField === null || codeField === undefined) {
|
||||||
|
codeField = this.$route.params.id || '';
|
||||||
|
}
|
||||||
|
this.invoiceNumber = codeField !== '' ? String(codeField) : '';
|
||||||
|
}
|
||||||
this.taxPercent = data.taxPercent;
|
this.taxPercent = data.taxPercent;
|
||||||
this.totalDiscount = Number(data.totalDiscount);
|
this.totalDiscount = Number(data.totalDiscount);
|
||||||
this.totalDiscountPercent = Number(data.discountPercent);
|
this.totalDiscountPercent = Number(data.discountPercent);
|
||||||
|
|
@ -985,6 +1041,10 @@ export default {
|
||||||
totalDiscount: this.showTotalPercentDiscount ? 0 : this.totalDiscount,
|
totalDiscount: this.showTotalPercentDiscount ? 0 : this.totalDiscount,
|
||||||
shippingCost: this.shippingCost,
|
shippingCost: this.shippingCost,
|
||||||
showTotalPercentDiscount: this.showTotalPercentDiscount,
|
showTotalPercentDiscount: this.showTotalPercentDiscount,
|
||||||
|
...(this.isNewInvoice ? {
|
||||||
|
numberingMode: this.numberingMode,
|
||||||
|
customNumber: this.numberingMode === 'custom' ? this.customNumber.trim() : null
|
||||||
|
} : {}),
|
||||||
items: this.items.map(item => ({
|
items: this.items.map(item => ({
|
||||||
name: {
|
name: {
|
||||||
id: item.name.id,
|
id: item.name.id,
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
|
|
||||||
<v-form :disabled="loading" ref="form" fast-fail @submit.prevent="submit()">
|
<v-form :disabled="loading" ref="form" fast-fail @submit.prevent="submit()">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-text-field class="mb-2" :label="$t('user.mobile')" :placeholder="$t('user.mobile_placeholder')"
|
<v-text-field class="mb-2" :label="$t('user.email_or_mobile')" :placeholder="$t('user.email_or_mobile_placeholder')"
|
||||||
single-line v-model="user.mobile" type="tel" variant="outlined" prepend-inner-icon="mdi-phone"
|
single-line v-model="user.mobile" type="text" variant="outlined" prepend-inner-icon="mdi-account"
|
||||||
:rules="rules.mobile" autocomplete="tel" name="mobile"></v-text-field>
|
:rules="rules.identifier" autocomplete="username" name="identifier"></v-text-field>
|
||||||
|
|
||||||
<v-text-field class="mb-2" :label="$t('user.password')" :placeholder="$t('user.password_placeholder')"
|
<v-text-field class="mb-2" :label="$t('user.password')" :placeholder="$t('user.password_placeholder')"
|
||||||
single-line type="password" variant="outlined" prepend-inner-icon="mdi-lock" :rules="rules.password"
|
single-line type="password" variant="outlined" prepend-inner-icon="mdi-lock" :rules="rules.password"
|
||||||
|
|
@ -88,8 +88,8 @@ export default {
|
||||||
standard: true,
|
standard: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
mobile: [
|
identifier: [
|
||||||
(value: any) => self.validate(value, 'mobile'),
|
(value: any) => self.validate(value, 'identifier'),
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
(value: any) => self.validate(value, "password"),
|
(value: any) => self.validate(value, "password"),
|
||||||
|
|
@ -102,8 +102,13 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
validate(input: string, type: string) {
|
validate(input: string, type: string) {
|
||||||
if (type === "mobile") {
|
if (type === "identifier") {
|
||||||
const normalizedInput = this.convertPersianToEnglish(input.replace(/\s/g, ''));
|
const raw = (input || '').toString().trim();
|
||||||
|
if (raw.includes('@')) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(raw) || this.$t("validator.email_not_valid");
|
||||||
|
}
|
||||||
|
const normalizedInput = this.convertPersianToEnglish(raw.replace(/\s/g, ''));
|
||||||
if (/^09\d{9}$/.test(normalizedInput)) return true;
|
if (/^09\d{9}$/.test(normalizedInput)) return true;
|
||||||
return this.$t("validator.mobile_not_valid");
|
return this.$t("validator.mobile_not_valid");
|
||||||
} else if (type === "password") {
|
} else if (type === "password") {
|
||||||
|
|
@ -141,8 +146,12 @@ export default {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
|
const raw = (this.user.mobile || '').toString().trim();
|
||||||
|
const isEmail = raw.includes('@');
|
||||||
|
const identifier = isEmail ? raw.toLowerCase() : this.convertPersianToEnglish(raw.replace(/\s/g, ''));
|
||||||
|
|
||||||
const userData: { mobile: string; password: string; standard: boolean; captcha_answer?: string } = {
|
const userData: { mobile: string; password: string; standard: boolean; captcha_answer?: string } = {
|
||||||
mobile: this.convertPersianToEnglish(this.user.mobile.replace(/\s/g, '')),
|
mobile: identifier,
|
||||||
password: this.user.password,
|
password: this.user.password,
|
||||||
standard: this.user.standard,
|
standard: this.user.standard,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue