diff --git a/app/Http/Controllers/Api/V1/AccountSubjectController.php b/app/Http/Controllers/Api/V1/AccountSubjectController.php index 20af5a2..4f8f094 100644 --- a/app/Http/Controllers/Api/V1/AccountSubjectController.php +++ b/app/Http/Controllers/Api/V1/AccountSubjectController.php @@ -5,6 +5,7 @@ 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; @@ -19,7 +20,10 @@ public function __construct( */ public function index(Request $request) { - $params = $request->only(['search', 'category']); + $params = $request->only([ + 'search', 'category', 'sub_category', + 'department_type', 'depth', 'is_active', 'selectable', + ]); $subjects = $this->service->index($params); @@ -36,6 +40,16 @@ public function store(StoreAccountSubjectRequest $request) 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')); + } + /** * 계정과목 활성/비활성 토글 */ @@ -57,4 +71,17 @@ public function destroy(int $id) return ApiResponse::success(null, __('message.deleted')); } + + /** + * 기본 계정과목표 일괄 생성 (더존 표준) + */ + public function seedDefaults() + { + $count = $this->service->seedDefaults(); + + return ApiResponse::success( + ['inserted_count' => $count], + __('message.created') + ); + } } diff --git a/app/Http/Controllers/Api/V1/BarobillSettingController.php b/app/Http/Controllers/Api/V1/BarobillSettingController.php index 9980397..ba16b88 100644 --- a/app/Http/Controllers/Api/V1/BarobillSettingController.php +++ b/app/Http/Controllers/Api/V1/BarobillSettingController.php @@ -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')); } } diff --git a/app/Http/Controllers/Api/V1/CardTransactionController.php b/app/Http/Controllers/Api/V1/CardTransactionController.php index 25a457a..73a880a 100644 --- a/app/Http/Controllers/Api/V1/CardTransactionController.php +++ b/app/Http/Controllers/Api/V1/CardTransactionController.php @@ -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')); + } } diff --git a/app/Http/Controllers/Api/V1/TaxInvoiceController.php b/app/Http/Controllers/Api/V1/TaxInvoiceController.php index 2fe8dcf..1d65ada 100644 --- a/app/Http/Controllers/Api/V1/TaxInvoiceController.php +++ b/app/Http/Controllers/Api/V1/TaxInvoiceController.php @@ -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')); } } diff --git a/app/Http/Controllers/Api/V1/WelfareController.php b/app/Http/Controllers/Api/V1/WelfareController.php index 64795f4..055bfcd 100644 --- a/app/Http/Controllers/Api/V1/WelfareController.php +++ b/app/Http/Controllers/Api/V1/WelfareController.php @@ -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')); } diff --git a/app/Http/Requests/TaxInvoice/CreateTaxInvoiceRequest.php b/app/Http/Requests/TaxInvoice/CreateTaxInvoiceRequest.php index d65e989..b776870 100644 --- a/app/Http/Requests/TaxInvoice/CreateTaxInvoiceRequest.php +++ b/app/Http/Requests/TaxInvoice/CreateTaxInvoiceRequest.php @@ -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'], diff --git a/app/Http/Requests/V1/AccountSubject/StoreAccountSubjectRequest.php b/app/Http/Requests/V1/AccountSubject/StoreAccountSubjectRequest.php index 74316ab..25fde5b 100644 --- a/app/Http/Requests/V1/AccountSubject/StoreAccountSubjectRequest.php +++ b/app/Http/Requests/V1/AccountSubject/StoreAccountSubjectRequest.php @@ -17,6 +17,12 @@ public function rules(): array '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'], ]; } @@ -26,6 +32,8 @@ public function messages(): array 'code.required' => '계정과목 코드를 입력하세요.', 'name.required' => '계정과목명을 입력하세요.', 'category.in' => '유효한 분류를 선택하세요.', + 'depth.in' => '계층은 1(대), 2(중), 3(소) 중 하나여야 합니다.', + 'department_type.in' => '부문은 common, manufacturing, admin 중 하나여야 합니다.', ]; } } diff --git a/app/Http/Requests/V1/AccountSubject/UpdateAccountSubjectRequest.php b/app/Http/Requests/V1/AccountSubject/UpdateAccountSubjectRequest.php new file mode 100644 index 0000000..5cc78d1 --- /dev/null +++ b/app/Http/Requests/V1/AccountSubject/UpdateAccountSubjectRequest.php @@ -0,0 +1,36 @@ + ['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 중 하나여야 합니다.', + ]; + } +} diff --git a/app/Models/Tenants/AccountCode.php b/app/Models/Tenants/AccountCode.php index 7eb465a..7ecb1b4 100644 --- a/app/Models/Tenants/AccountCode.php +++ b/app/Models/Tenants/AccountCode.php @@ -15,16 +15,22 @@ class AccountCode extends Model '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 + // Categories (대분류) public const CATEGORY_ASSET = 'asset'; public const CATEGORY_LIABILITY = 'liability'; public const CATEGORY_CAPITAL = 'capital'; @@ -39,6 +45,36 @@ class AccountCode extends Model 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; + /** * 활성 계정과목만 조회 */ @@ -46,4 +82,21 @@ 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); + } } diff --git a/app/Models/Tenants/ExpenseAccount.php b/app/Models/Tenants/ExpenseAccount.php index c9fe540..4351c35 100644 --- a/app/Models/Tenants/ExpenseAccount.php +++ b/app/Models/Tenants/ExpenseAccount.php @@ -35,6 +35,8 @@ class ExpenseAccount extends Model 'payment_method', 'card_no', 'loan_id', + 'journal_entry_id', + 'journal_entry_line_id', 'created_by', 'updated_by', 'deleted_by', diff --git a/app/Models/Tenants/JournalEntry.php b/app/Models/Tenants/JournalEntry.php index 17cdd6f..6a0970a 100644 --- a/app/Models/Tenants/JournalEntry.php +++ b/app/Models/Tenants/JournalEntry.php @@ -39,6 +39,8 @@ class JournalEntry extends Model // 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'; diff --git a/app/Services/AccountCodeService.php b/app/Services/AccountCodeService.php index c6342db..5524b4a 100644 --- a/app/Services/AccountCodeService.php +++ b/app/Services/AccountCodeService.php @@ -4,6 +4,7 @@ use App\Models\Tenants\AccountCode; use App\Models\Tenants\JournalEntryLine; +use Illuminate\Support\Facades\DB; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class AccountCodeService extends Service @@ -27,12 +28,37 @@ public function index(array $params): array }); } - // 분류 필터 + // 분류 필터 (대분류) if (! empty($params['category'])) { $query->where('category', $params['category']); } - return $query->orderBy('sort_order')->orderBy('code')->get()->toArray(); + // 중분류 필터 + if (! empty($params['sub_category'])) { + $query->where('sub_category', $params['sub_category']); + } + + // 부문 필터 + if (! empty($params['department_type'])) { + $query->where('department_type', $params['department_type']); + } + + // 계층 필터 + if (! empty($params['depth'])) { + $query->where('depth', (int) $params['depth']); + } + + // 활성 상태 필터 + if (isset($params['is_active'])) { + $query->where('is_active', filter_var($params['is_active'], FILTER_VALIDATE_BOOLEAN)); + } + + // 선택 가능한 계정만 (소분류만 = Select용) + if (! empty($params['selectable'])) { + $query->selectable(); + } + + return $query->orderBy('code')->orderBy('sort_order')->get()->toArray(); } /** @@ -57,6 +83,11 @@ public function store(array $data): AccountCode $accountCode->code = $data['code']; $accountCode->name = $data['name']; $accountCode->category = $data['category'] ?? null; + $accountCode->sub_category = $data['sub_category'] ?? null; + $accountCode->parent_code = $data['parent_code'] ?? null; + $accountCode->depth = $data['depth'] ?? AccountCode::DEPTH_MINOR; + $accountCode->department_type = $data['department_type'] ?? AccountCode::DEPT_COMMON; + $accountCode->description = $data['description'] ?? null; $accountCode->sort_order = $data['sort_order'] ?? 0; $accountCode->is_active = true; $accountCode->save(); @@ -64,6 +95,36 @@ public function store(array $data): AccountCode return $accountCode; } + /** + * 계정과목 수정 + */ + public function update(int $id, array $data): AccountCode + { + $tenantId = $this->tenantId(); + + $accountCode = AccountCode::query() + ->where('tenant_id', $tenantId) + ->findOrFail($id); + + // 코드 변경 시 중복 체크 + if (isset($data['code']) && $data['code'] !== $accountCode->code) { + $exists = AccountCode::query() + ->where('tenant_id', $tenantId) + ->where('code', $data['code']) + ->where('id', '!=', $id) + ->exists(); + + if ($exists) { + throw new BadRequestHttpException(__('error.account_subject.duplicate_code')); + } + } + + $accountCode->fill($data); + $accountCode->save(); + + return $accountCode; + } + /** * 계정과목 활성/비활성 토글 */ @@ -106,4 +167,242 @@ public function destroy(int $id): bool return true; } + + /** + * 기본 계정과목표 일괄 생성 (초기 세팅) + */ + public function seedDefaults(): int + { + $tenantId = $this->tenantId(); + + $defaults = $this->getDefaultAccountCodes(); + $insertedCount = 0; + + DB::transaction(function () use ($tenantId, $defaults, &$insertedCount) { + foreach ($defaults as $item) { + $exists = AccountCode::query() + ->where('tenant_id', $tenantId) + ->where('code', $item['code']) + ->exists(); + + if (! $exists) { + AccountCode::create(array_merge($item, ['tenant_id' => $tenantId])); + $insertedCount++; + } + } + }); + + return $insertedCount; + } + + /** + * 기본 계정과목표 데이터 (더존 Smart A 표준 기반) + * + * 코드 체계: 5자리 (10100~99900) + * - 10100~24000: 자산 + * - 25000~31700: 부채 + * - 33100~38700: 자본 + * - 40100~41000: 매출 + * - 50100~53700: 매출원가/제조경비 (제조부문) + * - 80100~84800: 판매비와관리비 (관리부문) + * - 90100~99900: 영업외수익/비용 + * + * 계층: depth 1(대분류) → depth 2(중분류) → depth 3(소분류=더존 실제코드) + */ + private function getDefaultAccountCodes(): array + { + $c = fn ($code, $name, $cat, $sub, $parent, $depth, $dept, $sort) => [ + 'code' => $code, 'name' => $name, 'category' => $cat, + 'sub_category' => $sub, 'parent_code' => $parent, + 'depth' => $depth, 'department_type' => $dept, 'sort_order' => $sort, + ]; + + return [ + // ============================================================ + // 자산 (Assets) + // ============================================================ + $c('1', '자산', 'asset', null, null, 1, 'common', 100), + + // -- 유동자산 -- + $c('11', '유동자산', 'asset', 'current_asset', '1', 2, 'common', 110), + $c('10100', '현금', 'asset', 'current_asset', '11', 3, 'common', 1010), + $c('10200', '당좌예금', 'asset', 'current_asset', '11', 3, 'common', 1020), + $c('10300', '보통예금', 'asset', 'current_asset', '11', 3, 'common', 1030), + $c('10400', '기타제예금', 'asset', 'current_asset', '11', 3, 'common', 1040), + $c('10500', '정기적금', 'asset', 'current_asset', '11', 3, 'common', 1050), + $c('10800', '외상매출금', 'asset', 'current_asset', '11', 3, 'common', 1080), + $c('10900', '대손충당금(외상매출금)', 'asset', 'current_asset', '11', 3, 'common', 1090), + $c('11000', '받을어음', 'asset', 'current_asset', '11', 3, 'common', 1100), + $c('11400', '단기대여금', 'asset', 'current_asset', '11', 3, 'common', 1140), + $c('11600', '미수수익', 'asset', 'current_asset', '11', 3, 'common', 1160), + $c('12000', '미수금', 'asset', 'current_asset', '11', 3, 'common', 1200), + $c('12200', '소모품', 'asset', 'current_asset', '11', 3, 'common', 1220), + $c('12500', '미환급세금', 'asset', 'current_asset', '11', 3, 'common', 1250), + $c('13100', '선급금', 'asset', 'current_asset', '11', 3, 'common', 1310), + $c('13300', '선급비용', 'asset', 'current_asset', '11', 3, 'common', 1330), + $c('13400', '가지급금', 'asset', 'current_asset', '11', 3, 'common', 1340), + $c('13500', '부가세대급금', 'asset', 'current_asset', '11', 3, 'common', 1350), + $c('13600', '선납세금', 'asset', 'current_asset', '11', 3, 'common', 1360), + $c('14000', '선납법인세', 'asset', 'current_asset', '11', 3, 'common', 1400), + + // -- 재고자산 -- + $c('12', '재고자산', 'asset', 'current_asset', '1', 2, 'common', 120), + $c('14600', '상품', 'asset', 'current_asset', '12', 3, 'common', 1460), + $c('15000', '제품', 'asset', 'current_asset', '12', 3, 'common', 1500), + $c('15300', '원재료', 'asset', 'current_asset', '12', 3, 'common', 1530), + $c('16200', '부재료', 'asset', 'current_asset', '12', 3, 'common', 1620), + $c('16700', '저장품', 'asset', 'current_asset', '12', 3, 'common', 1670), + $c('16900', '재공품', 'asset', 'current_asset', '12', 3, 'common', 1690), + + // -- 비유동자산 -- + $c('13', '비유동자산', 'asset', 'fixed_asset', '1', 2, 'common', 130), + $c('17600', '장기성예금', 'asset', 'fixed_asset', '13', 3, 'common', 1760), + $c('17900', '장기대여금', 'asset', 'fixed_asset', '13', 3, 'common', 1790), + $c('18700', '투자부동산', 'asset', 'fixed_asset', '13', 3, 'common', 1870), + $c('19200', '단체퇴직보험예치금', 'asset', 'fixed_asset', '13', 3, 'common', 1920), + $c('20100', '토지', 'asset', 'fixed_asset', '13', 3, 'common', 2010), + $c('20200', '건물', 'asset', 'fixed_asset', '13', 3, 'common', 2020), + $c('20300', '감가상각누계액(건물)', 'asset', 'fixed_asset', '13', 3, 'common', 2030), + $c('20400', '구축물', 'asset', 'fixed_asset', '13', 3, 'common', 2040), + $c('20500', '감가상각누계액(구축물)', 'asset', 'fixed_asset', '13', 3, 'common', 2050), + $c('20600', '기계장치', 'asset', 'fixed_asset', '13', 3, 'common', 2060), + $c('20700', '감가상각누계액(기계장치)', 'asset', 'fixed_asset', '13', 3, 'common', 2070), + $c('20800', '차량운반구', 'asset', 'fixed_asset', '13', 3, 'common', 2080), + $c('20900', '감가상각누계액(차량운반구)', 'asset', 'fixed_asset', '13', 3, 'common', 2090), + $c('21000', '공구와기구', 'asset', 'fixed_asset', '13', 3, 'common', 2100), + $c('21200', '비품', 'asset', 'fixed_asset', '13', 3, 'common', 2120), + $c('21300', '건설중인자산', 'asset', 'fixed_asset', '13', 3, 'common', 2130), + $c('24000', '소프트웨어', 'asset', 'fixed_asset', '13', 3, 'common', 2400), + + // ============================================================ + // 부채 (Liabilities) + // ============================================================ + $c('2', '부채', 'liability', null, null, 1, 'common', 200), + + // -- 유동부채 -- + $c('21', '유동부채', 'liability', 'current_liability', '2', 2, 'common', 210), + $c('25100', '외상매입금', 'liability', 'current_liability', '21', 3, 'common', 2510), + $c('25200', '지급어음', 'liability', 'current_liability', '21', 3, 'common', 2520), + $c('25300', '미지급금', 'liability', 'current_liability', '21', 3, 'common', 2530), + $c('25400', '예수금', 'liability', 'current_liability', '21', 3, 'common', 2540), + $c('25500', '부가세예수금', 'liability', 'current_liability', '21', 3, 'common', 2550), + $c('25900', '선수금', 'liability', 'current_liability', '21', 3, 'common', 2590), + $c('26000', '단기차입금', 'liability', 'current_liability', '21', 3, 'common', 2600), + $c('26100', '미지급세금', 'liability', 'current_liability', '21', 3, 'common', 2610), + $c('26200', '미지급비용', 'liability', 'current_liability', '21', 3, 'common', 2620), + $c('26400', '유동성장기차입금', 'liability', 'current_liability', '21', 3, 'common', 2640), + $c('26500', '미지급배당금', 'liability', 'current_liability', '21', 3, 'common', 2650), + + // -- 비유동부채 -- + $c('22', '비유동부채', 'liability', 'long_term_liability', '2', 2, 'common', 220), + $c('29300', '장기차입금', 'liability', 'long_term_liability', '22', 3, 'common', 2930), + $c('29400', '임대보증금', 'liability', 'long_term_liability', '22', 3, 'common', 2940), + $c('29500', '퇴직급여충당부채', 'liability', 'long_term_liability', '22', 3, 'common', 2950), + $c('30700', '장기임대보증금', 'liability', 'long_term_liability', '22', 3, 'common', 3070), + + // ============================================================ + // 자본 (Capital) + // ============================================================ + $c('3', '자본', 'capital', null, null, 1, 'common', 300), + + // -- 자본금 -- + $c('31', '자본금', 'capital', 'capital', '3', 2, 'common', 310), + $c('33100', '자본금', 'capital', 'capital', '31', 3, 'common', 3310), + $c('33200', '우선주자본금', 'capital', 'capital', '31', 3, 'common', 3320), + + // -- 잉여금 -- + $c('32', '잉여금', 'capital', 'capital', '3', 2, 'common', 320), + $c('34100', '주식발행초과금', 'capital', 'capital', '32', 3, 'common', 3410), + $c('35100', '이익준비금', 'capital', 'capital', '32', 3, 'common', 3510), + $c('37500', '이월이익잉여금', 'capital', 'capital', '32', 3, 'common', 3750), + $c('37900', '당기순이익', 'capital', 'capital', '32', 3, 'common', 3790), + + // ============================================================ + // 수익 (Revenue) + // ============================================================ + $c('4', '수익', 'revenue', null, null, 1, 'common', 400), + + // -- 매출 -- + $c('41', '매출', 'revenue', 'sales_revenue', '4', 2, 'common', 410), + $c('40100', '상품매출', 'revenue', 'sales_revenue', '41', 3, 'common', 4010), + $c('40400', '제품매출', 'revenue', 'sales_revenue', '41', 3, 'common', 4040), + $c('40700', '공사수입금', 'revenue', 'sales_revenue', '41', 3, 'common', 4070), + $c('41000', '임대료수입', 'revenue', 'sales_revenue', '41', 3, 'common', 4100), + + // -- 영업외수익 -- + $c('42', '영업외수익', 'revenue', 'other_revenue', '4', 2, 'common', 420), + $c('90100', '이자수익', 'revenue', 'other_revenue', '42', 3, 'common', 9010), + $c('90300', '배당금수익', 'revenue', 'other_revenue', '42', 3, 'common', 9030), + $c('90400', '수입임대료', 'revenue', 'other_revenue', '42', 3, 'common', 9040), + $c('90700', '외환차익', 'revenue', 'other_revenue', '42', 3, 'common', 9070), + $c('93000', '잡이익', 'revenue', 'other_revenue', '42', 3, 'common', 9300), + + // ============================================================ + // 비용 (Expenses) + // ============================================================ + $c('5', '비용', 'expense', null, null, 1, 'common', 500), + + // -- 매출원가/제조원가 (제조부문) -- + $c('51', '매출원가', 'expense', 'cogs', '5', 2, 'manufacturing', 510), + $c('50100', '원재료비', 'expense', 'cogs', '51', 3, 'manufacturing', 5010), + $c('50200', '외주가공비', 'expense', 'cogs', '51', 3, 'manufacturing', 5020), + $c('50300', '급여(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5030), + $c('50400', '임금', 'expense', 'cogs', '51', 3, 'manufacturing', 5040), + $c('50500', '상여금(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5050), + $c('50800', '퇴직급여(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5080), + $c('51100', '복리후생비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5110), + $c('51200', '여비교통비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5120), + $c('51300', '접대비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5130), + $c('51400', '통신비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5140), + $c('51600', '전력비', 'expense', 'cogs', '51', 3, 'manufacturing', 5160), + $c('51700', '세금과공과금(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5170), + $c('51800', '감가상각비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5180), + $c('51900', '지급임차료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5190), + $c('52000', '수선비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5200), + $c('52100', '보험료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5210), + $c('52200', '차량유지비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5220), + $c('52400', '운반비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5240), + $c('53000', '소모품비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5300), + $c('53100', '지급수수료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5310), + + // -- 판매비와관리비 (관리부문) -- + $c('52', '판매비와관리비', 'expense', 'selling_admin', '5', 2, 'admin', 520), + $c('80100', '임원급여', 'expense', 'selling_admin', '52', 3, 'admin', 8010), + $c('80200', '직원급여', 'expense', 'selling_admin', '52', 3, 'admin', 8020), + $c('80300', '상여금', 'expense', 'selling_admin', '52', 3, 'admin', 8030), + $c('80600', '퇴직급여', 'expense', 'selling_admin', '52', 3, 'admin', 8060), + $c('81100', '복리후생비', 'expense', 'selling_admin', '52', 3, 'admin', 8110), + $c('81200', '여비교통비', 'expense', 'selling_admin', '52', 3, 'admin', 8120), + $c('81300', '접대비', 'expense', 'selling_admin', '52', 3, 'admin', 8130), + $c('81400', '통신비', 'expense', 'selling_admin', '52', 3, 'admin', 8140), + $c('81500', '수도광열비', 'expense', 'selling_admin', '52', 3, 'admin', 8150), + $c('81700', '세금과공과금', 'expense', 'selling_admin', '52', 3, 'admin', 8170), + $c('81800', '감가상각비', 'expense', 'selling_admin', '52', 3, 'admin', 8180), + $c('81900', '지급임차료', 'expense', 'selling_admin', '52', 3, 'admin', 8190), + $c('82000', '수선비', 'expense', 'selling_admin', '52', 3, 'admin', 8200), + $c('82100', '보험료', 'expense', 'selling_admin', '52', 3, 'admin', 8210), + $c('82200', '차량유지비', 'expense', 'selling_admin', '52', 3, 'admin', 8220), + $c('82300', '경상연구개발비', 'expense', 'selling_admin', '52', 3, 'admin', 8230), + $c('82400', '운반비', 'expense', 'selling_admin', '52', 3, 'admin', 8240), + $c('82500', '교육훈련비', 'expense', 'selling_admin', '52', 3, 'admin', 8250), + $c('82600', '도서인쇄비', 'expense', 'selling_admin', '52', 3, 'admin', 8260), + $c('82700', '회의비', 'expense', 'selling_admin', '52', 3, 'admin', 8270), + $c('82900', '사무용품비', 'expense', 'selling_admin', '52', 3, 'admin', 8290), + $c('83000', '소모품비', 'expense', 'selling_admin', '52', 3, 'admin', 8300), + $c('83100', '지급수수료', 'expense', 'selling_admin', '52', 3, 'admin', 8310), + $c('83200', '보관료', 'expense', 'selling_admin', '52', 3, 'admin', 8320), + $c('83300', '광고선전비', 'expense', 'selling_admin', '52', 3, 'admin', 8330), + $c('83500', '대손상각비', 'expense', 'selling_admin', '52', 3, 'admin', 8350), + $c('84800', '잡비', 'expense', 'selling_admin', '52', 3, 'admin', 8480), + + // -- 영업외비용 -- + $c('53', '영업외비용', 'expense', 'other_expense', '5', 2, 'common', 530), + $c('93100', '이자비용', 'expense', 'other_expense', '53', 3, 'common', 9310), + $c('93200', '외환차손', 'expense', 'other_expense', '53', 3, 'common', 9320), + $c('93300', '기부금', 'expense', 'other_expense', '53', 3, 'common', 9330), + $c('96000', '잡손실', 'expense', 'other_expense', '53', 3, 'common', 9600), + $c('99800', '법인세', 'expense', 'other_expense', '53', 3, 'common', 9980), + $c('99900', '소득세등', 'expense', 'other_expense', '53', 3, 'common', 9990), + ]; + } } diff --git a/app/Services/BadDebtService.php b/app/Services/BadDebtService.php index 9b04565..b4383e0 100644 --- a/app/Services/BadDebtService.php +++ b/app/Services/BadDebtService.php @@ -86,8 +86,8 @@ public function summary(array $params = []): array // is_active=true인 악성채권만 통계 $query = BadDebt::query() - ->where('tenant_id', $tenantId) - ->where('is_active', true); + ->where('bad_debts.tenant_id', $tenantId) + ->where('bad_debts.is_active', true); // 거래처 필터 if (! empty($params['client_id'])) { @@ -110,6 +110,9 @@ public function summary(array $params = []): array ->distinct('client_id') ->count('client_id'); + // per-card sub_label: 각 상태별 최다 금액 거래처명 + 건수 + $subLabels = $this->buildPerCardSubLabels($query); + return [ 'total_amount' => (float) $totalAmount, 'collecting_amount' => (float) $collectingAmount, @@ -117,9 +120,56 @@ public function summary(array $params = []): array 'recovered_amount' => (float) $recoveredAmount, 'bad_debt_amount' => (float) $badDebtAmount, 'client_count' => $clientCount, + 'sub_labels' => $subLabels, ]; } + /** + * 카드별 sub_label 생성 (최다 금액 거래처명 + 건수) + */ + private function buildPerCardSubLabels($baseQuery): array + { + $result = []; + $statusScopes = [ + 'dc1' => null, // 전체 (누적) + 'dc2' => 'collecting', // 추심중 + 'dc3' => 'legalAction', // 법적조치 + 'dc4' => 'recovered', // 회수완료 + ]; + + foreach ($statusScopes as $cardId => $scope) { + $q = clone $baseQuery; + if ($scope) { + $q = $q->$scope(); + } + + $clientCount = (clone $q)->distinct('client_id')->count('client_id'); + + if ($clientCount <= 0) { + $result[$cardId] = null; + + continue; + } + + $topClient = (clone $q) + ->join('clients', 'bad_debts.client_id', '=', 'clients.id') + ->selectRaw('clients.name, SUM(bad_debts.debt_amount) as total_amount') + ->groupBy('clients.id', 'clients.name') + ->orderByDesc('total_amount') + ->first(); + + if ($topClient) { + $result[$cardId] = $clientCount > 1 + ? $topClient->name.' 외 '.($clientCount - 1).'건' + : $topClient->name; + } else { + $result[$cardId] = null; + } + } + + return $result; + } + /** * 악성채권 상세 조회 */ diff --git a/app/Services/GeneralJournalEntryService.php b/app/Services/GeneralJournalEntryService.php index 8056c0c..7fa3624 100644 --- a/app/Services/GeneralJournalEntryService.php +++ b/app/Services/GeneralJournalEntryService.php @@ -5,11 +5,13 @@ use App\Models\Tenants\AccountCode; use App\Models\Tenants\JournalEntry; use App\Models\Tenants\JournalEntryLine; +use App\Traits\SyncsExpenseAccounts; use Illuminate\Support\Facades\DB; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class GeneralJournalEntryService extends Service { + use SyncsExpenseAccounts; /** * 일반전표입력 통합 목록 (입금 + 출금 + 수기전표) * deposits/withdrawals는 계좌이체 건만, LEFT JOIN journal_entries로 분개 여부 표시 @@ -326,6 +328,9 @@ public function store(array $data): JournalEntry // 분개 행 생성 $this->createLines($entry, $data['rows'], $tenantId); + // expense_accounts 동기화 (복리후생비/접대비 → CEO 대시보드) + $this->syncExpenseAccounts($entry); + return $entry->load('lines'); }); } @@ -373,6 +378,9 @@ public function updateJournal(int $id, array $data): JournalEntry $entry->save(); + // expense_accounts 동기화 (복리후생비/접대비 → CEO 대시보드) + $this->syncExpenseAccounts($entry); + return $entry->load('lines'); }); } @@ -389,6 +397,9 @@ public function destroyJournal(int $id): bool ->where('tenant_id', $tenantId) ->findOrFail($id); + // expense_accounts 정리 (복리후생비/접대비 → CEO 대시보드) + $this->cleanupExpenseAccounts($tenantId, $entry->id); + // lines 먼저 삭제 (soft delete가 아니므로 물리 삭제) JournalEntryLine::query() ->where('journal_entry_id', $entry->id) @@ -503,6 +514,9 @@ private function resolveVendorName(?int $vendorId): string return $vendor ? $vendor->name : ''; } + // syncExpenseAccounts, cleanupExpenseAccounts, getExpenseAccountType + // → SyncsExpenseAccounts 트레이트로 이관 + /** * 원본 거래 정보 조회 (입금/출금) */ diff --git a/app/Services/JournalSyncService.php b/app/Services/JournalSyncService.php new file mode 100644 index 0000000..f42cd5a --- /dev/null +++ b/app/Services/JournalSyncService.php @@ -0,0 +1,214 @@ +tenantId(); + + return DB::transaction(function () use ($sourceType, $sourceKey, $entryDate, $description, $rows, $tenantId) { + // 기존 전표가 있으면 삭제 후 재생성 (교체 방식) + $existing = JournalEntry::query() + ->where('tenant_id', $tenantId) + ->where('source_type', $sourceType) + ->where('source_key', $sourceKey) + ->first(); + + if ($existing) { + $this->cleanupExpenseAccounts($tenantId, $existing->id); + JournalEntryLine::where('journal_entry_id', $existing->id)->delete(); + $existing->forceDelete(); + } + + // 합계 계산 + $totalDebit = 0; + $totalCredit = 0; + foreach ($rows as $row) { + $totalDebit += (int) ($row['debit_amount'] ?? 0); + $totalCredit += (int) ($row['credit_amount'] ?? 0); + } + + // 전표번호 생성 + $entryNo = $this->generateEntryNo($tenantId, $entryDate); + + // 전표 생성 + $entry = new JournalEntry; + $entry->tenant_id = $tenantId; + $entry->entry_no = $entryNo; + $entry->entry_date = $entryDate; + $entry->entry_type = JournalEntry::TYPE_GENERAL; + $entry->description = $description; + $entry->total_debit = $totalDebit; + $entry->total_credit = $totalCredit; + $entry->status = JournalEntry::STATUS_CONFIRMED; + $entry->source_type = $sourceType; + $entry->source_key = $sourceKey; + $entry->save(); + + // 분개 행 생성 + foreach ($rows as $index => $row) { + $accountCode = $row['account_code'] ?? ''; + $accountName = $row['account_name'] ?? $this->resolveAccountName($tenantId, $accountCode); + + $line = new JournalEntryLine; + $line->tenant_id = $tenantId; + $line->journal_entry_id = $entry->id; + $line->line_no = $index + 1; + $line->dc_type = $row['side']; + $line->account_code = $accountCode; + $line->account_name = $accountName; + $line->trading_partner_id = ! empty($row['vendor_id']) ? (int) $row['vendor_id'] : null; + $line->trading_partner_name = $row['vendor_name'] ?? ''; + $line->debit_amount = (int) ($row['debit_amount'] ?? 0); + $line->credit_amount = (int) ($row['credit_amount'] ?? 0); + $line->description = $row['memo'] ?? null; + $line->save(); + } + + // expense_accounts 동기화 (복리후생비/접대비 → CEO 대시보드) + $this->syncExpenseAccounts($entry); + + return $entry->load('lines'); + }); + } + + /** + * 소스에 대한 분개 조회 + */ + public function getForSource(string $sourceType, string $sourceKey): ?array + { + $tenantId = $this->tenantId(); + + $entry = JournalEntry::query() + ->where('tenant_id', $tenantId) + ->where('source_type', $sourceType) + ->where('source_key', $sourceKey) + ->whereNull('deleted_at') + ->with('lines') + ->first(); + + if (! $entry) { + return null; + } + + return [ + 'id' => $entry->id, + 'entry_no' => $entry->entry_no, + 'entry_date' => $entry->entry_date->format('Y-m-d'), + 'description' => $entry->description, + 'total_debit' => $entry->total_debit, + 'total_credit' => $entry->total_credit, + 'rows' => $entry->lines->map(function ($line) { + return [ + 'id' => $line->id, + 'side' => $line->dc_type, + 'account_code' => $line->account_code, + 'account_name' => $line->account_name, + 'vendor_id' => $line->trading_partner_id, + 'vendor_name' => $line->trading_partner_name ?? '', + 'debit_amount' => (int) $line->debit_amount, + 'credit_amount' => (int) $line->credit_amount, + 'memo' => $line->description ?? '', + ]; + })->toArray(), + ]; + } + + /** + * 소스에 대한 분개 삭제 + */ + public function deleteForSource(string $sourceType, string $sourceKey): bool + { + $tenantId = $this->tenantId(); + + return DB::transaction(function () use ($sourceType, $sourceKey, $tenantId) { + $entry = JournalEntry::query() + ->where('tenant_id', $tenantId) + ->where('source_type', $sourceType) + ->where('source_key', $sourceKey) + ->first(); + + if (! $entry) { + return false; + } + + $this->cleanupExpenseAccounts($tenantId, $entry->id); + JournalEntryLine::where('journal_entry_id', $entry->id)->delete(); + $entry->delete(); // soft delete + + return true; + }); + } + + /** + * 전표번호 생성: JE-YYYYMMDD-NNN + */ + private function generateEntryNo(int $tenantId, string $date): string + { + $dateStr = str_replace('-', '', substr($date, 0, 10)); + $prefix = "JE-{$dateStr}-"; + + $lastEntry = DB::table('journal_entries') + ->where('tenant_id', $tenantId) + ->where('entry_no', 'like', "{$prefix}%") + ->lockForUpdate() + ->orderBy('entry_no', 'desc') + ->first(['entry_no']); + + if ($lastEntry) { + $lastSeq = (int) substr($lastEntry->entry_no, -3); + $nextSeq = $lastSeq + 1; + } else { + $nextSeq = 1; + } + + return $prefix . str_pad($nextSeq, 3, '0', STR_PAD_LEFT); + } + + /** + * 계정과목 코드 → 이름 조회 + */ + private function resolveAccountName(int $tenantId, string $code): string + { + if (empty($code)) { + return ''; + } + + $account = AccountCode::query() + ->where('tenant_id', $tenantId) + ->where('code', $code) + ->first(['name']); + + return $account ? $account->name : $code; + } +} diff --git a/app/Services/TaxInvoiceService.php b/app/Services/TaxInvoiceService.php index 19cd6c8..58718ac 100644 --- a/app/Services/TaxInvoiceService.php +++ b/app/Services/TaxInvoiceService.php @@ -112,6 +112,12 @@ public function create(array $data): TaxInvoice // 합계금액 계산 $data['total_amount'] = ($data['supply_amount'] ?? 0) + ($data['tax_amount'] ?? 0); + // NOT NULL 컬럼: Laravel ConvertEmptyStringsToNull 미들웨어가 ''→null 변환하므로 보정 + $data['supplier_corp_num'] = $data['supplier_corp_num'] ?? ''; + $data['supplier_corp_name'] = $data['supplier_corp_name'] ?? ''; + $data['buyer_corp_num'] = $data['buyer_corp_num'] ?? ''; + $data['buyer_corp_name'] = $data['buyer_corp_name'] ?? ''; + $taxInvoice = TaxInvoice::create(array_merge($data, [ 'tenant_id' => $tenantId, 'status' => TaxInvoice::STATUS_DRAFT, diff --git a/app/Services/WelfareService.php b/app/Services/WelfareService.php index 6231f9f..683ba0d 100644 --- a/app/Services/WelfareService.php +++ b/app/Services/WelfareService.php @@ -498,7 +498,9 @@ public function getDetail( ?int $fixedAmountPerMonth = 200000, ?float $ratio = 0.05, ?int $year = null, - ?int $quarter = null + ?int $quarter = null, + ?string $startDate = null, + ?string $endDate = null ): array { $tenantId = $this->tenantId(); $now = Carbon::now(); @@ -562,8 +564,10 @@ public function getDetail( // 3. 항목별 분포 $categoryDistribution = $this->getCategoryDistribution($tenantId, $annualStartDate, $annualEndDate); - // 4. 일별 사용 내역 - $transactions = $this->getTransactions($tenantId, $quarterStartDate, $quarterEndDate); + // 4. 일별 사용 내역 (커스텀 날짜 범위가 있으면 해당 범위, 없으면 분기 기준) + $txStartDate = $startDate ?? $quarterStartDate; + $txEndDate = $endDate ?? $quarterEndDate; + $transactions = $this->getTransactions($tenantId, $txStartDate, $txEndDate); // 5. 계산 정보 $calculation = [ diff --git a/app/Traits/SyncsExpenseAccounts.php b/app/Traits/SyncsExpenseAccounts.php new file mode 100644 index 0000000..4216994 --- /dev/null +++ b/app/Traits/SyncsExpenseAccounts.php @@ -0,0 +1,98 @@ + ExpenseAccount::TYPE_WELFARE, + '접대비' => ExpenseAccount::TYPE_ENTERTAINMENT, + ]; + + /** + * 전표 저장/수정 후 expense_accounts 동기화 + * 복리후생비/접대비 계정과목이 포함된 lines → expense_accounts에 반영 + */ + protected function syncExpenseAccounts(JournalEntry $entry): void + { + $tenantId = $entry->tenant_id; + + // 1. 기존 이 전표에서 생성된 expense_accounts 삭제 + ExpenseAccount::where('tenant_id', $tenantId) + ->where('journal_entry_id', $entry->id) + ->forceDelete(); + + // 2. 현재 lines 중 복리후생비/접대비 해당하는 것만 insert + $lines = $entry->lines()->get(); + + foreach ($lines as $line) { + $accountType = $this->getExpenseAccountType($line->account_name); + if (! $accountType) { + continue; + } + + // 차변(debit)이 대변보다 큰 경우만 비용 발생으로 처리 + $amount = $line->debit_amount - $line->credit_amount; + if ($amount <= 0) { + continue; + } + + // source_type에 따라 payment_method 결정 + $paymentMethod = match ($entry->source_type) { + JournalEntry::SOURCE_CARD_TRANSACTION => ExpenseAccount::PAYMENT_CARD, + default => ExpenseAccount::PAYMENT_TRANSFER, + }; + + ExpenseAccount::create([ + 'tenant_id' => $tenantId, + 'account_type' => $accountType, + 'sub_type' => null, + 'expense_date' => $entry->entry_date, + 'amount' => $amount, + 'description' => $line->description ?? $entry->description, + 'receipt_no' => null, + 'vendor_id' => $line->trading_partner_id, + 'vendor_name' => $line->trading_partner_name, + 'payment_method' => $paymentMethod, + 'card_no' => null, + 'journal_entry_id' => $entry->id, + 'journal_entry_line_id' => $line->id, + ]); + } + } + + /** + * 전표 삭제 시 expense_accounts 정리 + */ + protected function cleanupExpenseAccounts(int $tenantId, int $entryId): void + { + ExpenseAccount::where('tenant_id', $tenantId) + ->where('journal_entry_id', $entryId) + ->forceDelete(); + } + + /** + * 계정과목명에서 비용 유형 판별 + */ + private function getExpenseAccountType(string $accountName): ?string + { + foreach (self::$expenseAccountMap as $keyword => $type) { + if (str_contains($accountName, $keyword)) { + return $type; + } + } + + return null; + } +} diff --git a/config/database.php b/config/database.php index 82a16ef..8a59ad0 100644 --- a/config/database.php +++ b/config/database.php @@ -82,6 +82,26 @@ ]) : [], ], + // Codebridge DB (이관된 Sales/Finance/Admin 등) + 'codebridge' => [ + 'driver' => 'mysql', + 'host' => env('CODEBRIDGE_DB_HOST', env('DB_HOST', '127.0.0.1')), + 'port' => env('CODEBRIDGE_DB_PORT', env('DB_PORT', '3306')), + 'database' => env('CODEBRIDGE_DB_DATABASE', 'codebridge'), + 'username' => env('CODEBRIDGE_DB_USERNAME', env('DB_USERNAME', 'root')), + 'password' => env('CODEBRIDGE_DB_PASSWORD', env('DB_PASSWORD', '')), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + // 5130 레거시 DB (chandj) 'chandj' => [ 'driver' => 'mysql', diff --git a/database/migrations/2026_03_06_210000_add_journal_link_to_expense_accounts_table.php b/database/migrations/2026_03_06_210000_add_journal_link_to_expense_accounts_table.php new file mode 100644 index 0000000..5a5c0a6 --- /dev/null +++ b/database/migrations/2026_03_06_210000_add_journal_link_to_expense_accounts_table.php @@ -0,0 +1,28 @@ +unsignedBigInteger('journal_entry_id')->nullable()->after('loan_id'); + $table->unsignedBigInteger('journal_entry_line_id')->nullable()->after('journal_entry_id'); + + $table->index(['tenant_id', 'journal_entry_id']); + $table->index(['journal_entry_line_id']); + }); + } + + public function down(): void + { + Schema::table('expense_accounts', function (Blueprint $table) { + $table->dropIndex(['tenant_id', 'journal_entry_id']); + $table->dropIndex(['journal_entry_line_id']); + $table->dropColumn(['journal_entry_id', 'journal_entry_line_id']); + }); + } +}; diff --git a/database/migrations/2026_03_06_220000_enhance_account_codes_table.php b/database/migrations/2026_03_06_220000_enhance_account_codes_table.php new file mode 100644 index 0000000..f7a689b --- /dev/null +++ b/database/migrations/2026_03_06_220000_enhance_account_codes_table.php @@ -0,0 +1,54 @@ +string('sub_category', 50)->nullable()->after('category') + ->comment('중분류 (current_asset, fixed_asset, selling_admin, cogs 등)'); + $table->string('parent_code', 10)->nullable()->after('sub_category') + ->comment('상위 계정과목 코드 (계층 구조)'); + $table->tinyInteger('depth')->default(3)->after('parent_code') + ->comment('계층 깊이 (1=대분류, 2=중분류, 3=소분류)'); + $table->string('department_type', 20)->default('common')->after('depth') + ->comment('부문 (common=공통, manufacturing=제조, admin=관리)'); + $table->string('description', 500)->nullable()->after('department_type') + ->comment('계정과목 설명'); + + $table->index(['tenant_id', 'category'], 'account_codes_tenant_category_idx'); + $table->index(['tenant_id', 'parent_code'], 'account_codes_tenant_parent_idx'); + $table->index(['tenant_id', 'depth'], 'account_codes_tenant_depth_idx'); + }); + } + + public function down(): void + { + Schema::table('account_codes', function (Blueprint $table) { + $table->dropIndex('account_codes_tenant_category_idx'); + $table->dropIndex('account_codes_tenant_parent_idx'); + $table->dropIndex('account_codes_tenant_depth_idx'); + + $table->dropColumn([ + 'sub_category', + 'parent_code', + 'depth', + 'department_type', + 'description', + ]); + }); + } +}; diff --git a/database/migrations/2026_03_06_220000_seed_default_account_codes_for_all_tenants.php b/database/migrations/2026_03_06_220000_seed_default_account_codes_for_all_tenants.php new file mode 100644 index 0000000..4b2745c --- /dev/null +++ b/database/migrations/2026_03_06_220000_seed_default_account_codes_for_all_tenants.php @@ -0,0 +1,218 @@ +whereNull('deleted_at') + ->pluck('id'); + + $defaults = $this->getDefaultAccountCodes(); + $now = now(); + + foreach ($tenantIds as $tenantId) { + // 이미 등록된 코드 조회 + $existingCodes = DB::table('account_codes') + ->where('tenant_id', $tenantId) + ->pluck('code') + ->toArray(); + + $inserts = []; + foreach ($defaults as $item) { + if (! in_array($item['code'], $existingCodes)) { + $inserts[] = array_merge($item, [ + 'tenant_id' => $tenantId, + 'is_active' => true, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + } + + if (! empty($inserts)) { + // 500건 단위 청크 insert + foreach (array_chunk($inserts, 500) as $chunk) { + DB::table('account_codes')->insert($chunk); + } + } + } + } + + public function down(): void + { + // 시드 데이터만 롤백 (수동 추가 데이터는 보호) + $defaultCodes = array_column($this->getDefaultAccountCodes(), 'code'); + + DB::table('account_codes') + ->whereIn('code', $defaultCodes) + ->delete(); + } + + private function getDefaultAccountCodes(): array + { + $c = fn ($code, $name, $cat, $sub, $parent, $depth, $dept, $sort) => [ + 'code' => $code, 'name' => $name, 'category' => $cat, + 'sub_category' => $sub, 'parent_code' => $parent, + 'depth' => $depth, 'department_type' => $dept, 'sort_order' => $sort, + ]; + + return [ + // 자산 (Assets) + $c('1', '자산', 'asset', null, null, 1, 'common', 100), + $c('11', '유동자산', 'asset', 'current_asset', '1', 2, 'common', 110), + $c('10100', '현금', 'asset', 'current_asset', '11', 3, 'common', 1010), + $c('10200', '당좌예금', 'asset', 'current_asset', '11', 3, 'common', 1020), + $c('10300', '보통예금', 'asset', 'current_asset', '11', 3, 'common', 1030), + $c('10400', '기타제예금', 'asset', 'current_asset', '11', 3, 'common', 1040), + $c('10500', '정기적금', 'asset', 'current_asset', '11', 3, 'common', 1050), + $c('10800', '외상매출금', 'asset', 'current_asset', '11', 3, 'common', 1080), + $c('10900', '대손충당금(외상매출금)', 'asset', 'current_asset', '11', 3, 'common', 1090), + $c('11000', '받을어음', 'asset', 'current_asset', '11', 3, 'common', 1100), + $c('11400', '단기대여금', 'asset', 'current_asset', '11', 3, 'common', 1140), + $c('11600', '미수수익', 'asset', 'current_asset', '11', 3, 'common', 1160), + $c('12000', '미수금', 'asset', 'current_asset', '11', 3, 'common', 1200), + $c('12200', '소모품', 'asset', 'current_asset', '11', 3, 'common', 1220), + $c('12500', '미환급세금', 'asset', 'current_asset', '11', 3, 'common', 1250), + $c('13100', '선급금', 'asset', 'current_asset', '11', 3, 'common', 1310), + $c('13300', '선급비용', 'asset', 'current_asset', '11', 3, 'common', 1330), + $c('13400', '가지급금', 'asset', 'current_asset', '11', 3, 'common', 1340), + $c('13500', '부가세대급금', 'asset', 'current_asset', '11', 3, 'common', 1350), + $c('13600', '선납세금', 'asset', 'current_asset', '11', 3, 'common', 1360), + $c('14000', '선납법인세', 'asset', 'current_asset', '11', 3, 'common', 1400), + $c('12', '재고자산', 'asset', 'current_asset', '1', 2, 'common', 120), + $c('14600', '상품', 'asset', 'current_asset', '12', 3, 'common', 1460), + $c('15000', '제품', 'asset', 'current_asset', '12', 3, 'common', 1500), + $c('15300', '원재료', 'asset', 'current_asset', '12', 3, 'common', 1530), + $c('16200', '부재료', 'asset', 'current_asset', '12', 3, 'common', 1620), + $c('16700', '저장품', 'asset', 'current_asset', '12', 3, 'common', 1670), + $c('16900', '재공품', 'asset', 'current_asset', '12', 3, 'common', 1690), + $c('13', '비유동자산', 'asset', 'fixed_asset', '1', 2, 'common', 130), + $c('17600', '장기성예금', 'asset', 'fixed_asset', '13', 3, 'common', 1760), + $c('17900', '장기대여금', 'asset', 'fixed_asset', '13', 3, 'common', 1790), + $c('18700', '투자부동산', 'asset', 'fixed_asset', '13', 3, 'common', 1870), + $c('19200', '단체퇴직보험예치금', 'asset', 'fixed_asset', '13', 3, 'common', 1920), + $c('20100', '토지', 'asset', 'fixed_asset', '13', 3, 'common', 2010), + $c('20200', '건물', 'asset', 'fixed_asset', '13', 3, 'common', 2020), + $c('20300', '감가상각누계액(건물)', 'asset', 'fixed_asset', '13', 3, 'common', 2030), + $c('20400', '구축물', 'asset', 'fixed_asset', '13', 3, 'common', 2040), + $c('20500', '감가상각누계액(구축물)', 'asset', 'fixed_asset', '13', 3, 'common', 2050), + $c('20600', '기계장치', 'asset', 'fixed_asset', '13', 3, 'common', 2060), + $c('20700', '감가상각누계액(기계장치)', 'asset', 'fixed_asset', '13', 3, 'common', 2070), + $c('20800', '차량운반구', 'asset', 'fixed_asset', '13', 3, 'common', 2080), + $c('20900', '감가상각누계액(차량운반구)', 'asset', 'fixed_asset', '13', 3, 'common', 2090), + $c('21000', '공구와기구', 'asset', 'fixed_asset', '13', 3, 'common', 2100), + $c('21200', '비품', 'asset', 'fixed_asset', '13', 3, 'common', 2120), + $c('21300', '건설중인자산', 'asset', 'fixed_asset', '13', 3, 'common', 2130), + $c('24000', '소프트웨어', 'asset', 'fixed_asset', '13', 3, 'common', 2400), + // 부채 (Liabilities) + $c('2', '부채', 'liability', null, null, 1, 'common', 200), + $c('21', '유동부채', 'liability', 'current_liability', '2', 2, 'common', 210), + $c('25100', '외상매입금', 'liability', 'current_liability', '21', 3, 'common', 2510), + $c('25200', '지급어음', 'liability', 'current_liability', '21', 3, 'common', 2520), + $c('25300', '미지급금', 'liability', 'current_liability', '21', 3, 'common', 2530), + $c('25400', '예수금', 'liability', 'current_liability', '21', 3, 'common', 2540), + $c('25500', '부가세예수금', 'liability', 'current_liability', '21', 3, 'common', 2550), + $c('25900', '선수금', 'liability', 'current_liability', '21', 3, 'common', 2590), + $c('26000', '단기차입금', 'liability', 'current_liability', '21', 3, 'common', 2600), + $c('26100', '미지급세금', 'liability', 'current_liability', '21', 3, 'common', 2610), + $c('26200', '미지급비용', 'liability', 'current_liability', '21', 3, 'common', 2620), + $c('26400', '유동성장기차입금', 'liability', 'current_liability', '21', 3, 'common', 2640), + $c('26500', '미지급배당금', 'liability', 'current_liability', '21', 3, 'common', 2650), + $c('22', '비유동부채', 'liability', 'long_term_liability', '2', 2, 'common', 220), + $c('29300', '장기차입금', 'liability', 'long_term_liability', '22', 3, 'common', 2930), + $c('29400', '임대보증금', 'liability', 'long_term_liability', '22', 3, 'common', 2940), + $c('29500', '퇴직급여충당부채', 'liability', 'long_term_liability', '22', 3, 'common', 2950), + $c('30700', '장기임대보증금', 'liability', 'long_term_liability', '22', 3, 'common', 3070), + // 자본 (Capital) + $c('3', '자본', 'capital', null, null, 1, 'common', 300), + $c('31', '자본금', 'capital', 'capital', '3', 2, 'common', 310), + $c('33100', '자본금', 'capital', 'capital', '31', 3, 'common', 3310), + $c('33200', '우선주자본금', 'capital', 'capital', '31', 3, 'common', 3320), + $c('32', '잉여금', 'capital', 'capital', '3', 2, 'common', 320), + $c('34100', '주식발행초과금', 'capital', 'capital', '32', 3, 'common', 3410), + $c('35100', '이익준비금', 'capital', 'capital', '32', 3, 'common', 3510), + $c('37500', '이월이익잉여금', 'capital', 'capital', '32', 3, 'common', 3750), + $c('37900', '당기순이익', 'capital', 'capital', '32', 3, 'common', 3790), + // 수익 (Revenue) + $c('4', '수익', 'revenue', null, null, 1, 'common', 400), + $c('41', '매출', 'revenue', 'sales_revenue', '4', 2, 'common', 410), + $c('40100', '상품매출', 'revenue', 'sales_revenue', '41', 3, 'common', 4010), + $c('40400', '제품매출', 'revenue', 'sales_revenue', '41', 3, 'common', 4040), + $c('40700', '공사수입금', 'revenue', 'sales_revenue', '41', 3, 'common', 4070), + $c('41000', '임대료수입', 'revenue', 'sales_revenue', '41', 3, 'common', 4100), + $c('42', '영업외수익', 'revenue', 'other_revenue', '4', 2, 'common', 420), + $c('90100', '이자수익', 'revenue', 'other_revenue', '42', 3, 'common', 9010), + $c('90300', '배당금수익', 'revenue', 'other_revenue', '42', 3, 'common', 9030), + $c('90400', '수입임대료', 'revenue', 'other_revenue', '42', 3, 'common', 9040), + $c('90700', '외환차익', 'revenue', 'other_revenue', '42', 3, 'common', 9070), + $c('93000', '잡이익', 'revenue', 'other_revenue', '42', 3, 'common', 9300), + // 비용 (Expenses) + $c('5', '비용', 'expense', null, null, 1, 'common', 500), + $c('51', '매출원가', 'expense', 'cogs', '5', 2, 'manufacturing', 510), + $c('50100', '원재료비', 'expense', 'cogs', '51', 3, 'manufacturing', 5010), + $c('50200', '외주가공비', 'expense', 'cogs', '51', 3, 'manufacturing', 5020), + $c('50300', '급여(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5030), + $c('50400', '임금', 'expense', 'cogs', '51', 3, 'manufacturing', 5040), + $c('50500', '상여금(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5050), + $c('50800', '퇴직급여(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5080), + $c('51100', '복리후생비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5110), + $c('51200', '여비교통비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5120), + $c('51300', '접대비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5130), + $c('51400', '통신비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5140), + $c('51600', '전력비', 'expense', 'cogs', '51', 3, 'manufacturing', 5160), + $c('51700', '세금과공과금(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5170), + $c('51800', '감가상각비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5180), + $c('51900', '지급임차료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5190), + $c('52000', '수선비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5200), + $c('52100', '보험료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5210), + $c('52200', '차량유지비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5220), + $c('52400', '운반비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5240), + $c('53000', '소모품비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5300), + $c('53100', '지급수수료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5310), + $c('52', '판매비와관리비', 'expense', 'selling_admin', '5', 2, 'admin', 520), + $c('80100', '임원급여', 'expense', 'selling_admin', '52', 3, 'admin', 8010), + $c('80200', '직원급여', 'expense', 'selling_admin', '52', 3, 'admin', 8020), + $c('80300', '상여금', 'expense', 'selling_admin', '52', 3, 'admin', 8030), + $c('80600', '퇴직급여', 'expense', 'selling_admin', '52', 3, 'admin', 8060), + $c('81100', '복리후생비', 'expense', 'selling_admin', '52', 3, 'admin', 8110), + $c('81200', '여비교통비', 'expense', 'selling_admin', '52', 3, 'admin', 8120), + $c('81300', '접대비', 'expense', 'selling_admin', '52', 3, 'admin', 8130), + $c('81400', '통신비', 'expense', 'selling_admin', '52', 3, 'admin', 8140), + $c('81500', '수도광열비', 'expense', 'selling_admin', '52', 3, 'admin', 8150), + $c('81700', '세금과공과금', 'expense', 'selling_admin', '52', 3, 'admin', 8170), + $c('81800', '감가상각비', 'expense', 'selling_admin', '52', 3, 'admin', 8180), + $c('81900', '지급임차료', 'expense', 'selling_admin', '52', 3, 'admin', 8190), + $c('82000', '수선비', 'expense', 'selling_admin', '52', 3, 'admin', 8200), + $c('82100', '보험료', 'expense', 'selling_admin', '52', 3, 'admin', 8210), + $c('82200', '차량유지비', 'expense', 'selling_admin', '52', 3, 'admin', 8220), + $c('82300', '경상연구개발비', 'expense', 'selling_admin', '52', 3, 'admin', 8230), + $c('82400', '운반비', 'expense', 'selling_admin', '52', 3, 'admin', 8240), + $c('82500', '교육훈련비', 'expense', 'selling_admin', '52', 3, 'admin', 8250), + $c('82600', '도서인쇄비', 'expense', 'selling_admin', '52', 3, 'admin', 8260), + $c('82700', '회의비', 'expense', 'selling_admin', '52', 3, 'admin', 8270), + $c('82900', '사무용품비', 'expense', 'selling_admin', '52', 3, 'admin', 8290), + $c('83000', '소모품비', 'expense', 'selling_admin', '52', 3, 'admin', 8300), + $c('83100', '지급수수료', 'expense', 'selling_admin', '52', 3, 'admin', 8310), + $c('83200', '보관료', 'expense', 'selling_admin', '52', 3, 'admin', 8320), + $c('83300', '광고선전비', 'expense', 'selling_admin', '52', 3, 'admin', 8330), + $c('83500', '대손상각비', 'expense', 'selling_admin', '52', 3, 'admin', 8350), + $c('84800', '잡비', 'expense', 'selling_admin', '52', 3, 'admin', 8480), + $c('53', '영업외비용', 'expense', 'other_expense', '5', 2, 'common', 530), + $c('93100', '이자비용', 'expense', 'other_expense', '53', 3, 'common', 9310), + $c('93200', '외환차손', 'expense', 'other_expense', '53', 3, 'common', 9320), + $c('93300', '기부금', 'expense', 'other_expense', '53', 3, 'common', 9330), + $c('96000', '잡손실', 'expense', 'other_expense', '53', 3, 'common', 9600), + $c('99800', '법인세', 'expense', 'other_expense', '53', 3, 'common', 9980), + $c('99900', '소득세등', 'expense', 'other_expense', '53', 3, 'common', 9990), + ]; + } +}; diff --git a/database/migrations/2026_03_09_000000_seed_additional_account_codes_for_all_tenants.php b/database/migrations/2026_03_09_000000_seed_additional_account_codes_for_all_tenants.php new file mode 100644 index 0000000..8dca605 --- /dev/null +++ b/database/migrations/2026_03_09_000000_seed_additional_account_codes_for_all_tenants.php @@ -0,0 +1,467 @@ +whereNull('deleted_at') + ->pluck('id'); + + $defaults = $this->getAdditionalAccountCodes(); + $now = now(); + + foreach ($tenantIds as $tenantId) { + $existingCodes = DB::table('account_codes') + ->where('tenant_id', $tenantId) + ->pluck('code') + ->toArray(); + + $inserts = []; + foreach ($defaults as $item) { + if (! in_array($item['code'], $existingCodes)) { + $inserts[] = array_merge($item, [ + 'tenant_id' => $tenantId, + 'is_active' => true, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + } + + if (! empty($inserts)) { + foreach (array_chunk($inserts, 500) as $chunk) { + DB::table('account_codes')->insert($chunk); + } + } + } + } + + public function down(): void + { + $codes = array_column($this->getAdditionalAccountCodes(), 'code'); + DB::table('account_codes')->whereIn('code', $codes)->delete(); + } + + private function getAdditionalAccountCodes(): array + { + $c = fn ($code, $name, $cat, $sub, $parent, $depth, $dept, $sort) => [ + 'code' => $code, 'name' => $name, 'category' => $cat, + 'sub_category' => $sub, 'parent_code' => $parent, + 'depth' => $depth, 'department_type' => $dept, 'sort_order' => $sort, + ]; + + return [ + // ================================================================ + // 새 depth-2 카테고리 + // ================================================================ + $c('33', '자본조정', 'capital', 'capital_adjustment', '3', 2, 'common', 330), + $c('54', '건설원가', 'expense', 'construction_cost', '5', 2, 'construction', 540), + + // ================================================================ + // 자산 — 유동자산 추가 (parent: '11') + // ================================================================ + $c('10600', '기타단기금융상품예금', 'asset', 'current_asset', '11', 3, 'common', 1060), + $c('10700', '단기투자자산', 'asset', 'current_asset', '11', 3, 'common', 1070), + $c('11100', '대손충당금(받을어음)', 'asset', 'current_asset', '11', 3, 'common', 1110), + $c('11200', '공사미수금', 'asset', 'current_asset', '11', 3, 'common', 1120), + $c('11300', '대손충당금(공사미수금)', 'asset', 'current_asset', '11', 3, 'common', 1130), + $c('11500', '대손충당금(단기대여금)', 'asset', 'current_asset', '11', 3, 'common', 1150), + $c('11700', '대손충당금(미수수익)', 'asset', 'current_asset', '11', 3, 'common', 1170), + $c('11800', '분양미수금', 'asset', 'current_asset', '11', 3, 'common', 1180), + $c('11900', '대손충당금(분양미수금)', 'asset', 'current_asset', '11', 3, 'common', 1190), + $c('12100', '대손충당금(미수금)', 'asset', 'current_asset', '11', 3, 'common', 1210), + $c('12300', '매도가능증권', 'asset', 'current_asset', '11', 3, 'common', 1230), + $c('12400', '만기보유증권', 'asset', 'current_asset', '11', 3, 'common', 1240), + $c('13200', '대손충당금(선급금)', 'asset', 'current_asset', '11', 3, 'common', 1320), + $c('13700', '주임종단기채권', 'asset', 'current_asset', '11', 3, 'common', 1370), + $c('13800', '전도금', 'asset', 'current_asset', '11', 3, 'common', 1380), + $c('13900', '선급공사비', 'asset', 'current_asset', '11', 3, 'common', 1390), + + // ================================================================ + // 자산 — 재고자산 추가 (parent: '12') + // ================================================================ + $c('14700', '매입환출및에누리(상품)', 'asset', 'current_asset', '12', 3, 'common', 1470), + $c('14800', '매입할인(상품)', 'asset', 'current_asset', '12', 3, 'common', 1480), + $c('14900', '관세환급금(상품)', 'asset', 'current_asset', '12', 3, 'common', 1490), + $c('15100', '관세환급금(제품)', 'asset', 'current_asset', '12', 3, 'common', 1510), + $c('15200', '완성건물', 'asset', 'current_asset', '12', 3, 'common', 1520), + $c('15400', '매입환출및에누리(원재료)', 'asset', 'current_asset', '12', 3, 'common', 1540), + $c('15500', '매입할인(원재료)', 'asset', 'current_asset', '12', 3, 'common', 1550), + $c('15600', '원재료(도급)', 'asset', 'current_asset', '12', 3, 'common', 1560), + $c('15700', '매입환출및에누리(원재료-도급)', 'asset', 'current_asset', '12', 3, 'common', 1570), + $c('15800', '매입할인(원재료-도급)', 'asset', 'current_asset', '12', 3, 'common', 1580), + $c('15900', '원재료(분양)', 'asset', 'current_asset', '12', 3, 'common', 1590), + $c('16000', '매입환출및에누리(원재료-분양)', 'asset', 'current_asset', '12', 3, 'common', 1600), + $c('16100', '매입할인(원재료-분양)', 'asset', 'current_asset', '12', 3, 'common', 1610), + $c('16300', '매입환출및에누리(부재료)', 'asset', 'current_asset', '12', 3, 'common', 1630), + $c('16400', '매입할인(부재료)', 'asset', 'current_asset', '12', 3, 'common', 1640), + $c('16500', '건설용지', 'asset', 'current_asset', '12', 3, 'common', 1650), + $c('16600', '가설재', 'asset', 'current_asset', '12', 3, 'common', 1660), + $c('16800', '미착품', 'asset', 'current_asset', '12', 3, 'common', 1680), + $c('17000', '미완성공사(도급)', 'asset', 'current_asset', '12', 3, 'common', 1700), + $c('17100', '미완성공사(분양)', 'asset', 'current_asset', '12', 3, 'common', 1710), + + // ================================================================ + // 자산 — 비유동자산 추가 (parent: '13') + // ================================================================ + $c('17700', '특정현금과예금', 'asset', 'fixed_asset', '13', 3, 'common', 1770), + $c('17800', '장기투자증권', 'asset', 'fixed_asset', '13', 3, 'common', 1780), + $c('18000', '대손충당금(장기대여금)', 'asset', 'fixed_asset', '13', 3, 'common', 1800), + $c('18100', '만기보유증권(장기)', 'asset', 'fixed_asset', '13', 3, 'common', 1810), + $c('18200', '지분법적용투자주식', 'asset', 'fixed_asset', '13', 3, 'common', 1820), + $c('19100', '출자금', 'asset', 'fixed_asset', '13', 3, 'common', 1910), + $c('19700', '투자임대계약자산', 'asset', 'fixed_asset', '13', 3, 'common', 1970), + $c('19800', '출자금(장기)', 'asset', 'fixed_asset', '13', 3, 'common', 1980), + $c('19900', '퇴직보험예치금', 'asset', 'fixed_asset', '13', 3, 'common', 1990), + $c('20000', '국민연금전환금', 'asset', 'fixed_asset', '13', 3, 'common', 2000), + $c('21100', '감가상각누계액(공구와기구)', 'asset', 'fixed_asset', '13', 3, 'common', 2110), + $c('21400', '건설중인자산(임대)', 'asset', 'fixed_asset', '13', 3, 'common', 2140), + $c('21500', '미착기계', 'asset', 'fixed_asset', '13', 3, 'common', 2150), + $c('21600', '감가상각누계액(비품)', 'asset', 'fixed_asset', '13', 3, 'common', 2160), + // 무형자산 + $c('23100', '영업권', 'asset', 'fixed_asset', '13', 3, 'common', 2310), + $c('23200', '특허권', 'asset', 'fixed_asset', '13', 3, 'common', 2320), + $c('23300', '상표권', 'asset', 'fixed_asset', '13', 3, 'common', 2330), + $c('23400', '실용신안권', 'asset', 'fixed_asset', '13', 3, 'common', 2340), + $c('23500', '의장권', 'asset', 'fixed_asset', '13', 3, 'common', 2350), + $c('23600', '면허권', 'asset', 'fixed_asset', '13', 3, 'common', 2360), + $c('23700', '광업권', 'asset', 'fixed_asset', '13', 3, 'common', 2370), + $c('23800', '창업비', 'asset', 'fixed_asset', '13', 3, 'common', 2380), + $c('23900', '개발비', 'asset', 'fixed_asset', '13', 3, 'common', 2390), + // 기타비유동자산 (96xxx-97xxx) + $c('96100', '이연법인세자산', 'asset', 'fixed_asset', '13', 3, 'common', 9610), + $c('96200', '임차보증금', 'asset', 'fixed_asset', '13', 3, 'common', 9620), + $c('96300', '전세금', 'asset', 'fixed_asset', '13', 3, 'common', 9630), + $c('96400', '기타보증금', 'asset', 'fixed_asset', '13', 3, 'common', 9640), + $c('96500', '장기외상매출금', 'asset', 'fixed_asset', '13', 3, 'common', 9650), + $c('96600', '현재가치할인차금(장기외상매출금)', 'asset', 'fixed_asset', '13', 3, 'common', 9660), + $c('96700', '대손충당금(장기외상매출금)', 'asset', 'fixed_asset', '13', 3, 'common', 9670), + $c('96800', '장기받을어음', 'asset', 'fixed_asset', '13', 3, 'common', 9680), + $c('96900', '현재가치할인차금(장기받을어음)', 'asset', 'fixed_asset', '13', 3, 'common', 9690), + $c('97000', '대손충당금(장기받을어음)', 'asset', 'fixed_asset', '13', 3, 'common', 9700), + $c('97100', '장기미수금', 'asset', 'fixed_asset', '13', 3, 'common', 9710), + $c('97200', '현재가치할인차금(장기미수금)', 'asset', 'fixed_asset', '13', 3, 'common', 9720), + $c('97300', '대손충당금(장기미수금)', 'asset', 'fixed_asset', '13', 3, 'common', 9730), + $c('97400', '장기선급비용', 'asset', 'fixed_asset', '13', 3, 'common', 9740), + $c('97500', '장기선급금', 'asset', 'fixed_asset', '13', 3, 'common', 9750), + $c('97600', '부도어음과수표', 'asset', 'fixed_asset', '13', 3, 'common', 9760), + $c('97700', '대손충당금(부도어음)', 'asset', 'fixed_asset', '13', 3, 'common', 9770), + $c('97800', '전신전화가입권', 'asset', 'fixed_asset', '13', 3, 'common', 9780), + + // ================================================================ + // 부채 — 유동부채 추가 (parent: '21') + // ================================================================ + $c('25600', '당좌차월', 'liability', 'current_liability', '21', 3, 'common', 2560), + $c('25700', '가수금', 'liability', 'current_liability', '21', 3, 'common', 2570), + $c('25800', '예수보증금', 'liability', 'current_liability', '21', 3, 'common', 2580), + $c('26300', '수입금', 'liability', 'current_liability', '21', 3, 'common', 2630), + $c('26600', '지급보증채무', 'liability', 'current_liability', '21', 3, 'common', 2660), + $c('26700', '수출금융', 'liability', 'current_liability', '21', 3, 'common', 2670), + $c('26800', '수입금융', 'liability', 'current_liability', '21', 3, 'common', 2680), + $c('26900', '공사손실충당금', 'liability', 'current_liability', '21', 3, 'common', 2690), + $c('27000', '하자보수충당금', 'liability', 'current_liability', '21', 3, 'common', 2700), + $c('27100', '공사선수금', 'liability', 'current_liability', '21', 3, 'common', 2710), + $c('27200', '분양선수금', 'liability', 'current_liability', '21', 3, 'common', 2720), + $c('27300', '이연법인세부채', 'liability', 'current_liability', '21', 3, 'common', 2730), + + // ================================================================ + // 부채 — 비유동부채 추가 (parent: '22') + // ================================================================ + $c('29100', '사채', 'liability', 'long_term_liability', '22', 3, 'common', 2910), + $c('29200', '사채할인발행차금', 'liability', 'long_term_liability', '22', 3, 'common', 2920), + $c('29600', '퇴직보험충당부채', 'liability', 'long_term_liability', '22', 3, 'common', 2960), + $c('29700', '중소기업투자준비금', 'liability', 'long_term_liability', '22', 3, 'common', 2970), + $c('29800', '기술개발준비금', 'liability', 'long_term_liability', '22', 3, 'common', 2980), + $c('29900', '해외시장개척준비금', 'liability', 'long_term_liability', '22', 3, 'common', 2990), + $c('30100', '지방이전준비금', 'liability', 'long_term_liability', '22', 3, 'common', 3010), + $c('30200', '수출손실준비금', 'liability', 'long_term_liability', '22', 3, 'common', 3020), + $c('30300', '주임종장기차입금', 'liability', 'long_term_liability', '22', 3, 'common', 3030), + $c('30400', '관계회사장기차입금', 'liability', 'long_term_liability', '22', 3, 'common', 3040), + $c('30500', '외화장기차입금', 'liability', 'long_term_liability', '22', 3, 'common', 3050), + $c('30600', '공사선수금(장기)', 'liability', 'long_term_liability', '22', 3, 'common', 3060), + $c('30800', '장기성지급어음', 'liability', 'long_term_liability', '22', 3, 'common', 3080), + $c('30900', '환율조정대', 'liability', 'long_term_liability', '22', 3, 'common', 3090), + $c('31000', '이연법인세대', 'liability', 'long_term_liability', '22', 3, 'common', 3100), + $c('31100', '신주인수권부사채', 'liability', 'long_term_liability', '22', 3, 'common', 3110), + $c('31200', '전환사채', 'liability', 'long_term_liability', '22', 3, 'common', 3120), + $c('31300', '사채할증발행차금', 'liability', 'long_term_liability', '22', 3, 'common', 3130), + $c('31400', '장기제품보증부채', 'liability', 'long_term_liability', '22', 3, 'common', 3140), + + // ================================================================ + // 자본 — 잉여금 추가 (parent: '32') + // ================================================================ + $c('34200', '감자차익', 'capital', 'capital', '32', 3, 'common', 3420), + $c('34300', '자기주식처분이익', 'capital', 'capital', '32', 3, 'common', 3430), + $c('34900', '기타자본잉여금', 'capital', 'capital', '32', 3, 'common', 3490), + $c('35000', '재평가적립금', 'capital', 'capital', '32', 3, 'common', 3500), + $c('35200', '기업합리화적립금', 'capital', 'capital', '32', 3, 'common', 3520), + $c('35300', '법정적립금', 'capital', 'capital', '32', 3, 'common', 3530), + $c('35400', '재무구조개선적립금', 'capital', 'capital', '32', 3, 'common', 3540), + $c('35500', '임의적립금', 'capital', 'capital', '32', 3, 'common', 3550), + $c('35600', '사업확장적립금', 'capital', 'capital', '32', 3, 'common', 3560), + $c('35700', '감채적립금', 'capital', 'capital', '32', 3, 'common', 3570), + $c('35800', '배당평균적립금', 'capital', 'capital', '32', 3, 'common', 3580), + $c('35900', '주식할인발행차손', 'capital', 'capital', '32', 3, 'common', 3590), + $c('36000', '배당건설이자상각', 'capital', 'capital', '32', 3, 'common', 3600), + $c('36100', '자기주식상환액', 'capital', 'capital', '32', 3, 'common', 3610), + $c('36200', '자기주식처분차금', 'capital', 'capital', '32', 3, 'common', 3620), + $c('36300', '중소기업투자준비금(자본)', 'capital', 'capital', '32', 3, 'common', 3630), + $c('36400', '기술개발준비금(자본)', 'capital', 'capital', '32', 3, 'common', 3640), + $c('36500', '해외시장개척준비금(자본)', 'capital', 'capital', '32', 3, 'common', 3650), + $c('36600', '지방이전준비금(자본)', 'capital', 'capital', '32', 3, 'common', 3660), + $c('36700', '수출손실준비금(자본)', 'capital', 'capital', '32', 3, 'common', 3670), + $c('36800', '기타임의적립금', 'capital', 'capital', '32', 3, 'common', 3680), + $c('36900', '회계변경의누적효과', 'capital', 'capital', '32', 3, 'common', 3690), + $c('37000', '전기오류수정이익', 'capital', 'capital', '32', 3, 'common', 3700), + $c('37100', '전기오류수정손실', 'capital', 'capital', '32', 3, 'common', 3710), + $c('37200', '중간배당금', 'capital', 'capital', '32', 3, 'common', 3720), + $c('37400', '기타이익잉여금', 'capital', 'capital', '32', 3, 'common', 3740), + $c('37600', '이월결손금', 'capital', 'capital', '32', 3, 'common', 3760), + $c('37800', '처분전이익잉여금', 'capital', 'capital', '32', 3, 'common', 3780), + + // ================================================================ + // 자본 — 자본조정 (parent: '33') + // ================================================================ + $c('38000', '당기순손실', 'capital', 'capital_adjustment', '33', 3, 'common', 3800), + $c('38100', '주식할인발행차금', 'capital', 'capital_adjustment', '33', 3, 'common', 3810), + $c('38200', '배당건설이자', 'capital', 'capital_adjustment', '33', 3, 'common', 3820), + $c('38300', '자기주식', 'capital', 'capital_adjustment', '33', 3, 'common', 3830), + $c('38400', '환전대가', 'capital', 'capital_adjustment', '33', 3, 'common', 3840), + $c('38500', '신주인수권대가', 'capital', 'capital_adjustment', '33', 3, 'common', 3850), + $c('38600', '신주발행비', 'capital', 'capital_adjustment', '33', 3, 'common', 3860), + $c('38700', '미교부주식배당금', 'capital', 'capital_adjustment', '33', 3, 'common', 3870), + $c('38800', '신주청약증거금', 'capital', 'capital_adjustment', '33', 3, 'common', 3880), + $c('39200', '국고보조금', 'capital', 'capital_adjustment', '33', 3, 'common', 3920), + $c('39300', '공사부담금', 'capital', 'capital_adjustment', '33', 3, 'common', 3930), + $c('39400', '감자차손', 'capital', 'capital_adjustment', '33', 3, 'common', 3940), + $c('39500', '자기주식처분손실', 'capital', 'capital_adjustment', '33', 3, 'common', 3950), + $c('39600', '주식매입선택권', 'capital', 'capital_adjustment', '33', 3, 'common', 3960), + // 기타포괄손익누계액 + $c('98100', '매도가능증권평가이익', 'capital', 'capital_adjustment', '33', 3, 'common', 9810), + $c('98200', '매도가능증권평가손실', 'capital', 'capital_adjustment', '33', 3, 'common', 9820), + $c('98300', '해외사업환산이익', 'capital', 'capital_adjustment', '33', 3, 'common', 9830), + $c('98400', '해외사업환산손실', 'capital', 'capital_adjustment', '33', 3, 'common', 9840), + $c('98500', '파생상품평가이익', 'capital', 'capital_adjustment', '33', 3, 'common', 9850), + $c('98600', '파생상품평가손실', 'capital', 'capital_adjustment', '33', 3, 'common', 9860), + + // ================================================================ + // 수익 — 매출 추가 (parent: '41') + // ================================================================ + $c('40200', '매출환입및에누리(상품)', 'revenue', 'sales_revenue', '41', 3, 'common', 4020), + $c('40300', '매출할인(상품)', 'revenue', 'sales_revenue', '41', 3, 'common', 4030), + $c('40500', '매출환입및에누리(제품)', 'revenue', 'sales_revenue', '41', 3, 'common', 4050), + $c('40600', '매출할인(제품)', 'revenue', 'sales_revenue', '41', 3, 'common', 4060), + $c('40800', '매출할인(공사)', 'revenue', 'sales_revenue', '41', 3, 'common', 4080), + $c('40900', '완성건물매출', 'revenue', 'sales_revenue', '41', 3, 'common', 4090), + + // ================================================================ + // 수익 — 영업외수익 추가 (parent: '42') + // ================================================================ + $c('90200', '만기보유증권이자', 'revenue', 'other_revenue', '42', 3, 'common', 9020), + $c('90500', '단기투자자산평가이익', 'revenue', 'other_revenue', '42', 3, 'common', 9050), + $c('90600', '단기투자자산처분이익', 'revenue', 'other_revenue', '42', 3, 'common', 9060), + $c('90800', '대손충당금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9080), + $c('90900', '수입수수료', 'revenue', 'other_revenue', '42', 3, 'common', 9090), + $c('91000', '외화환산이익', 'revenue', 'other_revenue', '42', 3, 'common', 9100), + $c('91100', '사채상환이익', 'revenue', 'other_revenue', '42', 3, 'common', 9110), + $c('91200', '전기오류수정이익', 'revenue', 'other_revenue', '42', 3, 'common', 9120), + $c('91300', '하자보수충당금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9130), + $c('91400', '유형자산처분이익', 'revenue', 'other_revenue', '42', 3, 'common', 9140), + $c('91500', '투자자산처분이익', 'revenue', 'other_revenue', '42', 3, 'common', 9150), + $c('91600', '상각채권추심이익', 'revenue', 'other_revenue', '42', 3, 'common', 9160), + $c('91700', '자산수증이익', 'revenue', 'other_revenue', '42', 3, 'common', 9170), + $c('91800', '채무면제이익', 'revenue', 'other_revenue', '42', 3, 'common', 9180), + $c('92000', '투자증권손상차환입', 'revenue', 'other_revenue', '42', 3, 'common', 9200), + $c('92100', '지분법이익', 'revenue', 'other_revenue', '42', 3, 'common', 9210), + $c('92400', '중소투자준비금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9240), + $c('92500', '기술개발준비금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9250), + $c('92600', '해외개척준비금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9260), + $c('92700', '지방이전준비금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9270), + $c('92800', '수출손실준비금환입', 'revenue', 'other_revenue', '42', 3, 'common', 9280), + + // ================================================================ + // 비용 — 매출원가 추가: 45xxx (parent: '51') + // ================================================================ + $c('45100', '상품매출원가', 'expense', 'cogs', '51', 3, 'common', 4510), + $c('45200', '도급공사매출원가', 'expense', 'cogs', '51', 3, 'common', 4520), + $c('45300', '분양공사매출원가', 'expense', 'cogs', '51', 3, 'common', 4530), + $c('45500', '제품매출원가', 'expense', 'cogs', '51', 3, 'common', 4550), + + // ================================================================ + // 비용 — 제조원가 추가: 50xxx-53xxx (parent: '51') + // ================================================================ + $c('50600', '제수당(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5060), + $c('50700', '잡급(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5070), + $c('50900', '퇴직보험충당금전입(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5090), + $c('51000', '퇴직금여(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5100), + $c('51500', '가스수도료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5150), + $c('52300', '경상연구개발비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5230), + $c('52500', '교육훈련비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5250), + $c('52600', '도서인쇄비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5260), + $c('52700', '회의비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5270), + $c('52800', '포장비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5280), + $c('52900', '사무용품비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5290), + $c('53200', '보관료(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5320), + $c('53300', '외주가공비(제조경비)', 'expense', 'cogs', '51', 3, 'manufacturing', 5330), + $c('53400', '시험비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5340), + $c('53500', '기밀비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5350), + $c('53600', '잡비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5360), + $c('53700', '폐기물처리비(제조)', 'expense', 'cogs', '51', 3, 'manufacturing', 5370), + + // ================================================================ + // 비용 — 건설원가 60xxx (parent: '54') + // ================================================================ + $c('60100', '원재료비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6010), + $c('60200', '외주비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6020), + $c('60300', '급여(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6030), + $c('60400', '임금(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6040), + $c('60500', '상여금(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6050), + $c('60600', '잡급(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6060), + $c('60700', '퇴직급여(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6070), + $c('60800', '퇴직보험충당금전입(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6080), + $c('60900', '퇴직금여(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6090), + $c('61000', '중기및운반비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6100), + $c('61100', '복리후생비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6110), + $c('61200', '여비교통비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6120), + $c('61300', '접대비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6130), + $c('61400', '통신비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6140), + $c('61500', '가스수도료(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6150), + $c('61600', '전력비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6160), + $c('61700', '세금과공과금(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6170), + $c('61800', '감가상각비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6180), + $c('61900', '지급임차료(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6190), + $c('62000', '수선비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6200), + $c('62100', '보험료(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6210), + $c('62200', '차량유지비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6220), + $c('62300', '운반비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6230), + $c('62400', '잡자재대(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6240), + $c('62500', '교육훈련비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6250), + $c('62600', '도서인쇄비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6260), + $c('62700', '회의비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6270), + $c('62800', '포장비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6280), + $c('62900', '사무용품비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6290), + $c('63000', '소모품비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6300), + $c('63100', '지급수수료(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6310), + $c('63200', '보관료(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6320), + $c('63300', '외주용역비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6330), + $c('63400', '장비사용료(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6340), + $c('63500', '설계용역비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6350), + $c('63600', '광고선전비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6360), + $c('63700', '소모공구비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6370), + $c('63800', '외주시공비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6380), + $c('63900', '협비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6390), + $c('64000', '잡비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6400), + $c('64100', '공사손실충당금전입(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6410), + $c('64200', '공사손실충당금환입(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6420), + $c('64300', '외주비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 6430), + $c('64400', '유류비(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 6440), + + // ================================================================ + // 비용 — 건설원가 70xxx (parent: '54') + // ================================================================ + $c('70100', '원재료비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7010), + $c('70200', '중기및운반비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7020), + $c('70300', '급여(건설노무비)', 'expense', 'construction_cost', '54', 3, 'construction', 7030), + $c('70400', '임금(건설노무비)', 'expense', 'construction_cost', '54', 3, 'construction', 7040), + $c('70500', '상여금(건설노무비)', 'expense', 'construction_cost', '54', 3, 'construction', 7050), + $c('70600', '제수당(건설노무비)', 'expense', 'construction_cost', '54', 3, 'construction', 7060), + $c('70700', '퇴직급여(건설노무비)', 'expense', 'construction_cost', '54', 3, 'construction', 7070), + $c('70800', '퇴직보험충당금전입(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7080), + $c('70900', '퇴직금여(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7090), + $c('71000', '건설용지비', 'expense', 'construction_cost', '54', 3, 'construction', 7100), + $c('71100', '복리후생비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7110), + $c('71200', '여비교통비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7120), + $c('71300', '접대비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7130), + $c('71400', '통신비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7140), + $c('71500', '가스수도료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7150), + $c('71600', '전력비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7160), + $c('71700', '세금과공과금(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7170), + $c('71800', '감가상각비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7180), + $c('71900', '지급임차료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7190), + $c('72000', '수선비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7200), + $c('72100', '보험료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7210), + $c('72200', '차량유지비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7220), + $c('72300', '경상연구개발비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7230), + $c('72400', '잡자재대(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7240), + $c('72500', '교육훈련비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7250), + $c('72600', '도서인쇄비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7260), + $c('72700', '회의비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7270), + $c('72800', '포장비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7280), + $c('72900', '사무용품비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7290), + $c('73000', '소모품비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7300), + $c('73100', '지급수수료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7310), + $c('73200', '보관료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7320), + $c('73300', '외주가공비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7330), + $c('73400', '시험비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7340), + $c('73500', '설계용역비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7350), + $c('73600', '가설재손료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7360), + $c('73700', '잡비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7370), + $c('73800', '폐기물처리비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7380), + $c('73900', '장비사용료(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7390), + $c('74100', '공사손실충당금전입(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7410), + $c('74200', '공사손실충당금환입(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7420), + $c('74300', '외주비(건설경비)', 'expense', 'construction_cost', '54', 3, 'construction', 7430), + $c('74900', '명예퇴직금(건설)', 'expense', 'construction_cost', '54', 3, 'construction', 7490), + + // ================================================================ + // 비용 — 판관비 추가 (parent: '52') + // ================================================================ + $c('80400', '제수당', 'expense', 'selling_admin', '52', 3, 'admin', 8040), + $c('80500', '잡급', 'expense', 'selling_admin', '52', 3, 'admin', 8050), + $c('80700', '퇴직보험충당금전입', 'expense', 'selling_admin', '52', 3, 'admin', 8070), + $c('80800', '퇴직금여', 'expense', 'selling_admin', '52', 3, 'admin', 8080), + $c('81600', '전력비', 'expense', 'selling_admin', '52', 3, 'admin', 8160), + $c('82800', '포장비', 'expense', 'selling_admin', '52', 3, 'admin', 8280), + $c('83400', '판매촉진비', 'expense', 'selling_admin', '52', 3, 'admin', 8340), + $c('83600', '기밀비', 'expense', 'selling_admin', '52', 3, 'admin', 8360), + $c('83700', '건물관리비', 'expense', 'selling_admin', '52', 3, 'admin', 8370), + $c('83800', '수출제비용', 'expense', 'selling_admin', '52', 3, 'admin', 8380), + $c('83900', '판매수수료', 'expense', 'selling_admin', '52', 3, 'admin', 8390), + $c('84000', '무형고정자산상각', 'expense', 'selling_admin', '52', 3, 'admin', 8400), + $c('84100', '환가료', 'expense', 'selling_admin', '52', 3, 'admin', 8410), + $c('84200', '견본비', 'expense', 'selling_admin', '52', 3, 'admin', 8420), + $c('84300', '해외접대비', 'expense', 'selling_admin', '52', 3, 'admin', 8430), + $c('84400', '해외시장개척비', 'expense', 'selling_admin', '52', 3, 'admin', 8440), + $c('84500', '미분양주택관리비', 'expense', 'selling_admin', '52', 3, 'admin', 8450), + $c('84600', '수주비', 'expense', 'selling_admin', '52', 3, 'admin', 8460), + $c('84700', '하자보수충당금전입', 'expense', 'selling_admin', '52', 3, 'admin', 8470), + $c('84900', '명예퇴직금', 'expense', 'selling_admin', '52', 3, 'admin', 8490), + + // ================================================================ + // 비용 — 영업외비용 추가 (parent: '53') + // ================================================================ + $c('93400', '기타의대손상각비', 'expense', 'other_expense', '53', 3, 'common', 9340), + $c('93500', '외화환산손실', 'expense', 'other_expense', '53', 3, 'common', 9350), + $c('93600', '매출채권처분손실', 'expense', 'other_expense', '53', 3, 'common', 9360), + $c('93700', '단기투자자산평가손실', 'expense', 'other_expense', '53', 3, 'common', 9370), + $c('93800', '단기투자자산처분손실', 'expense', 'other_expense', '53', 3, 'common', 9380), + $c('93900', '재고자산감모손실', 'expense', 'other_expense', '53', 3, 'common', 9390), + $c('94000', '재고자산평가손실', 'expense', 'other_expense', '53', 3, 'common', 9400), + $c('94100', '재해손실', 'expense', 'other_expense', '53', 3, 'common', 9410), + $c('94200', '전기오류수정손실', 'expense', 'other_expense', '53', 3, 'common', 9420), + $c('94300', '투자증권손상차손', 'expense', 'other_expense', '53', 3, 'common', 9430), + $c('94700', '사채상환손실', 'expense', 'other_expense', '53', 3, 'common', 9470), + $c('95000', '투자자산처분손실', 'expense', 'other_expense', '53', 3, 'common', 9500), + $c('95100', '중소투자준비금전입', 'expense', 'other_expense', '53', 3, 'common', 9510), + $c('95200', '기술개발준비금전입', 'expense', 'other_expense', '53', 3, 'common', 9520), + $c('95300', '해외개척준비금전입', 'expense', 'other_expense', '53', 3, 'common', 9530), + $c('95400', '지방이전준비금전입', 'expense', 'other_expense', '53', 3, 'common', 9540), + $c('95500', '수출손실준비금전입', 'expense', 'other_expense', '53', 3, 'common', 9550), + $c('95700', '특별상각', 'expense', 'other_expense', '53', 3, 'common', 9570), + // 중단사업 + $c('99100', '사업중단직접비', 'expense', 'other_expense', '53', 3, 'common', 9910), + $c('99200', '중단사업자산손상차손', 'expense', 'other_expense', '53', 3, 'common', 9920), + $c('99300', '중단사업손상차환입', 'expense', 'other_expense', '53', 3, 'common', 9930), + $c('99700', '중단손익', 'expense', 'other_expense', '53', 3, 'common', 9970), + ]; + } +}; diff --git a/routes/api/v1/finance.php b/routes/api/v1/finance.php index 64924f9..2b996bd 100644 --- a/routes/api/v1/finance.php +++ b/routes/api/v1/finance.php @@ -163,6 +163,8 @@ Route::get('/{id}', [CardTransactionController::class, 'show'])->whereNumber('id')->name('v1.card-transactions.show'); Route::put('/{id}', [CardTransactionController::class, 'update'])->whereNumber('id')->name('v1.card-transactions.update'); Route::delete('/{id}', [CardTransactionController::class, 'destroy'])->whereNumber('id')->name('v1.card-transactions.destroy'); + Route::get('/{id}/journal-entries', [CardTransactionController::class, 'getJournalEntries'])->whereNumber('id')->name('v1.card-transactions.journal-entries.show'); + Route::post('/{id}/journal-entries', [CardTransactionController::class, 'storeJournalEntries'])->whereNumber('id')->name('v1.card-transactions.journal-entries.store'); }); // Bank Transaction API (은행 거래 조회) @@ -287,6 +289,10 @@ Route::post('/{id}/cancel', [TaxInvoiceController::class, 'cancel'])->whereNumber('id')->name('v1.tax-invoices.cancel'); Route::get('/{id}/check-status', [TaxInvoiceController::class, 'checkStatus'])->whereNumber('id')->name('v1.tax-invoices.check-status'); Route::post('/bulk-issue', [TaxInvoiceController::class, 'bulkIssue'])->name('v1.tax-invoices.bulk-issue'); + Route::get('/{id}/journal-entries', [TaxInvoiceController::class, 'getJournalEntries'])->whereNumber('id')->name('v1.tax-invoices.journal-entries.show'); + Route::post('/{id}/journal-entries', [TaxInvoiceController::class, 'storeJournalEntries'])->whereNumber('id')->name('v1.tax-invoices.journal-entries.store'); + Route::put('/{id}/journal-entries', [TaxInvoiceController::class, 'storeJournalEntries'])->whereNumber('id')->name('v1.tax-invoices.journal-entries.update'); + Route::delete('/{id}/journal-entries', [TaxInvoiceController::class, 'deleteJournalEntries'])->whereNumber('id')->name('v1.tax-invoices.journal-entries.destroy'); }); // Bad Debt API (악성채권 추심관리) @@ -320,6 +326,8 @@ Route::prefix('account-subjects')->group(function () { Route::get('', [AccountSubjectController::class, 'index'])->name('v1.account-subjects.index'); Route::post('', [AccountSubjectController::class, 'store'])->name('v1.account-subjects.store'); + Route::post('/seed-defaults', [AccountSubjectController::class, 'seedDefaults'])->name('v1.account-subjects.seed-defaults'); + Route::put('/{id}', [AccountSubjectController::class, 'update'])->whereNumber('id')->name('v1.account-subjects.update'); Route::patch('/{id}/status', [AccountSubjectController::class, 'toggleStatus'])->whereNumber('id')->name('v1.account-subjects.toggle-status'); Route::delete('/{id}', [AccountSubjectController::class, 'destroy'])->whereNumber('id')->name('v1.account-subjects.destroy'); });