Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bbaeefb6b5 | |||
| 079f4b0ffb | |||
| e061faadc2 | |||
| 30c2484440 | |||
| 334e39d2de | |||
| e372b9543b | |||
|
|
4e192d1c00 | ||
|
|
6d1925fcd1 | ||
| 22f72f1bbc | |||
|
|
6f0ad1cf2d | ||
|
|
d8f2361c88 | ||
| 74e3c21ee0 | |||
| 45a207d4a8 | |||
| 3fc5f511bc | |||
|
|
ee9f4d0b8f | ||
|
|
ca259ccb18 | ||
|
|
3929c5fd1e | ||
|
|
56c60ec3df | ||
|
|
60c4256bd0 | ||
|
|
1861f4daf2 | ||
|
|
c62e59ad17 | ||
|
|
e6f13e3870 | ||
|
|
1d5d161e05 | ||
|
|
0044779eb4 | ||
| 3ac64d5b76 | |||
| 2231c9a48f | |||
| ff8553055c | |||
| f2eede6e3a | |||
| c5d5b5d076 | |||
| 5ebf940873 | |||
| 293330c418 | |||
| a845f52fc0 | |||
| 0f26ea546a | |||
| 3600c7b12b | |||
| a6e29bc1f3 | |||
| 0aa0a8592d | |||
| 38c2402771 | |||
| 59d13eeb9f | |||
| 2df8ecf765 | |||
|
|
ad93743bdc | ||
|
|
449fce1d2b | ||
|
|
9d4143a4dc | ||
|
|
c5a0115e01 | ||
|
|
eb28b577e0 | ||
|
|
22160e5904 | ||
|
|
0ea5fa5eb9 | ||
|
|
58fedb0d43 | ||
|
|
56e7164243 | ||
|
|
a67c5d9fca | ||
|
|
816c25a631 | ||
|
|
12d172e4c3 | ||
|
|
be9c1baa34 | ||
|
|
a7973bb555 | ||
|
|
96def0d71e | ||
|
|
846ced3ead | ||
|
|
0f25a5d4e1 | ||
|
|
c57e768b87 | ||
|
|
7fe856f3b7 | ||
|
|
652ac3d1ec | ||
|
|
03f86f375e | ||
|
|
31d2f08dd8 | ||
|
|
8c9f2fcfb5 | ||
|
|
f41605ca73 | ||
|
|
66d1004bc2 | ||
|
|
ce1f91074e | ||
|
|
558a393c85 | ||
|
|
ac72487eff | ||
| 3d4dd9f252 | |||
| 2507dcf142 | |||
| 9b8cdfa2a5 | |||
| 7432fb16aa | |||
| d4f21f06d6 | |||
| 1f7f45ee60 | |||
| cd847e01a0 | |||
| ef7d9fae24 | |||
| 1a8bb46137 | |||
| 897511cb55 | |||
| 5ee97c2d74 | |||
| 8518621432 | |||
| fc537898fc | |||
|
|
fefd129795 | ||
|
|
1b2363d661 | ||
|
|
f1a3e0f164 | ||
|
|
e8da2ea1b1 | ||
|
|
e0bb19a017 | ||
|
|
74a60e06bc | ||
|
|
2f3ec13b24 | ||
|
|
94b96e22f6 | ||
|
|
a173a5a4fc | ||
|
|
66da2972fa | ||
|
|
282bf26eec | ||
|
|
b86af29cc9 | ||
|
|
f665d3aea8 | ||
|
|
e637e3d1f7 | ||
|
|
7cf70dbcaa | ||
|
|
76192fc177 | ||
|
|
da04b84bb5 | ||
|
|
1deeafc4de | ||
|
|
4f3467c3b0 | ||
|
|
e9fd75fa74 | ||
|
|
23c6cf6919 | ||
|
|
42443349dc | ||
|
|
ad27090bfc | ||
|
|
b7465becab | ||
|
|
83a774572a | ||
|
|
da1142af62 | ||
|
|
b3c7d08b2c | ||
| 7e309e4057 | |||
|
|
f79d008777 | ||
|
|
abe04607e4 | ||
|
|
3ca161e9e2 | ||
|
|
587bdf5d80 | ||
|
|
f2d36c3616 | ||
|
|
397d50de1f | ||
|
|
73949b1282 | ||
|
|
e40555ad37 | ||
|
|
79da7a6da7 | ||
|
|
b04d407fdb | ||
|
|
c32d68f069 | ||
| afc1aa72a8 | |||
| f970b6bf4b | |||
| e6c02292d2 | |||
| 9e84fa04a6 | |||
|
|
ff8b37670e | ||
|
|
75408d925f | ||
|
|
dd11f780b4 | ||
|
|
c94bef1dae | ||
| f53f04de65 | |||
|
|
96c4f71607 | ||
|
|
0d1d056b13 | ||
| ac7279606d | |||
|
|
fcb377a40c | ||
|
|
1a2350db7d | ||
|
|
0ff731ab09 | ||
| 0345ddcce3 | |||
|
|
a6e547f40d | ||
|
|
e819635ea6 |
@@ -54,4 +54,4 @@ ## 관련 파일
|
||||
|
||||
- `api/app/Services/ComprehensiveAnalysisService.php`
|
||||
- `api/database/seeders/ComprehensiveAnalysisSeeder.php`
|
||||
- `docs/plans/react-mock-remaining-tasks.md`
|
||||
- `docs/dev/dev_plans/react-mock-remaining-tasks.md`
|
||||
|
||||
@@ -15,7 +15,7 @@ ## Phase 구성
|
||||
- Phase 5: MNG 관리자 패널 (4항목) — mng/ /system/alerts
|
||||
|
||||
## 핵심 파일
|
||||
- 계획 문서: docs/plans/db-backup-system-plan.md
|
||||
- 계획 문서: docs/dev/dev_plans/db-backup-system-plan.md
|
||||
- 개발서버: 114.203.209.83 (SSH: hskwon)
|
||||
- DB: sam (메인) + sam_stat (통계)
|
||||
- Slack 웹훅: api/.env → LOG_SLACK_WEBHOOK_URL (이미 설정됨)
|
||||
|
||||
@@ -16,7 +16,7 @@ ### 생성된 파일
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `app/Http/Requests/Quote/QuoteBomBulkCalculateRequest.php` | 다건 BOM 산출 FormRequest |
|
||||
| `api/docs/changes/20260102_1300_quote_bom_bulk_calculation.md` | 변경 내용 문서 |
|
||||
| `api/docs/dev/changes/20260102_1300_quote_bom_bulk_calculation.md` | 변경 내용 문서 |
|
||||
|
||||
### 수정된 파일
|
||||
| 파일 | 설명 |
|
||||
@@ -93,9 +93,9 @@ ### QuoteCalculationService::calculateBomBulk()
|
||||
- 개별 품목 실패가 전체에 영향 없음 (예외 처리)
|
||||
|
||||
## 관련 문서
|
||||
- 계획 문서: `docs/plans/quote-calculation-api-plan.md`
|
||||
- Phase 1.1 문서: `docs/changes/20260102_quote_bom_calculation_api.md`
|
||||
- Phase 1.2 문서: `docs/changes/20260102_1300_quote_bom_bulk_calculation.md`
|
||||
- 계획 문서: `docs/dev/dev_plans/quote-calculation-api-plan.md`
|
||||
- Phase 1.1 문서: `docs/dev/changes/20260102_quote_bom_calculation_api.md`
|
||||
- Phase 1.2 문서: `docs/dev/changes/20260102_1300_quote_bom_bulk_calculation.md`
|
||||
|
||||
## 다음 단계
|
||||
- React 프론트엔드에서 `/calculate/bom/bulk` API 연동
|
||||
|
||||
@@ -107,3 +107,10 @@ fixed_tools: []
|
||||
# override of the corresponding setting in serena_config.yml, see the documentation there.
|
||||
# If null or missing, the value from the global config is used.
|
||||
symbol_info_budget:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
@@ -509,6 +509,7 @@ ### 2. Multi-tenancy & Models
|
||||
- SoftDeletes by default
|
||||
- Common columns: tenant_id, created_by, updated_by, deleted_by (COMMENT required)
|
||||
- FK constraints: Created during design, minimal in production
|
||||
- **🔴 쿼리 수정 시 모델 스코프 우선**: `where('컬럼', '값')` 하드코딩 전에 반드시 모델에 정의된 스코프(scopeActive 등)를 먼저 확인하고, 스코프가 있으면 `Model::active()` 형태로 사용할 것
|
||||
|
||||
### 3. Middleware Stack
|
||||
- ApiKeyMiddleware, CheckSwaggerAuth, CorsMiddleware, CheckPermission, PermMapper
|
||||
|
||||
36
Jenkinsfile
vendored
36
Jenkinsfile
vendored
@@ -17,7 +17,7 @@ pipeline {
|
||||
script {
|
||||
env.GIT_COMMIT_MSG = sh(script: "git log -1 --pretty=format:'%s'", returnStdout: true).trim()
|
||||
}
|
||||
slackSend channel: '#product_infra', color: '#439FE0', tokenCredentialId: 'slack-token',
|
||||
slackSend channel: '#deploy_api', color: '#439FE0', tokenCredentialId: 'slack-token',
|
||||
message: "🚀 *api* 빌드 시작 (`${env.BRANCH_NAME}`)\n${env.GIT_COMMIT_MSG}\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>"
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,10 @@ pipeline {
|
||||
ssh ${DEPLOY_USER}@211.117.60.189 '
|
||||
cd /home/webservice/api-stage/releases/${RELEASE_ID} &&
|
||||
mkdir -p bootstrap/cache storage/framework/{views,cache/data,sessions} storage/logs &&
|
||||
sudo chown -R www-data:webservice storage bootstrap/cache &&
|
||||
sudo chmod -R 775 storage bootstrap/cache &&
|
||||
ln -sfn /home/webservice/api-stage/shared/.env .env &&
|
||||
sudo chmod 640 /home/webservice/api-stage/shared/.env &&
|
||||
ln -sfn /home/webservice/api-stage/shared/storage/app storage/app &&
|
||||
composer install --no-dev --optimize-autoloader --no-interaction &&
|
||||
php artisan config:cache &&
|
||||
@@ -53,18 +56,18 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 운영 배포 승인 ──
|
||||
stage('Production Approval') {
|
||||
when { branch 'main' }
|
||||
steps {
|
||||
slackSend channel: '#product_deploy', color: '#FF9800', tokenCredentialId: 'slack-token',
|
||||
message: "🔔 *api* 운영 배포 승인 대기 중\n${env.GIT_COMMIT_MSG}\nStage API: https://stage-api.sam.it.kr\n<${env.BUILD_URL}input|승인하러 가기>"
|
||||
timeout(time: 24, unit: 'HOURS') {
|
||||
input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage API: https://stage-api.sam.it.kr',
|
||||
ok: '운영 배포 진행'
|
||||
}
|
||||
}
|
||||
}
|
||||
// ── 운영 배포 승인 (런칭 후 활성화) ──
|
||||
// stage('Production Approval') {
|
||||
// when { branch 'main' }
|
||||
// steps {
|
||||
// slackSend channel: '#product_deploy', color: '#FF9800', tokenCredentialId: 'slack-token',
|
||||
// message: "🔔 *api* 운영 배포 승인 대기 중\n${env.GIT_COMMIT_MSG}\nStage API: https://stage-api.sam.it.kr\n<${env.BUILD_URL}input|승인하러 가기>"
|
||||
// timeout(time: 24, unit: 'HOURS') {
|
||||
// input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage API: https://stage-api.sam.it.kr',
|
||||
// ok: '운영 배포 진행'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// ── main → 운영서버 Production 배포 ──
|
||||
stage('Deploy Production') {
|
||||
@@ -81,7 +84,10 @@ pipeline {
|
||||
ssh ${DEPLOY_USER}@211.117.60.189 '
|
||||
cd /home/webservice/api/releases/${RELEASE_ID} &&
|
||||
mkdir -p bootstrap/cache storage/framework/{views,cache/data,sessions} storage/logs &&
|
||||
sudo chown -R www-data:webservice storage bootstrap/cache &&
|
||||
sudo chmod -R 775 storage bootstrap/cache &&
|
||||
ln -sfn /home/webservice/api/shared/.env .env &&
|
||||
sudo chmod 640 /home/webservice/api/shared/.env &&
|
||||
ln -sfn /home/webservice/api/shared/storage/app storage/app &&
|
||||
composer install --no-dev --optimize-autoloader --no-interaction &&
|
||||
php artisan config:cache &&
|
||||
@@ -103,11 +109,11 @@ pipeline {
|
||||
|
||||
post {
|
||||
success {
|
||||
slackSend channel: '#product_infra', color: 'good', tokenCredentialId: 'slack-token',
|
||||
slackSend channel: '#deploy_api', color: 'good', tokenCredentialId: 'slack-token',
|
||||
message: "✅ *api* 배포 성공 (`${env.BRANCH_NAME}`)\n${env.GIT_COMMIT_MSG}\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>"
|
||||
}
|
||||
failure {
|
||||
slackSend channel: '#product_infra', color: 'danger', tokenCredentialId: 'slack-token',
|
||||
slackSend channel: '#deploy_api', color: 'danger', tokenCredentialId: 'slack-token',
|
||||
message: "❌ *api* 배포 실패 (`${env.BRANCH_NAME}`)\n${env.GIT_COMMIT_MSG}\n<${env.BUILD_URL}|빌드 #${env.BUILD_NUMBER}>"
|
||||
script {
|
||||
if (env.BRANCH_NAME == 'main') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 논리적 데이터베이스 관계 문서
|
||||
|
||||
> **자동 생성**: 2026-02-21 16:28:35
|
||||
> **자동 생성**: 2026-03-06 21:25:05
|
||||
> **소스**: Eloquent 모델 관계 분석
|
||||
|
||||
## 📊 모델별 관계 현황
|
||||
@@ -580,17 +580,10 @@ ### roles
|
||||
**모델**: `App\Models\Permissions\Role`
|
||||
|
||||
- **tenant()**: belongsTo → `tenants`
|
||||
- **menuPermissions()**: hasMany → `role_menu_permissions`
|
||||
- **userRoles()**: hasMany → `user_roles`
|
||||
- **users()**: belongsToMany → `users`
|
||||
- **permissions()**: belongsToMany → `permissions`
|
||||
|
||||
### role_menu_permissions
|
||||
**모델**: `App\Models\Permissions\RoleMenuPermission`
|
||||
|
||||
- **role()**: belongsTo → `roles`
|
||||
- **menu()**: belongsTo → `menus`
|
||||
|
||||
### popups
|
||||
**모델**: `App\Models\Popups\Popup`
|
||||
|
||||
@@ -637,6 +630,7 @@ ### work_orders
|
||||
- **stepProgress()**: hasMany → `work_order_step_progress`
|
||||
- **materialInputs()**: hasMany → `work_order_material_inputs`
|
||||
- **shipments()**: hasMany → `shipments`
|
||||
- **inspections()**: hasMany → `inspections`
|
||||
- **bendingDetail()**: hasOne → `work_order_bending_details`
|
||||
- **documents()**: morphMany → `documents`
|
||||
|
||||
@@ -743,6 +737,7 @@ ### push_notification_settings
|
||||
### inspections
|
||||
**모델**: `App\Models\Qualitys\Inspection`
|
||||
|
||||
- **workOrder()**: belongsTo → `work_orders`
|
||||
- **item()**: belongsTo → `items`
|
||||
- **inspector()**: belongsTo → `users`
|
||||
- **creator()**: belongsTo → `users`
|
||||
@@ -758,6 +753,38 @@ ### lot_sales
|
||||
|
||||
- **lot()**: belongsTo → `lots`
|
||||
|
||||
### performance_reports
|
||||
**모델**: `App\Models\Qualitys\PerformanceReport`
|
||||
|
||||
- **qualityDocument()**: belongsTo → `quality_documents`
|
||||
- **confirmer()**: belongsTo → `users`
|
||||
- **creator()**: belongsTo → `users`
|
||||
|
||||
### quality_documents
|
||||
**모델**: `App\Models\Qualitys\QualityDocument`
|
||||
|
||||
- **client()**: belongsTo → `clients`
|
||||
- **inspector()**: belongsTo → `users`
|
||||
- **creator()**: belongsTo → `users`
|
||||
- **documentOrders()**: hasMany → `quality_document_orders`
|
||||
- **locations()**: hasMany → `quality_document_locations`
|
||||
- **performanceReport()**: hasOne → `performance_reports`
|
||||
|
||||
### quality_document_locations
|
||||
**모델**: `App\Models\Qualitys\QualityDocumentLocation`
|
||||
|
||||
- **qualityDocument()**: belongsTo → `quality_documents`
|
||||
- **qualityDocumentOrder()**: belongsTo → `quality_document_orders`
|
||||
- **orderItem()**: belongsTo → `order_items`
|
||||
- **document()**: belongsTo → `documents`
|
||||
|
||||
### quality_document_orders
|
||||
**모델**: `App\Models\Qualitys\QualityDocumentOrder`
|
||||
|
||||
- **qualityDocument()**: belongsTo → `quality_documents`
|
||||
- **order()**: belongsTo → `orders`
|
||||
- **locations()**: hasMany → `quality_document_locations`
|
||||
|
||||
### quotes
|
||||
**모델**: `App\Models\Quote\Quote`
|
||||
|
||||
@@ -836,6 +863,7 @@ ### approvals
|
||||
- **steps()**: hasMany → `approval_steps`
|
||||
- **approverSteps()**: hasMany → `approval_steps`
|
||||
- **referenceSteps()**: hasMany → `approval_steps`
|
||||
- **linkable()**: morphTo → `(Polymorphic)`
|
||||
|
||||
### approval_forms
|
||||
**모델**: `App\Models\Tenants\ApprovalForm`
|
||||
@@ -933,6 +961,16 @@ ### expense_accounts
|
||||
|
||||
- **vendor()**: belongsTo → `clients`
|
||||
|
||||
### journal_entrys
|
||||
**모델**: `App\Models\Tenants\JournalEntry`
|
||||
|
||||
- **lines()**: hasMany → `journal_entry_lines`
|
||||
|
||||
### journal_entry_lines
|
||||
**모델**: `App\Models\Tenants\JournalEntryLine`
|
||||
|
||||
- **journalEntry()**: belongsTo → `journal_entries`
|
||||
|
||||
### leaves
|
||||
**모델**: `App\Models\Tenants\Leave`
|
||||
|
||||
@@ -961,7 +999,10 @@ ### leave_policys
|
||||
### loans
|
||||
**모델**: `App\Models\Tenants\Loan`
|
||||
|
||||
- **user()**: belongsTo → `users`
|
||||
- **withdrawal()**: belongsTo → `withdrawals`
|
||||
- **creator()**: belongsTo → `users`
|
||||
- **updater()**: belongsTo → `users`
|
||||
|
||||
### payments
|
||||
**모델**: `App\Models\Tenants\Payment`
|
||||
@@ -1043,6 +1084,7 @@ ### shipments
|
||||
- **creator()**: belongsTo → `users`
|
||||
- **updater()**: belongsTo → `users`
|
||||
- **items()**: hasMany → `shipment_items`
|
||||
- **vehicleDispatches()**: hasMany → `shipment_vehicle_dispatches`
|
||||
|
||||
### shipment_items
|
||||
**모델**: `App\Models\Tenants\ShipmentItem`
|
||||
@@ -1050,6 +1092,11 @@ ### shipment_items
|
||||
- **shipment()**: belongsTo → `shipments`
|
||||
- **stockLot()**: belongsTo → `stock_lots`
|
||||
|
||||
### shipment_vehicle_dispatchs
|
||||
**모델**: `App\Models\Tenants\ShipmentVehicleDispatch`
|
||||
|
||||
- **shipment()**: belongsTo → `shipments`
|
||||
|
||||
### sites
|
||||
**모델**: `App\Models\Tenants\Site`
|
||||
|
||||
@@ -1147,6 +1194,8 @@ ### tenant_user_profiles
|
||||
### today_issues
|
||||
**모델**: `App\Models\Tenants\TodayIssue`
|
||||
|
||||
- **reader()**: belongsTo → `users`
|
||||
- **targetUser()**: belongsTo → `users`
|
||||
|
||||
### withdrawals
|
||||
**모델**: `App\Models\Tenants\Withdrawal`
|
||||
|
||||
54
app/Console/Commands/BackfillQuoteProductCodeCommand.php
Normal file
54
app/Console/Commands/BackfillQuoteProductCodeCommand.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Quote\Quote;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class BackfillQuoteProductCodeCommand extends Command
|
||||
{
|
||||
protected $signature = 'data:backfill-quote-product-code {--dry-run : 실제 저장하지 않고 결과만 출력}';
|
||||
|
||||
protected $description = 'quotes.product_code가 비어있는 레코드에 calculation_inputs.items[0].productCode 값 보정';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = $this->option('dry-run');
|
||||
|
||||
$quotes = Quote::whereNull('product_code')
|
||||
->whereNotNull('calculation_inputs')
|
||||
->get();
|
||||
|
||||
$this->info("대상: {$quotes->count()}건".($dryRun ? ' (dry-run)' : ''));
|
||||
|
||||
$updated = 0;
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($quotes as $quote) {
|
||||
$inputs = $quote->calculation_inputs;
|
||||
if (! is_array($inputs)) {
|
||||
$inputs = json_decode($inputs, true);
|
||||
}
|
||||
|
||||
$productCode = $inputs['items'][0]['productCode'] ?? null;
|
||||
|
||||
if (! $productCode) {
|
||||
$skipped++;
|
||||
$this->line(" SKIP #{$quote->id} ({$quote->quote_number}) — productCode 없음");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $dryRun) {
|
||||
$quote->update(['product_code' => $productCode]);
|
||||
}
|
||||
|
||||
$updated++;
|
||||
$this->line(' '.($dryRun ? 'WOULD ' : '')."UPDATE #{$quote->id} ({$quote->quote_number}) → {$productCode}");
|
||||
}
|
||||
|
||||
$this->info("완료: 보정 {$updated}건, 스킵 {$skipped}건");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class RecordStorageUsage extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$tenants = Tenant::where('status', 'active')->get();
|
||||
$tenants = Tenant::active()->get();
|
||||
|
||||
$recorded = 0;
|
||||
foreach ($tenants as $tenant) {
|
||||
|
||||
@@ -17,25 +17,13 @@
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* 특정 IP에서 발생하는 예외를 슬랙/로그에서 무시할지 확인
|
||||
* 슬랙 알림에서 무시할 예외인지 확인
|
||||
*/
|
||||
protected function shouldIgnoreException(Throwable $e): bool
|
||||
{
|
||||
$ignoredIps = array_filter(
|
||||
array_map('trim', explode(',', env('EXCEPTION_IGNORED_IPS', '')))
|
||||
);
|
||||
|
||||
if (empty($ignoredIps)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentIp = request()?->ip();
|
||||
|
||||
// 무시할 IP 목록에 있고, '회원정보 정보 없음' 예외인 경우
|
||||
if (in_array($currentIp, $ignoredIps, true)) {
|
||||
if ($e instanceof AuthenticationException && $e->getMessage() === '회원정보 정보 없음') {
|
||||
return true;
|
||||
}
|
||||
// 세션 만료로 인한 인증 실패는 슬랙 알림 제외 (API Key 검증 통과 후 발생하므로 정상 케이스)
|
||||
if ($e instanceof AuthenticationException && $e->getMessage() === '회원정보 정보 없음') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class DailyReportExport implements FromArray, ShouldAutoSize, WithHeadings, WithStyles, WithTitle
|
||||
@@ -31,10 +32,10 @@ public function headings(): array
|
||||
return [
|
||||
['일일 일보 - '.$this->report['date']],
|
||||
[],
|
||||
['전일 잔액', number_format($this->report['previous_balance']).'원'],
|
||||
['당일 입금액', number_format($this->report['daily_deposit']).'원'],
|
||||
['당일 출금액', number_format($this->report['daily_withdrawal']).'원'],
|
||||
['당일 잔액', number_format($this->report['current_balance']).'원'],
|
||||
['전월 이월', number_format($this->report['previous_balance']).'원'],
|
||||
['당월 입금', number_format($this->report['daily_deposit']).'원'],
|
||||
['당월 출금', number_format($this->report['daily_withdrawal']).'원'],
|
||||
['잔액', number_format($this->report['current_balance']).'원'],
|
||||
[],
|
||||
['구분', '거래처명', '계정과목', '입금액', '출금액', '적요'],
|
||||
];
|
||||
@@ -47,6 +48,7 @@ public function array(): array
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
// ── 예금 입출금 내역 ──
|
||||
foreach ($this->report['details'] as $detail) {
|
||||
$rows[] = [
|
||||
$detail['type_label'],
|
||||
@@ -58,7 +60,7 @@ public function array(): array
|
||||
];
|
||||
}
|
||||
|
||||
// 합계 행 추가
|
||||
// 합계 행
|
||||
$rows[] = [];
|
||||
$rows[] = [
|
||||
'합계',
|
||||
@@ -69,6 +71,37 @@ public function array(): array
|
||||
'',
|
||||
];
|
||||
|
||||
// ── 어음 및 외상매출채권 현황 ──
|
||||
$noteReceivables = $this->report['note_receivables'] ?? [];
|
||||
|
||||
$rows[] = [];
|
||||
$rows[] = [];
|
||||
$rows[] = ['어음 및 외상매출채권 현황'];
|
||||
$rows[] = ['No.', '내용', '금액', '발행일', '만기일'];
|
||||
|
||||
$noteTotal = 0;
|
||||
$no = 1;
|
||||
foreach ($noteReceivables as $item) {
|
||||
$amount = $item['current_balance'] ?? 0;
|
||||
$noteTotal += $amount;
|
||||
$rows[] = [
|
||||
$no++,
|
||||
$item['content'] ?? '-',
|
||||
$amount > 0 ? number_format($amount) : '',
|
||||
$item['issue_date'] ?? '-',
|
||||
$item['due_date'] ?? '-',
|
||||
];
|
||||
}
|
||||
|
||||
// 어음 합계
|
||||
$rows[] = [
|
||||
'합계',
|
||||
'',
|
||||
number_format($noteTotal),
|
||||
'',
|
||||
'',
|
||||
];
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
@@ -77,7 +110,7 @@ public function array(): array
|
||||
*/
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [
|
||||
$styles = [
|
||||
1 => ['font' => ['bold' => true, 'size' => 14]],
|
||||
3 => ['font' => ['bold' => true]],
|
||||
4 => ['font' => ['bold' => true]],
|
||||
@@ -86,10 +119,32 @@ public function styles(Worksheet $sheet): array
|
||||
8 => [
|
||||
'font' => ['bold' => true],
|
||||
'fill' => [
|
||||
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
|
||||
'fillType' => Fill::FILL_SOLID,
|
||||
'startColor' => ['rgb' => 'E0E0E0'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// 어음 섹션 헤더 스타일 (동적 행 번호)
|
||||
// headings 8행 + details 수 + 합계 2행 + 빈 2행 + 어음 제목 1행 + 어음 헤더 1행
|
||||
$detailCount = count($this->report['details']);
|
||||
$noteHeaderTitleRow = 8 + $detailCount + 2 + 2 + 1; // 어음 제목 행
|
||||
$noteHeaderRow = $noteHeaderTitleRow + 1; // 어음 컬럼 헤더 행
|
||||
|
||||
$styles[$noteHeaderTitleRow] = ['font' => ['bold' => true, 'size' => 12]];
|
||||
$styles[$noteHeaderRow] = [
|
||||
'font' => ['bold' => true],
|
||||
'fill' => [
|
||||
'fillType' => Fill::FILL_SOLID,
|
||||
'startColor' => ['rgb' => 'E0E0E0'],
|
||||
],
|
||||
];
|
||||
|
||||
// 어음 합계 행
|
||||
$noteCount = count($this->report['note_receivables'] ?? []);
|
||||
$noteTotalRow = $noteHeaderRow + $noteCount + 1;
|
||||
$styles[$noteTotalRow] = ['font' => ['bold' => true]];
|
||||
|
||||
return $styles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* - 두께 매핑 (normalizeThickness)
|
||||
* - 면적 계산 (calculateArea)
|
||||
*
|
||||
* @see docs/plans/5130-sam-data-migration-plan.md 섹션 4.5
|
||||
* @see docs/dev_plans/5130-sam-data-migration-plan.md 섹션 4.5
|
||||
*/
|
||||
class Legacy5130Calculator
|
||||
{
|
||||
|
||||
87
app/Http/Controllers/Api/V1/AccountSubjectController.php
Normal file
87
app/Http/Controllers/Api/V1/AccountSubjectController.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\V1\AccountSubject\StoreAccountSubjectRequest;
|
||||
use App\Http\Requests\V1\AccountSubject\UpdateAccountSubjectRequest;
|
||||
use App\Services\AccountCodeService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AccountSubjectController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AccountCodeService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 계정과목 목록 조회
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'search', 'category', 'sub_category',
|
||||
'department_type', 'depth', 'is_active', 'selectable',
|
||||
]);
|
||||
|
||||
$subjects = $this->service->index($params);
|
||||
|
||||
return ApiResponse::success($subjects, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정과목 등록
|
||||
*/
|
||||
public function store(StoreAccountSubjectRequest $request)
|
||||
{
|
||||
$subject = $this->service->store($request->validated());
|
||||
|
||||
return ApiResponse::success($subject, __('message.created'), [], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정과목 수정
|
||||
*/
|
||||
public function update(int $id, UpdateAccountSubjectRequest $request)
|
||||
{
|
||||
$subject = $this->service->update($id, $request->validated());
|
||||
|
||||
return ApiResponse::success($subject, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정과목 활성/비활성 토글
|
||||
*/
|
||||
public function toggleStatus(int $id, Request $request)
|
||||
{
|
||||
$isActive = (bool) $request->input('is_active', true);
|
||||
|
||||
$subject = $this->service->toggleStatus($id, $isActive);
|
||||
|
||||
return ApiResponse::success($subject, __('message.toggled'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 계정과목 삭제
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->service->destroy($id);
|
||||
|
||||
return ApiResponse::success(null, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 계정과목표 일괄 생성 (더존 표준)
|
||||
*/
|
||||
public function seedDefaults()
|
||||
{
|
||||
$count = $this->service->seedDefaults();
|
||||
|
||||
return ApiResponse::success(
|
||||
['inserted_count' => $count],
|
||||
__('message.created')
|
||||
);
|
||||
}
|
||||
}
|
||||
112
app/Http/Controllers/Api/V1/AuditChecklistController.php
Normal file
112
app/Http/Controllers/Api/V1/AuditChecklistController.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Qms\AuditChecklistStoreRequest;
|
||||
use App\Http\Requests\Qms\AuditChecklistUpdateRequest;
|
||||
use App\Services\AuditChecklistService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AuditChecklistController extends Controller
|
||||
{
|
||||
public function __construct(private AuditChecklistService $service) {}
|
||||
|
||||
/**
|
||||
* 점검표 목록
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->index($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 점검표 생성 (카테고리+항목 일괄)
|
||||
*/
|
||||
public function store(AuditChecklistStoreRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->store($request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 점검표 상세
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->show($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 점검표 수정
|
||||
*/
|
||||
public function update(AuditChecklistUpdateRequest $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->service->update($id, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 점검표 완료 처리
|
||||
*/
|
||||
public function complete(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->complete($id);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 항목 완료/미완료 토글
|
||||
*/
|
||||
public function toggleItem(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->toggleItem($id);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 항목별 기준 문서 조회
|
||||
*/
|
||||
public function itemDocuments(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->itemDocuments($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 기준 문서 연결
|
||||
*/
|
||||
public function attachDocument(Request $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->service->attachDocument($id, $request->validate([
|
||||
'title' => 'required|string|max:200',
|
||||
'version' => 'nullable|string|max:20',
|
||||
'date' => 'nullable|date',
|
||||
'document_id' => 'nullable|integer|exists:documents,id',
|
||||
]));
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 기준 문서 연결 해제
|
||||
*/
|
||||
public function detachDocument(int $id, int $docId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $docId) {
|
||||
$this->service->detachDocument($id, $docId);
|
||||
|
||||
return null;
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
144
app/Http/Controllers/Api/V1/BarobillController.php
Normal file
144
app/Http/Controllers/Api/V1/BarobillController.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\BarobillService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BarobillController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private BarobillService $barobillService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 연동 현황 조회
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$setting = $this->barobillService->getSetting();
|
||||
|
||||
return [
|
||||
'bank_service_count' => 0,
|
||||
'account_link_count' => 0,
|
||||
'member' => $setting ? [
|
||||
'barobill_id' => $setting->barobill_id,
|
||||
'biz_no' => $setting->corp_num,
|
||||
'status' => $setting->isVerified() ? 'active' : 'inactive',
|
||||
'server_mode' => config('services.barobill.test_mode', true) ? 'test' : 'production',
|
||||
] : null,
|
||||
];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 바로빌 로그인 정보 등록
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'barobill_id' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
return $this->barobillService->saveSetting([
|
||||
'barobill_id' => $data['barobill_id'],
|
||||
]);
|
||||
}, __('message.saved'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 바로빌 회원가입 정보 등록
|
||||
*/
|
||||
public function signup(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'business_number' => 'required|string|size:10',
|
||||
'company_name' => 'required|string',
|
||||
'ceo_name' => 'required|string',
|
||||
'business_type' => 'nullable|string',
|
||||
'business_category' => 'nullable|string',
|
||||
'address' => 'nullable|string',
|
||||
'barobill_id' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'manager_name' => 'nullable|string',
|
||||
'manager_phone' => 'nullable|string',
|
||||
'manager_email' => 'nullable|email',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($data) {
|
||||
return $this->barobillService->saveSetting([
|
||||
'corp_num' => $data['business_number'],
|
||||
'corp_name' => $data['company_name'],
|
||||
'ceo_name' => $data['ceo_name'],
|
||||
'biz_type' => $data['business_type'] ?? null,
|
||||
'biz_class' => $data['business_category'] ?? null,
|
||||
'addr' => $data['address'] ?? null,
|
||||
'barobill_id' => $data['barobill_id'],
|
||||
'contact_name' => $data['manager_name'] ?? null,
|
||||
'contact_tel' => $data['manager_phone'] ?? null,
|
||||
'contact_id' => $data['manager_email'] ?? null,
|
||||
]);
|
||||
}, __('message.saved'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 은행 빠른조회 서비스 URL 조회
|
||||
*/
|
||||
public function bankServiceUrl(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Bank/BankAccountService'];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 계좌 연동 등록 URL 조회
|
||||
*/
|
||||
public function accountLinkUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Bank/AccountLink'];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 카드 연동 등록 URL 조회
|
||||
*/
|
||||
public function cardLinkUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Card/CardLink'];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 공인인증서 등록 URL 조회
|
||||
*/
|
||||
public function certificateUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Certificate/Register'];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,9 @@ public function __construct(
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$setting = $this->barobillService->getSetting();
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $setting,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
return ApiResponse::handle(function () {
|
||||
return $this->barobillService->getSetting();
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,12 +28,9 @@ public function show()
|
||||
*/
|
||||
public function save(SaveBarobillSettingRequest $request)
|
||||
{
|
||||
$setting = $this->barobillService->saveSetting($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $setting,
|
||||
message: __('message.saved')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->barobillService->saveSetting($request->validated());
|
||||
}, __('message.saved'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,11 +38,8 @@ public function save(SaveBarobillSettingRequest $request)
|
||||
*/
|
||||
public function testConnection()
|
||||
{
|
||||
$result = $this->barobillService->testConnection();
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $result,
|
||||
message: __('message.barobill.connection_success')
|
||||
);
|
||||
return ApiResponse::handle(function () {
|
||||
return $this->barobillService->testConnection();
|
||||
}, __('message.barobill.connection_success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,56 @@ public function summary(Request $request)
|
||||
);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 일정 등록
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:200',
|
||||
'description' => 'nullable|string|max:1000',
|
||||
'start_date' => 'required|date_format:Y-m-d',
|
||||
'end_date' => 'required|date_format:Y-m-d|after_or_equal:start_date',
|
||||
'start_time' => 'nullable|date_format:H:i',
|
||||
'end_time' => 'nullable|date_format:H:i',
|
||||
'is_all_day' => 'boolean',
|
||||
'color' => 'nullable|string|max:20',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($validated) {
|
||||
return $this->calendarService->createSchedule($validated);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 일정 수정
|
||||
*/
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:200',
|
||||
'description' => 'nullable|string|max:1000',
|
||||
'start_date' => 'required|date_format:Y-m-d',
|
||||
'end_date' => 'required|date_format:Y-m-d|after_or_equal:start_date',
|
||||
'start_time' => 'nullable|date_format:H:i',
|
||||
'end_time' => 'nullable|date_format:H:i',
|
||||
'is_all_day' => 'boolean',
|
||||
'color' => 'nullable|string|max:20',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($id, $validated) {
|
||||
return $this->calendarService->updateSchedule($id, $validated);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 일정 삭제
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->calendarService->deleteSchedule($id);
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
133
app/Http/Controllers/Api/V1/CalendarScheduleController.php
Normal file
133
app/Http/Controllers/Api/V1/CalendarScheduleController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\CalendarScheduleService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CalendarScheduleController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CalendarScheduleService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 일정 목록 조회
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'year' => 'required|integer|min:2000|max:2100',
|
||||
'type' => 'nullable|string',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->list(
|
||||
(int) $request->input('year'),
|
||||
$request->input('type')
|
||||
),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 통계 조회
|
||||
*/
|
||||
public function stats(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'year' => 'required|integer|min:2000|max:2100',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->stats((int) $request->input('year')),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 단건 조회
|
||||
*/
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->show($id),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'type' => 'required|string|in:public_holiday,temporary_holiday,substitute_holiday,tax_deadline,company_event',
|
||||
'is_recurring' => 'boolean',
|
||||
'memo' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->store($validated),
|
||||
__('message.created')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 수정
|
||||
*/
|
||||
public function update(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'type' => 'required|string|in:public_holiday,temporary_holiday,substitute_holiday,tax_deadline,company_event',
|
||||
'is_recurring' => 'boolean',
|
||||
'memo' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->update($id, $validated),
|
||||
__('message.updated')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 삭제
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->delete($id),
|
||||
__('message.deleted')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 대량 등록
|
||||
*/
|
||||
public function bulkStore(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'schedules' => 'required|array|min:1',
|
||||
'schedules.*.name' => 'required|string|max:100',
|
||||
'schedules.*.start_date' => 'required|date',
|
||||
'schedules.*.end_date' => 'required|date|after_or_equal:schedules.*.start_date',
|
||||
'schedules.*.type' => 'required|string|in:public_holiday,temporary_holiday,substitute_holiday,tax_deadline,company_event',
|
||||
'schedules.*.is_recurring' => 'boolean',
|
||||
'schedules.*.memo' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->bulkStore($validated['schedules']),
|
||||
__('message.created')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tenants\JournalEntry;
|
||||
use App\Services\CardTransactionService;
|
||||
use App\Services\JournalSyncService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -14,7 +16,8 @@
|
||||
class CardTransactionController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected CardTransactionService $service
|
||||
protected CardTransactionService $service,
|
||||
protected JournalSyncService $journalSyncService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -148,4 +151,105 @@ public function destroy(int $id): JsonResponse
|
||||
return $this->service->destroy($id);
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 분개 (Journal Entries)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 카드 거래 분개 조회
|
||||
*/
|
||||
public function getJournalEntries(int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$sourceKey = "card_{$id}";
|
||||
$data = $this->journalSyncService->getForSource(
|
||||
JournalEntry::SOURCE_CARD_TRANSACTION,
|
||||
$sourceKey
|
||||
);
|
||||
|
||||
if (! $data) {
|
||||
return ['items' => []];
|
||||
}
|
||||
|
||||
// 프론트엔드가 기대하는 items 형식으로 변환
|
||||
$items = array_map(fn ($row) => [
|
||||
'id' => $row['id'],
|
||||
'supply_amount' => $row['debit_amount'],
|
||||
'tax_amount' => 0,
|
||||
'account_code' => $row['account_code'],
|
||||
'deduction_type' => 'deductible',
|
||||
'vendor_name' => $row['vendor_name'],
|
||||
'description' => $row['memo'],
|
||||
'memo' => '',
|
||||
], $data['rows']);
|
||||
|
||||
return ['items' => $items];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 카드 거래 분개 저장
|
||||
*/
|
||||
public function storeJournalEntries(Request $request, int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
$validated = $request->validate([
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.supply_amount' => 'required|integer|min:0',
|
||||
'items.*.tax_amount' => 'required|integer|min:0',
|
||||
'items.*.account_code' => 'required|string|max:20',
|
||||
'items.*.deduction_type' => 'nullable|string|max:20',
|
||||
'items.*.vendor_name' => 'nullable|string|max:200',
|
||||
'items.*.description' => 'nullable|string|max:500',
|
||||
'items.*.memo' => 'nullable|string|max:500',
|
||||
]);
|
||||
|
||||
// 카드 거래 정보 조회 (날짜용)
|
||||
$transaction = $this->service->show($id);
|
||||
if (! $transaction) {
|
||||
throw new \Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
}
|
||||
|
||||
$entryDate = $transaction->used_at
|
||||
? $transaction->used_at->format('Y-m-d')
|
||||
: ($transaction->withdrawal_date?->format('Y-m-d') ?? now()->format('Y-m-d'));
|
||||
|
||||
// items → journal rows 변환 (각 item을 차변 행으로)
|
||||
$rows = [];
|
||||
foreach ($validated['items'] as $item) {
|
||||
$amount = ($item['supply_amount'] ?? 0) + ($item['tax_amount'] ?? 0);
|
||||
$rows[] = [
|
||||
'side' => 'debit',
|
||||
'account_code' => $item['account_code'],
|
||||
'debit_amount' => $amount,
|
||||
'credit_amount' => 0,
|
||||
'vendor_name' => $item['vendor_name'] ?? '',
|
||||
'memo' => $item['description'] ?? $item['memo'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
// 대변 합계 행 (카드미지급금)
|
||||
$totalAmount = array_sum(array_column($rows, 'debit_amount'));
|
||||
$rows[] = [
|
||||
'side' => 'credit',
|
||||
'account_code' => '25300', // 미지급금 (표준 코드)
|
||||
'account_name' => '미지급금',
|
||||
'debit_amount' => 0,
|
||||
'credit_amount' => $totalAmount,
|
||||
'vendor_name' => $transaction->merchant_name ?? '',
|
||||
'memo' => '카드결제',
|
||||
];
|
||||
|
||||
$sourceKey = "card_{$id}";
|
||||
|
||||
return $this->journalSyncService->saveForSource(
|
||||
JournalEntry::SOURCE_CARD_TRANSACTION,
|
||||
$sourceKey,
|
||||
$entryDate,
|
||||
"카드거래 분개 (#{$id})",
|
||||
$rows,
|
||||
);
|
||||
}, __('message.created'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Exports\DailyReportExport;
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DailyReportService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
/**
|
||||
* 일일 보고서 컨트롤러
|
||||
@@ -58,4 +61,19 @@ public function summary(Request $request): JsonResponse
|
||||
return $this->service->summary($params);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 일일 보고서 엑셀 다운로드
|
||||
*/
|
||||
public function export(Request $request): BinaryFileResponse
|
||||
{
|
||||
$params = $request->validate([
|
||||
'date' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$reportData = $this->service->exportData($params);
|
||||
$filename = '일일일보_'.$reportData['date'].'.xlsx';
|
||||
|
||||
return Excel::download(new DailyReportExport($reportData), $filename);
|
||||
}
|
||||
}
|
||||
|
||||
92
app/Http/Controllers/Api/V1/DashboardCeoController.php
Normal file
92
app/Http/Controllers/Api/V1/DashboardCeoController.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\DashboardCeoService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* CEO 대시보드 섹션별 API 컨트롤러
|
||||
*
|
||||
* 6개 섹션: 매출, 매입, 생산, 미출고, 시공, 근태
|
||||
*/
|
||||
class DashboardCeoController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DashboardCeoService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 매출 현황 요약
|
||||
* GET /api/v1/dashboard/sales/summary
|
||||
*/
|
||||
public function salesSummary(): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->salesSummary(),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 매입 현황 요약
|
||||
* GET /api/v1/dashboard/purchases/summary
|
||||
*/
|
||||
public function purchasesSummary(): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->purchasesSummary(),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 생산 현황 요약
|
||||
* GET /api/v1/dashboard/production/summary
|
||||
*/
|
||||
public function productionSummary(): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->productionSummary(),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 미출고 내역 요약
|
||||
* GET /api/v1/dashboard/unshipped/summary
|
||||
*/
|
||||
public function unshippedSummary(): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->unshippedSummary(),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시공 현황 요약
|
||||
* GET /api/v1/dashboard/construction/summary
|
||||
*/
|
||||
public function constructionSummary(): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->constructionSummary(),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 근태 현황 요약
|
||||
* GET /api/v1/dashboard/attendance/summary
|
||||
*/
|
||||
public function attendanceSummary(): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
fn () => $this->service->attendanceSummary(),
|
||||
__('message.fetched')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,22 @@ public function destroy(int $id): JsonResponse
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* rendered_html 스냅샷 저장 (Lazy Snapshot)
|
||||
* PATCH /v1/documents/{id}/snapshot
|
||||
*/
|
||||
public function patchSnapshot(int $id, UpdateRequest $request): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
$renderedHtml = $request->validated()['rendered_html'] ?? null;
|
||||
if (! $renderedHtml) {
|
||||
throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException('rendered_html is required');
|
||||
}
|
||||
|
||||
return $this->service->patchSnapshot($id, $renderedHtml);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// FQC 일괄생성 (제품검사)
|
||||
// =========================================================================
|
||||
|
||||
@@ -33,4 +33,20 @@ public function summary(Request $request): JsonResponse
|
||||
return $this->entertainmentService->getSummary($limitType, $companyType, $year, $quarter);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 접대비 상세 조회 (모달용)
|
||||
*/
|
||||
public function detail(Request $request): JsonResponse
|
||||
{
|
||||
$companyType = $request->query('company_type', 'medium');
|
||||
$year = $request->query('year') ? (int) $request->query('year') : null;
|
||||
$quarter = $request->query('quarter') ? (int) $request->query('quarter') : null;
|
||||
$startDate = $request->query('start_date');
|
||||
$endDate = $request->query('end_date');
|
||||
|
||||
return ApiResponse::handle(function () use ($companyType, $year, $quarter, $startDate, $endDate) {
|
||||
return $this->entertainmentService->getDetail($companyType, $year, $quarter, $startDate, $endDate);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,13 +128,16 @@ public function summary(Request $request)
|
||||
/**
|
||||
* 대시보드 상세 조회 (CEO 대시보드 당월 예상 지출내역 모달용)
|
||||
*
|
||||
* @param Request $request transaction_type 쿼리 파라미터 (purchase, card, bill, null=전체)
|
||||
* @param Request $request transaction_type (purchase, card, bill, null=전체), start_date, end_date, search
|
||||
*/
|
||||
public function dashboardDetail(Request $request)
|
||||
{
|
||||
$transactionType = $request->query('transaction_type');
|
||||
$startDate = $request->query('start_date');
|
||||
$endDate = $request->query('end_date');
|
||||
$search = $request->query('search');
|
||||
|
||||
$data = $this->service->dashboardDetail($transactionType);
|
||||
$data = $this->service->dashboardDetail($transactionType, $startDate, $endDate, $search);
|
||||
|
||||
return ApiResponse::success($data, __('message.fetched'));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\V1\GeneralJournalEntry\StoreManualJournalRequest;
|
||||
use App\Http\Requests\V1\GeneralJournalEntry\UpdateJournalRequest;
|
||||
use App\Services\GeneralJournalEntryService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GeneralJournalEntryController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GeneralJournalEntryService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 일반전표 통합 목록 조회
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'start_date', 'end_date', 'search', 'page', 'per_page',
|
||||
]);
|
||||
|
||||
$result = $this->service->index($params);
|
||||
|
||||
return ApiResponse::success($result, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 요약 통계
|
||||
*/
|
||||
public function summary(Request $request)
|
||||
{
|
||||
$params = $request->only([
|
||||
'start_date', 'end_date', 'search',
|
||||
]);
|
||||
|
||||
$summary = $this->service->summary($params);
|
||||
|
||||
return ApiResponse::success($summary, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 수기전표 등록
|
||||
*/
|
||||
public function store(StoreManualJournalRequest $request)
|
||||
{
|
||||
$entry = $this->service->store($request->validated());
|
||||
|
||||
return ApiResponse::success($entry, __('message.created'), [], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 전표 상세 조회 (분개 수정 모달용)
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
$detail = $this->service->show($id);
|
||||
|
||||
return ApiResponse::success($detail, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 분개 수정
|
||||
*/
|
||||
public function updateJournal(int $id, UpdateJournalRequest $request)
|
||||
{
|
||||
$entry = $this->service->updateJournal($id, $request->validated());
|
||||
|
||||
return ApiResponse::success($entry, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 분개 삭제
|
||||
*/
|
||||
public function destroyJournal(int $id)
|
||||
{
|
||||
$this->service->destroyJournal($id);
|
||||
|
||||
return ApiResponse::success(null, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,16 @@ public function stats(Request $request)
|
||||
}, __('message.inspection.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 캘린더 스케줄 조회
|
||||
*/
|
||||
public function calendar(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->calendar($request->all());
|
||||
}, __('message.inspection.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 단건 조회
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
use App\Http\Requests\Loan\LoanUpdateRequest;
|
||||
use App\Services\LoanService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LoanController extends Controller
|
||||
{
|
||||
@@ -33,8 +34,10 @@ public function index(LoanIndexRequest $request): JsonResponse
|
||||
*/
|
||||
public function summary(LoanIndexRequest $request): JsonResponse
|
||||
{
|
||||
$userId = $request->validated()['user_id'] ?? null;
|
||||
$result = $this->loanService->summary($userId);
|
||||
$validated = $request->validated();
|
||||
$userId = $validated['user_id'] ?? null;
|
||||
$category = $validated['category'] ?? null;
|
||||
$result = $this->loanService->summary($userId, $category);
|
||||
|
||||
return ApiResponse::success($result, __('message.fetched'));
|
||||
}
|
||||
@@ -42,9 +45,12 @@ public function summary(LoanIndexRequest $request): JsonResponse
|
||||
/**
|
||||
* 가지급금 대시보드
|
||||
*/
|
||||
public function dashboard(): JsonResponse
|
||||
public function dashboard(Request $request): JsonResponse
|
||||
{
|
||||
$result = $this->loanService->dashboard();
|
||||
$startDate = $request->query('start_date');
|
||||
$endDate = $request->query('end_date');
|
||||
|
||||
$result = $this->loanService->dashboard($startDate, $endDate);
|
||||
|
||||
return ApiResponse::success($result, __('message.fetched'));
|
||||
}
|
||||
|
||||
59
app/Http/Controllers/Api/V1/PerformanceReportController.php
Normal file
59
app/Http/Controllers/Api/V1/PerformanceReportController.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Quality\PerformanceReportConfirmRequest;
|
||||
use App\Http\Requests\Quality\PerformanceReportMemoRequest;
|
||||
use App\Services\PerformanceReportService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PerformanceReportController extends Controller
|
||||
{
|
||||
public function __construct(private PerformanceReportService $service) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->index($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function stats(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->stats($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function confirm(PerformanceReportConfirmRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->confirm($request->validated()['ids']);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function unconfirm(PerformanceReportConfirmRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->unconfirm($request->validated()['ids']);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function updateMemo(PerformanceReportMemoRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$data = $request->validated();
|
||||
|
||||
return $this->service->updateMemo($data['ids'], $data['memo']);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function missing(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->missing($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
50
app/Http/Controllers/Api/V1/ProductionOrderController.php
Normal file
50
app/Http/Controllers/Api/V1/ProductionOrderController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ProductionOrder\ProductionOrderIndexRequest;
|
||||
use App\Services\ProductionOrderService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ProductionOrderController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductionOrderService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 생산지시 목록 조회
|
||||
*/
|
||||
public function index(ProductionOrderIndexRequest $request): JsonResponse
|
||||
{
|
||||
$result = $this->service->index($request->validated());
|
||||
|
||||
return ApiResponse::success($result, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 생산지시 상태별 통계
|
||||
*/
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
$stats = $this->service->stats();
|
||||
|
||||
return ApiResponse::success($stats, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 생산지시 상세 조회
|
||||
*/
|
||||
public function show(int $orderId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$detail = $this->service->show($orderId);
|
||||
|
||||
return ApiResponse::success($detail, __('message.fetched'));
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
return ApiResponse::error(__('error.order.not_found'), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
app/Http/Controllers/Api/V1/QmsLotAuditController.php
Normal file
65
app/Http/Controllers/Api/V1/QmsLotAuditController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Qms\QmsLotAuditConfirmRequest;
|
||||
use App\Http\Requests\Qms\QmsLotAuditDocumentDetailRequest;
|
||||
use App\Http\Requests\Qms\QmsLotAuditIndexRequest;
|
||||
use App\Services\QmsLotAuditService;
|
||||
|
||||
class QmsLotAuditController extends Controller
|
||||
{
|
||||
public function __construct(private QmsLotAuditService $service) {}
|
||||
|
||||
/**
|
||||
* 품질관리서 목록 (로트 추적 심사용)
|
||||
*/
|
||||
public function index(QmsLotAuditIndexRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->index($request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 품질관리서 상세 — 수주/개소 목록
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->show($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주 루트별 8종 서류 목록
|
||||
*/
|
||||
public function routeDocuments(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->routeDocuments($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 서류 상세 조회 (2단계 로딩)
|
||||
*/
|
||||
public function documentDetail(QmsLotAuditDocumentDetailRequest $request, string $type, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($type, $id) {
|
||||
return $this->service->documentDetail($type, $id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 개소별 로트 심사 확인 토글
|
||||
*/
|
||||
public function confirm(QmsLotAuditConfirmRequest $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->service->confirm($id, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
}
|
||||
127
app/Http/Controllers/Api/V1/QualityDocumentController.php
Normal file
127
app/Http/Controllers/Api/V1/QualityDocumentController.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Quality\QualityDocumentStoreRequest;
|
||||
use App\Http\Requests\Quality\QualityDocumentUpdateRequest;
|
||||
use App\Services\QualityDocumentService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class QualityDocumentController extends Controller
|
||||
{
|
||||
public function __construct(private QualityDocumentService $service) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->index($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function stats(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->stats($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function calendar(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->calendar($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function availableOrders(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->availableOrders($request->all());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->show($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function store(QualityDocumentStoreRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->store($request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
public function update(QualityDocumentUpdateRequest $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->service->update($id, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$this->service->destroy($id);
|
||||
|
||||
return 'success';
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
public function complete(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->complete($id);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function attachOrders(Request $request, int $id)
|
||||
{
|
||||
$request->validate([
|
||||
'order_ids' => ['required', 'array', 'min:1'],
|
||||
'order_ids.*' => ['required', 'integer'],
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->service->attachOrders($id, $request->input('order_ids'));
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function detachOrder(int $id, int $orderId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $orderId) {
|
||||
return $this->service->detachOrder($id, $orderId);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function inspectLocation(Request $request, int $id, int $locId)
|
||||
{
|
||||
$request->validate([
|
||||
'post_width' => ['nullable', 'integer'],
|
||||
'post_height' => ['nullable', 'integer'],
|
||||
'change_reason' => ['nullable', 'string', 'max:500'],
|
||||
'inspection_status' => ['nullable', 'string', 'in:pending,completed'],
|
||||
]);
|
||||
|
||||
return ApiResponse::handle(function () use ($request, $id, $locId) {
|
||||
return $this->service->inspectLocation($id, $locId, $request->all());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function requestDocument(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->requestDocument($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function resultDocument(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->resultDocument($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ public function index(Request $request): JsonResponse
|
||||
'sort_dir',
|
||||
'per_page',
|
||||
'page',
|
||||
'start_date',
|
||||
'end_date',
|
||||
]);
|
||||
|
||||
$stocks = $this->service->index($params);
|
||||
|
||||
@@ -10,12 +10,17 @@
|
||||
use App\Http\Requests\TaxInvoice\TaxInvoiceSummaryRequest;
|
||||
use App\Http\Requests\TaxInvoice\UpdateTaxInvoiceRequest;
|
||||
use App\Http\Requests\V1\TaxInvoice\BulkIssueRequest;
|
||||
use App\Models\Tenants\JournalEntry;
|
||||
use App\Services\JournalSyncService;
|
||||
use App\Services\TaxInvoiceService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TaxInvoiceController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private TaxInvoiceService $taxInvoiceService
|
||||
private TaxInvoiceService $taxInvoiceService,
|
||||
private JournalSyncService $journalSyncService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -23,12 +28,9 @@ public function __construct(
|
||||
*/
|
||||
public function index(TaxInvoiceListRequest $request)
|
||||
{
|
||||
$taxInvoices = $this->taxInvoiceService->list($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoices,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->taxInvoiceService->list($request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,12 +38,9 @@ public function index(TaxInvoiceListRequest $request)
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->show($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->taxInvoiceService->show($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,13 +48,9 @@ public function show(int $id)
|
||||
*/
|
||||
public function store(CreateTaxInvoiceRequest $request)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->create($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.created'),
|
||||
status: 201
|
||||
);
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->taxInvoiceService->create($request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,12 +58,9 @@ public function store(CreateTaxInvoiceRequest $request)
|
||||
*/
|
||||
public function update(UpdateTaxInvoiceRequest $request, int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->update($id, $request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.updated')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->taxInvoiceService->update($id, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,12 +68,11 @@ public function update(UpdateTaxInvoiceRequest $request, int $id)
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->taxInvoiceService->delete($id);
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$this->taxInvoiceService->delete($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: null,
|
||||
message: __('message.deleted')
|
||||
);
|
||||
return null;
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,12 +80,9 @@ public function destroy(int $id)
|
||||
*/
|
||||
public function issue(int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->issue($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.tax_invoice.issued')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->taxInvoiceService->issue($id);
|
||||
}, __('message.tax_invoice.issued'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,12 +90,9 @@ public function issue(int $id)
|
||||
*/
|
||||
public function bulkIssue(BulkIssueRequest $request)
|
||||
{
|
||||
$result = $this->taxInvoiceService->bulkIssue($request->getIds());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $result,
|
||||
message: __('message.tax_invoice.bulk_issued')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->taxInvoiceService->bulkIssue($request->getIds());
|
||||
}, __('message.tax_invoice.bulk_issued'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,12 +100,9 @@ public function bulkIssue(BulkIssueRequest $request)
|
||||
*/
|
||||
public function cancel(CancelTaxInvoiceRequest $request, int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->cancel($id, $request->validated()['reason']);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.tax_invoice.cancelled')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
return $this->taxInvoiceService->cancel($id, $request->validated()['reason']);
|
||||
}, __('message.tax_invoice.cancelled'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,12 +110,9 @@ public function cancel(CancelTaxInvoiceRequest $request, int $id)
|
||||
*/
|
||||
public function checkStatus(int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->checkStatus($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->taxInvoiceService->checkStatus($id);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,11 +120,79 @@ public function checkStatus(int $id)
|
||||
*/
|
||||
public function summary(TaxInvoiceSummaryRequest $request)
|
||||
{
|
||||
$summary = $this->taxInvoiceService->summary($request->validated());
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->taxInvoiceService->summary($request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $summary,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
// =========================================================================
|
||||
// 분개 (Journal Entries)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 세금계산서 분개 조회
|
||||
*/
|
||||
public function getJournalEntries(int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$sourceKey = "tax_invoice_{$id}";
|
||||
$data = $this->journalSyncService->getForSource(
|
||||
JournalEntry::SOURCE_TAX_INVOICE,
|
||||
$sourceKey
|
||||
);
|
||||
|
||||
return $data ?? ['rows' => []];
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 분개 저장/수정
|
||||
*/
|
||||
public function storeJournalEntries(Request $request, int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
$validated = $request->validate([
|
||||
'rows' => 'required|array|min:1',
|
||||
'rows.*.side' => 'required|in:debit,credit',
|
||||
'rows.*.account_subject' => 'required|string|max:20',
|
||||
'rows.*.debit_amount' => 'required|integer|min:0',
|
||||
'rows.*.credit_amount' => 'required|integer|min:0',
|
||||
]);
|
||||
|
||||
// 세금계산서 정보 조회 (entry_date용)
|
||||
$taxInvoice = $this->taxInvoiceService->show($id);
|
||||
|
||||
$rows = array_map(fn ($row) => [
|
||||
'side' => $row['side'],
|
||||
'account_code' => $row['account_subject'],
|
||||
'debit_amount' => $row['debit_amount'],
|
||||
'credit_amount' => $row['credit_amount'],
|
||||
], $validated['rows']);
|
||||
|
||||
$sourceKey = "tax_invoice_{$id}";
|
||||
|
||||
return $this->journalSyncService->saveForSource(
|
||||
JournalEntry::SOURCE_TAX_INVOICE,
|
||||
$sourceKey,
|
||||
$taxInvoice->issue_date?->format('Y-m-d') ?? now()->format('Y-m-d'),
|
||||
"세금계산서 분개 (#{$id})",
|
||||
$rows,
|
||||
);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 분개 삭제
|
||||
*/
|
||||
public function deleteJournalEntries(int $id): JsonResponse
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$sourceKey = "tax_invoice_{$id}";
|
||||
|
||||
return $this->journalSyncService->deleteForSource(
|
||||
JournalEntry::SOURCE_TAX_INVOICE,
|
||||
$sourceKey
|
||||
);
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@ public function __construct(
|
||||
public function summary(Request $request): JsonResponse
|
||||
{
|
||||
$limit = (int) $request->input('limit', 30);
|
||||
$date = $request->input('date'); // YYYY-MM-DD (이전 이슈 조회용)
|
||||
|
||||
return ApiResponse::handle(function () use ($limit) {
|
||||
return $this->todayIssueService->summary($limit);
|
||||
return ApiResponse::handle(function () use ($limit, $date) {
|
||||
return $this->todayIssueService->summary($limit, null, $date);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,4 +32,18 @@ public function summary(Request $request): JsonResponse
|
||||
return $this->vatService->getSummary($periodType, $year, $period);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 부가세 상세 조회 (모달용)
|
||||
*/
|
||||
public function detail(Request $request): JsonResponse
|
||||
{
|
||||
$periodType = $request->query('period_type', 'quarter');
|
||||
$year = $request->query('year') ? (int) $request->query('year') : null;
|
||||
$period = $request->query('period') ? (int) $request->query('period') : null;
|
||||
|
||||
return ApiResponse::handle(function () use ($periodType, $year, $period) {
|
||||
return $this->vatService->getDetail($periodType, $year, $period);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
|
||||
74
app/Http/Controllers/Api/V1/VehicleDispatchController.php
Normal file
74
app/Http/Controllers/Api/V1/VehicleDispatchController.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\VehicleDispatch\VehicleDispatchUpdateRequest;
|
||||
use App\Services\VehicleDispatchService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VehicleDispatchController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly VehicleDispatchService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 배차차량 목록 조회
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$params = $request->only([
|
||||
'search',
|
||||
'status',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'per_page',
|
||||
'page',
|
||||
]);
|
||||
|
||||
$dispatches = $this->service->index($params);
|
||||
|
||||
return ApiResponse::success($dispatches, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 배차차량 통계 조회
|
||||
*/
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
$stats = $this->service->stats();
|
||||
|
||||
return ApiResponse::success($stats, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 배차차량 상세 조회
|
||||
*/
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$dispatch = $this->service->show($id);
|
||||
|
||||
return ApiResponse::success($dispatch, __('message.fetched'));
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
return ApiResponse::error(__('error.not_found'), 404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배차차량 수정
|
||||
*/
|
||||
public function update(VehicleDispatchUpdateRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$dispatch = $this->service->update($id, $request->validated());
|
||||
|
||||
return ApiResponse::success($dispatch, __('message.updated'));
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
return ApiResponse::error(__('error.not_found'), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,14 +61,18 @@ public function detail(Request $request): JsonResponse
|
||||
: 0.05;
|
||||
$year = $request->query('year') ? (int) $request->query('year') : null;
|
||||
$quarter = $request->query('quarter') ? (int) $request->query('quarter') : null;
|
||||
$startDate = $request->query('start_date');
|
||||
$endDate = $request->query('end_date');
|
||||
|
||||
return ApiResponse::handle(function () use ($calculationType, $fixedAmountPerMonth, $ratio, $year, $quarter) {
|
||||
return ApiResponse::handle(function () use ($calculationType, $fixedAmountPerMonth, $ratio, $year, $quarter, $startDate, $endDate) {
|
||||
return $this->welfareService->getDetail(
|
||||
$calculationType,
|
||||
$fixedAmountPerMonth,
|
||||
$ratio,
|
||||
$year,
|
||||
$quarter
|
||||
$quarter,
|
||||
$startDate,
|
||||
$endDate
|
||||
);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
@@ -230,6 +230,16 @@ public function inspectionReport(int $id)
|
||||
}, __('message.work_order.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업지시 검사 설정 조회 (공정 자동 판별 + 구성품 목록)
|
||||
*/
|
||||
public function inspectionConfig(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->getInspectionConfig($id);
|
||||
}, __('message.work_order.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업지시의 검사용 문서 템플릿 조회
|
||||
*/
|
||||
@@ -310,7 +320,14 @@ public function materialsForItem(int $id, int $itemId)
|
||||
public function registerMaterialInputForItem(MaterialInputForItemRequest $request, int $id, int $itemId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id, $itemId) {
|
||||
return $this->service->registerMaterialInputForItem($id, $itemId, $request->validated()['inputs']);
|
||||
$validated = $request->validated();
|
||||
|
||||
return $this->service->registerMaterialInputForItem(
|
||||
$id,
|
||||
$itemId,
|
||||
$validated['inputs'],
|
||||
(bool) ($validated['replace'] ?? false)
|
||||
);
|
||||
}, __('message.work_order.material_input_registered'));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ public function rules(): array
|
||||
'sort_dir' => 'nullable|string|in:asc,desc',
|
||||
'per_page' => 'nullable|integer|min:1',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public function rules(): array
|
||||
'mobile' => 'nullable|string|max:20',
|
||||
'fax' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:100',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:500',
|
||||
// 담당자 정보
|
||||
'manager_name' => 'nullable|string|max:50',
|
||||
'manager_tel' => 'nullable|string|max:20',
|
||||
|
||||
@@ -65,7 +65,7 @@ public function rules(): array
|
||||
'mobile' => 'nullable|string|max:20',
|
||||
'fax' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:100',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:500',
|
||||
// 담당자 정보
|
||||
'manager_name' => 'nullable|string|max:50',
|
||||
'manager_tel' => 'nullable|string|max:20',
|
||||
|
||||
@@ -28,6 +28,9 @@ public function rules(): array
|
||||
'approvers.*.user_id' => 'required_with:approvers|integer|exists:users,id',
|
||||
'approvers.*.role' => 'nullable|string|max:50',
|
||||
|
||||
// HTML 스냅샷
|
||||
'rendered_html' => 'nullable|string',
|
||||
|
||||
// 문서 데이터 (EAV)
|
||||
'data' => 'nullable|array',
|
||||
'data.*.section_id' => 'nullable|integer',
|
||||
|
||||
@@ -27,6 +27,9 @@ public function rules(): array
|
||||
'approvers.*.user_id' => 'required_with:approvers|integer|exists:users,id',
|
||||
'approvers.*.role' => 'nullable|string|max:50',
|
||||
|
||||
// HTML 스냅샷
|
||||
'rendered_html' => 'nullable|string',
|
||||
|
||||
// 문서 데이터 (EAV)
|
||||
'data' => 'nullable|array',
|
||||
'data.*.section_id' => 'nullable|integer',
|
||||
|
||||
@@ -30,6 +30,9 @@ public function rules(): array
|
||||
'data.*.field_key' => 'required_with:data|string|max:100',
|
||||
'data.*.field_value' => 'nullable|string',
|
||||
|
||||
// HTML 스냅샷
|
||||
'rendered_html' => 'nullable|string',
|
||||
|
||||
// 첨부파일
|
||||
'attachments' => 'nullable|array',
|
||||
'attachments.*.file_id' => 'required_with:attachments|integer|exists:files,id',
|
||||
|
||||
@@ -22,6 +22,7 @@ public function rules(): array
|
||||
Inspection::TYPE_FQC,
|
||||
])],
|
||||
'lot_no' => ['required', 'string', 'max:50'],
|
||||
'work_order_id' => ['nullable', 'integer', 'exists:work_orders,id'],
|
||||
'item_name' => ['nullable', 'string', 'max:200'],
|
||||
'process_name' => ['nullable', 'string', 'max:100'],
|
||||
'quantity' => ['nullable', 'numeric', 'min:0'],
|
||||
|
||||
@@ -29,6 +29,7 @@ public function rules(): array
|
||||
return [
|
||||
'user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'status' => ['nullable', 'string', Rule::in(Loan::STATUSES)],
|
||||
'category' => ['nullable', 'string', Rule::in(Loan::CATEGORIES)],
|
||||
'start_date' => ['nullable', 'date', 'date_format:Y-m-d'],
|
||||
'end_date' => ['nullable', 'date', 'date_format:Y-m-d', 'after_or_equal:start_date'],
|
||||
'search' => ['nullable', 'string', 'max:100'],
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Http\Requests\Loan;
|
||||
|
||||
use App\Models\Tenants\Loan;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class LoanStoreRequest extends FormRequest
|
||||
{
|
||||
@@ -21,12 +23,27 @@ public function authorize(): bool
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$isGiftCertificate = $this->input('category') === Loan::CATEGORY_GIFT_CERTIFICATE;
|
||||
|
||||
return [
|
||||
'user_id' => ['required', 'integer', 'exists:users,id'],
|
||||
'user_id' => [$isGiftCertificate ? 'nullable' : 'required', 'integer', 'exists:users,id'],
|
||||
'loan_date' => ['required', 'date', 'date_format:Y-m-d'],
|
||||
'amount' => ['required', 'numeric', 'min:0', 'max:999999999999.99'],
|
||||
'purpose' => ['nullable', 'string', 'max:1000'],
|
||||
'withdrawal_id' => ['nullable', 'integer', 'exists:withdrawals,id'],
|
||||
'category' => ['nullable', 'string', Rule::in(Loan::CATEGORIES)],
|
||||
'status' => ['nullable', 'string', Rule::in(Loan::STATUSES)],
|
||||
'metadata' => ['nullable', 'array'],
|
||||
'metadata.serial_number' => ['nullable', 'string', 'max:100'],
|
||||
'metadata.cert_name' => ['nullable', 'string', 'max:200'],
|
||||
'metadata.vendor_id' => ['nullable', 'string', 'max:50'],
|
||||
'metadata.vendor_name' => ['nullable', 'string', 'max:200'],
|
||||
'metadata.purchase_purpose' => ['nullable', 'string', 'max:50'],
|
||||
'metadata.entertainment_expense' => ['nullable', 'string', 'max:50'],
|
||||
'metadata.recipient_name' => ['nullable', 'string', 'max:100'],
|
||||
'metadata.recipient_organization' => ['nullable', 'string', 'max:200'],
|
||||
'metadata.usage_description' => ['nullable', 'string', 'max:1000'],
|
||||
'metadata.memo' => ['nullable', 'string', 'max:2000'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Http\Requests\Loan;
|
||||
|
||||
use App\Models\Tenants\Loan;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class LoanUpdateRequest extends FormRequest
|
||||
{
|
||||
@@ -27,6 +29,20 @@ public function rules(): array
|
||||
'amount' => ['sometimes', 'numeric', 'min:0', 'max:999999999999.99'],
|
||||
'purpose' => ['nullable', 'string', 'max:1000'],
|
||||
'withdrawal_id' => ['nullable', 'integer', 'exists:withdrawals,id'],
|
||||
'category' => ['sometimes', 'string', Rule::in(Loan::CATEGORIES)],
|
||||
'status' => ['sometimes', 'string', Rule::in(Loan::STATUSES)],
|
||||
'settlement_date' => ['nullable', 'date', 'date_format:Y-m-d'],
|
||||
'metadata' => ['nullable', 'array'],
|
||||
'metadata.serial_number' => ['nullable', 'string', 'max:100'],
|
||||
'metadata.cert_name' => ['nullable', 'string', 'max:200'],
|
||||
'metadata.vendor_id' => ['nullable', 'string', 'max:50'],
|
||||
'metadata.vendor_name' => ['nullable', 'string', 'max:200'],
|
||||
'metadata.purchase_purpose' => ['nullable', 'string', 'max:50'],
|
||||
'metadata.entertainment_expense' => ['nullable', 'string', 'max:50'],
|
||||
'metadata.recipient_name' => ['nullable', 'string', 'max:100'],
|
||||
'metadata.recipient_organization' => ['nullable', 'string', 'max:200'],
|
||||
'metadata.usage_description' => ['nullable', 'string', 'max:1000'],
|
||||
'metadata.memo' => ['nullable', 'string', 'max:2000'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\ProductionOrder;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProductionOrderIndexRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'search' => 'nullable|string|max:100',
|
||||
'production_status' => 'nullable|in:waiting,in_production,completed',
|
||||
'sort_by' => 'nullable|in:created_at,delivery_date,order_no',
|
||||
'sort_dir' => 'nullable|in:asc,desc',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'per_page' => 'nullable|integer|min:1|max:100',
|
||||
];
|
||||
}
|
||||
}
|
||||
39
app/Http/Requests/Qms/AuditChecklistStoreRequest.php
Normal file
39
app/Http/Requests/Qms/AuditChecklistStoreRequest.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Qms;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AuditChecklistStoreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'year' => 'required|integer|min:2020|max:2100',
|
||||
'quarter' => 'required|integer|in:1,2,3,4',
|
||||
'type' => 'nullable|string|max:30',
|
||||
'categories' => 'required|array|min:1',
|
||||
'categories.*.title' => 'required|string|max:200',
|
||||
'categories.*.sort_order' => 'nullable|integer|min:0',
|
||||
'categories.*.items' => 'required|array|min:1',
|
||||
'categories.*.items.*.name' => 'required|string|max:200',
|
||||
'categories.*.items.*.description' => 'nullable|string',
|
||||
'categories.*.items.*.sort_order' => 'nullable|integer|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'categories.required' => __('validation.required', ['attribute' => '카테고리']),
|
||||
'categories.*.title.required' => __('validation.required', ['attribute' => '카테고리 제목']),
|
||||
'categories.*.items.required' => __('validation.required', ['attribute' => '점검 항목']),
|
||||
'categories.*.items.*.name.required' => __('validation.required', ['attribute' => '항목명']),
|
||||
];
|
||||
}
|
||||
}
|
||||
28
app/Http/Requests/Qms/AuditChecklistUpdateRequest.php
Normal file
28
app/Http/Requests/Qms/AuditChecklistUpdateRequest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Qms;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AuditChecklistUpdateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'categories' => 'sometimes|array|min:1',
|
||||
'categories.*.id' => 'nullable|integer|exists:audit_checklist_categories,id',
|
||||
'categories.*.title' => 'required|string|max:200',
|
||||
'categories.*.sort_order' => 'nullable|integer|min:0',
|
||||
'categories.*.items' => 'required|array|min:1',
|
||||
'categories.*.items.*.id' => 'nullable|integer|exists:audit_checklist_items,id',
|
||||
'categories.*.items.*.name' => 'required|string|max:200',
|
||||
'categories.*.items.*.description' => 'nullable|string',
|
||||
'categories.*.items.*.sort_order' => 'nullable|integer|min:0',
|
||||
];
|
||||
}
|
||||
}
|
||||
28
app/Http/Requests/Qms/QmsLotAuditConfirmRequest.php
Normal file
28
app/Http/Requests/Qms/QmsLotAuditConfirmRequest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Qms;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class QmsLotAuditConfirmRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'confirmed' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'confirmed.required' => __('validation.required', ['attribute' => '확인 상태']),
|
||||
'confirmed.boolean' => __('validation.boolean', ['attribute' => '확인 상태']),
|
||||
];
|
||||
}
|
||||
}
|
||||
34
app/Http/Requests/Qms/QmsLotAuditDocumentDetailRequest.php
Normal file
34
app/Http/Requests/Qms/QmsLotAuditDocumentDetailRequest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Qms;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class QmsLotAuditDocumentDetailRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'type' => $this->route('type'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => 'required|string|in:import,order,log,report,confirmation,shipping,product,quality',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'type.in' => __('validation.in', ['attribute' => '서류 타입']),
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Http/Requests/Qms/QmsLotAuditIndexRequest.php
Normal file
23
app/Http/Requests/Qms/QmsLotAuditIndexRequest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Qms;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class QmsLotAuditIndexRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'year' => 'nullable|integer|min:2020|max:2100',
|
||||
'quarter' => 'nullable|integer|in:1,2,3,4',
|
||||
'q' => 'nullable|string|max:100',
|
||||
'per_page' => 'nullable|integer|min:1|max:100',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Quality;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PerformanceReportConfirmRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'ids' => ['required', 'array', 'min:1'],
|
||||
'ids.*' => ['required', 'integer', 'exists:performance_reports,id'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'ids.required' => __('validation.required', ['attribute' => '실적신고 ID']),
|
||||
'ids.min' => __('validation.min.array', ['attribute' => '실적신고 ID', 'min' => 1]),
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/Quality/PerformanceReportMemoRequest.php
Normal file
30
app/Http/Requests/Quality/PerformanceReportMemoRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Quality;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PerformanceReportMemoRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'ids' => ['required', 'array', 'min:1'],
|
||||
'ids.*' => ['required', 'integer', 'exists:performance_reports,id'],
|
||||
'memo' => ['required', 'string', 'max:1000'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'ids.required' => __('validation.required', ['attribute' => '실적신고 ID']),
|
||||
'memo.required' => __('validation.required', ['attribute' => '메모']),
|
||||
];
|
||||
}
|
||||
}
|
||||
45
app/Http/Requests/Quality/QualityDocumentStoreRequest.php
Normal file
45
app/Http/Requests/Quality/QualityDocumentStoreRequest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Quality;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class QualityDocumentStoreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'site_name' => ['required', 'string', 'max:200'],
|
||||
'client_id' => ['nullable', 'integer', 'exists:clients,id'],
|
||||
'inspector_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'received_date' => ['nullable', 'date'],
|
||||
'options' => ['nullable', 'array'],
|
||||
'options.manager' => ['nullable', 'array'],
|
||||
'options.manager.name' => ['nullable', 'string', 'max:50'],
|
||||
'options.manager.phone' => ['nullable', 'string', 'max:30'],
|
||||
'options.inspection' => ['nullable', 'array'],
|
||||
'options.inspection.request_date' => ['nullable', 'date'],
|
||||
'options.inspection.start_date' => ['nullable', 'date'],
|
||||
'options.inspection.end_date' => ['nullable', 'date'],
|
||||
'options.site_address' => ['nullable', 'array'],
|
||||
'options.construction_site' => ['nullable', 'array'],
|
||||
'options.material_distributor' => ['nullable', 'array'],
|
||||
'options.contractor' => ['nullable', 'array'],
|
||||
'options.supervisor' => ['nullable', 'array'],
|
||||
'order_ids' => ['nullable', 'array'],
|
||||
'order_ids.*' => ['integer', 'exists:orders,id'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'site_name.required' => __('validation.required', ['attribute' => '현장명']),
|
||||
];
|
||||
}
|
||||
}
|
||||
44
app/Http/Requests/Quality/QualityDocumentUpdateRequest.php
Normal file
44
app/Http/Requests/Quality/QualityDocumentUpdateRequest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Quality;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class QualityDocumentUpdateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'site_name' => ['sometimes', 'string', 'max:200'],
|
||||
'client_id' => ['nullable', 'integer', 'exists:clients,id'],
|
||||
'inspector_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'received_date' => ['nullable', 'date'],
|
||||
'options' => ['nullable', 'array'],
|
||||
'options.manager' => ['nullable', 'array'],
|
||||
'options.manager.name' => ['nullable', 'string', 'max:50'],
|
||||
'options.manager.phone' => ['nullable', 'string', 'max:30'],
|
||||
'options.inspection' => ['nullable', 'array'],
|
||||
'options.inspection.request_date' => ['nullable', 'date'],
|
||||
'options.inspection.start_date' => ['nullable', 'date'],
|
||||
'options.inspection.end_date' => ['nullable', 'date'],
|
||||
'options.site_address' => ['nullable', 'array'],
|
||||
'options.construction_site' => ['nullable', 'array'],
|
||||
'options.material_distributor' => ['nullable', 'array'],
|
||||
'options.contractor' => ['nullable', 'array'],
|
||||
'options.supervisor' => ['nullable', 'array'],
|
||||
'order_ids' => ['nullable', 'array'],
|
||||
'order_ids.*' => ['integer', 'exists:orders,id'],
|
||||
'locations' => ['nullable', 'array'],
|
||||
'locations.*.id' => ['required', 'integer'],
|
||||
'locations.*.post_width' => ['nullable', 'integer'],
|
||||
'locations.*.post_height' => ['nullable', 'integer'],
|
||||
'locations.*.change_reason' => ['nullable', 'string', 'max:500'],
|
||||
'locations.*.inspection_data' => ['nullable', 'array'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public function rules(): array
|
||||
'scheduled_date' => 'required|date',
|
||||
'status' => 'nullable|in:scheduled,ready,shipping,completed',
|
||||
'priority' => 'nullable|in:urgent,normal,low',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics,direct_dispatch,loading,kyungdong_delivery,daesin_delivery,kyungdong_freight,daesin_freight,self_pickup',
|
||||
|
||||
// 발주처/배송 정보
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
@@ -55,6 +55,16 @@ public function rules(): array
|
||||
// 기타
|
||||
'remarks' => 'nullable|string',
|
||||
|
||||
// 배차정보
|
||||
'vehicle_dispatches' => 'nullable|array',
|
||||
'vehicle_dispatches.*.seq' => 'nullable|integer|min:1',
|
||||
'vehicle_dispatches.*.logistics_company' => 'nullable|string|max:100',
|
||||
'vehicle_dispatches.*.arrival_datetime' => 'nullable|date',
|
||||
'vehicle_dispatches.*.tonnage' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.vehicle_no' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.driver_contact' => 'nullable|string|max:50',
|
||||
'vehicle_dispatches.*.remarks' => 'nullable|string',
|
||||
|
||||
// 출하 품목
|
||||
'items' => 'nullable|array',
|
||||
'items.*.seq' => 'nullable|integer|min:1',
|
||||
|
||||
@@ -19,7 +19,7 @@ public function rules(): array
|
||||
'order_id' => 'nullable|integer|exists:orders,id',
|
||||
'scheduled_date' => 'nullable|date',
|
||||
'priority' => 'nullable|in:urgent,normal,low',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics,direct_dispatch,loading,kyungdong_delivery,daesin_delivery,kyungdong_freight,daesin_freight,self_pickup',
|
||||
|
||||
// 발주처/배송 정보
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
@@ -53,6 +53,16 @@ public function rules(): array
|
||||
// 기타
|
||||
'remarks' => 'nullable|string',
|
||||
|
||||
// 배차정보
|
||||
'vehicle_dispatches' => 'nullable|array',
|
||||
'vehicle_dispatches.*.seq' => 'nullable|integer|min:1',
|
||||
'vehicle_dispatches.*.logistics_company' => 'nullable|string|max:100',
|
||||
'vehicle_dispatches.*.arrival_datetime' => 'nullable|date',
|
||||
'vehicle_dispatches.*.tonnage' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.vehicle_no' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.driver_contact' => 'nullable|string|max:50',
|
||||
'vehicle_dispatches.*.remarks' => 'nullable|string',
|
||||
|
||||
// 출하 품목
|
||||
'items' => 'nullable|array',
|
||||
'items.*.seq' => 'nullable|integer|min:1',
|
||||
|
||||
@@ -29,7 +29,7 @@ public function rules(): array
|
||||
'briefing_time' => 'nullable|string|max:10',
|
||||
'briefing_type' => ['nullable', 'string', Rule::in(SiteBriefing::TYPES)],
|
||||
'location' => 'nullable|string|max:200',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:500',
|
||||
|
||||
// 상태 정보
|
||||
'status' => ['nullable', 'string', Rule::in(SiteBriefing::STATUSES)],
|
||||
|
||||
@@ -29,7 +29,7 @@ public function rules(): array
|
||||
'briefing_time' => 'nullable|string|max:10',
|
||||
'briefing_type' => ['nullable', 'string', Rule::in(SiteBriefing::TYPES)],
|
||||
'location' => 'nullable|string|max:200',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:500',
|
||||
|
||||
// 상태 정보
|
||||
'status' => ['nullable', 'string', Rule::in(SiteBriefing::STATUSES)],
|
||||
|
||||
@@ -20,18 +20,18 @@ public function rules(): array
|
||||
'issue_type' => ['required', 'string', Rule::in(TaxInvoice::ISSUE_TYPES)],
|
||||
'direction' => ['required', 'string', Rule::in(TaxInvoice::DIRECTIONS)],
|
||||
|
||||
// 공급자 정보
|
||||
'supplier_corp_num' => ['required', 'string', 'max:20'],
|
||||
'supplier_corp_name' => ['required', 'string', 'max:100'],
|
||||
// 공급자 정보 (매입 시 필수, 매출 시 선택)
|
||||
'supplier_corp_num' => ['required_if:direction,purchases', 'nullable', 'string', 'max:20'],
|
||||
'supplier_corp_name' => ['required_if:direction,purchases', 'nullable', 'string', 'max:100'],
|
||||
'supplier_ceo_name' => ['nullable', 'string', 'max:50'],
|
||||
'supplier_addr' => ['nullable', 'string', 'max:200'],
|
||||
'supplier_biz_type' => ['nullable', 'string', 'max:100'],
|
||||
'supplier_biz_class' => ['nullable', 'string', 'max:100'],
|
||||
'supplier_contact_id' => ['nullable', 'string', 'email', 'max:100'],
|
||||
|
||||
// 공급받는자 정보
|
||||
'buyer_corp_num' => ['required', 'string', 'max:20'],
|
||||
'buyer_corp_name' => ['required', 'string', 'max:100'],
|
||||
// 공급받는자 정보 (매출 시 필수, 매입 시 선택)
|
||||
'buyer_corp_num' => ['required_if:direction,sales', 'nullable', 'string', 'max:20'],
|
||||
'buyer_corp_name' => ['required_if:direction,sales', 'nullable', 'string', 'max:100'],
|
||||
'buyer_ceo_name' => ['nullable', 'string', 'max:50'],
|
||||
'buyer_addr' => ['nullable', 'string', 'max:200'],
|
||||
'buyer_biz_type' => ['nullable', 'string', 'max:100'],
|
||||
|
||||
@@ -17,7 +17,7 @@ public function rules(): array
|
||||
'company_name' => 'required|string|max:100',
|
||||
'email' => 'nullable|email|max:100',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:500',
|
||||
'business_num' => 'nullable|string|max:20',
|
||||
'ceo_name' => 'nullable|string|max:100',
|
||||
];
|
||||
|
||||
@@ -18,7 +18,7 @@ public function rules(): array
|
||||
'company_name' => 'sometimes|string|max:100',
|
||||
'email' => 'nullable|email|max:100',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'address' => 'nullable|string|max:255',
|
||||
'address' => 'nullable|string|max:500',
|
||||
'business_num' => 'nullable|string|max:20',
|
||||
'ceo_name' => 'nullable|string|max:100',
|
||||
'logo' => 'nullable|string|max:255',
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\AccountSubject;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreAccountSubjectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'code' => ['required', 'string', 'max:10'],
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'category' => ['nullable', 'string', 'in:asset,liability,capital,revenue,expense'],
|
||||
'sub_category' => ['nullable', 'string', 'max:50'],
|
||||
'parent_code' => ['nullable', 'string', 'max:10'],
|
||||
'depth' => ['nullable', 'integer', 'in:1,2,3'],
|
||||
'department_type' => ['nullable', 'string', 'in:common,manufacturing,admin'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'sort_order' => ['nullable', 'integer'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'code.required' => '계정과목 코드를 입력하세요.',
|
||||
'name.required' => '계정과목명을 입력하세요.',
|
||||
'category.in' => '유효한 분류를 선택하세요.',
|
||||
'depth.in' => '계층은 1(대), 2(중), 3(소) 중 하나여야 합니다.',
|
||||
'department_type.in' => '부문은 common, manufacturing, admin 중 하나여야 합니다.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\AccountSubject;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateAccountSubjectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'category' => ['nullable', 'string', 'in:asset,liability,capital,revenue,expense'],
|
||||
'sub_category' => ['nullable', 'string', 'max:50'],
|
||||
'parent_code' => ['nullable', 'string', 'max:10'],
|
||||
'depth' => ['nullable', 'integer', 'in:1,2,3'],
|
||||
'department_type' => ['nullable', 'string', 'in:common,manufacturing,admin'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'sort_order' => ['nullable', 'integer'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'category.in' => '유효한 분류를 선택하세요.',
|
||||
'depth.in' => '계층은 1(대), 2(중), 3(소) 중 하나여야 합니다.',
|
||||
'department_type.in' => '부문은 common, manufacturing, admin 중 하나여야 합니다.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ public function rules(): array
|
||||
$tenantId = app('tenant_id') ?? 0;
|
||||
|
||||
return [
|
||||
// === 기존 필드 ===
|
||||
'bill_number' => [
|
||||
'nullable',
|
||||
'string',
|
||||
@@ -30,16 +31,99 @@ public function rules(): array
|
||||
'client_name' => ['nullable', 'string', 'max:100'],
|
||||
'amount' => ['required', 'numeric', 'min:0'],
|
||||
'issue_date' => ['required', 'date'],
|
||||
'maturity_date' => ['required', 'date', 'after_or_equal:issue_date'],
|
||||
'status' => ['nullable', 'string', 'in:stored,maturityAlert,maturityResult,paymentComplete,dishonored,collectionRequest,collectionComplete,suing'],
|
||||
'maturity_date' => ['nullable', 'date', 'after_or_equal:issue_date'],
|
||||
'status' => ['nullable', 'string', 'max:30'],
|
||||
'reason' => ['nullable', 'string', 'max:255'],
|
||||
'installment_count' => ['nullable', 'integer', 'min:0'],
|
||||
'note' => ['nullable', 'string', 'max:1000'],
|
||||
'is_electronic' => ['nullable', 'boolean'],
|
||||
'bank_account_id' => ['nullable', 'integer', 'exists:bank_accounts,id'],
|
||||
|
||||
// === V8 증권종류/매체/구분 ===
|
||||
'instrument_type' => ['nullable', 'string', 'in:promissory,exchange,cashierCheck,currentCheck'],
|
||||
'medium' => ['nullable', 'string', 'in:electronic,paper'],
|
||||
'bill_category' => ['nullable', 'string', 'in:commercial,other'],
|
||||
|
||||
// === 전자어음 ===
|
||||
'electronic_bill_no' => ['nullable', 'string', 'max:100'],
|
||||
'registration_org' => ['nullable', 'string', 'in:kftc,bank'],
|
||||
|
||||
// === 환어음 ===
|
||||
'drawee' => ['nullable', 'string', 'max:100'],
|
||||
'acceptance_status' => ['nullable', 'string', 'in:accepted,pending,refused'],
|
||||
'acceptance_date' => ['nullable', 'date'],
|
||||
'acceptance_refusal_date' => ['nullable', 'date'],
|
||||
'acceptance_refusal_reason' => ['nullable', 'string', 'max:50'],
|
||||
|
||||
// === 받을어음 전용 ===
|
||||
'endorsement' => ['nullable', 'string', 'in:endorsable,nonEndorsable'],
|
||||
'endorsement_order' => ['nullable', 'string', 'max:5'],
|
||||
'storage_place' => ['nullable', 'string', 'in:safe,bank,other'],
|
||||
'issuer_bank' => ['nullable', 'string', 'max:100'],
|
||||
|
||||
// 할인
|
||||
'is_discounted' => ['nullable', 'boolean'],
|
||||
'discount_date' => ['nullable', 'date'],
|
||||
'discount_bank' => ['nullable', 'string', 'max:100'],
|
||||
'discount_rate' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'discount_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
|
||||
// 배서양도
|
||||
'endorsement_date' => ['nullable', 'date'],
|
||||
'endorsee' => ['nullable', 'string', 'max:100'],
|
||||
'endorsement_reason' => ['nullable', 'string', 'in:payment,guarantee,collection,other'],
|
||||
|
||||
// 추심
|
||||
'collection_bank' => ['nullable', 'string', 'max:100'],
|
||||
'collection_request_date' => ['nullable', 'date'],
|
||||
'collection_fee' => ['nullable', 'numeric', 'min:0'],
|
||||
'collection_complete_date' => ['nullable', 'date'],
|
||||
'collection_result' => ['nullable', 'string', 'in:success,partial,failed,pending'],
|
||||
'collection_deposit_date' => ['nullable', 'date'],
|
||||
'collection_deposit_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
|
||||
// === 지급어음 전용 ===
|
||||
'settlement_bank' => ['nullable', 'string', 'max:100'],
|
||||
'payment_method' => ['nullable', 'string', 'in:autoTransfer,currentAccount,other'],
|
||||
'actual_payment_date' => ['nullable', 'date'],
|
||||
|
||||
// === 공통 ===
|
||||
'payment_place' => ['nullable', 'string', 'max:30'],
|
||||
'payment_place_detail' => ['nullable', 'string', 'max:200'],
|
||||
|
||||
// 개서
|
||||
'renewal_date' => ['nullable', 'date'],
|
||||
'renewal_new_bill_no' => ['nullable', 'string', 'max:50'],
|
||||
'renewal_reason' => ['nullable', 'string', 'in:maturityExtension,amountChange,conditionChange,other'],
|
||||
|
||||
// 소구
|
||||
'recourse_date' => ['nullable', 'date'],
|
||||
'recourse_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'recourse_target' => ['nullable', 'string', 'max:100'],
|
||||
'recourse_reason' => ['nullable', 'string', 'in:endorsedDishonor,discountDishonor,other'],
|
||||
|
||||
// 환매
|
||||
'buyback_date' => ['nullable', 'date'],
|
||||
'buyback_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'buyback_bank' => ['nullable', 'string', 'max:100'],
|
||||
|
||||
// 부도/법적절차
|
||||
'dishonored_date' => ['nullable', 'date'],
|
||||
'dishonored_reason' => ['nullable', 'string', 'max:30'],
|
||||
'has_protest' => ['nullable', 'boolean'],
|
||||
'protest_date' => ['nullable', 'date'],
|
||||
'recourse_notice_date' => ['nullable', 'date'],
|
||||
'recourse_notice_deadline' => ['nullable', 'date'],
|
||||
|
||||
// 분할배서
|
||||
'is_split' => ['nullable', 'boolean'],
|
||||
|
||||
// === 차수 관리 ===
|
||||
'installments' => ['nullable', 'array'],
|
||||
'installments.*.date' => ['required_with:installments', 'date'],
|
||||
'installments.*.amount' => ['required_with:installments', 'numeric', 'min:0'],
|
||||
'installments.*.type' => ['nullable', 'string', 'max:30'],
|
||||
'installments.*.counterparty' => ['nullable', 'string', 'max:100'],
|
||||
'installments.*.note' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public function authorize(): bool
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
// === 기존 필드 ===
|
||||
'bill_number' => ['nullable', 'string', 'max:50'],
|
||||
'bill_type' => ['nullable', 'string', 'in:received,issued'],
|
||||
'client_id' => ['nullable', 'integer', 'exists:clients,id'],
|
||||
@@ -21,15 +22,72 @@ public function rules(): array
|
||||
'amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'issue_date' => ['nullable', 'date'],
|
||||
'maturity_date' => ['nullable', 'date', 'after_or_equal:issue_date'],
|
||||
'status' => ['nullable', 'string', 'in:stored,maturityAlert,maturityResult,paymentComplete,dishonored,collectionRequest,collectionComplete,suing'],
|
||||
'status' => ['nullable', 'string', 'max:30'],
|
||||
'reason' => ['nullable', 'string', 'max:255'],
|
||||
'installment_count' => ['nullable', 'integer', 'min:0'],
|
||||
'note' => ['nullable', 'string', 'max:1000'],
|
||||
'is_electronic' => ['nullable', 'boolean'],
|
||||
'bank_account_id' => ['nullable', 'integer', 'exists:bank_accounts,id'],
|
||||
|
||||
// === V8 확장 ===
|
||||
'instrument_type' => ['nullable', 'string', 'in:promissory,exchange,cashierCheck,currentCheck'],
|
||||
'medium' => ['nullable', 'string', 'in:electronic,paper'],
|
||||
'bill_category' => ['nullable', 'string', 'in:commercial,other'],
|
||||
'electronic_bill_no' => ['nullable', 'string', 'max:100'],
|
||||
'registration_org' => ['nullable', 'string', 'in:kftc,bank'],
|
||||
'drawee' => ['nullable', 'string', 'max:100'],
|
||||
'acceptance_status' => ['nullable', 'string', 'in:accepted,pending,refused'],
|
||||
'acceptance_date' => ['nullable', 'date'],
|
||||
'acceptance_refusal_date' => ['nullable', 'date'],
|
||||
'acceptance_refusal_reason' => ['nullable', 'string', 'max:50'],
|
||||
'endorsement' => ['nullable', 'string', 'in:endorsable,nonEndorsable'],
|
||||
'endorsement_order' => ['nullable', 'string', 'max:5'],
|
||||
'storage_place' => ['nullable', 'string', 'in:safe,bank,other'],
|
||||
'issuer_bank' => ['nullable', 'string', 'max:100'],
|
||||
'is_discounted' => ['nullable', 'boolean'],
|
||||
'discount_date' => ['nullable', 'date'],
|
||||
'discount_bank' => ['nullable', 'string', 'max:100'],
|
||||
'discount_rate' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'discount_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'endorsement_date' => ['nullable', 'date'],
|
||||
'endorsee' => ['nullable', 'string', 'max:100'],
|
||||
'endorsement_reason' => ['nullable', 'string', 'in:payment,guarantee,collection,other'],
|
||||
'collection_bank' => ['nullable', 'string', 'max:100'],
|
||||
'collection_request_date' => ['nullable', 'date'],
|
||||
'collection_fee' => ['nullable', 'numeric', 'min:0'],
|
||||
'collection_complete_date' => ['nullable', 'date'],
|
||||
'collection_result' => ['nullable', 'string', 'in:success,partial,failed,pending'],
|
||||
'collection_deposit_date' => ['nullable', 'date'],
|
||||
'collection_deposit_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'settlement_bank' => ['nullable', 'string', 'max:100'],
|
||||
'payment_method' => ['nullable', 'string', 'in:autoTransfer,currentAccount,other'],
|
||||
'actual_payment_date' => ['nullable', 'date'],
|
||||
'payment_place' => ['nullable', 'string', 'max:30'],
|
||||
'payment_place_detail' => ['nullable', 'string', 'max:200'],
|
||||
'renewal_date' => ['nullable', 'date'],
|
||||
'renewal_new_bill_no' => ['nullable', 'string', 'max:50'],
|
||||
'renewal_reason' => ['nullable', 'string', 'in:maturityExtension,amountChange,conditionChange,other'],
|
||||
'recourse_date' => ['nullable', 'date'],
|
||||
'recourse_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'recourse_target' => ['nullable', 'string', 'max:100'],
|
||||
'recourse_reason' => ['nullable', 'string', 'in:endorsedDishonor,discountDishonor,other'],
|
||||
'buyback_date' => ['nullable', 'date'],
|
||||
'buyback_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'buyback_bank' => ['nullable', 'string', 'max:100'],
|
||||
'dishonored_date' => ['nullable', 'date'],
|
||||
'dishonored_reason' => ['nullable', 'string', 'max:30'],
|
||||
'has_protest' => ['nullable', 'boolean'],
|
||||
'protest_date' => ['nullable', 'date'],
|
||||
'recourse_notice_date' => ['nullable', 'date'],
|
||||
'recourse_notice_deadline' => ['nullable', 'date'],
|
||||
'is_split' => ['nullable', 'boolean'],
|
||||
|
||||
// === 차수 관리 ===
|
||||
'installments' => ['nullable', 'array'],
|
||||
'installments.*.date' => ['required_with:installments', 'date'],
|
||||
'installments.*.amount' => ['required_with:installments', 'numeric', 'min:0'],
|
||||
'installments.*.type' => ['nullable', 'string', 'max:30'],
|
||||
'installments.*.counterparty' => ['nullable', 'string', 'max:100'],
|
||||
'installments.*.note' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\GeneralJournalEntry;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreManualJournalRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'journal_date' => ['required', 'date'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'rows' => ['required', 'array', 'min:2'],
|
||||
'rows.*.side' => ['required', 'in:debit,credit'],
|
||||
'rows.*.account_subject_id' => ['required', 'string', 'max:10'],
|
||||
'rows.*.vendor_id' => ['nullable', 'integer'],
|
||||
'rows.*.debit_amount' => ['required', 'integer', 'min:0'],
|
||||
'rows.*.credit_amount' => ['required', 'integer', 'min:0'],
|
||||
'rows.*.memo' => ['nullable', 'string', 'max:300'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'journal_date.required' => '전표일자를 입력하세요.',
|
||||
'rows.required' => '분개 행을 입력하세요.',
|
||||
'rows.min' => '최소 2개 이상의 분개 행이 필요합니다.',
|
||||
'rows.*.side.required' => '차/대 구분을 선택하세요.',
|
||||
'rows.*.side.in' => '차/대 구분이 올바르지 않습니다.',
|
||||
'rows.*.account_subject_id.required' => '계정과목을 선택하세요.',
|
||||
'rows.*.debit_amount.required' => '차변 금액을 입력하세요.',
|
||||
'rows.*.credit_amount.required' => '대변 금액을 입력하세요.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\GeneralJournalEntry;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateJournalRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'journal_memo' => ['sometimes', 'nullable', 'string', 'max:1000'],
|
||||
'rows' => ['sometimes', 'array', 'min:1'],
|
||||
'rows.*.side' => ['required_with:rows', 'in:debit,credit'],
|
||||
'rows.*.account_subject_id' => ['required_with:rows', 'string', 'max:10'],
|
||||
'rows.*.vendor_id' => ['nullable', 'integer'],
|
||||
'rows.*.debit_amount' => ['required_with:rows', 'integer', 'min:0'],
|
||||
'rows.*.credit_amount' => ['required_with:rows', 'integer', 'min:0'],
|
||||
'rows.*.memo' => ['nullable', 'string', 'max:300'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'rows.*.side.required_with' => '차/대 구분을 선택하세요.',
|
||||
'rows.*.side.in' => '차/대 구분이 올바르지 않습니다.',
|
||||
'rows.*.account_subject_id.required_with' => '계정과목을 선택하세요.',
|
||||
'rows.*.debit_amount.required_with' => '차변 금액을 입력하세요.',
|
||||
'rows.*.credit_amount.required_with' => '대변 금액을 입력하세요.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,12 @@ public function rules(): array
|
||||
'connection_type' => ['nullable', 'string', 'max:20'],
|
||||
'connection_target' => ['nullable', 'string', 'max:255'],
|
||||
'completion_type' => ['nullable', 'string', 'in:click_complete,selection_complete,inspection_complete'],
|
||||
'options' => ['nullable', 'array'],
|
||||
'options.inspection_setting' => ['nullable', 'array'],
|
||||
'options.inspection_scope' => ['nullable', 'array'],
|
||||
'options.inspection_scope.type' => ['nullable', 'string', 'in:all,sampling,group'],
|
||||
'options.inspection_scope.sample_size' => ['nullable', 'integer', 'min:1'],
|
||||
'options.inspection_scope.sample_base' => ['nullable', 'string', 'in:order,lot'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -36,6 +42,12 @@ public function attributes(): array
|
||||
'connection_type' => '연결유형',
|
||||
'connection_target' => '연결대상',
|
||||
'completion_type' => '완료유형',
|
||||
'options' => '옵션',
|
||||
'options.inspection_setting' => '검사설정',
|
||||
'options.inspection_scope' => '검사범위',
|
||||
'options.inspection_scope.type' => '검사범위 유형',
|
||||
'options.inspection_scope.sample_size' => '샘플 크기',
|
||||
'options.inspection_scope.sample_base' => '샘플 기준',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,12 @@ public function rules(): array
|
||||
'connection_type' => ['nullable', 'string', 'max:20'],
|
||||
'connection_target' => ['nullable', 'string', 'max:255'],
|
||||
'completion_type' => ['nullable', 'string', 'in:click_complete,selection_complete,inspection_complete'],
|
||||
'options' => ['nullable', 'array'],
|
||||
'options.inspection_setting' => ['nullable', 'array'],
|
||||
'options.inspection_scope' => ['nullable', 'array'],
|
||||
'options.inspection_scope.type' => ['nullable', 'string', 'in:all,sampling,group'],
|
||||
'options.inspection_scope.sample_size' => ['nullable', 'integer', 'min:1'],
|
||||
'options.inspection_scope.sample_base' => ['nullable', 'string', 'in:order,lot'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -36,6 +42,12 @@ public function attributes(): array
|
||||
'connection_type' => '연결유형',
|
||||
'connection_target' => '연결대상',
|
||||
'completion_type' => '완료유형',
|
||||
'options' => '옵션',
|
||||
'options.inspection_setting' => '검사설정',
|
||||
'options.inspection_scope' => '검사범위',
|
||||
'options.inspection_scope.type' => '검사범위 유형',
|
||||
'options.inspection_scope.sample_size' => '샘플 크기',
|
||||
'options.inspection_scope.sample_base' => '샘플 기준',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'address' => ['nullable', 'string', 'max:255'],
|
||||
'address' => ['nullable', 'string', 'max:500'],
|
||||
'latitude' => ['nullable', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['nullable', 'numeric', 'between:-180,180'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
|
||||
@@ -15,7 +15,7 @@ public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'address' => ['nullable', 'string', 'max:255'],
|
||||
'address' => ['nullable', 'string', 'max:500'],
|
||||
'latitude' => ['nullable', 'numeric', 'between:-90,90'],
|
||||
'longitude' => ['nullable', 'numeric', 'between:-180,180'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\VehicleDispatch;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class VehicleDispatchUpdateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'freight_cost_type' => 'nullable|in:prepaid,collect',
|
||||
'logistics_company' => 'nullable|string|max:100',
|
||||
'arrival_datetime' => 'nullable|date',
|
||||
'tonnage' => 'nullable|string|max:20',
|
||||
'vehicle_no' => 'nullable|string|max:20',
|
||||
'driver_contact' => 'nullable|string|max:50',
|
||||
'remarks' => 'nullable|string',
|
||||
'supply_amount' => 'nullable|numeric|min:0',
|
||||
'vat' => 'nullable|numeric|min:0',
|
||||
'total_amount' => 'nullable|numeric|min:0',
|
||||
'status' => 'nullable|in:draft,completed',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ public function rules(): array
|
||||
'inputs' => 'required|array|min:1',
|
||||
'inputs.*.stock_lot_id' => 'required|integer',
|
||||
'inputs.*.qty' => 'required|numeric|gt:0',
|
||||
'inputs.*.bom_group_key' => 'sometimes|nullable|string|max:100',
|
||||
'replace' => 'sometimes|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,16 @@ public function rules(): array
|
||||
'inspection_data.nonConformingContent' => 'nullable|string|max:1000',
|
||||
'inspection_data.templateValues' => 'nullable|array',
|
||||
'inspection_data.templateValues.*' => 'nullable',
|
||||
// 절곡 제품별 검사 데이터
|
||||
'inspection_data.products' => 'nullable|array',
|
||||
'inspection_data.products.*.id' => 'required_with:inspection_data.products|string',
|
||||
'inspection_data.products.*.bendingStatus' => ['nullable', Rule::in(['양호', '불량'])],
|
||||
'inspection_data.products.*.lengthMeasured' => 'nullable|string|max:50',
|
||||
'inspection_data.products.*.widthMeasured' => 'nullable|string|max:50',
|
||||
'inspection_data.products.*.gapPoints' => 'nullable|array',
|
||||
'inspection_data.products.*.gapPoints.*.point' => 'nullable|string',
|
||||
'inspection_data.products.*.gapPoints.*.designValue' => 'nullable|string',
|
||||
'inspection_data.products.*.gapPoints.*.measured' => 'nullable|string|max:50',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
41
app/Models/Commons/Holiday.php
Normal file
41
app/Models/Commons/Holiday.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Holiday extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'holidays';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'name',
|
||||
'type',
|
||||
'is_recurring',
|
||||
'memo',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
'is_recurring' => 'boolean',
|
||||
];
|
||||
|
||||
public function scopeForTenant($query, int $tenantId)
|
||||
{
|
||||
return $query->where('tenant_id', $tenantId);
|
||||
}
|
||||
|
||||
public function scopeForYear($query, int $year)
|
||||
{
|
||||
return $query->whereYear('start_date', $year);
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ class Document extends Model
|
||||
'linkable_id',
|
||||
'submitted_at',
|
||||
'completed_at',
|
||||
'rendered_html',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
|
||||
@@ -22,6 +22,7 @@ class DocumentTemplateSection extends Model
|
||||
protected $fillable = [
|
||||
'template_id',
|
||||
'title',
|
||||
'description',
|
||||
'image_path',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
@@ -22,6 +22,7 @@ class ProcessStep extends Model
|
||||
'connection_type',
|
||||
'connection_target',
|
||||
'completion_type',
|
||||
'options',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -30,6 +31,7 @@ class ProcessStep extends Model
|
||||
'needs_inspection' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Orders\Order;
|
||||
use App\Models\Process;
|
||||
use App\Models\Qualitys\Inspection;
|
||||
use App\Models\Tenants\Department;
|
||||
use App\Models\Tenants\Shipment;
|
||||
use App\Traits\Auditable;
|
||||
@@ -234,6 +235,14 @@ public function shipments(): HasMany
|
||||
return $this->hasMany(Shipment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 품질검사 (IQC/PQC/FQC)
|
||||
*/
|
||||
public function inspections(): HasMany
|
||||
{
|
||||
return $this->hasMany(Inspection::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
*/
|
||||
|
||||
@@ -27,6 +27,7 @@ class WorkOrderMaterialInput extends Model
|
||||
'work_order_item_id',
|
||||
'stock_lot_id',
|
||||
'item_id',
|
||||
'bom_group_key',
|
||||
'qty',
|
||||
'input_by',
|
||||
'input_at',
|
||||
|
||||
57
app/Models/Qualitys/AuditChecklist.php
Normal file
57
app/Models/Qualitys/AuditChecklist.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Traits\Auditable;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class AuditChecklist extends Model
|
||||
{
|
||||
use Auditable, BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $table = 'audit_checklists';
|
||||
|
||||
const STATUS_DRAFT = 'draft';
|
||||
|
||||
const STATUS_IN_PROGRESS = 'in_progress';
|
||||
|
||||
const STATUS_COMPLETED = 'completed';
|
||||
|
||||
const TYPE_STANDARD_MANUAL = 'standard_manual';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'year',
|
||||
'quarter',
|
||||
'type',
|
||||
'status',
|
||||
'options',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'year' => 'integer',
|
||||
'quarter' => 'integer',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
public function categories(): HasMany
|
||||
{
|
||||
return $this->hasMany(AuditChecklistCategory::class, 'checklist_id')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function isDraft(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_COMPLETED;
|
||||
}
|
||||
}
|
||||
35
app/Models/Qualitys/AuditChecklistCategory.php
Normal file
35
app/Models/Qualitys/AuditChecklistCategory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class AuditChecklistCategory extends Model
|
||||
{
|
||||
protected $table = 'audit_checklist_categories';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'checklist_id',
|
||||
'title',
|
||||
'sort_order',
|
||||
'options',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort_order' => 'integer',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
public function checklist(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AuditChecklist::class, 'checklist_id');
|
||||
}
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(AuditChecklistItem::class, 'category_id')->orderBy('sort_order');
|
||||
}
|
||||
}
|
||||
47
app/Models/Qualitys/AuditChecklistItem.php
Normal file
47
app/Models/Qualitys/AuditChecklistItem.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class AuditChecklistItem extends Model
|
||||
{
|
||||
protected $table = 'audit_checklist_items';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'category_id',
|
||||
'name',
|
||||
'description',
|
||||
'is_completed',
|
||||
'completed_at',
|
||||
'completed_by',
|
||||
'sort_order',
|
||||
'options',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_completed' => 'boolean',
|
||||
'completed_at' => 'datetime',
|
||||
'sort_order' => 'integer',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AuditChecklistCategory::class, 'category_id');
|
||||
}
|
||||
|
||||
public function completedByUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'completed_by');
|
||||
}
|
||||
|
||||
public function standardDocuments(): HasMany
|
||||
{
|
||||
return $this->hasMany(AuditStandardDocument::class, 'checklist_item_id');
|
||||
}
|
||||
}
|
||||
37
app/Models/Qualitys/AuditStandardDocument.php
Normal file
37
app/Models/Qualitys/AuditStandardDocument.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\Documents\Document;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class AuditStandardDocument extends Model
|
||||
{
|
||||
protected $table = 'audit_standard_documents';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'checklist_item_id',
|
||||
'title',
|
||||
'version',
|
||||
'date',
|
||||
'document_id',
|
||||
'options',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'date',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
public function checklistItem(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AuditChecklistItem::class, 'checklist_item_id');
|
||||
}
|
||||
|
||||
public function document(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Document::class);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use App\Models\Items\Item;
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Production\WorkOrder;
|
||||
use App\Traits\Auditable;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -23,6 +24,7 @@
|
||||
* @property string|null $inspection_date 검사일
|
||||
* @property int|null $item_id 품목 ID
|
||||
* @property string $lot_no LOT번호
|
||||
* @property int|null $work_order_id 작업지시 ID (PQC/FQC용)
|
||||
* @property int|null $inspector_id 검사자 ID
|
||||
* @property array|null $meta 메타정보 (process_name, quantity, unit 등)
|
||||
* @property array|null $items 검사항목 배열
|
||||
@@ -47,6 +49,7 @@ class Inspection extends Model
|
||||
'inspection_date',
|
||||
'item_id',
|
||||
'lot_no',
|
||||
'work_order_id',
|
||||
'inspector_id',
|
||||
'meta',
|
||||
'items',
|
||||
@@ -92,6 +95,14 @@ class Inspection extends Model
|
||||
|
||||
// ===== Relationships =====
|
||||
|
||||
/**
|
||||
* 작업지시 (PQC/FQC용)
|
||||
*/
|
||||
public function workOrder()
|
||||
{
|
||||
return $this->belongsTo(WorkOrder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목
|
||||
*/
|
||||
|
||||
76
app/Models/Qualitys/PerformanceReport.php
Normal file
76
app/Models/Qualitys/PerformanceReport.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use App\Traits\Auditable;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PerformanceReport extends Model
|
||||
{
|
||||
use Auditable, BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $table = 'performance_reports';
|
||||
|
||||
const STATUS_UNCONFIRMED = 'unconfirmed';
|
||||
|
||||
const STATUS_CONFIRMED = 'confirmed';
|
||||
|
||||
const STATUS_REPORTED = 'reported';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'quality_document_id',
|
||||
'year',
|
||||
'quarter',
|
||||
'confirmation_status',
|
||||
'confirmed_date',
|
||||
'confirmed_by',
|
||||
'memo',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'confirmed_date' => 'date',
|
||||
'year' => 'integer',
|
||||
'quarter' => 'integer',
|
||||
];
|
||||
|
||||
// ===== Relationships =====
|
||||
|
||||
public function qualityDocument()
|
||||
{
|
||||
return $this->belongsTo(QualityDocument::class);
|
||||
}
|
||||
|
||||
public function confirmer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'confirmed_by');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
// ===== Status Helpers =====
|
||||
|
||||
public function isUnconfirmed(): bool
|
||||
{
|
||||
return $this->confirmation_status === self::STATUS_UNCONFIRMED;
|
||||
}
|
||||
|
||||
public function isConfirmed(): bool
|
||||
{
|
||||
return $this->confirmation_status === self::STATUS_CONFIRMED;
|
||||
}
|
||||
|
||||
public function isReported(): bool
|
||||
{
|
||||
return $this->confirmation_status === self::STATUS_REPORTED;
|
||||
}
|
||||
}
|
||||
131
app/Models/Qualitys/QualityDocument.php
Normal file
131
app/Models/Qualitys/QualityDocument.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use App\Models\Orders\Client;
|
||||
use App\Traits\Auditable;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class QualityDocument extends Model
|
||||
{
|
||||
use Auditable, BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $table = 'quality_documents';
|
||||
|
||||
const STATUS_RECEIVED = 'received';
|
||||
|
||||
const STATUS_IN_PROGRESS = 'in_progress';
|
||||
|
||||
const STATUS_COMPLETED = 'completed';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'quality_doc_number',
|
||||
'site_name',
|
||||
'status',
|
||||
'client_id',
|
||||
'inspector_id',
|
||||
'received_date',
|
||||
'options',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'options' => 'array',
|
||||
'received_date' => 'date',
|
||||
];
|
||||
|
||||
// ===== Relationships =====
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class, 'client_id');
|
||||
}
|
||||
|
||||
public function inspector()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'inspector_id');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function documentOrders()
|
||||
{
|
||||
return $this->hasMany(QualityDocumentOrder::class);
|
||||
}
|
||||
|
||||
public function locations()
|
||||
{
|
||||
return $this->hasMany(QualityDocumentLocation::class);
|
||||
}
|
||||
|
||||
public function performanceReport()
|
||||
{
|
||||
return $this->hasOne(PerformanceReport::class);
|
||||
}
|
||||
|
||||
// ===== 채번 =====
|
||||
|
||||
public static function generateDocNumber(int $tenantId): string
|
||||
{
|
||||
$prefix = 'KD-QD';
|
||||
$yearMonth = now()->format('Ym');
|
||||
|
||||
$lastNo = static::withoutGlobalScopes()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('quality_doc_number', 'like', "{$prefix}-{$yearMonth}-%")
|
||||
->orderByDesc('quality_doc_number')
|
||||
->value('quality_doc_number');
|
||||
|
||||
if ($lastNo) {
|
||||
$seq = (int) substr($lastNo, -4) + 1;
|
||||
} else {
|
||||
$seq = 1;
|
||||
}
|
||||
|
||||
return sprintf('%s-%s-%04d', $prefix, $yearMonth, $seq);
|
||||
}
|
||||
|
||||
// ===== Status Helpers =====
|
||||
|
||||
public function isReceived(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_RECEIVED;
|
||||
}
|
||||
|
||||
public function isInProgress(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_IN_PROGRESS;
|
||||
}
|
||||
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
public static function mapStatusToFrontend(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
self::STATUS_RECEIVED => 'reception',
|
||||
self::STATUS_IN_PROGRESS => 'in_progress',
|
||||
self::STATUS_COMPLETED => 'completed',
|
||||
default => $status,
|
||||
};
|
||||
}
|
||||
|
||||
public static function mapStatusFromFrontend(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
'reception' => self::STATUS_RECEIVED,
|
||||
default => $status,
|
||||
};
|
||||
}
|
||||
}
|
||||
66
app/Models/Qualitys/QualityDocumentLocation.php
Normal file
66
app/Models/Qualitys/QualityDocumentLocation.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\Documents\Document;
|
||||
use App\Models\Orders\OrderItem;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class QualityDocumentLocation extends Model
|
||||
{
|
||||
protected $table = 'quality_document_locations';
|
||||
|
||||
const STATUS_PENDING = 'pending';
|
||||
|
||||
const STATUS_IN_PROGRESS = 'in_progress';
|
||||
|
||||
const STATUS_COMPLETED = 'completed';
|
||||
|
||||
protected $fillable = [
|
||||
'quality_document_id',
|
||||
'quality_document_order_id',
|
||||
'order_item_id',
|
||||
'post_width',
|
||||
'post_height',
|
||||
'change_reason',
|
||||
'inspection_data',
|
||||
'document_id',
|
||||
'inspection_status',
|
||||
'options',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'inspection_data' => 'array',
|
||||
'options' => 'array',
|
||||
];
|
||||
|
||||
public function qualityDocument()
|
||||
{
|
||||
return $this->belongsTo(QualityDocument::class);
|
||||
}
|
||||
|
||||
public function qualityDocumentOrder()
|
||||
{
|
||||
return $this->belongsTo(QualityDocumentOrder::class);
|
||||
}
|
||||
|
||||
public function orderItem()
|
||||
{
|
||||
return $this->belongsTo(OrderItem::class);
|
||||
}
|
||||
|
||||
public function document()
|
||||
{
|
||||
return $this->belongsTo(Document::class);
|
||||
}
|
||||
|
||||
public function isPending(): bool
|
||||
{
|
||||
return $this->inspection_status === self::STATUS_PENDING;
|
||||
}
|
||||
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return $this->inspection_status === self::STATUS_COMPLETED;
|
||||
}
|
||||
}
|
||||
31
app/Models/Qualitys/QualityDocumentOrder.php
Normal file
31
app/Models/Qualitys/QualityDocumentOrder.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Qualitys;
|
||||
|
||||
use App\Models\Orders\Order;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class QualityDocumentOrder extends Model
|
||||
{
|
||||
protected $table = 'quality_document_orders';
|
||||
|
||||
protected $fillable = [
|
||||
'quality_document_id',
|
||||
'order_id',
|
||||
];
|
||||
|
||||
public function qualityDocument()
|
||||
{
|
||||
return $this->belongsTo(QualityDocument::class);
|
||||
}
|
||||
|
||||
public function order()
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
|
||||
public function locations()
|
||||
{
|
||||
return $this->hasMany(QualityDocumentLocation::class);
|
||||
}
|
||||
}
|
||||
102
app/Models/Tenants/AccountCode.php
Normal file
102
app/Models/Tenants/AccountCode.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountCode extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'code',
|
||||
'name',
|
||||
'category',
|
||||
'sub_category',
|
||||
'parent_code',
|
||||
'depth',
|
||||
'department_type',
|
||||
'description',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'depth' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
// Categories (대분류)
|
||||
public const CATEGORY_ASSET = 'asset';
|
||||
public const CATEGORY_LIABILITY = 'liability';
|
||||
public const CATEGORY_CAPITAL = 'capital';
|
||||
public const CATEGORY_REVENUE = 'revenue';
|
||||
public const CATEGORY_EXPENSE = 'expense';
|
||||
|
||||
public const CATEGORIES = [
|
||||
self::CATEGORY_ASSET => '자산',
|
||||
self::CATEGORY_LIABILITY => '부채',
|
||||
self::CATEGORY_CAPITAL => '자본',
|
||||
self::CATEGORY_REVENUE => '수익',
|
||||
self::CATEGORY_EXPENSE => '비용',
|
||||
];
|
||||
|
||||
// Sub-categories (중분류)
|
||||
public const SUB_CATEGORIES = [
|
||||
'current_asset' => '유동자산',
|
||||
'fixed_asset' => '비유동자산',
|
||||
'current_liability' => '유동부채',
|
||||
'long_term_liability' => '비유동부채',
|
||||
'capital' => '자본',
|
||||
'sales_revenue' => '매출',
|
||||
'other_revenue' => '영업외수익',
|
||||
'cogs' => '매출원가',
|
||||
'selling_admin' => '판매비와관리비',
|
||||
'other_expense' => '영업외비용',
|
||||
];
|
||||
|
||||
// Department types (부문)
|
||||
public const DEPT_COMMON = 'common';
|
||||
public const DEPT_MANUFACTURING = 'manufacturing';
|
||||
public const DEPT_ADMIN = 'admin';
|
||||
|
||||
public const DEPARTMENT_TYPES = [
|
||||
self::DEPT_COMMON => '공통',
|
||||
self::DEPT_MANUFACTURING => '제조',
|
||||
self::DEPT_ADMIN => '관리',
|
||||
];
|
||||
|
||||
// Depth levels (계층)
|
||||
public const DEPTH_MAJOR = 1;
|
||||
public const DEPTH_MIDDLE = 2;
|
||||
public const DEPTH_MINOR = 3;
|
||||
|
||||
/**
|
||||
* 활성 계정과목만 조회
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 소분류(입력 가능 계정)만 조회
|
||||
*/
|
||||
public function scopeSelectable(Builder $query): Builder
|
||||
{
|
||||
return $query->where('depth', self::DEPTH_MINOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 하위 계정과목 관계
|
||||
*/
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_code', 'code')
|
||||
->where('tenant_id', $this->tenant_id);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,8 @@ class Approval extends Model
|
||||
'completed_at',
|
||||
'current_step',
|
||||
'attachments',
|
||||
'linkable_type',
|
||||
'linkable_id',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
@@ -135,6 +138,14 @@ public function referenceSteps(): HasMany
|
||||
->orderBy('step_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* 연결 대상 (Document 등)
|
||||
*/
|
||||
public function linkable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,58 @@ class Bill extends Model
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
// V8 확장 필드
|
||||
'instrument_type',
|
||||
'medium',
|
||||
'bill_category',
|
||||
'electronic_bill_no',
|
||||
'registration_org',
|
||||
'drawee',
|
||||
'acceptance_status',
|
||||
'acceptance_date',
|
||||
'acceptance_refusal_date',
|
||||
'acceptance_refusal_reason',
|
||||
'endorsement',
|
||||
'endorsement_order',
|
||||
'storage_place',
|
||||
'issuer_bank',
|
||||
'is_discounted',
|
||||
'discount_date',
|
||||
'discount_bank',
|
||||
'discount_rate',
|
||||
'discount_amount',
|
||||
'endorsement_date',
|
||||
'endorsee',
|
||||
'endorsement_reason',
|
||||
'collection_bank',
|
||||
'collection_request_date',
|
||||
'collection_fee',
|
||||
'collection_complete_date',
|
||||
'collection_result',
|
||||
'collection_deposit_date',
|
||||
'collection_deposit_amount',
|
||||
'settlement_bank',
|
||||
'payment_method',
|
||||
'actual_payment_date',
|
||||
'payment_place',
|
||||
'payment_place_detail',
|
||||
'renewal_date',
|
||||
'renewal_new_bill_no',
|
||||
'renewal_reason',
|
||||
'recourse_date',
|
||||
'recourse_amount',
|
||||
'recourse_target',
|
||||
'recourse_reason',
|
||||
'buyback_date',
|
||||
'buyback_amount',
|
||||
'buyback_bank',
|
||||
'dishonored_date',
|
||||
'dishonored_reason',
|
||||
'has_protest',
|
||||
'protest_date',
|
||||
'recourse_notice_date',
|
||||
'recourse_notice_deadline',
|
||||
'is_split',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -41,21 +93,57 @@ class Bill extends Model
|
||||
'bank_account_id' => 'integer',
|
||||
'installment_count' => 'integer',
|
||||
'is_electronic' => 'boolean',
|
||||
// V8 확장 casts
|
||||
'acceptance_date' => 'date',
|
||||
'acceptance_refusal_date' => 'date',
|
||||
'discount_date' => 'date',
|
||||
'discount_rate' => 'decimal:2',
|
||||
'discount_amount' => 'decimal:2',
|
||||
'endorsement_date' => 'date',
|
||||
'collection_request_date' => 'date',
|
||||
'collection_fee' => 'decimal:2',
|
||||
'collection_complete_date' => 'date',
|
||||
'collection_deposit_date' => 'date',
|
||||
'collection_deposit_amount' => 'decimal:2',
|
||||
'actual_payment_date' => 'date',
|
||||
'renewal_date' => 'date',
|
||||
'recourse_date' => 'date',
|
||||
'recourse_amount' => 'decimal:2',
|
||||
'buyback_date' => 'date',
|
||||
'buyback_amount' => 'decimal:2',
|
||||
'dishonored_date' => 'date',
|
||||
'protest_date' => 'date',
|
||||
'recourse_notice_date' => 'date',
|
||||
'recourse_notice_deadline' => 'date',
|
||||
'is_discounted' => 'boolean',
|
||||
'has_protest' => 'boolean',
|
||||
'is_split' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* 배열/JSON 변환 시 날짜 형식 지정
|
||||
*/
|
||||
/**
|
||||
* 날짜 cast 필드 목록 (toArray에서 Y-m-d 형식 변환용)
|
||||
*/
|
||||
private const DATE_FIELDS = [
|
||||
'issue_date', 'maturity_date',
|
||||
'acceptance_date', 'acceptance_refusal_date',
|
||||
'discount_date', 'endorsement_date',
|
||||
'collection_request_date', 'collection_complete_date', 'collection_deposit_date',
|
||||
'actual_payment_date',
|
||||
'renewal_date', 'recourse_date', 'buyback_date',
|
||||
'dishonored_date', 'protest_date', 'recourse_notice_date', 'recourse_notice_deadline',
|
||||
];
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = parent::toArray();
|
||||
|
||||
// 날짜 필드를 Y-m-d 형식으로 변환
|
||||
if (isset($array['issue_date']) && $this->issue_date) {
|
||||
$array['issue_date'] = $this->issue_date->format('Y-m-d');
|
||||
}
|
||||
if (isset($array['maturity_date']) && $this->maturity_date) {
|
||||
$array['maturity_date'] = $this->maturity_date->format('Y-m-d');
|
||||
foreach (self::DATE_FIELDS as $field) {
|
||||
if (isset($array[$field]) && $this->{$field}) {
|
||||
$array[$field] = $this->{$field}->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
@@ -69,14 +157,42 @@ public function toArray(): array
|
||||
'issued' => '발행',
|
||||
];
|
||||
|
||||
/**
|
||||
* 증권종류
|
||||
*/
|
||||
public const INSTRUMENT_TYPES = [
|
||||
'promissory' => '약속어음',
|
||||
'exchange' => '환어음',
|
||||
'cashierCheck' => '자기앞수표',
|
||||
'currentCheck' => '당좌수표',
|
||||
];
|
||||
|
||||
/**
|
||||
* 수취 어음 상태 목록
|
||||
*/
|
||||
public const RECEIVED_STATUSES = [
|
||||
'stored' => '보관중',
|
||||
'endorsed' => '배서양도',
|
||||
'discounted' => '할인',
|
||||
'collectionRequest' => '추심의뢰',
|
||||
'collectionComplete' => '추심완료',
|
||||
'maturityDeposit' => '만기입금',
|
||||
'paymentComplete' => '결제완료',
|
||||
'dishonored' => '부도',
|
||||
'renewed' => '개서',
|
||||
'buyback' => '환매',
|
||||
// 하위호환
|
||||
'maturityAlert' => '만기입금(7일전)',
|
||||
'maturityResult' => '만기결과',
|
||||
'paymentComplete' => '결제완료',
|
||||
];
|
||||
|
||||
/**
|
||||
* 수취 수표 상태 목록
|
||||
*/
|
||||
public const RECEIVED_CHECK_STATUSES = [
|
||||
'stored' => '보관중',
|
||||
'endorsed' => '배서양도',
|
||||
'deposited' => '입금',
|
||||
'dishonored' => '부도',
|
||||
];
|
||||
|
||||
@@ -85,10 +201,25 @@ public function toArray(): array
|
||||
*/
|
||||
public const ISSUED_STATUSES = [
|
||||
'stored' => '보관중',
|
||||
'issued' => '지급대기',
|
||||
'maturityPayment' => '만기결제',
|
||||
'paymentComplete' => '결제완료',
|
||||
'dishonored' => '부도',
|
||||
'renewed' => '개서',
|
||||
// 하위호환
|
||||
'maturityAlert' => '만기입금(7일전)',
|
||||
'collectionRequest' => '추심의뢰',
|
||||
'collectionComplete' => '추심완료',
|
||||
'suing' => '추소중',
|
||||
];
|
||||
|
||||
/**
|
||||
* 발행 수표 상태 목록
|
||||
*/
|
||||
public const ISSUED_CHECK_STATUSES = [
|
||||
'stored' => '보관중',
|
||||
'issued' => '지급대기',
|
||||
'cashed' => '현금화',
|
||||
'dishonored' => '부도',
|
||||
];
|
||||
|
||||
@@ -149,11 +280,25 @@ public function getBillTypeLabelAttribute(): string
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
$isCheck = in_array($this->instrument_type, ['cashierCheck', 'currentCheck']);
|
||||
|
||||
if ($this->bill_type === 'received') {
|
||||
return self::RECEIVED_STATUSES[$this->status] ?? $this->status;
|
||||
$statuses = $isCheck ? self::RECEIVED_CHECK_STATUSES : self::RECEIVED_STATUSES;
|
||||
|
||||
return $statuses[$this->status] ?? self::RECEIVED_STATUSES[$this->status] ?? $this->status;
|
||||
}
|
||||
|
||||
return self::ISSUED_STATUSES[$this->status] ?? $this->status;
|
||||
$statuses = $isCheck ? self::ISSUED_CHECK_STATUSES : self::ISSUED_STATUSES;
|
||||
|
||||
return $statuses[$this->status] ?? self::ISSUED_STATUSES[$this->status] ?? $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 증권종류 라벨
|
||||
*/
|
||||
public function getInstrumentTypeLabelAttribute(): string
|
||||
{
|
||||
return self::INSTRUMENT_TYPES[$this->instrument_type] ?? $this->instrument_type ?? '약속어음';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,8 +12,10 @@ class BillInstallment extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'bill_id',
|
||||
'type',
|
||||
'installment_date',
|
||||
'amount',
|
||||
'counterparty',
|
||||
'note',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
@@ -34,6 +34,9 @@ class ExpenseAccount extends Model
|
||||
'vendor_name',
|
||||
'payment_method',
|
||||
'card_no',
|
||||
'loan_id',
|
||||
'journal_entry_id',
|
||||
'journal_entry_line_id',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
@@ -53,6 +56,9 @@ class ExpenseAccount extends Model
|
||||
|
||||
public const TYPE_OFFICE = 'office';
|
||||
|
||||
// 세부 유형 상수 (접대비)
|
||||
public const SUB_TYPE_GIFT_CERTIFICATE = 'gift_certificate';
|
||||
|
||||
// 세부 유형 상수 (복리후생)
|
||||
public const SUB_TYPE_MEAL = 'meal';
|
||||
|
||||
|
||||
55
app/Models/Tenants/JournalEntry.php
Normal file
55
app/Models/Tenants/JournalEntry.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class JournalEntry extends Model
|
||||
{
|
||||
use BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'entry_no',
|
||||
'entry_date',
|
||||
'entry_type',
|
||||
'description',
|
||||
'total_debit',
|
||||
'total_credit',
|
||||
'status',
|
||||
'source_type',
|
||||
'source_key',
|
||||
'created_by_name',
|
||||
'attachment_note',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'entry_date' => 'date',
|
||||
'total_debit' => 'integer',
|
||||
'total_credit' => 'integer',
|
||||
];
|
||||
|
||||
// Status
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_CONFIRMED = 'confirmed';
|
||||
|
||||
// Source type
|
||||
public const SOURCE_MANUAL = 'manual';
|
||||
public const SOURCE_BANK_TRANSACTION = 'bank_transaction';
|
||||
public const SOURCE_TAX_INVOICE = 'tax_invoice';
|
||||
public const SOURCE_CARD_TRANSACTION = 'card_transaction';
|
||||
|
||||
// Entry type
|
||||
public const TYPE_GENERAL = 'general';
|
||||
|
||||
/**
|
||||
* 분개 행 관계
|
||||
*/
|
||||
public function lines(): HasMany
|
||||
{
|
||||
return $this->hasMany(JournalEntryLine::class)->orderBy('line_no');
|
||||
}
|
||||
}
|
||||
45
app/Models/Tenants/JournalEntryLine.php
Normal file
45
app/Models/Tenants/JournalEntryLine.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class JournalEntryLine extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'journal_entry_id',
|
||||
'line_no',
|
||||
'dc_type',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'trading_partner_id',
|
||||
'trading_partner_name',
|
||||
'debit_amount',
|
||||
'credit_amount',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'line_no' => 'integer',
|
||||
'debit_amount' => 'integer',
|
||||
'credit_amount' => 'integer',
|
||||
'trading_partner_id' => 'integer',
|
||||
];
|
||||
|
||||
// DC Type
|
||||
public const DC_DEBIT = 'debit';
|
||||
public const DC_CREDIT = 'credit';
|
||||
|
||||
/**
|
||||
* 전표 관계
|
||||
*/
|
||||
public function journalEntry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(JournalEntry::class);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user