forked from morrning/hesabixCore
some bug fix and login with email
This commit is contained in:
parent
9ad415a23b
commit
8d528f70c0
|
|
@ -8,7 +8,6 @@ security:
|
|||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: mobile
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
|
|
|
|||
|
|
@ -1164,6 +1164,7 @@ class SellController extends AbstractController
|
|||
}
|
||||
}
|
||||
$entityManager->flush();
|
||||
// سیاست جدید: در ویرایش، شماره فاکتور تغییر نمیکند (نادیده گرفتن هر ورودی مربوط به شماره)
|
||||
} else {
|
||||
// ایجاد فاکتور جدید
|
||||
$doc = new HesabdariDoc();
|
||||
|
|
@ -1173,7 +1174,36 @@ class SellController extends AbstractController
|
|||
$doc->setType('sell');
|
||||
$doc->setSubmitter($this->getUser());
|
||||
$doc->setMoney($acc['money']);
|
||||
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||
// انتخاب شماره فاکتور بر اساس حالت شمارهگذاری
|
||||
$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'));
|
||||
}
|
||||
if ($TwoStepApproval) {
|
||||
$doc->setIsPreview(true);
|
||||
$doc->setIsApproved(false);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Doctrine\Persistence\ManagerRegistry;
|
|||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<User>
|
||||
|
|
@ -17,13 +18,53 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
|||
* @method User[] findAll()
|
||||
* @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)
|
||||
{
|
||||
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
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ const en_lang = {
|
|||
password_sended: "کلمه عبور جدید به شماره تلفن شما ارسال شد.",
|
||||
mobile_placeholder:"مثلا 09121234567",
|
||||
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",
|
||||
editNumber: "Edit Number",
|
||||
resendCodeLabel: "Send Again",
|
||||
|
|
|
|||
|
|
@ -683,6 +683,8 @@ const fa_lang = {
|
|||
password_sended: "کلمه عبور جدید به شماره تلفن شما ارسال شد.",
|
||||
mobile_placeholder: "مثلا 09121234567",
|
||||
mobile: "تلفن همراه",
|
||||
email_or_mobile: "ایمیل یا شماره موبایل",
|
||||
email_or_mobile_placeholder: "ایمیل یا شماره موبایل خود را وارد کنید",
|
||||
send_new_password: "ارسال کلمه عبور جدید",
|
||||
editNumber: "ویرایش شماره",
|
||||
resendCodeLabel: "ارسال مجدد",
|
||||
|
|
|
|||
|
|
@ -305,8 +305,6 @@ export default {
|
|||
).then((response) => {
|
||||
this.items = response.data;
|
||||
this.items.forEach((item) => {
|
||||
item.bs = this.$filters.formatNumber(item.bs)
|
||||
item.bd = this.$filters.formatNumber(item.bd)
|
||||
item.accounts = []; // Initialize accounts array
|
||||
})
|
||||
this.loadCounterpartAccounts(id);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,40 @@
|
|||
<Hpersonsearch v-model="customer" label="خریدار" :rules="[v => !!v || 'خریدار الزامی است']" required></Hpersonsearch>
|
||||
</v-col>
|
||||
</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">
|
||||
<template v-slot:prepend-inner>
|
||||
<mostdes v-model="invoiceDescription" :submitData="{ id: null, des: invoiceDescription }" type="sell" label=""></mostdes>
|
||||
|
|
@ -624,6 +658,9 @@ export default {
|
|||
const hasChanges = ref(false);
|
||||
const isNewInvoice = ref(true);
|
||||
const isInitializing = ref(true);
|
||||
const numberingMode = ref('auto');
|
||||
const customNumber = ref('');
|
||||
const invoiceNumber = ref('');
|
||||
|
||||
// بارگذاری تنظیمات از لوکال استوریج
|
||||
const loadSettings = () => {
|
||||
|
|
@ -710,7 +747,10 @@ export default {
|
|||
showDraftDialog,
|
||||
hasChanges,
|
||||
isNewInvoice,
|
||||
isInitializing
|
||||
isInitializing,
|
||||
numberingMode,
|
||||
customNumber,
|
||||
invoiceNumber
|
||||
};
|
||||
},
|
||||
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) {
|
||||
this.showValidationErrors = true;
|
||||
}
|
||||
|
|
@ -913,6 +961,14 @@ export default {
|
|||
this.invoiceDate = data.date;
|
||||
this.customer = data.person.id;
|
||||
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.totalDiscount = Number(data.totalDiscount);
|
||||
this.totalDiscountPercent = Number(data.discountPercent);
|
||||
|
|
@ -985,6 +1041,10 @@ export default {
|
|||
totalDiscount: this.showTotalPercentDiscount ? 0 : this.totalDiscount,
|
||||
shippingCost: this.shippingCost,
|
||||
showTotalPercentDiscount: this.showTotalPercentDiscount,
|
||||
...(this.isNewInvoice ? {
|
||||
numberingMode: this.numberingMode,
|
||||
customNumber: this.numberingMode === 'custom' ? this.customNumber.trim() : null
|
||||
} : {}),
|
||||
items: this.items.map(item => ({
|
||||
name: {
|
||||
id: item.name.id,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
<v-form :disabled="loading" ref="form" fast-fail @submit.prevent="submit()">
|
||||
<v-card-text>
|
||||
<v-text-field class="mb-2" :label="$t('user.mobile')" :placeholder="$t('user.mobile_placeholder')"
|
||||
single-line v-model="user.mobile" type="tel" variant="outlined" prepend-inner-icon="mdi-phone"
|
||||
:rules="rules.mobile" autocomplete="tel" name="mobile"></v-text-field>
|
||||
<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="text" variant="outlined" prepend-inner-icon="mdi-account"
|
||||
: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')"
|
||||
single-line type="password" variant="outlined" prepend-inner-icon="mdi-lock" :rules="rules.password"
|
||||
|
|
@ -88,8 +88,8 @@ export default {
|
|||
standard: true,
|
||||
},
|
||||
rules: {
|
||||
mobile: [
|
||||
(value: any) => self.validate(value, 'mobile'),
|
||||
identifier: [
|
||||
(value: any) => self.validate(value, 'identifier'),
|
||||
],
|
||||
password: [
|
||||
(value: any) => self.validate(value, "password"),
|
||||
|
|
@ -102,8 +102,13 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
validate(input: string, type: string) {
|
||||
if (type === "mobile") {
|
||||
const normalizedInput = this.convertPersianToEnglish(input.replace(/\s/g, ''));
|
||||
if (type === "identifier") {
|
||||
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;
|
||||
return this.$t("validator.mobile_not_valid");
|
||||
} else if (type === "password") {
|
||||
|
|
@ -141,8 +146,12 @@ export default {
|
|||
if (valid) {
|
||||
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 } = {
|
||||
mobile: this.convertPersianToEnglish(this.user.mobile.replace(/\s/g, '')),
|
||||
mobile: identifier,
|
||||
password: this.user.password,
|
||||
standard: this.user.standard,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue