'date', 'total_debit' => 'integer', 'total_credit' => 'integer', ]; public function lines() { return $this->hasMany(JournalEntryLine::class)->orderBy('line_no'); } public function scopeForTenant($query, $tenantId) { return $query->where('tenant_id', $tenantId); } /** * 분개 완료된 source_key 일괄 조회 */ public static function getJournaledSourceKeys(int $tenantId, string $sourceType, array $sourceKeys): array { if (empty($sourceKeys)) { return []; } return static::where('tenant_id', $tenantId) ->where('source_type', $sourceType) ->whereIn('source_key', $sourceKeys) ->pluck('source_key') ->toArray(); } /** * source_key로 분개 전표 조회 (ID 포함) */ public static function getJournalBySourceKey(int $tenantId, string $sourceType, string $sourceKey) { return static::where('tenant_id', $tenantId) ->where('source_type', $sourceType) ->where('source_key', $sourceKey) ->first(); } /** * 전표번호 자동채번: JE-YYYYMMDD-NNN */ public static function generateEntryNo($tenantId, $date) { $dateStr = date('Ymd', strtotime($date)); $prefix = "JE-{$dateStr}-"; // withTrashed: soft-deleted 레코드도 포함하여 채번 (DB unique 제약 충돌 방지) $last = static::withTrashed() ->where('tenant_id', $tenantId) ->where('entry_no', 'like', $prefix . '%') ->lockForUpdate() ->orderByDesc('entry_no') ->value('entry_no'); if ($last) { $seq = (int) substr($last, -3) + 1; } else { $seq = 1; } return $prefix . str_pad($seq, 3, '0', STR_PAD_LEFT); } }