feat: [finance] 계정과목 확장 및 전표 연동 시스템 구현
- AccountCode 모델/서비스 확장 (업데이트, 기본 계정과목 시딩) - JournalSyncService 추가 (전표 자동 연동) - SyncsExpenseAccounts 트레이트 추가 - CardTransactionController, TaxInvoiceController 기능 확장 - expense_accounts 테이블에 전표 연결 컬럼 마이그레이션 - account_codes 테이블 확장 마이그레이션 - 전체 테넌트 기본 계정과목 시딩 마이그레이션 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -148,4 +153,75 @@ public function summary(TaxInvoiceSummaryRequest $request)
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 중 하나여야 합니다.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\V1\AccountSubject;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateAccountSubjectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'category' => ['nullable', 'string', 'in:asset,liability,capital,revenue,expense'],
|
||||
'sub_category' => ['nullable', 'string', 'max:50'],
|
||||
'parent_code' => ['nullable', 'string', 'max:10'],
|
||||
'depth' => ['nullable', 'integer', 'in:1,2,3'],
|
||||
'department_type' => ['nullable', 'string', 'in:common,manufacturing,admin'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'sort_order' => ['nullable', 'integer'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'category.in' => '유효한 분류를 선택하세요.',
|
||||
'depth.in' => '계층은 1(대), 2(중), 3(소) 중 하나여야 합니다.',
|
||||
'department_type.in' => '부문은 common, manufacturing, admin 중 하나여야 합니다.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 트레이트로 이관
|
||||
|
||||
/**
|
||||
* 원본 거래 정보 조회 (입금/출금)
|
||||
*/
|
||||
|
||||
214
app/Services/JournalSyncService.php
Normal file
214
app/Services/JournalSyncService.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Tenants\AccountCode;
|
||||
use App\Models\Tenants\JournalEntry;
|
||||
use App\Models\Tenants\JournalEntryLine;
|
||||
use App\Traits\SyncsExpenseAccounts;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 세금계산서/카드거래 등 외부 소스의 분개 통합 관리 서비스
|
||||
*
|
||||
* journal_entries + journal_entry_lines에 저장하고
|
||||
* 복리후생비/접대비는 expense_accounts에 동기화 (CEO 대시보드)
|
||||
*/
|
||||
class JournalSyncService extends Service
|
||||
{
|
||||
use SyncsExpenseAccounts;
|
||||
|
||||
/**
|
||||
* 소스에 대한 분개 저장 (생성 또는 교체)
|
||||
*
|
||||
* @param string $sourceType JournalEntry::SOURCE_TAX_INVOICE 등
|
||||
* @param string $sourceKey 'tax_invoice_123' 등
|
||||
* @param string $entryDate 전표일자 (Y-m-d)
|
||||
* @param string|null $description 적요
|
||||
* @param array $rows 분개 행 [{side, account_code, account_name?, debit_amount, credit_amount, vendor_id?, vendor_name?, memo?}]
|
||||
*/
|
||||
public function saveForSource(
|
||||
string $sourceType,
|
||||
string $sourceKey,
|
||||
string $entryDate,
|
||||
?string $description,
|
||||
array $rows,
|
||||
): JournalEntry {
|
||||
$tenantId = $this->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;
|
||||
}
|
||||
}
|
||||
98
app/Traits/SyncsExpenseAccounts.php
Normal file
98
app/Traits/SyncsExpenseAccounts.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Tenants\ExpenseAccount;
|
||||
use App\Models\Tenants\JournalEntry;
|
||||
|
||||
/**
|
||||
* 전표 저장/수정/삭제 시 expense_accounts 동기화 (복리후생비/접대비 → CEO 대시보드)
|
||||
*
|
||||
* 사용처: GeneralJournalEntryService, JournalSyncService
|
||||
*/
|
||||
trait SyncsExpenseAccounts
|
||||
{
|
||||
/**
|
||||
* 계정과목명 → expense_accounts account_type 매핑
|
||||
*/
|
||||
private static array $expenseAccountMap = [
|
||||
'복리후생비' => 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('expense_accounts', function (Blueprint $table) {
|
||||
$table->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']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 계정과목 마스터 확장
|
||||
*
|
||||
* - sub_category: 중분류 (유동자산, 판관비, 매출원가 등)
|
||||
* - parent_code: 상위 계정과목 코드 (계층 구조)
|
||||
* - depth: 계층 깊이 (1=대, 2=중, 3=소)
|
||||
* - department_type: 부문 (common=공통, manufacturing=제조, admin=관리)
|
||||
* - description: 계정과목 설명
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('account_codes', function (Blueprint $table) {
|
||||
$table->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',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 모든 기존 테넌트에 더존 Smart A 표준 계정과목 128건 자동 시드
|
||||
*
|
||||
* 조건: tenant_id + code 중복 시 skip (기존 데이터 보호)
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$tenantIds = DB::table('tenants')
|
||||
->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),
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user