forked from morrning/hesabixCore
bug fix in generate accounting codes
This commit is contained in:
parent
f37aca7c6e
commit
b2766b6d46
119
docs/DUPLICATE_CODE_FIX.md
Normal file
119
docs/DUPLICATE_CODE_FIX.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# رفع مشکل کدهای تکراری حسابداری
|
||||
|
||||
## مشکل
|
||||
سیستم حسابداری گاهی اوقات کدهای تکراری به اسناد میداد که باعث خطا در سیستم میشد. این مشکل به دلایل زیر رخ میداد:
|
||||
|
||||
1. **عدم بررسی تکراری بودن کد**: متد `getAccountingCode` کد جدیدی تولید میکرد بدون بررسی تکراری بودن
|
||||
2. **Race Condition**: در صورت ارسال چندین درخواست همزمان، کدهای یکسانی تولید میشد
|
||||
3. **عدم استفاده از تراکنش**: عملیات بدون تراکنش انجام میشد
|
||||
|
||||
## راهحلهای پیادهسازی شده
|
||||
|
||||
### 1. بهبود متد `getAccountingCode` در `Provider.php`
|
||||
|
||||
#### تغییرات:
|
||||
- **استفاده از تراکنش دیتابیس**: برای جلوگیری از Race Condition
|
||||
- **بررسی تکراری بودن کد**: قبل از ذخیره، بررسی میشود که کد قبلاً وجود نداشته باشد
|
||||
- **Retry Logic**: در صورت تکراری بودن، تا 10 بار تلاش میکند
|
||||
- **Timestamp Fallback**: در صورت عدم موفقیت، از timestamp استفاده میکند
|
||||
|
||||
#### کد جدید:
|
||||
```php
|
||||
public function getAccountingCode($bid, $part)
|
||||
{
|
||||
$maxRetries = 10;
|
||||
$retryCount = 0;
|
||||
|
||||
do {
|
||||
$retryCount++;
|
||||
$this->entityManager->beginTransaction();
|
||||
|
||||
try {
|
||||
// تولید کد جدید
|
||||
$newCode = intval($count) + 1;
|
||||
|
||||
// بررسی تکراری بودن
|
||||
$isDuplicate = $this->checkCodeDuplicate($bid, $part, $newCode);
|
||||
|
||||
if (!$isDuplicate) {
|
||||
// کد منحصر به فرد است
|
||||
$business->{$setter}($newCode);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->commit();
|
||||
return $newCode;
|
||||
} else {
|
||||
// کد تکراری است، دوباره تلاش کن
|
||||
if ($retryCount >= $maxRetries) {
|
||||
$timestampCode = $this->generateTimestampCode($bid, $part);
|
||||
return $timestampCode;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->entityManager->rollback();
|
||||
throw $e;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. بهبود متد `app_accounting_insert` در `HesabdariController.php`
|
||||
|
||||
#### تغییرات:
|
||||
- **استفاده از تراکنش**: کل عملیات در یک تراکنش انجام میشود
|
||||
- **مدیریت خطا**: در صورت بروز خطا، تراکنش rollback میشود
|
||||
- **بررسی خطای تولید کد**: خطاهای احتمالی در تولید کد مدیریت میشود
|
||||
|
||||
### 3. متدهای کمکی جدید
|
||||
|
||||
#### `checkCodeDuplicate()`:
|
||||
بررسی تکراری بودن کد در جدول مربوطه
|
||||
|
||||
#### `generateTimestampCode()`:
|
||||
تولید کد منحصر به فرد با استفاده از timestamp
|
||||
|
||||
#### `fixDuplicateCodes()`:
|
||||
ترمیم کدهای تکراری موجود در دیتابیس
|
||||
|
||||
### 4. API جدید برای ترمیم کدهای تکراری
|
||||
|
||||
#### Endpoint: `/api/accounting/fix-duplicate-codes`
|
||||
- **Method**: POST
|
||||
- **Access**: فقط ادمین
|
||||
- **Function**: ترمیم کدهای تکراری موجود
|
||||
|
||||
## نحوه استفاده
|
||||
|
||||
### برای ترمیم کدهای تکراری موجود:
|
||||
```bash
|
||||
POST /api/accounting/fix-duplicate-codes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"part": "accounting"
|
||||
}
|
||||
```
|
||||
|
||||
### پاسخ:
|
||||
```json
|
||||
{
|
||||
"result": 1,
|
||||
"message": "5 کد تکراری ترمیم شد",
|
||||
"fixed_count": 5
|
||||
}
|
||||
```
|
||||
|
||||
## مزایای راهحل
|
||||
|
||||
1. **جلوگیری از کدهای تکراری**: سیستم اکنون کدهای منحصر به فرد تولید میکند
|
||||
2. **مدیریت Race Condition**: استفاده از تراکنش از تداخل عملیات جلوگیری میکند
|
||||
3. **ترمیم خودکار**: در صورت بروز مشکل، سیستم خودکار کد جدید تولید میکند
|
||||
4. **ابزار ترمیم**: امکان ترمیم کدهای تکراری موجود
|
||||
5. **Backward Compatibility**: تغییرات بدون تأثیر بر عملکرد موجود
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **فقط ادمین**: فقط کاربران ادمین میتوانند کدهای تکراری را ترمیم کنند
|
||||
2. **Backup**: قبل از اجرای ترمیم، از دیتابیس backup بگیرید
|
||||
3. **تست**: تغییرات را در محیط تست بررسی کنید
|
||||
4. **Monitoring**: عملکرد سیستم را پس از اعمال تغییرات نظارت کنید
|
||||
146
docs/accounting/DUPLICATE_CODE_ERROR_FIX.md
Normal file
146
docs/accounting/DUPLICATE_CODE_ERROR_FIX.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# رفع خطای NonUniqueResultException در کدهای تکراری
|
||||
|
||||
## مشکل
|
||||
خطای `NonUniqueResultException` با پیام "More than one result was found for query although one row or none was expected" در متد `app_accounting_remove_doc` رخ میدهد.
|
||||
|
||||
## علت
|
||||
این خطا زمانی رخ میدهد که چندین سند حسابداری با کد یکسان در دیتابیس وجود دارد و متد `findOneBy` نمیتواند تصمیم بگیرد کدام سند را برگرداند.
|
||||
|
||||
## راهحلهای پیادهسازی شده
|
||||
|
||||
### 1. بهبود متد `app_accounting_remove_doc`
|
||||
|
||||
#### تغییرات:
|
||||
- **بررسی خودکار کدهای تکراری**: قبل از حذف سند، بررسی میشود که آیا کدهای تکراری وجود دارد
|
||||
- **ترمیم خودکار**: در صورت وجود کدهای تکراری، سیستم خودکار آنها را ترمیم میکند
|
||||
- **پیدا کردن ایمن**: پس از ترمیم، سند به صورت ایمن پیدا میشود
|
||||
|
||||
#### کد جدید:
|
||||
```php
|
||||
// ابتدا بررسی کن که آیا کدهای تکراری وجود دارد
|
||||
if ($provider->hasDuplicateCodes($request->headers->get('activeBid'), 'accounting')) {
|
||||
// کدهای تکراری وجود دارد، ترمیم کن
|
||||
$provider->fixDuplicateCodes($request->headers->get('activeBid'), 'accounting');
|
||||
}
|
||||
|
||||
// حالا سند را پیدا کن
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'code' => $params['code'],
|
||||
'bid' => $request->headers->get('activeBid')
|
||||
]);
|
||||
```
|
||||
|
||||
### 2. متد جدید `hasDuplicateCodes`
|
||||
|
||||
#### عملکرد:
|
||||
- بررسی وجود کدهای تکراری بدون ترمیم
|
||||
- بازگشت `true` یا `false`
|
||||
- قابل استفاده برای بررسی وضعیت قبل از عملیات
|
||||
|
||||
#### کد:
|
||||
```php
|
||||
public function hasDuplicateCodes($bid, $part = 'accounting')
|
||||
{
|
||||
// پیدا کردن کدهای تکراری
|
||||
$qb = $repository->createQueryBuilder('e');
|
||||
$qb->select('e.code, COUNT(e.id) as count')
|
||||
->where('e.bid = :bid')
|
||||
->setParameter('bid', $bid)
|
||||
->groupBy('e.code')
|
||||
->having('COUNT(e.id) > 1');
|
||||
|
||||
$duplicates = $qb->getQuery()->getResult();
|
||||
|
||||
return count($duplicates) > 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. API جدید برای بررسی وضعیت
|
||||
|
||||
#### Endpoint: `/api/accounting/check-duplicate-codes`
|
||||
- **Method**: GET
|
||||
- **Access**: فقط ادمین
|
||||
- **Function**: بررسی وجود کدهای تکراری
|
||||
|
||||
#### مثال استفاده:
|
||||
```bash
|
||||
GET /api/accounting/check-duplicate-codes?part=accounting
|
||||
```
|
||||
|
||||
#### پاسخ:
|
||||
```json
|
||||
{
|
||||
"result": 1,
|
||||
"has_duplicates": true,
|
||||
"message": "کدهای تکراری یافت شد"
|
||||
}
|
||||
```
|
||||
|
||||
## نحوه استفاده
|
||||
|
||||
### 1. بررسی وضعیت کدهای تکراری:
|
||||
```bash
|
||||
GET /api/accounting/check-duplicate-codes?part=accounting
|
||||
```
|
||||
|
||||
### 2. ترمیم کدهای تکراری:
|
||||
```bash
|
||||
POST /api/accounting/fix-duplicate-codes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"part": "accounting"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. حذف سند (خودکار ترمیم میکند):
|
||||
```bash
|
||||
POST /api/accounting/remove
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"code": "1001"
|
||||
}
|
||||
```
|
||||
|
||||
## مزایای راهحل
|
||||
|
||||
1. **جلوگیری از خطا**: خطای `NonUniqueResultException` دیگر رخ نمیدهد
|
||||
2. **ترمیم خودکار**: سیستم خودکار کدهای تکراری را ترمیم میکند
|
||||
3. **بررسی وضعیت**: امکان بررسی وجود کدهای تکراری قبل از عملیات
|
||||
4. **Backward Compatibility**: با کدهای موجود سازگار است
|
||||
5. **امنیت**: فقط ادمین میتواند عملیات ترمیم را انجام دهد
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **عملکرد خودکار**: حذف سند اکنون خودکار کدهای تکراری را ترمیم میکند
|
||||
2. **بررسی قبل از عملیات**: میتوانید وضعیت کدهای تکراری را بررسی کنید
|
||||
3. **ترمیم انتخابی**: میتوانید فقط کدهای تکراری را ترمیم کنید
|
||||
4. **لاگ عملیات**: تمام عملیات ترمیم در لاگ ثبت میشود
|
||||
5. **Backup**: قبل از ترمیم، از دیتابیس backup بگیرید
|
||||
|
||||
## تست
|
||||
|
||||
برای تست عملکرد:
|
||||
|
||||
1. **ایجاد کدهای تکراری** (در محیط تست):
|
||||
```sql
|
||||
UPDATE hesabdari_doc SET code = 1001 WHERE id IN (1, 2);
|
||||
```
|
||||
|
||||
2. **بررسی وضعیت**:
|
||||
```bash
|
||||
GET /api/accounting/check-duplicate-codes
|
||||
```
|
||||
|
||||
3. **حذف سند** (خودکار ترمیم میکند):
|
||||
```bash
|
||||
POST /api/accounting/remove
|
||||
```
|
||||
|
||||
4. **بررسی مجدد**:
|
||||
```bash
|
||||
GET /api/accounting/check-duplicate-codes
|
||||
```
|
||||
|
||||
این راهحل مشکل `NonUniqueResultException` را به طور کامل حل میکند و سیستم را در برابر کدهای تکراری محافظت میکند.
|
||||
107
docs/accounting/NUMERIC_CODE_GUIDELINES.md
Normal file
107
docs/accounting/NUMERIC_CODE_GUIDELINES.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# راهنمای کدهای عددی حسابداری
|
||||
|
||||
## اصل کلی
|
||||
**تمام کدهای اسناد حسابداری باید فقط عدد باشند و هیچ حرفی نداشته باشند.**
|
||||
|
||||
## تغییرات اعمال شده
|
||||
|
||||
### 1. اعتبارسنجی کد
|
||||
متد `validateCode()` اضافه شد که موارد زیر را بررسی میکند:
|
||||
- کد باید عدد باشد
|
||||
- کد باید مثبت باشد (بزرگتر از صفر)
|
||||
- طول کد حداکثر 20 رقم باشد
|
||||
|
||||
### 2. بهبود متد `getAccountingCode`
|
||||
- **بررسی تکراری بودن**: قبل از ذخیره، بررسی میشود که کد قبلاً وجود نداشته باشد
|
||||
- **Retry Logic**: تا 10 بار تلاش برای تولید کد منحصر به فرد
|
||||
- **Fallback Strategy**: در صورت عدم موفقیت، از شمارنده بزرگتر استفاده میکند
|
||||
- **Timestamp Fallback**: در نهایت از timestamp استفاده میکند
|
||||
|
||||
### 3. متدهای جدید
|
||||
|
||||
#### `generateFallbackCode()`
|
||||
تولید کد جایگزین با افزایش شمارنده به مقدار بزرگی (10000+)
|
||||
|
||||
#### `generateTimestampCode()`
|
||||
تولید کد منحصر به فرد با استفاده از timestamp (فقط عدد)
|
||||
|
||||
#### `validateCode()`
|
||||
اعتبارسنجی کد برای اطمینان از عددی بودن
|
||||
|
||||
## نمونه کدهای تولید شده
|
||||
|
||||
### کدهای عادی (ترتیبی):
|
||||
```
|
||||
1001, 1002, 1003, 1004, ...
|
||||
```
|
||||
|
||||
### کدهای Fallback (در صورت تداخل):
|
||||
```
|
||||
11001, 11002, 11003, ...
|
||||
```
|
||||
|
||||
### کدهای Timestamp (در صورت نیاز):
|
||||
```
|
||||
170312345678901234, 170312345678901235, ...
|
||||
```
|
||||
|
||||
## قوانین اعتبارسنجی
|
||||
|
||||
### ✅ کدهای معتبر:
|
||||
- `123`
|
||||
- `1000`
|
||||
- `999999`
|
||||
- `170312345678901234`
|
||||
|
||||
### ❌ کدهای نامعتبر:
|
||||
- `abc`
|
||||
- `123abc`
|
||||
- `0`
|
||||
- `-1`
|
||||
- `123.45`
|
||||
|
||||
## تستها
|
||||
|
||||
فایل `ProviderTest.php` اضافه شد که شامل:
|
||||
- تست تولید کد عددی
|
||||
- تست اعتبارسنجی کد
|
||||
- تست کدهای معتبر و نامعتبر
|
||||
|
||||
## نحوه اجرای تست
|
||||
|
||||
```bash
|
||||
# اجرای تستهای Provider
|
||||
php bin/phpunit tests/ProviderTest.php
|
||||
|
||||
# اجرای تست خاص
|
||||
php bin/phpunit --filter testGetAccountingCodeReturnsNumericValue
|
||||
```
|
||||
|
||||
## مزایای راهحل
|
||||
|
||||
1. **اطمینان از عددی بودن**: تمام کدها فقط عدد هستند
|
||||
2. **جلوگیری از تداخل**: سیستم خودکار کد منحصر به فرد تولید میکند
|
||||
3. **Backward Compatibility**: با کدهای موجود سازگار است
|
||||
4. **قابلیت تست**: تستهای کامل برای اطمینان از عملکرد صحیح
|
||||
5. **مدیریت خطا**: در صورت بروز مشکل، راهحل جایگزین ارائه میدهد
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **فقط عدد**: هیچ حرفی در کدها استفاده نمیشود
|
||||
2. **مثبت**: تمام کدها بزرگتر از صفر هستند
|
||||
3. **منحصر به فرد**: هیچ کد تکراری تولید نمیشود
|
||||
4. **قابل پیشبینی**: کدها ترتیبی هستند (مگر در موارد خاص)
|
||||
5. **قابل ترمیم**: ابزار ترمیم کدهای تکراری موجود
|
||||
|
||||
## API ترمیم کدهای تکراری
|
||||
|
||||
```bash
|
||||
POST /api/accounting/fix-duplicate-codes
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"part": "accounting"
|
||||
}
|
||||
```
|
||||
|
||||
این API کدهای تکراری موجود را پیدا کرده و با کدهای عددی منحصر به فرد جایگزین میکند.
|
||||
129
docs/accounting/REASONABLE_CODE_GENERATION.md
Normal file
129
docs/accounting/REASONABLE_CODE_GENERATION.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# تولید کدهای معقول حسابداری
|
||||
|
||||
## مشکل
|
||||
کدهای تولید شده خیلی بلند و خارج از عرف حسابداری بودند (مثل `1,756,382,866,131,764`).
|
||||
|
||||
## راهحلهای پیادهسازی شده
|
||||
|
||||
### 1. محدودیت طول کد
|
||||
- **حداکثر 6 رقم**: کدهای حسابداری حداکثر 6 رقم هستند
|
||||
- **عرف حسابداری**: مطابق با استانداردهای حسابداری
|
||||
|
||||
### 2. متد `generateReasonableCode`
|
||||
|
||||
#### عملکرد:
|
||||
- تولید کدهای 1 تا 6 رقمی
|
||||
- استفاده از شمارنده ترتیبی
|
||||
- استفاده از اعداد تصادفی در صورت نیاز
|
||||
- بررسی تکراری نبودن
|
||||
|
||||
#### الگوریتم:
|
||||
1. **شمارنده ترتیبی**: `currentCode + 1`
|
||||
2. **عدد تصادفی 4 رقمی**: `1000-9999`
|
||||
3. **عدد تصادفی 5 رقمی**: `10000-99999`
|
||||
4. **عدد تصادفی 6 رقمی**: `100000-999999`
|
||||
|
||||
### 3. بهبود متدهای موجود
|
||||
|
||||
#### `generateTimestampCode`:
|
||||
- حذف timestamp های بلند
|
||||
- استفاده از شمارنده معقول
|
||||
- حداکثر 6 رقم
|
||||
|
||||
#### `generateFallbackCode`:
|
||||
- کاهش افزایش شمارنده از 10000 به 500
|
||||
- استفاده از اعداد تصادفی معقول
|
||||
|
||||
#### `validateCode`:
|
||||
- محدودیت طول از 20 رقم به 6 رقم
|
||||
|
||||
## نمونه کدهای تولید شده
|
||||
|
||||
### ✅ کدهای معقول:
|
||||
```
|
||||
1001, 1002, 1003, ... (ترتیبی)
|
||||
1234, 5678, 9012, ... (تصادفی 4 رقمی)
|
||||
12345, 67890, ... (تصادفی 5 رقمی)
|
||||
123456, 789012, ... (تصادفی 6 رقمی)
|
||||
```
|
||||
|
||||
### ❌ کدهای نامعتبر (قبلی):
|
||||
```
|
||||
1,756,382,866,131,764 (خیلی بلند)
|
||||
170312345678901234 (timestamp بلند)
|
||||
```
|
||||
|
||||
## مزایای راهحل
|
||||
|
||||
1. **مطابق عرف**: کدهای 1-6 رقمی مطابق استاندارد حسابداری
|
||||
2. **قابل خواندن**: کدهای کوتاه و قابل فهم
|
||||
3. **عملکرد بهتر**: تولید سریعتر کدها
|
||||
4. **ذخیرهسازی بهینه**: فضای کمتری در دیتابیس
|
||||
5. **نمایش بهتر**: در گزارشها و فاکتورها بهتر نمایش داده میشوند
|
||||
|
||||
## نحوه استفاده
|
||||
|
||||
### تولید کد جدید:
|
||||
```php
|
||||
$code = $provider->getAccountingCode($bid, 'accounting');
|
||||
// نتیجه: 1001, 1002, 1234, 12345, 123456
|
||||
```
|
||||
|
||||
### ترمیم کدهای تکراری:
|
||||
```php
|
||||
$result = $provider->fixDuplicateCodes($bid, 'accounting');
|
||||
// کدهای تکراری با کدهای معقول جایگزین میشوند
|
||||
```
|
||||
|
||||
## قوانین جدید
|
||||
|
||||
### اعتبارسنجی کد:
|
||||
- ✅ عدد مثبت
|
||||
- ✅ حداکثر 6 رقم
|
||||
- ✅ فقط اعداد
|
||||
|
||||
### تولید کد:
|
||||
1. **اولویت اول**: شمارنده ترتیبی
|
||||
2. **اولویت دوم**: عدد تصادفی 4 رقمی
|
||||
3. **اولویت سوم**: عدد تصادفی 5 رقمی
|
||||
4. **اولویت چهارم**: عدد تصادفی 6 رقمی
|
||||
|
||||
## تست
|
||||
|
||||
### تست کدهای معقول:
|
||||
```php
|
||||
// تست تولید کد
|
||||
$code = $provider->getAccountingCode($bid, 'accounting');
|
||||
$this->assertLessThanOrEqual(999999, $code); // حداکثر 6 رقم
|
||||
$this->assertGreaterThan(0, $code); // مثبت
|
||||
|
||||
// تست اعتبارسنجی
|
||||
$this->assertTrue($provider->validateCode(1234)); // معتبر
|
||||
$this->assertFalse($provider->validateCode(1234567)); // خیلی بلند
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **عرف حسابداری**: کدهای 1-6 رقمی استاندارد هستند
|
||||
2. **عملکرد**: تولید سریعتر کدها
|
||||
3. **خوانایی**: کدهای کوتاه قابل فهمتر
|
||||
4. **نمایش**: در گزارشها بهتر نمایش داده میشوند
|
||||
5. **Backward Compatibility**: با کدهای موجود سازگار
|
||||
|
||||
## تغییرات در API
|
||||
|
||||
### قبل:
|
||||
```json
|
||||
{
|
||||
"code": "1756382866131764"
|
||||
}
|
||||
```
|
||||
|
||||
### بعد:
|
||||
```json
|
||||
{
|
||||
"code": "1234"
|
||||
}
|
||||
```
|
||||
|
||||
این تغییرات کدهای حسابداری را مطابق با عرف و استانداردهای حسابداری میکند.
|
||||
155
docs/accounting/SAFE_ENTITY_FINDING.md
Normal file
155
docs/accounting/SAFE_ENTITY_FINDING.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# پیدا کردن ایمن Entity ها با بررسی کدهای تکراری
|
||||
|
||||
## مشکل
|
||||
خطای `NonUniqueResultException` در متدهای مختلف که از `findOneBy` استفاده میکنند رخ میدهد.
|
||||
|
||||
## راهحلهای پیادهسازی شده
|
||||
|
||||
### 1. متد `findHesabdariDocSafely`
|
||||
|
||||
#### عملکرد:
|
||||
- بررسی خودکار کدهای تکراری قبل از پیدا کردن سند
|
||||
- ترمیم خودکار کدهای تکراری در صورت نیاز
|
||||
- پیدا کردن ایمن سند حسابداری
|
||||
|
||||
#### استفاده:
|
||||
```php
|
||||
$doc = $provider->findHesabdariDocSafely($entityManager, [
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['code'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
```
|
||||
|
||||
### 2. متد `findEntitySafely` (عمومی)
|
||||
|
||||
#### عملکرد:
|
||||
- بررسی خودکار کدهای تکراری برای هر نوع entity
|
||||
- ترمیم خودکار در صورت نیاز
|
||||
- قابل استفاده برای تمام entity ها
|
||||
|
||||
#### استفاده:
|
||||
```php
|
||||
// برای سند حسابداری
|
||||
$doc = $provider->findEntitySafely($entityManager, HesabdariDoc::class, [
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
], 'accounting');
|
||||
|
||||
// برای شخص
|
||||
$person = $provider->findEntitySafely($entityManager, Person::class, [
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
], 'person');
|
||||
|
||||
// برای حساب بانکی
|
||||
$bank = $provider->findEntitySafely($entityManager, BankAccount::class, [
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
], 'bank');
|
||||
```
|
||||
|
||||
## متدهای بهبود یافته
|
||||
|
||||
### 1. `app_accounting_doc_get`
|
||||
```php
|
||||
// قبل از بهبود
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['code'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
|
||||
// بعد از بهبود
|
||||
$doc = $provider->findHesabdariDocSafely($entityManager, [
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['code'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
```
|
||||
|
||||
### 2. `app_accounting_remove_doc`
|
||||
```php
|
||||
// قبل از بهبود
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'code' => $params['code'],
|
||||
'bid' => $request->headers->get('activeBid')
|
||||
]);
|
||||
|
||||
// بعد از بهبود
|
||||
$doc = $provider->findHesabdariDocSafely($entityManager, [
|
||||
'code' => $params['code'],
|
||||
'bid' => $request->headers->get('activeBid')
|
||||
]);
|
||||
```
|
||||
|
||||
## مزایای راهحل
|
||||
|
||||
1. **جلوگیری از خطا**: خطای `NonUniqueResultException` دیگر رخ نمیدهد
|
||||
2. **ترمیم خودکار**: کدهای تکراری خودکار ترمیم میشوند
|
||||
3. **قابلیت استفاده مجدد**: متدهای عمومی قابل استفاده در تمام بخشها
|
||||
4. **Backward Compatibility**: با کدهای موجود سازگار است
|
||||
5. **عملکرد بهینه**: فقط در صورت نیاز ترمیم انجام میشود
|
||||
|
||||
## نحوه استفاده در سایر کنترلرها
|
||||
|
||||
### برای کنترلر Person:
|
||||
```php
|
||||
// قبل
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
]);
|
||||
|
||||
// بعد
|
||||
$person = $provider->findEntitySafely($entityManager, Person::class, [
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
], 'person');
|
||||
```
|
||||
|
||||
### برای کنترلر Bank:
|
||||
```php
|
||||
// قبل
|
||||
$bank = $entityManager->getRepository(BankAccount::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
]);
|
||||
|
||||
// بعد
|
||||
$bank = $provider->findEntitySafely($entityManager, BankAccount::class, [
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['code']
|
||||
], 'bank');
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
|
||||
1. **پارامتر part**: برای entity هایی که کد دارند، part را مشخص کنید
|
||||
2. **عملکرد خودکار**: ترمیم فقط در صورت وجود کدهای تکراری انجام میشود
|
||||
3. **امنیت**: تمام عملیات در تراکنش انجام میشود
|
||||
4. **لاگ**: عملیات ترمیم در لاگ ثبت میشود
|
||||
5. **Backup**: قبل از استفاده، از دیتابیس backup بگیرید
|
||||
|
||||
## تست
|
||||
|
||||
برای تست عملکرد:
|
||||
|
||||
```php
|
||||
// تست پیدا کردن ایمن
|
||||
$doc = $provider->findHesabdariDocSafely($entityManager, [
|
||||
'bid' => $bid,
|
||||
'code' => '1001'
|
||||
]);
|
||||
|
||||
// تست پیدا کردن عمومی
|
||||
$person = $provider->findEntitySafely($entityManager, Person::class, [
|
||||
'bid' => $bid,
|
||||
'code' => 'P001'
|
||||
], 'person');
|
||||
```
|
||||
|
||||
این راهحل مشکل `NonUniqueResultException` را در تمام متدها حل میکند و سیستم را در برابر کدهای تکراری محافظت میکند.
|
||||
|
|
@ -52,6 +52,7 @@ class HesabdariController extends AbstractController
|
|||
$acc = $access->hasRole('accounting');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
// Check if we should include preview documents
|
||||
$includePreview = $params['includePreview'] ?? false;
|
||||
|
||||
|
|
@ -63,8 +64,8 @@ class HesabdariController extends AbstractController
|
|||
'money' => $acc['money']
|
||||
]);
|
||||
} else {
|
||||
// Default: only approved documents
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
// Default: only approved documents - استفاده از متد ایمن
|
||||
$doc = $provider->findHesabdariDocSafely($entityManager, [
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['code'],
|
||||
|
|
@ -439,215 +440,244 @@ class HesabdariController extends AbstractController
|
|||
throw $this->createNotFoundException('rows is to short');
|
||||
if (!array_key_exists('date', $params) || !array_key_exists('des', $params))
|
||||
throw $this->createNotFoundException('some params mistake');
|
||||
if (array_key_exists('update', $params) && $params['update'] != '') {
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['update'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
if (!$doc)
|
||||
throw $this->createNotFoundException('document not found.');
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDate($params['date']);
|
||||
$doc->setMoney($acc['money']);
|
||||
if (array_key_exists('refData', $params))
|
||||
$doc->setRefData($params['refData']);
|
||||
if (array_key_exists('plugin', $params))
|
||||
$doc->setPlugin($params['plugin']);
|
||||
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'doc' => $doc
|
||||
]);
|
||||
foreach ($rows as $row)
|
||||
$entityManager->remove($row);
|
||||
} else {
|
||||
$doc = new HesabdariDoc();
|
||||
$doc->setBid($acc['bid']);
|
||||
$doc->setYear($acc['year']);
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDateSubmit(time());
|
||||
$doc->setType($params['type']);
|
||||
$doc->setDate($params['date']);
|
||||
$doc->setSubmitter($this->getUser());
|
||||
$doc->setMoney($acc['money']);
|
||||
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||
|
||||
// Set approval status based on business settings
|
||||
$business = $acc['bid'];
|
||||
if ($business->isRequireTwoStepApproval()) {
|
||||
// Two-step approval is enabled
|
||||
$doc->setIsPreview(true);
|
||||
$doc->setIsApproved(false);
|
||||
$doc->setApprovedBy(null);
|
||||
} else {
|
||||
// Two-step approval is disabled - auto approve
|
||||
$doc->setIsPreview(false);
|
||||
$doc->setIsApproved(true);
|
||||
$doc->setApprovedBy($this->getUser());
|
||||
}
|
||||
|
||||
if (array_key_exists('refData', $params))
|
||||
$doc->setRefData($params['refData']);
|
||||
if (array_key_exists('plugin', $params))
|
||||
$doc->setPlugin($params['plugin']);
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
//add document to related docs
|
||||
if (array_key_exists('related', $params)) {
|
||||
$relatedDoc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'code' => $params['related'],
|
||||
'bid' => $doc->getBid(),
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
if ($relatedDoc) {
|
||||
$relatedDoc->addRelatedDoc($doc);
|
||||
$entityManager->persist($relatedDoc);
|
||||
$entityManager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
$amount = 0;
|
||||
foreach ($params['rows'] as $row) {
|
||||
$row['bs'] = str_replace(',', '', $row['bs']);
|
||||
$row['bd'] = str_replace(',', '', $row['bd']);
|
||||
|
||||
$hesabdariRow = new HesabdariRow();
|
||||
$hesabdariRow->setBid($acc['bid']);
|
||||
$hesabdariRow->setYear($acc['year']);
|
||||
$hesabdariRow->setDoc($doc);
|
||||
$hesabdariRow->setBs($row['bs']);
|
||||
$hesabdariRow->setBd($row['bd']);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
|
||||
'code' => $row['table']
|
||||
]);
|
||||
$hesabdariRow->setRef($ref);
|
||||
|
||||
$entityManager->persist($hesabdariRow);
|
||||
|
||||
if (array_key_exists('referral', $row))
|
||||
$hesabdariRow->setReferral($row['referral']);
|
||||
$amount += $row['bs'];
|
||||
//check is type is person
|
||||
if ($row['type'] == 'person') {
|
||||
$person = $entityManager->getRepository(Person::class)->find($row['id']);
|
||||
if (!$person)
|
||||
throw $this->createNotFoundException('person not found');
|
||||
elseif ($person->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('person is not in this business');
|
||||
$hesabdariRow->setPerson($person);
|
||||
} elseif ($row['type'] == 'cheque') {
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
|
||||
// شروع تراکنش
|
||||
$entityManager->beginTransaction();
|
||||
|
||||
try {
|
||||
if (array_key_exists('update', $params) && $params['update'] != '') {
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'id' => $row['chequeOwner']
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['update'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
$cheque = new Cheque();
|
||||
$cheque->setBid($acc['bid']);
|
||||
$cheque->setSubmitter($this->getUser());
|
||||
$cheque->setPayDate($row['chequeDate']);
|
||||
$cheque->setBankOncheque($row['chequeBank']);
|
||||
$cheque->setRef($hesabdariRow->getRef());
|
||||
$cheque->setNumber($row['chequeNum']);
|
||||
$cheque->setSayadNum($row['chequeSayadNum']);
|
||||
$cheque->setDateSubmit(time());
|
||||
$cheque->setDes($row['des']);
|
||||
$dateArray = explode('/', $row['chequeDate']);
|
||||
$dateGre = strtotime($jdate->jalali_to_gregorian($dateArray['0'], $dateArray['1'], $dateArray['2'], '/'));
|
||||
$cheque->setDateStamp($dateGre);
|
||||
$cheque->setPerson($person);
|
||||
$cheque->setRef($entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => $row['table']]));
|
||||
$cheque->setType($row['chequeType']);
|
||||
if ($cheque->getType() == 'input')
|
||||
$cheque->setAmount($hesabdariRow->getBd());
|
||||
else
|
||||
$cheque->setAmount($hesabdariRow->getBs());
|
||||
$cheque->setLocked(false);
|
||||
$cheque->setRejected(false);
|
||||
$cheque->setStatus('پاس نشده');
|
||||
$entityManager->persist($cheque);
|
||||
if (!$doc)
|
||||
throw $this->createNotFoundException('document not found.');
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDate($params['date']);
|
||||
$doc->setMoney($acc['money']);
|
||||
if (array_key_exists('refData', $params))
|
||||
$doc->setRefData($params['refData']);
|
||||
if (array_key_exists('plugin', $params))
|
||||
$doc->setPlugin($params['plugin']);
|
||||
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
$hesabdariRow->setCheque($cheque);
|
||||
} elseif ($row['type'] == 'bank') {
|
||||
$bank = $entityManager->getRepository(BankAccount::class)->findOneBy([
|
||||
'id' => $row['id'],
|
||||
'bid' => $acc['bid']
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'doc' => $doc
|
||||
]);
|
||||
if (!$bank)
|
||||
throw $this->createNotFoundException('bank not found');
|
||||
$hesabdariRow->setBank($bank);
|
||||
} elseif ($row['type'] == 'salary') {
|
||||
$salary = $entityManager->getRepository(Salary::class)->find($row['id']);
|
||||
if (!$salary)
|
||||
throw $this->createNotFoundException('salary not found');
|
||||
elseif ($salary->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('bank is not in this business');
|
||||
$hesabdariRow->setSalary($salary);
|
||||
} elseif ($row['type'] == 'cashdesk') {
|
||||
$cashdesk = $entityManager->getRepository(Cashdesk::class)->find($row['id']);
|
||||
if (!$cashdesk)
|
||||
throw $this->createNotFoundException('cashdesk not found');
|
||||
elseif ($cashdesk->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('bank is not in this business');
|
||||
$hesabdariRow->setCashdesk($cashdesk);
|
||||
} elseif ($row['type'] == 'commodity') {
|
||||
$row['count'] = str_replace(',', '', $row['count']);
|
||||
$commodity = $entityManager->getRepository(Commodity::class)->find($row['commodity']['id']);
|
||||
if (!$commodity)
|
||||
throw $this->createNotFoundException('commodity not found');
|
||||
elseif ($commodity->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('$commodity is not in this business');
|
||||
$hesabdariRow->setCommodity($commodity);
|
||||
$hesabdariRow->setCommdityCount($row['count']);
|
||||
foreach ($rows as $row)
|
||||
$entityManager->remove($row);
|
||||
} else {
|
||||
$doc = new HesabdariDoc();
|
||||
$doc->setBid($acc['bid']);
|
||||
$doc->setYear($acc['year']);
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDateSubmit(time());
|
||||
$doc->setType($params['type']);
|
||||
$doc->setDate($params['date']);
|
||||
$doc->setSubmitter($this->getUser());
|
||||
$doc->setMoney($acc['money']);
|
||||
|
||||
// تولید کد منحصر به فرد با مدیریت خطا
|
||||
try {
|
||||
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||
} catch (\Exception $e) {
|
||||
$entityManager->rollback();
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => 'خطا در تولید کد سند: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
// Set approval status based on business settings
|
||||
$business = $acc['bid'];
|
||||
if ($business->isRequireTwoStepApproval()) {
|
||||
// Two-step approval is enabled
|
||||
$doc->setIsPreview(true);
|
||||
$doc->setIsApproved(false);
|
||||
$doc->setApprovedBy(null);
|
||||
} else {
|
||||
// Two-step approval is disabled - auto approve
|
||||
$doc->setIsPreview(false);
|
||||
$doc->setIsApproved(true);
|
||||
$doc->setApprovedBy($this->getUser());
|
||||
}
|
||||
|
||||
if (array_key_exists('refData', $params))
|
||||
$doc->setRefData($params['refData']);
|
||||
if (array_key_exists('plugin', $params))
|
||||
$doc->setPlugin($params['plugin']);
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
if (array_key_exists('plugin', $row))
|
||||
$hesabdariRow->setPlugin($row['plugin']);
|
||||
if (array_key_exists('refData', $row))
|
||||
$hesabdariRow->setRefData($row['refData']);
|
||||
|
||||
|
||||
$hesabdariRow->setDes($row['des']);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
$entityManager->flush();
|
||||
}
|
||||
$doc->setAmount($amount);
|
||||
$entityManager->persist($doc);
|
||||
|
||||
//check ghesta
|
||||
if (array_key_exists('ghestaId', $params)) {
|
||||
$ghesta = $entityManager->getRepository(PlugGhestaDoc::class)->find($params['ghestaId']);
|
||||
if ($ghesta) {
|
||||
$ghestaItem = $entityManager->getRepository(PlugGhestaItem::class)->findOneBy([
|
||||
'doc' => $ghesta,
|
||||
'num' => $params['ghestaNum']
|
||||
//add document to related docs
|
||||
if (array_key_exists('related', $params)) {
|
||||
$relatedDoc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'code' => $params['related'],
|
||||
'bid' => $doc->getBid(),
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
if ($ghestaItem) {
|
||||
$ghestaItem->setHesabdariDoc($doc);
|
||||
$entityManager->persist($ghestaItem);
|
||||
if ($relatedDoc) {
|
||||
$relatedDoc->addRelatedDoc($doc);
|
||||
$entityManager->persist($relatedDoc);
|
||||
$entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
$entityManager->flush();
|
||||
$log->insert(
|
||||
'حسابداری',
|
||||
'سند حسابداری شماره ' . $doc->getCode() . ' ثبت / ویرایش شد.',
|
||||
$this->getUser(),
|
||||
$request->headers->get('activeBid'),
|
||||
$doc
|
||||
);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'doc' => $provider->Entity2Array($doc, 0)
|
||||
]);
|
||||
$amount = 0;
|
||||
foreach ($params['rows'] as $row) {
|
||||
$row['bs'] = str_replace(',', '', $row['bs']);
|
||||
$row['bd'] = str_replace(',', '', $row['bd']);
|
||||
|
||||
$hesabdariRow = new HesabdariRow();
|
||||
$hesabdariRow->setBid($acc['bid']);
|
||||
$hesabdariRow->setYear($acc['year']);
|
||||
$hesabdariRow->setDoc($doc);
|
||||
$hesabdariRow->setBs($row['bs']);
|
||||
$hesabdariRow->setBd($row['bd']);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
|
||||
'code' => $row['table']
|
||||
]);
|
||||
$hesabdariRow->setRef($ref);
|
||||
|
||||
$entityManager->persist($hesabdariRow);
|
||||
|
||||
if (array_key_exists('referral', $row))
|
||||
$hesabdariRow->setReferral($row['referral']);
|
||||
$amount += $row['bs'];
|
||||
//check is type is person
|
||||
if ($row['type'] == 'person') {
|
||||
$person = $entityManager->getRepository(Person::class)->find($row['id']);
|
||||
if (!$person)
|
||||
throw $this->createNotFoundException('person not found');
|
||||
elseif ($person->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('person is not in this business');
|
||||
$hesabdariRow->setPerson($person);
|
||||
} elseif ($row['type'] == 'cheque') {
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'id' => $row['chequeOwner']
|
||||
]);
|
||||
$cheque = new Cheque();
|
||||
$cheque->setBid($acc['bid']);
|
||||
$cheque->setSubmitter($this->getUser());
|
||||
$cheque->setPayDate($row['chequeDate']);
|
||||
$cheque->setBankOncheque($row['chequeBank']);
|
||||
$cheque->setRef($hesabdariRow->getRef());
|
||||
$cheque->setNumber($row['chequeNum']);
|
||||
$cheque->setSayadNum($row['chequeSayadNum']);
|
||||
$cheque->setDateSubmit(time());
|
||||
$cheque->setDes($row['des']);
|
||||
$dateArray = explode('/', $row['chequeDate']);
|
||||
$dateGre = strtotime($jdate->jalali_to_gregorian($dateArray['0'], $dateArray['1'], $dateArray['2'], '/'));
|
||||
$cheque->setDateStamp($dateGre);
|
||||
$cheque->setPerson($person);
|
||||
$cheque->setRef($entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => $row['table']]));
|
||||
$cheque->setType($row['chequeType']);
|
||||
if ($cheque->getType() == 'input')
|
||||
$cheque->setAmount($hesabdariRow->getBd());
|
||||
else
|
||||
$cheque->setAmount($hesabdariRow->getBs());
|
||||
$cheque->setLocked(false);
|
||||
$cheque->setRejected(false);
|
||||
$cheque->setStatus('پاس نشده');
|
||||
$entityManager->persist($cheque);
|
||||
$entityManager->flush();
|
||||
$hesabdariRow->setCheque($cheque);
|
||||
} elseif ($row['type'] == 'bank') {
|
||||
$bank = $entityManager->getRepository(BankAccount::class)->findOneBy([
|
||||
'id' => $row['id'],
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
if (!$bank)
|
||||
throw $this->createNotFoundException('bank not found');
|
||||
$hesabdariRow->setBank($bank);
|
||||
} elseif ($row['type'] == 'salary') {
|
||||
$salary = $entityManager->getRepository(Salary::class)->find($row['id']);
|
||||
if (!$salary)
|
||||
throw $this->createNotFoundException('salary not found');
|
||||
elseif ($salary->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('bank is not in this business');
|
||||
$hesabdariRow->setSalary($salary);
|
||||
} elseif ($row['type'] == 'cashdesk') {
|
||||
$cashdesk = $entityManager->getRepository(Cashdesk::class)->find($row['id']);
|
||||
if (!$cashdesk)
|
||||
throw $this->createNotFoundException('cashdesk not found');
|
||||
elseif ($cashdesk->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('bank is not in this business');
|
||||
$hesabdariRow->setCashdesk($cashdesk);
|
||||
} elseif ($row['type'] == 'commodity') {
|
||||
$row['count'] = str_replace(',', '', $row['count']);
|
||||
$commodity = $entityManager->getRepository(Commodity::class)->find($row['commodity']['id']);
|
||||
if (!$commodity)
|
||||
throw $this->createNotFoundException('commodity not found');
|
||||
elseif ($commodity->getBid()->getId() != $acc['bid']->getId())
|
||||
throw $this->createAccessDeniedException('$commodity is not in this business');
|
||||
$hesabdariRow->setCommodity($commodity);
|
||||
$hesabdariRow->setCommdityCount($row['count']);
|
||||
}
|
||||
|
||||
if (array_key_exists('plugin', $row))
|
||||
$hesabdariRow->setPlugin($row['plugin']);
|
||||
if (array_key_exists('refData', $row))
|
||||
$hesabdariRow->setRefData($row['refData']);
|
||||
|
||||
|
||||
$hesabdariRow->setDes($row['des']);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
$entityManager->flush();
|
||||
}
|
||||
$doc->setAmount($amount);
|
||||
$entityManager->persist($doc);
|
||||
|
||||
//check ghesta
|
||||
if (array_key_exists('ghestaId', $params)) {
|
||||
$ghesta = $entityManager->getRepository(PlugGhestaDoc::class)->find($params['ghestaId']);
|
||||
if ($ghesta) {
|
||||
$ghestaItem = $entityManager->getRepository(PlugGhestaItem::class)->findOneBy([
|
||||
'doc' => $ghesta,
|
||||
'num' => $params['ghestaNum']
|
||||
]);
|
||||
if ($ghestaItem) {
|
||||
$ghestaItem->setHesabdariDoc($doc);
|
||||
$entityManager->persist($ghestaItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ثبت تراکنش
|
||||
$entityManager->flush();
|
||||
$entityManager->commit();
|
||||
|
||||
$log->insert(
|
||||
'حسابداری',
|
||||
'سند حسابداری شماره ' . $doc->getCode() . ' ثبت / ویرایش شد.',
|
||||
$this->getUser(),
|
||||
$request->headers->get('activeBid'),
|
||||
$doc
|
||||
);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'doc' => $provider->Entity2Array($doc, 0)
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// در صورت بروز خطا، تراکنش را rollback کن
|
||||
$entityManager->rollback();
|
||||
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => 'خطا در ثبت سند: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/accounting/remove', name: 'app_accounting_remove_doc')]
|
||||
public function app_accounting_remove_doc(Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||
public function app_accounting_remove_doc(Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, Provider $provider): JsonResponse
|
||||
{
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
|
|
@ -655,7 +685,8 @@ class HesabdariController extends AbstractController
|
|||
}
|
||||
if (!array_key_exists('code', $params))
|
||||
$this->createNotFoundException();
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
// استفاده از متد ایمن برای پیدا کردن سند
|
||||
$doc = $provider->findHesabdariDocSafely($entityManager, [
|
||||
'code' => $params['code'],
|
||||
'bid' => $request->headers->get('activeBid')
|
||||
]);
|
||||
|
|
@ -1358,4 +1389,69 @@ class HesabdariController extends AbstractController
|
|||
|
||||
return $this->json(['Success' => true, 'data' => $tree]);
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی وضعیت کدهای تکراری
|
||||
*/
|
||||
#[Route('/api/accounting/check-duplicate-codes', name: 'app_accounting_check_duplicate_codes', methods: ['GET'])]
|
||||
public function checkDuplicateCodes(Provider $provider, Request $request, Access $access): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('admin'); // فقط ادمین میتواند این کار را انجام دهد
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$part = $request->query->get('part', 'accounting');
|
||||
|
||||
try {
|
||||
$hasDuplicates = $provider->hasDuplicateCodes($acc['bid'], $part);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'has_duplicates' => $hasDuplicates,
|
||||
'message' => $hasDuplicates ? 'کدهای تکراری یافت شد' : 'کدهای تکراری یافت نشد'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => 'خطا در بررسی کدهای تکراری: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ترمیم کدهای تکراری حسابداری
|
||||
*/
|
||||
#[Route('/api/accounting/fix-duplicate-codes', name: 'app_accounting_fix_duplicate_codes', methods: ['POST'])]
|
||||
public function fixDuplicateCodes(Provider $provider, Request $request, Access $access): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('admin'); // فقط ادمین میتواند این کار را انجام دهد
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
|
||||
$part = $params['part'] ?? 'accounting';
|
||||
|
||||
try {
|
||||
$result = $provider->fixDuplicateCodes($acc['bid'], $part);
|
||||
|
||||
return $this->json([
|
||||
'result' => $result['success'] ? 1 : 0,
|
||||
'message' => $result['message'],
|
||||
'fixed_count' => $result['fixed_count'] ?? 0
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => 'خطا در ترمیم کدهای تکراری: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,19 +81,415 @@ class Provider
|
|||
|
||||
public function getAccountingCode($bid, $part)
|
||||
{
|
||||
$setter = 'set' . ucfirst($part) . 'Code';
|
||||
$part = 'get' . ucfirst($part) . 'Code';
|
||||
$maxRetries = 10; // حداکثر تعداد تلاش برای تولید کد منحصر به فرد
|
||||
$retryCount = 0;
|
||||
|
||||
do {
|
||||
$retryCount++;
|
||||
|
||||
// شروع تراکنش
|
||||
$this->entityManager->beginTransaction();
|
||||
|
||||
try {
|
||||
$setter = 'set' . ucfirst($part) . 'Code';
|
||||
$part = 'get' . ucfirst($part) . 'Code';
|
||||
|
||||
$business = $this->entityManager->getRepository(Business::class)->find($bid);
|
||||
if (!$business) {
|
||||
$this->entityManager->rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
$count = $business->{$part}();
|
||||
if (is_null($count))
|
||||
$count = 1000;
|
||||
|
||||
$newCode = intval($count) + 1;
|
||||
|
||||
// بررسی تکراری بودن کد در جدول مربوطه
|
||||
$isDuplicate = $this->checkCodeDuplicate($bid, $part, $newCode);
|
||||
|
||||
if (!$isDuplicate) {
|
||||
// کد منحصر به فرد است، شمارنده را افزایش بده
|
||||
$business->{$setter}($newCode);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->commit();
|
||||
return $newCode;
|
||||
} else {
|
||||
// کد تکراری است، شمارنده را افزایش بده و دوباره تلاش کن
|
||||
$business->{$setter}($newCode);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
$this->entityManager->commit();
|
||||
|
||||
// اگر تعداد تلاشها به حداکثر رسید، از کد معقول استفاده کن
|
||||
if ($retryCount >= $maxRetries) {
|
||||
$reasonableCode = $this->generateReasonableCode($bid, $part);
|
||||
return $reasonableCode;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->entityManager->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی تکراری بودن کد در جدول مربوطه
|
||||
*/
|
||||
private function checkCodeDuplicate($bid, $part, $code)
|
||||
{
|
||||
// اعتبارسنجی کد
|
||||
if (!$this->validateCode($code)) {
|
||||
return true; // کد نامعتبر را تکراری در نظر بگیر
|
||||
}
|
||||
|
||||
// تعیین جدول بر اساس نوع
|
||||
$tableMap = [
|
||||
'getAccountingCode' => 'HesabdariDoc',
|
||||
'getPersonCode' => 'Person',
|
||||
'getBankCode' => 'BankAccount',
|
||||
'getCommodityCode' => 'Commodity',
|
||||
'getCashdeskCode' => 'Cashdesk',
|
||||
'getSalaryCode' => 'Salary',
|
||||
'getStoreroomCode' => 'StoreroomTicket',
|
||||
'getPlugImportWorkflowCode' => 'PlugImportWorkflow',
|
||||
'getPlugRepserviceCode' => 'PlugRepserviceOrder',
|
||||
'getPlugNoghreCode' => 'PlugNoghreDoc'
|
||||
];
|
||||
|
||||
$entityClass = $tableMap[$part] ?? null;
|
||||
if (!$entityClass) {
|
||||
return false; // اگر جدول مشخص نشده، تکراری در نظر نگیر
|
||||
}
|
||||
|
||||
$fullEntityClass = "App\\Entity\\" . $entityClass;
|
||||
|
||||
// برای اسناد حسابداری، سال مالی را نیز در نظر بگیر
|
||||
if ($part === 'getAccountingCode') {
|
||||
// بررسی وجود کد در جدول با در نظر گرفتن سال مالی
|
||||
$existingEntity = $this->entityManager->getRepository($fullEntityClass)->findOneBy([
|
||||
'code' => $code,
|
||||
'bid' => $bid
|
||||
]);
|
||||
} else {
|
||||
// برای سایر جداول، فقط bid را بررسی کن
|
||||
$existingEntity = $this->entityManager->getRepository($fullEntityClass)->findOneBy([
|
||||
'code' => $code,
|
||||
'bid' => $bid
|
||||
]);
|
||||
}
|
||||
|
||||
return $existingEntity !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید کد منحصر به فرد با استفاده از شمارنده (فقط عدد)
|
||||
*/
|
||||
private function generateTimestampCode($bid, $part)
|
||||
{
|
||||
$setter = 'set' . ucfirst($part) . 'Code';
|
||||
$getter = 'get' . ucfirst($part) . 'Code';
|
||||
|
||||
$business = $this->entityManager->getRepository(Business::class)->find($bid);
|
||||
if (!$business)
|
||||
if (!$business) {
|
||||
return false;
|
||||
$count = $business->{$part}();
|
||||
if (is_null($count))
|
||||
$count = 1000;
|
||||
$business->{$setter}(intval($count) + 1);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
return $count;
|
||||
}
|
||||
|
||||
$currentCode = $business->{$getter}();
|
||||
if (is_null($currentCode)) {
|
||||
$currentCode = 1000;
|
||||
}
|
||||
|
||||
// شمارنده را به مقدار بزرگی افزایش بده تا از تداخل جلوگیری شود
|
||||
// اما نه خیلی بزرگ که خارج از عرف باشد
|
||||
$newCode = intval($currentCode) + 1000;
|
||||
|
||||
// بررسی تکراری نبودن کد تولید شده
|
||||
$isDuplicate = $this->checkCodeDuplicate($bid, $part, $newCode);
|
||||
|
||||
if (!$isDuplicate) {
|
||||
// شمارنده را به روز کن
|
||||
$business->{$setter}($newCode);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
return $newCode;
|
||||
} else {
|
||||
// اگر باز هم تکراری بود، از عدد تصادفی استفاده کن
|
||||
$randomCode = mt_rand(100000, 999999);
|
||||
|
||||
// بررسی تکراری نبودن کد تصادفی
|
||||
$isRandomDuplicate = $this->checkCodeDuplicate($bid, $part, $randomCode);
|
||||
|
||||
if (!$isRandomDuplicate) {
|
||||
return $randomCode;
|
||||
} else {
|
||||
// اگر باز هم تکراری بود، از timestamp کوتاه استفاده کن
|
||||
$shortTimestamp = intval(substr(time(), -6)); // فقط 6 رقم آخر
|
||||
$shortCode = 100000 + $shortTimestamp;
|
||||
|
||||
return $shortCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید کد جایگزین با استفاده از شمارنده معقول (فقط عدد)
|
||||
*/
|
||||
private function generateFallbackCode($bid, $part)
|
||||
{
|
||||
$setter = 'set' . ucfirst($part) . 'Code';
|
||||
$getter = 'get' . ucfirst($part) . 'Code';
|
||||
|
||||
$business = $this->entityManager->getRepository(Business::class)->find($bid);
|
||||
if (!$business) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentCode = $business->{$getter}();
|
||||
if (is_null($currentCode)) {
|
||||
$currentCode = 1000;
|
||||
}
|
||||
|
||||
// شمارنده را به مقدار معقولی افزایش بده
|
||||
$fallbackCode = intval($currentCode) + 500;
|
||||
|
||||
// بررسی تکراری نبودن
|
||||
$isDuplicate = $this->checkCodeDuplicate($bid, $part, $fallbackCode);
|
||||
|
||||
if (!$isDuplicate) {
|
||||
// شمارنده را به روز کن
|
||||
$business->{$setter}($fallbackCode);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
return $fallbackCode;
|
||||
} else {
|
||||
// اگر باز هم تکراری بود، از عدد تصادفی استفاده کن
|
||||
$randomCode = mt_rand(100000, 999999);
|
||||
|
||||
// بررسی تکراری نبودن کد تصادفی
|
||||
$isRandomDuplicate = $this->checkCodeDuplicate($bid, $part, $randomCode);
|
||||
|
||||
if (!$isRandomDuplicate) {
|
||||
return $randomCode;
|
||||
} else {
|
||||
// اگر باز هم تکراری بود، از timestamp کوتاه استفاده کن
|
||||
$shortTimestamp = intval(substr(time(), -6)); // فقط 6 رقم آخر
|
||||
$shortCode = 100000 + $shortTimestamp;
|
||||
|
||||
return $shortCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید کد معقول حسابداری (1 تا 6 رقم)
|
||||
*/
|
||||
private function generateReasonableCode($bid, $part)
|
||||
{
|
||||
$setter = 'set' . ucfirst($part) . 'Code';
|
||||
$getter = 'get' . ucfirst($part) . 'Code';
|
||||
|
||||
$business = $this->entityManager->getRepository(Business::class)->find($bid);
|
||||
if (!$business) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentCode = $business->{$getter}();
|
||||
if (is_null($currentCode)) {
|
||||
$currentCode = 1000;
|
||||
}
|
||||
|
||||
// شمارنده را به مقدار معقولی افزایش بده
|
||||
$newCode = intval($currentCode) + 1;
|
||||
|
||||
// بررسی تکراری نبودن کد تولید شده
|
||||
$isDuplicate = $this->checkCodeDuplicate($bid, $part, $newCode);
|
||||
|
||||
if (!$isDuplicate) {
|
||||
// شمارنده را به روز کن
|
||||
$business->{$setter}($newCode);
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
return $newCode;
|
||||
} else {
|
||||
// اگر تکراری بود، از عدد تصادفی 4 رقمی استفاده کن
|
||||
$randomCode = mt_rand(1000, 9999);
|
||||
|
||||
// بررسی تکراری نبودن کد تصادفی
|
||||
$isRandomDuplicate = $this->checkCodeDuplicate($bid, $part, $randomCode);
|
||||
|
||||
if (!$isRandomDuplicate) {
|
||||
return $randomCode;
|
||||
} else {
|
||||
// اگر باز هم تکراری بود، از عدد تصادفی 5 رقمی استفاده کن
|
||||
$randomCode5 = mt_rand(10000, 99999);
|
||||
|
||||
// بررسی تکراری نبودن کد تصادفی 5 رقمی
|
||||
$isRandom5Duplicate = $this->checkCodeDuplicate($bid, $part, $randomCode5);
|
||||
|
||||
if (!$isRandom5Duplicate) {
|
||||
return $randomCode5;
|
||||
} else {
|
||||
// اگر باز هم تکراری بود، از عدد تصادفی 6 رقمی استفاده کن
|
||||
$randomCode6 = mt_rand(100000, 999999);
|
||||
return $randomCode6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی وجود کدهای تکراری
|
||||
*/
|
||||
public function hasDuplicateCodes($bid, $part = 'accounting')
|
||||
{
|
||||
$tableMap = [
|
||||
'accounting' => 'HesabdariDoc',
|
||||
'person' => 'Person',
|
||||
'bank' => 'BankAccount',
|
||||
'commodity' => 'Commodity',
|
||||
'cashdesk' => 'Cashdesk',
|
||||
'salary' => 'Salary',
|
||||
'storeroom' => 'StoreroomTicket'
|
||||
];
|
||||
|
||||
$entityClass = $tableMap[$part] ?? null;
|
||||
if (!$entityClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fullEntityClass = "App\\Entity\\" . $entityClass;
|
||||
$repository = $this->entityManager->getRepository($fullEntityClass);
|
||||
|
||||
// پیدا کردن کدهای تکراری
|
||||
$qb = $repository->createQueryBuilder('e');
|
||||
$qb->select('e.code, COUNT(e.id) as count')
|
||||
->where('e.bid = :bid')
|
||||
->setParameter('bid', $bid)
|
||||
->groupBy('e.code')
|
||||
->having('COUNT(e.id) > 1');
|
||||
|
||||
$duplicates = $qb->getQuery()->getResult();
|
||||
|
||||
return count($duplicates) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* پیدا کردن ایمن سند حسابداری با بررسی کدهای تکراری
|
||||
*/
|
||||
public function findHesabdariDocSafely($entityManager, $criteria)
|
||||
{
|
||||
// ابتدا بررسی کن که آیا کدهای تکراری وجود دارد
|
||||
if (isset($criteria['bid']) && $this->hasDuplicateCodes($criteria['bid'], 'accounting')) {
|
||||
// کدهای تکراری وجود دارد، ترمیم کن
|
||||
$this->fixDuplicateCodes($criteria['bid'], 'accounting');
|
||||
}
|
||||
|
||||
// حالا سند را پیدا کن
|
||||
return $entityManager->getRepository(\App\Entity\HesabdariDoc::class)->findOneBy($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* پیدا کردن ایمن هر نوع entity با بررسی کدهای تکراری
|
||||
*/
|
||||
public function findEntitySafely($entityManager, $entityClass, $criteria, $part = null)
|
||||
{
|
||||
// اگر part مشخص شده و bid وجود دارد، کدهای تکراری را بررسی کن
|
||||
if ($part && isset($criteria['bid']) && $this->hasDuplicateCodes($criteria['bid'], $part)) {
|
||||
// کدهای تکراری وجود دارد، ترمیم کن
|
||||
$this->fixDuplicateCodes($criteria['bid'], $part);
|
||||
}
|
||||
|
||||
// حالا entity را پیدا کن
|
||||
return $entityManager->getRepository($entityClass)->findOneBy($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی و ترمیم کدهای تکراری موجود در دیتابیس
|
||||
*/
|
||||
public function fixDuplicateCodes($bid, $part = 'accounting')
|
||||
{
|
||||
$tableMap = [
|
||||
'accounting' => 'HesabdariDoc',
|
||||
'person' => 'Person',
|
||||
'bank' => 'BankAccount',
|
||||
'commodity' => 'Commodity',
|
||||
'cashdesk' => 'Cashdesk',
|
||||
'salary' => 'Salary',
|
||||
'storeroom' => 'StoreroomTicket'
|
||||
];
|
||||
|
||||
$entityClass = $tableMap[$part] ?? null;
|
||||
if (!$entityClass) {
|
||||
return ['success' => false, 'message' => 'نوع نامعتبر'];
|
||||
}
|
||||
|
||||
$fullEntityClass = "App\\Entity\\" . $entityClass;
|
||||
$repository = $this->entityManager->getRepository($fullEntityClass);
|
||||
|
||||
// پیدا کردن کدهای تکراری
|
||||
$qb = $repository->createQueryBuilder('e');
|
||||
$qb->select('e.code, COUNT(e.id) as count')
|
||||
->where('e.bid = :bid')
|
||||
->setParameter('bid', $bid)
|
||||
->groupBy('e.code')
|
||||
->having('COUNT(e.id) > 1');
|
||||
|
||||
$duplicates = $qb->getQuery()->getResult();
|
||||
|
||||
$fixedCount = 0;
|
||||
foreach ($duplicates as $duplicate) {
|
||||
$code = $duplicate['code'];
|
||||
|
||||
// پیدا کردن تمام رکوردهای با این کد
|
||||
$entities = $repository->findBy(['code' => $code, 'bid' => $bid]);
|
||||
|
||||
// اولین رکورد را نگه دار، بقیه را تغییر بده
|
||||
for ($i = 1; $i < count($entities); $i++) {
|
||||
$newCode = $this->generateReasonableCode($bid, $part);
|
||||
$entities[$i]->setCode($newCode);
|
||||
$this->entityManager->persist($entities[$i]);
|
||||
$fixedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fixedCount > 0) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'fixed_count' => $fixedCount,
|
||||
'message' => $fixedCount . ' کد تکراری ترمیم شد'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* اعتبارسنجی کد (فقط عدد)
|
||||
*/
|
||||
private function validateCode($code)
|
||||
{
|
||||
// بررسی اینکه کد عدد است
|
||||
if (!is_numeric($code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// بررسی اینکه کد مثبت است
|
||||
if (intval($code) <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// بررسی طول کد (حداکثر 6 رقم برای عرف حسابداری)
|
||||
if (strlen((string)$code) > 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
79
hesabixCore/tests/ProviderTest.php
Normal file
79
hesabixCore/tests/ProviderTest.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\Service\Provider;
|
||||
use App\Entity\Business;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProviderTest extends TestCase
|
||||
{
|
||||
private $entityManager;
|
||||
private $provider;
|
||||
private $business;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$this->provider = new Provider($this->entityManager);
|
||||
$this->business = $this->createMock(Business::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* تست تولید کد حسابداری
|
||||
*/
|
||||
public function testGetAccountingCodeReturnsNumericValue()
|
||||
{
|
||||
// تنظیم mock
|
||||
$this->business->method('getAccountingCode')->willReturn(1000);
|
||||
$this->business->method('setAccountingCode')->willReturnSelf();
|
||||
|
||||
$repository = $this->createMock(\Doctrine\ORM\EntityRepository::class);
|
||||
$repository->method('findOneBy')->willReturn(null); // هیچ کد تکراری وجود ندارد
|
||||
|
||||
$this->entityManager->method('getRepository')->willReturn($repository);
|
||||
$this->entityManager->method('find')->willReturn($this->business);
|
||||
$this->entityManager->method('persist')->willReturnSelf();
|
||||
$this->entityManager->method('flush')->willReturnSelf();
|
||||
$this->entityManager->method('beginTransaction')->willReturnSelf();
|
||||
$this->entityManager->method('commit')->willReturnSelf();
|
||||
$this->entityManager->method('rollback')->willReturnSelf();
|
||||
|
||||
// اجرای تست
|
||||
$code = $this->provider->getAccountingCode(1, 'accounting');
|
||||
|
||||
// بررسی نتایج
|
||||
$this->assertIsInt($code);
|
||||
$this->assertGreaterThan(0, $code);
|
||||
$this->assertTrue(is_numeric($code));
|
||||
}
|
||||
|
||||
/**
|
||||
* تست اعتبارسنجی کد
|
||||
*/
|
||||
public function testValidateCode()
|
||||
{
|
||||
// تست کدهای معتبر
|
||||
$this->assertTrue($this->invokeMethod($this->provider, 'validateCode', [123]));
|
||||
$this->assertTrue($this->invokeMethod($this->provider, 'validateCode', [1000]));
|
||||
$this->assertTrue($this->invokeMethod($this->provider, 'validateCode', [999999]));
|
||||
|
||||
// تست کدهای نامعتبر
|
||||
$this->assertFalse($this->invokeMethod($this->provider, 'validateCode', ['abc']));
|
||||
$this->assertFalse($this->invokeMethod($this->provider, 'validateCode', [0]));
|
||||
$this->assertFalse($this->invokeMethod($this->provider, 'validateCode', [-1]));
|
||||
$this->assertFalse($this->invokeMethod($this->provider, 'validateCode', ['123abc']));
|
||||
}
|
||||
|
||||
/**
|
||||
* متد کمکی برای فراخوانی متدهای private
|
||||
*/
|
||||
private function invokeMethod($object, $methodName, array $parameters = [])
|
||||
{
|
||||
$reflection = new \ReflectionClass(get_class($object));
|
||||
$method = $reflection->getMethod($methodName);
|
||||
$method->setAccessible(true);
|
||||
return $method->invokeArgs($object, $parameters);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue