feat: 예상 지출 자동 동기화 Observer 구현
- expected_expenses 테이블에 source_type, source_id 컬럼 추가 - PurchaseExpenseSyncObserver: 매입 → 예상 지출 동기화 - WithdrawalExpenseSyncObserver: 카드결제만 → 예상 지출 동기화 - BillExpenseSyncObserver: 발행어음만 → 예상 지출 동기화 - 생성/수정/삭제/복원/강제삭제 이벤트 모두 처리 - 조건 변경 시 자동 동기화 해제 (카드→현금, 발행→수취) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,10 @@
|
|||||||
namespace App\Models\Tenants;
|
namespace App\Models\Tenants;
|
||||||
|
|
||||||
use App\Traits\BelongsToTenant;
|
use App\Traits\BelongsToTenant;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
class ExpectedExpense extends Model
|
class ExpectedExpense extends Model
|
||||||
@@ -24,6 +26,8 @@ class ExpectedExpense extends Model
|
|||||||
'payment_status',
|
'payment_status',
|
||||||
'approval_status',
|
'approval_status',
|
||||||
'description',
|
'description',
|
||||||
|
'source_type',
|
||||||
|
'source_id',
|
||||||
'created_by',
|
'created_by',
|
||||||
'updated_by',
|
'updated_by',
|
||||||
'deleted_by',
|
'deleted_by',
|
||||||
@@ -42,6 +46,8 @@ class ExpectedExpense extends Model
|
|||||||
*/
|
*/
|
||||||
public const TRANSACTION_TYPES = [
|
public const TRANSACTION_TYPES = [
|
||||||
'purchase' => '매입',
|
'purchase' => '매입',
|
||||||
|
'card' => '카드결제',
|
||||||
|
'bill' => '발행어음',
|
||||||
'advance' => '선급금',
|
'advance' => '선급금',
|
||||||
'suspense' => '가지급금',
|
'suspense' => '가지급금',
|
||||||
'rent' => '임대료',
|
'rent' => '임대료',
|
||||||
@@ -131,4 +137,29 @@ public function getApprovalStatusLabelAttribute(): string
|
|||||||
{
|
{
|
||||||
return self::APPROVAL_STATUSES[$this->approval_status] ?? $this->approval_status;
|
return self::APPROVAL_STATUSES[$this->approval_status] ?? $this->approval_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 원본 소스 관계 (Polymorphic)
|
||||||
|
*/
|
||||||
|
public function source(): MorphTo
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 원본 소스로 검색
|
||||||
|
*/
|
||||||
|
public function scopeBySource(Builder $query, string $sourceType, int $sourceId): Builder
|
||||||
|
{
|
||||||
|
return $query->where('source_type', $sourceType)
|
||||||
|
->where('source_id', $sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화된 레코드 여부
|
||||||
|
*/
|
||||||
|
public function isSynced(): bool
|
||||||
|
{
|
||||||
|
return ! is_null($this->source_type) && ! is_null($this->source_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
169
app/Observers/ExpenseSync/BillExpenseSyncObserver.php
Normal file
169
app/Observers/ExpenseSync/BillExpenseSyncObserver.php
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers\ExpenseSync;
|
||||||
|
|
||||||
|
use App\Models\Tenants\Bill;
|
||||||
|
use App\Models\Tenants\ExpectedExpense;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 발행어음 데이터를 예상 지출(expected_expenses)로 자동 동기화
|
||||||
|
* (bill_type = 'issued' 인 경우만)
|
||||||
|
*/
|
||||||
|
class BillExpenseSyncObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 어음 생성 시 → 발행어음인 경우 예상 지출 생성
|
||||||
|
*/
|
||||||
|
public function created(Bill $bill): void
|
||||||
|
{
|
||||||
|
if ($this->isIssuedBill($bill)) {
|
||||||
|
$this->syncToExpectedExpense($bill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어음 수정 시 → 발행어음 상태 변경 처리
|
||||||
|
*/
|
||||||
|
public function updated(Bill $bill): void
|
||||||
|
{
|
||||||
|
$wasIssued = $bill->getOriginal('bill_type') === 'issued';
|
||||||
|
$isIssued = $this->isIssuedBill($bill);
|
||||||
|
|
||||||
|
if ($isIssued) {
|
||||||
|
// 발행어음 → 동기화
|
||||||
|
$this->syncToExpectedExpense($bill);
|
||||||
|
} elseif ($wasIssued && ! $isIssued) {
|
||||||
|
// 발행어음에서 수취어음으로 변경 → 예상 지출 삭제
|
||||||
|
$this->deleteExpectedExpense($bill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어음 삭제 시 → 예상 지출 삭제
|
||||||
|
*/
|
||||||
|
public function deleted(Bill $bill): void
|
||||||
|
{
|
||||||
|
if ($this->isIssuedBill($bill)) {
|
||||||
|
$this->deleteExpectedExpense($bill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어음 복원 시 → 발행어음인 경우 예상 지출 복원
|
||||||
|
*/
|
||||||
|
public function restored(Bill $bill): void
|
||||||
|
{
|
||||||
|
if (! $this->isIssuedBill($bill)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expense = ExpectedExpense::withTrashed()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->bySource('bills', $bill->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($expense) {
|
||||||
|
$expense->restore();
|
||||||
|
$this->updateExpectedExpense($expense, $bill);
|
||||||
|
} else {
|
||||||
|
$this->createExpectedExpense($bill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어음 강제 삭제 시 → 예상 지출 강제 삭제
|
||||||
|
*/
|
||||||
|
public function forceDeleted(Bill $bill): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::withTrashed()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->bySource('bills', $bill->id)
|
||||||
|
->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 발행어음 여부 확인
|
||||||
|
*/
|
||||||
|
protected function isIssuedBill(Bill $bill): bool
|
||||||
|
{
|
||||||
|
return $bill->bill_type === 'issued';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출에 동기화
|
||||||
|
*/
|
||||||
|
protected function syncToExpectedExpense(Bill $bill): void
|
||||||
|
{
|
||||||
|
$expense = ExpectedExpense::withoutGlobalScopes()
|
||||||
|
->bySource('bills', $bill->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($expense) {
|
||||||
|
$this->updateExpectedExpense($expense, $bill);
|
||||||
|
} else {
|
||||||
|
$this->createExpectedExpense($bill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 삭제
|
||||||
|
*/
|
||||||
|
protected function deleteExpectedExpense(Bill $bill): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::withoutGlobalScopes()
|
||||||
|
->bySource('bills', $bill->id)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 어음 상태 → 지급상태 매핑
|
||||||
|
*/
|
||||||
|
protected function mapPaymentStatus(string $billStatus): string
|
||||||
|
{
|
||||||
|
return match ($billStatus) {
|
||||||
|
'paymentComplete', 'collectionComplete' => 'paid',
|
||||||
|
'dishonored' => 'overdue',
|
||||||
|
default => 'pending',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 생성
|
||||||
|
*/
|
||||||
|
protected function createExpectedExpense(Bill $bill): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::create([
|
||||||
|
'tenant_id' => $bill->tenant_id,
|
||||||
|
'expected_payment_date' => $bill->maturity_date, // 만기일을 예상 지급일로
|
||||||
|
'transaction_type' => 'bill',
|
||||||
|
'amount' => $bill->amount,
|
||||||
|
'client_id' => $bill->client_id,
|
||||||
|
'client_name' => $bill->client_name,
|
||||||
|
'bank_account_id' => $bill->bank_account_id,
|
||||||
|
'description' => $bill->note ?? "어음번호: {$bill->bill_number}",
|
||||||
|
'payment_status' => $this->mapPaymentStatus($bill->status),
|
||||||
|
'approval_status' => 'none',
|
||||||
|
'source_type' => 'bills',
|
||||||
|
'source_id' => $bill->id,
|
||||||
|
'created_by' => $bill->created_by,
|
||||||
|
'updated_by' => $bill->updated_by,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 업데이트
|
||||||
|
*/
|
||||||
|
protected function updateExpectedExpense(ExpectedExpense $expense, Bill $bill): void
|
||||||
|
{
|
||||||
|
$expense->update([
|
||||||
|
'expected_payment_date' => $bill->maturity_date,
|
||||||
|
'amount' => $bill->amount,
|
||||||
|
'client_id' => $bill->client_id,
|
||||||
|
'client_name' => $bill->client_name,
|
||||||
|
'bank_account_id' => $bill->bank_account_id,
|
||||||
|
'description' => $bill->note ?? "어음번호: {$bill->bill_number}",
|
||||||
|
'payment_status' => $this->mapPaymentStatus($bill->status),
|
||||||
|
'updated_by' => $bill->updated_by,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
120
app/Observers/ExpenseSync/PurchaseExpenseSyncObserver.php
Normal file
120
app/Observers/ExpenseSync/PurchaseExpenseSyncObserver.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers\ExpenseSync;
|
||||||
|
|
||||||
|
use App\Models\Tenants\ExpectedExpense;
|
||||||
|
use App\Models\Tenants\Purchase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매입 데이터를 예상 지출(expected_expenses)로 자동 동기화
|
||||||
|
*/
|
||||||
|
class PurchaseExpenseSyncObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 매입 생성 시 → 예상 지출 생성
|
||||||
|
*/
|
||||||
|
public function created(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
$this->syncToExpectedExpense($purchase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매입 수정 시 → 예상 지출 업데이트
|
||||||
|
*/
|
||||||
|
public function updated(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
$this->syncToExpectedExpense($purchase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매입 삭제 시 → 예상 지출 삭제
|
||||||
|
*/
|
||||||
|
public function deleted(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::withoutGlobalScopes()
|
||||||
|
->bySource('purchases', $purchase->id)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매입 복원 시 → 예상 지출 복원
|
||||||
|
*/
|
||||||
|
public function restored(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
// 기존 soft deleted 레코드가 있으면 복원, 없으면 생성
|
||||||
|
$expense = ExpectedExpense::withTrashed()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->bySource('purchases', $purchase->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($expense) {
|
||||||
|
$expense->restore();
|
||||||
|
$this->updateExpectedExpense($expense, $purchase);
|
||||||
|
} else {
|
||||||
|
$this->syncToExpectedExpense($purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매입 강제 삭제 시 → 예상 지출 강제 삭제
|
||||||
|
*/
|
||||||
|
public function forceDeleted(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::withTrashed()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->bySource('purchases', $purchase->id)
|
||||||
|
->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출에 동기화
|
||||||
|
*/
|
||||||
|
protected function syncToExpectedExpense(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
$expense = ExpectedExpense::withoutGlobalScopes()
|
||||||
|
->bySource('purchases', $purchase->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($expense) {
|
||||||
|
$this->updateExpectedExpense($expense, $purchase);
|
||||||
|
} else {
|
||||||
|
$this->createExpectedExpense($purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 생성
|
||||||
|
*/
|
||||||
|
protected function createExpectedExpense(Purchase $purchase): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::create([
|
||||||
|
'tenant_id' => $purchase->tenant_id,
|
||||||
|
'expected_payment_date' => $purchase->purchase_date,
|
||||||
|
'transaction_type' => 'purchase',
|
||||||
|
'amount' => $purchase->total_amount,
|
||||||
|
'client_id' => $purchase->client_id,
|
||||||
|
'description' => $purchase->description ?? "매입번호: {$purchase->purchase_number}",
|
||||||
|
'payment_status' => $purchase->withdrawal_id ? 'paid' : 'pending',
|
||||||
|
'approval_status' => 'none',
|
||||||
|
'source_type' => 'purchases',
|
||||||
|
'source_id' => $purchase->id,
|
||||||
|
'created_by' => $purchase->created_by,
|
||||||
|
'updated_by' => $purchase->updated_by,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 업데이트
|
||||||
|
*/
|
||||||
|
protected function updateExpectedExpense(ExpectedExpense $expense, Purchase $purchase): void
|
||||||
|
{
|
||||||
|
$expense->update([
|
||||||
|
'expected_payment_date' => $purchase->purchase_date,
|
||||||
|
'amount' => $purchase->total_amount,
|
||||||
|
'client_id' => $purchase->client_id,
|
||||||
|
'description' => $purchase->description ?? "매입번호: {$purchase->purchase_number}",
|
||||||
|
'payment_status' => $purchase->withdrawal_id ? 'paid' : 'pending',
|
||||||
|
'updated_by' => $purchase->updated_by,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
158
app/Observers/ExpenseSync/WithdrawalExpenseSyncObserver.php
Normal file
158
app/Observers/ExpenseSync/WithdrawalExpenseSyncObserver.php
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers\ExpenseSync;
|
||||||
|
|
||||||
|
use App\Models\Tenants\ExpectedExpense;
|
||||||
|
use App\Models\Tenants\Withdrawal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드결제 데이터를 예상 지출(expected_expenses)로 자동 동기화
|
||||||
|
* (payment_method = 'card' 인 경우만)
|
||||||
|
*/
|
||||||
|
class WithdrawalExpenseSyncObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 출금 생성 시 → 카드결제인 경우 예상 지출 생성
|
||||||
|
*/
|
||||||
|
public function created(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
if ($this->isCardPayment($withdrawal)) {
|
||||||
|
$this->syncToExpectedExpense($withdrawal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 출금 수정 시 → 카드결제 상태 변경 처리
|
||||||
|
*/
|
||||||
|
public function updated(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
$wasCard = $withdrawal->getOriginal('payment_method') === 'card';
|
||||||
|
$isCard = $this->isCardPayment($withdrawal);
|
||||||
|
|
||||||
|
if ($isCard) {
|
||||||
|
// 카드결제 → 동기화
|
||||||
|
$this->syncToExpectedExpense($withdrawal);
|
||||||
|
} elseif ($wasCard && ! $isCard) {
|
||||||
|
// 카드결제에서 다른 방법으로 변경 → 예상 지출 삭제
|
||||||
|
$this->deleteExpectedExpense($withdrawal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 출금 삭제 시 → 예상 지출 삭제
|
||||||
|
*/
|
||||||
|
public function deleted(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
if ($this->isCardPayment($withdrawal)) {
|
||||||
|
$this->deleteExpectedExpense($withdrawal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 출금 복원 시 → 카드결제인 경우 예상 지출 복원
|
||||||
|
*/
|
||||||
|
public function restored(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
if (! $this->isCardPayment($withdrawal)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expense = ExpectedExpense::withTrashed()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->bySource('withdrawals', $withdrawal->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($expense) {
|
||||||
|
$expense->restore();
|
||||||
|
$this->updateExpectedExpense($expense, $withdrawal);
|
||||||
|
} else {
|
||||||
|
$this->createExpectedExpense($withdrawal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 출금 강제 삭제 시 → 예상 지출 강제 삭제
|
||||||
|
*/
|
||||||
|
public function forceDeleted(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::withTrashed()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->bySource('withdrawals', $withdrawal->id)
|
||||||
|
->forceDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드결제 여부 확인
|
||||||
|
*/
|
||||||
|
protected function isCardPayment(Withdrawal $withdrawal): bool
|
||||||
|
{
|
||||||
|
return $withdrawal->payment_method === 'card';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출에 동기화
|
||||||
|
*/
|
||||||
|
protected function syncToExpectedExpense(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
$expense = ExpectedExpense::withoutGlobalScopes()
|
||||||
|
->bySource('withdrawals', $withdrawal->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($expense) {
|
||||||
|
$this->updateExpectedExpense($expense, $withdrawal);
|
||||||
|
} else {
|
||||||
|
$this->createExpectedExpense($withdrawal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 삭제
|
||||||
|
*/
|
||||||
|
protected function deleteExpectedExpense(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::withoutGlobalScopes()
|
||||||
|
->bySource('withdrawals', $withdrawal->id)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 생성
|
||||||
|
*/
|
||||||
|
protected function createExpectedExpense(Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
ExpectedExpense::create([
|
||||||
|
'tenant_id' => $withdrawal->tenant_id,
|
||||||
|
'expected_payment_date' => $withdrawal->withdrawal_date,
|
||||||
|
'transaction_type' => 'card',
|
||||||
|
'amount' => $withdrawal->amount,
|
||||||
|
'client_id' => $withdrawal->client_id,
|
||||||
|
'client_name' => $withdrawal->client_name ?? $withdrawal->merchant_name,
|
||||||
|
'bank_account_id' => $withdrawal->bank_account_id,
|
||||||
|
'account_code' => $withdrawal->account_code,
|
||||||
|
'description' => $withdrawal->description,
|
||||||
|
'payment_status' => 'paid', // 카드결제는 이미 결제 완료 상태
|
||||||
|
'approval_status' => 'none',
|
||||||
|
'source_type' => 'withdrawals',
|
||||||
|
'source_id' => $withdrawal->id,
|
||||||
|
'created_by' => $withdrawal->created_by,
|
||||||
|
'updated_by' => $withdrawal->updated_by,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 예상 지출 업데이트
|
||||||
|
*/
|
||||||
|
protected function updateExpectedExpense(ExpectedExpense $expense, Withdrawal $withdrawal): void
|
||||||
|
{
|
||||||
|
$expense->update([
|
||||||
|
'expected_payment_date' => $withdrawal->withdrawal_date,
|
||||||
|
'amount' => $withdrawal->amount,
|
||||||
|
'client_id' => $withdrawal->client_id,
|
||||||
|
'client_name' => $withdrawal->client_name ?? $withdrawal->merchant_name,
|
||||||
|
'bank_account_id' => $withdrawal->bank_account_id,
|
||||||
|
'account_code' => $withdrawal->account_code,
|
||||||
|
'description' => $withdrawal->description,
|
||||||
|
'updated_by' => $withdrawal->updated_by,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
use App\Models\Tenants\Deposit;
|
use App\Models\Tenants\Deposit;
|
||||||
use App\Models\Tenants\ExpectedExpense;
|
use App\Models\Tenants\ExpectedExpense;
|
||||||
use App\Models\Tenants\Stock;
|
use App\Models\Tenants\Stock;
|
||||||
|
use App\Models\Tenants\Bill;
|
||||||
|
use App\Models\Tenants\Purchase;
|
||||||
use App\Models\Tenants\Tenant;
|
use App\Models\Tenants\Tenant;
|
||||||
use App\Models\Tenants\Withdrawal;
|
use App\Models\Tenants\Withdrawal;
|
||||||
use App\Observers\MenuObserver;
|
use App\Observers\MenuObserver;
|
||||||
@@ -24,6 +26,9 @@
|
|||||||
use App\Observers\TodayIssue\OrderIssueObserver;
|
use App\Observers\TodayIssue\OrderIssueObserver;
|
||||||
use App\Observers\TodayIssue\StockIssueObserver;
|
use App\Observers\TodayIssue\StockIssueObserver;
|
||||||
use App\Observers\TodayIssue\WithdrawalIssueObserver;
|
use App\Observers\TodayIssue\WithdrawalIssueObserver;
|
||||||
|
use App\Observers\ExpenseSync\BillExpenseSyncObserver;
|
||||||
|
use App\Observers\ExpenseSync\PurchaseExpenseSyncObserver;
|
||||||
|
use App\Observers\ExpenseSync\WithdrawalExpenseSyncObserver;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -83,5 +88,10 @@ public function boot(): void
|
|||||||
Client::observe(ClientIssueObserver::class);
|
Client::observe(ClientIssueObserver::class);
|
||||||
Deposit::observe(DepositIssueObserver::class);
|
Deposit::observe(DepositIssueObserver::class);
|
||||||
Withdrawal::observe(WithdrawalIssueObserver::class);
|
Withdrawal::observe(WithdrawalIssueObserver::class);
|
||||||
|
|
||||||
|
// 예상 지출 자동 동기화 (매입/카드결제/발행어음 → expected_expenses)
|
||||||
|
Purchase::observe(PurchaseExpenseSyncObserver::class);
|
||||||
|
Withdrawal::observe(WithdrawalExpenseSyncObserver::class);
|
||||||
|
Bill::observe(BillExpenseSyncObserver::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?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('expected_expenses', function (Blueprint $table) {
|
||||||
|
$table->string('source_type', 50)->nullable()->after('description')->comment('원본 테이블: purchases/withdrawals/bills');
|
||||||
|
$table->unsignedBigInteger('source_id')->nullable()->after('source_type')->comment('원본 레코드 ID');
|
||||||
|
|
||||||
|
// 복합 인덱스: 동기화된 레코드 검색용
|
||||||
|
$table->index(['source_type', 'source_id'], 'idx_source');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('expected_expenses', function (Blueprint $table) {
|
||||||
|
$table->dropIndex('idx_source');
|
||||||
|
$table->dropColumn(['source_type', 'source_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user