diff --git a/TaxBaik.Domain/Entities/Client.cs b/TaxBaik.Domain/Entities/Client.cs index 85c86d6..00f4f7b 100644 --- a/TaxBaik.Domain/Entities/Client.cs +++ b/TaxBaik.Domain/Entities/Client.cs @@ -3,15 +3,28 @@ namespace TaxBaik.Domain.Entities; public class Client { public int Id { get; set; } - public string Name { get; set; } = null!; + public int? CompanyId { get; set; } + public string Name { get; set; } = ""; public string? CompanyName { get; set; } public string? Phone { get; set; } public string? Email { get; set; } + public string? ContactPerson { get; set; } public string? ServiceType { get; set; } public string? TaxType { get; set; } public string Status { get; set; } = "active"; public string? Source { get; set; } public string? Memo { get; set; } + + // Tax-specific fields + public string? BusinessRegistrationNumber { get; set; } + public string? BusinessType { get; set; } + public DateTime? EstablishmentDate { get; set; } + public string? AnnualRevenueRange { get; set; } + public int? EmployeeCount { get; set; } + public DateTime? LastTaxFilingDate { get; set; } + public string TaxRiskLevel { get; set; } = "normal"; + public DateTime? NextFilingDueDate { get; set; } + public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } } diff --git a/TaxBaik.Domain/Entities/ConsultingActivity.cs b/TaxBaik.Domain/Entities/ConsultingActivity.cs new file mode 100644 index 0000000..4ccb793 --- /dev/null +++ b/TaxBaik.Domain/Entities/ConsultingActivity.cs @@ -0,0 +1,17 @@ +namespace TaxBaik.Domain.Entities; + +public class ConsultingActivity +{ + public int Id { get; set; } + public int ClientId { get; set; } + public string ActivityType { get; set; } = ""; + public DateTime ActivityDate { get; set; } + public TimeOnly? ActivityTime { get; set; } + public int? AssignedConsultantId { get; set; } + public string Description { get; set; } = ""; + public string? Outcome { get; set; } + public DateTime? NextFollowupDate { get; set; } + public string? Notes { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/TaxBaik.Domain/Entities/Contract.cs b/TaxBaik.Domain/Entities/Contract.cs new file mode 100644 index 0000000..9cf4a8a --- /dev/null +++ b/TaxBaik.Domain/Entities/Contract.cs @@ -0,0 +1,19 @@ +namespace TaxBaik.Domain.Entities; + +public class Contract +{ + public int Id { get; set; } + public int ClientId { get; set; } + public string ContractNumber { get; set; } = ""; + public string ServiceType { get; set; } = ""; + public DateTime ContractDate { get; set; } + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public decimal? MonthlyFee { get; set; } + public decimal? TotalAmount { get; set; } + public string PaymentStatus { get; set; } = "pending"; + public string Status { get; set; } = "active"; + public string? Notes { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/TaxBaik.Domain/Entities/RevenueTracking.cs b/TaxBaik.Domain/Entities/RevenueTracking.cs new file mode 100644 index 0000000..0916ef3 --- /dev/null +++ b/TaxBaik.Domain/Entities/RevenueTracking.cs @@ -0,0 +1,17 @@ +namespace TaxBaik.Domain.Entities; + +public class RevenueTracking +{ + public int Id { get; set; } + public int ClientId { get; set; } + public string InvoiceNumber { get; set; } = ""; + public DateTime InvoiceDate { get; set; } + public string? ServiceType { get; set; } + public decimal Amount { get; set; } + public string PaymentStatus { get; set; } = "pending"; + public DateTime? PaymentDate { get; set; } + public DateTime? DueDate { get; set; } + public string? Notes { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/TaxBaik.Domain/Entities/TaxFilingSchedule.cs b/TaxBaik.Domain/Entities/TaxFilingSchedule.cs new file mode 100644 index 0000000..ae4c1ef --- /dev/null +++ b/TaxBaik.Domain/Entities/TaxFilingSchedule.cs @@ -0,0 +1,16 @@ +namespace TaxBaik.Domain.Entities; + +public class TaxFilingSchedule +{ + public int Id { get; set; } + public int ClientId { get; set; } + public string FilingType { get; set; } = ""; + public DateTime DueDate { get; set; } + public int FilingYear { get; set; } + public string Status { get; set; } = "pending"; + public int? AssignedToId { get; set; } + public DateTime? CompletedDate { get; set; } + public string? Notes { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/TaxBaik.Domain/Entities/TaxProfile.cs b/TaxBaik.Domain/Entities/TaxProfile.cs new file mode 100644 index 0000000..9c9079e --- /dev/null +++ b/TaxBaik.Domain/Entities/TaxProfile.cs @@ -0,0 +1,21 @@ +namespace TaxBaik.Domain.Entities; + +public class TaxProfile +{ + public int Id { get; set; } + public int ClientId { get; set; } + public string? BusinessRegistration { get; set; } + public string? BusinessType { get; set; } + public DateTime? EstablishmentDate { get; set; } + public string? AnnualRevenueRange { get; set; } + public int? EmployeeCount { get; set; } + public string? AccountingMethod { get; set; } + public string? FiscalYearEnd { get; set; } + public DateTime? LastFilingDate { get; set; } + public DateTime? NextFilingDueDate { get; set; } + public string TaxRiskLevel { get; set; } = "normal"; + public bool PreviousAuditHistory { get; set; } + public string? SpecialNotes { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} diff --git a/TaxBaik.Domain/Interfaces/IConsultingActivityRepository.cs b/TaxBaik.Domain/Interfaces/IConsultingActivityRepository.cs new file mode 100644 index 0000000..2a71662 --- /dev/null +++ b/TaxBaik.Domain/Interfaces/IConsultingActivityRepository.cs @@ -0,0 +1,12 @@ +namespace TaxBaik.Domain.Interfaces; + +using TaxBaik.Domain.Entities; + +public interface IConsultingActivityRepository +{ + Task CreateAsync(ConsultingActivity activity, CancellationToken cancellationToken = default); + Task> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default); + Task> GetPendingFollowupsAsync(CancellationToken cancellationToken = default); + Task> GetByConsultantAsync(int consultantId, DateTime fromDate, CancellationToken cancellationToken = default); + Task UpdateAsync(ConsultingActivity activity, CancellationToken cancellationToken = default); +} diff --git a/TaxBaik.Domain/Interfaces/IContractRepository.cs b/TaxBaik.Domain/Interfaces/IContractRepository.cs new file mode 100644 index 0000000..7815545 --- /dev/null +++ b/TaxBaik.Domain/Interfaces/IContractRepository.cs @@ -0,0 +1,14 @@ +namespace TaxBaik.Domain.Interfaces; + +using TaxBaik.Domain.Entities; + +public interface IContractRepository +{ + Task CreateAsync(Contract contract, CancellationToken cancellationToken = default); + Task GetByIdAsync(int id, CancellationToken cancellationToken = default); + Task> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default); + Task> GetActiveContractsAsync(CancellationToken cancellationToken = default); + Task> GetExpiringContractsAsync(int daysAhead = 30, CancellationToken cancellationToken = default); + Task UpdateAsync(Contract contract, CancellationToken cancellationToken = default); + Task GetMonthlyRecurringRevenueAsync(CancellationToken cancellationToken = default); +} diff --git a/TaxBaik.Domain/Interfaces/IRevenueTrackingRepository.cs b/TaxBaik.Domain/Interfaces/IRevenueTrackingRepository.cs new file mode 100644 index 0000000..f1a04aa --- /dev/null +++ b/TaxBaik.Domain/Interfaces/IRevenueTrackingRepository.cs @@ -0,0 +1,14 @@ +namespace TaxBaik.Domain.Interfaces; + +using TaxBaik.Domain.Entities; + +public interface IRevenueTrackingRepository +{ + Task CreateAsync(RevenueTracking revenue, CancellationToken cancellationToken = default); + Task> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default); + Task> GetPendingPaymentsAsync(CancellationToken cancellationToken = default); + Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); + Task UpdateAsync(RevenueTracking revenue, CancellationToken cancellationToken = default); + Task MarkPaidAsync(int id, DateTime paymentDate, CancellationToken cancellationToken = default); + Task GetTotalRevenueAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); +} diff --git a/TaxBaik.Domain/Interfaces/ITaxFilingScheduleRepository.cs b/TaxBaik.Domain/Interfaces/ITaxFilingScheduleRepository.cs new file mode 100644 index 0000000..5bfefd2 --- /dev/null +++ b/TaxBaik.Domain/Interfaces/ITaxFilingScheduleRepository.cs @@ -0,0 +1,14 @@ +namespace TaxBaik.Domain.Interfaces; + +using TaxBaik.Domain.Entities; + +public interface ITaxFilingScheduleRepository +{ + Task CreateAsync(TaxFilingSchedule schedule, CancellationToken cancellationToken = default); + Task GetByIdAsync(int id, CancellationToken cancellationToken = default); + Task> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default); + Task> GetUpcomingDuesAsync(int daysAhead = 30, CancellationToken cancellationToken = default); + Task> GetByStatusAsync(string status, CancellationToken cancellationToken = default); + Task UpdateAsync(TaxFilingSchedule schedule, CancellationToken cancellationToken = default); + Task MarkCompletedAsync(int id, CancellationToken cancellationToken = default); +} diff --git a/TaxBaik.Domain/Interfaces/ITaxProfileRepository.cs b/TaxBaik.Domain/Interfaces/ITaxProfileRepository.cs new file mode 100644 index 0000000..970327e --- /dev/null +++ b/TaxBaik.Domain/Interfaces/ITaxProfileRepository.cs @@ -0,0 +1,12 @@ +namespace TaxBaik.Domain.Interfaces; + +using TaxBaik.Domain.Entities; + +public interface ITaxProfileRepository +{ + Task CreateAsync(TaxProfile profile, CancellationToken cancellationToken = default); + Task GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default); + Task UpdateAsync(TaxProfile profile, CancellationToken cancellationToken = default); + Task> GetByRiskLevelAsync(string riskLevel, CancellationToken cancellationToken = default); + Task> GetUpcomingFilingDuesAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); +} diff --git a/db/migrations/V015__AddTaxProfileAndExtendClients.sql b/db/migrations/V015__AddTaxProfileAndExtendClients.sql new file mode 100644 index 0000000..bfffa91 --- /dev/null +++ b/db/migrations/V015__AddTaxProfileAndExtendClients.sql @@ -0,0 +1,105 @@ +-- Extend clients table with tax-specific fields +ALTER TABLE clients ADD COLUMN IF NOT EXISTS business_registration_number VARCHAR(20); +ALTER TABLE clients ADD COLUMN IF NOT EXISTS business_type VARCHAR(50); +ALTER TABLE clients ADD COLUMN IF NOT EXISTS establishment_date DATE; +ALTER TABLE clients ADD COLUMN IF NOT EXISTS annual_revenue_range VARCHAR(50); +ALTER TABLE clients ADD COLUMN IF NOT EXISTS employee_count INT; +ALTER TABLE clients ADD COLUMN IF NOT EXISTS last_tax_filing_date DATE; +ALTER TABLE clients ADD COLUMN IF NOT EXISTS tax_risk_level VARCHAR(20) DEFAULT 'normal'; +ALTER TABLE clients ADD COLUMN IF NOT EXISTS next_filing_due_date DATE; +ALTER TABLE clients ADD COLUMN IF NOT EXISTS contact_person VARCHAR(100); +ALTER TABLE clients ADD COLUMN IF NOT EXISTS company_id INT REFERENCES companies(id) ON DELETE SET NULL; + +-- Create tax_profiles table for detailed tax information +CREATE TABLE IF NOT EXISTS tax_profiles ( + id SERIAL PRIMARY KEY, + client_id INT NOT NULL UNIQUE REFERENCES clients(id) ON DELETE CASCADE, + business_registration VARCHAR(20), + business_type VARCHAR(50), + establishment_date DATE, + annual_revenue_range VARCHAR(50), + employee_count INT, + accounting_method VARCHAR(50), + fiscal_year_end VARCHAR(10), + last_filing_date DATE, + next_filing_due_date DATE, + tax_risk_level VARCHAR(20) DEFAULT 'normal', + previous_audit_history BOOLEAN DEFAULT FALSE, + special_notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Create tax_filing_schedules table for tracking schedules +CREATE TABLE IF NOT EXISTS tax_filing_schedules ( + id SERIAL PRIMARY KEY, + client_id INT NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + filing_type VARCHAR(100) NOT NULL, + due_date DATE NOT NULL, + filing_year INT NOT NULL, + status VARCHAR(50) DEFAULT 'pending', + assigned_to INT REFERENCES admin_users(id) ON DELETE SET NULL, + completed_date DATE, + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Create consulting_activities table for CRM (상담 활동 추적) +CREATE TABLE IF NOT EXISTS consulting_activities ( + id SERIAL PRIMARY KEY, + client_id INT NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + activity_type VARCHAR(50) NOT NULL, + activity_date DATE NOT NULL, + activity_time TIME, + assigned_consultant INT REFERENCES admin_users(id) ON DELETE SET NULL, + description TEXT NOT NULL, + outcome VARCHAR(100), + next_followup_date DATE, + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Create contracts table (계약 관리) +CREATE TABLE IF NOT EXISTS contracts ( + id SERIAL PRIMARY KEY, + client_id INT NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + contract_number VARCHAR(50) NOT NULL UNIQUE, + service_type VARCHAR(100) NOT NULL, + contract_date DATE NOT NULL, + start_date DATE NOT NULL, + end_date DATE, + monthly_fee NUMERIC(10, 2), + total_amount NUMERIC(10, 2), + payment_status VARCHAR(50) DEFAULT 'pending', + status VARCHAR(50) DEFAULT 'active', + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Create revenue_tracking table (매출 추적) +CREATE TABLE IF NOT EXISTS revenue_tracking ( + id SERIAL PRIMARY KEY, + client_id INT NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + invoice_number VARCHAR(50) NOT NULL UNIQUE, + invoice_date DATE NOT NULL, + service_type VARCHAR(100), + amount NUMERIC(10, 2) NOT NULL, + payment_status VARCHAR(50) DEFAULT 'pending', + payment_date DATE, + due_date DATE, + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Create indexes for performance +CREATE INDEX IF NOT EXISTS idx_clients_company ON clients(company_id); +CREATE INDEX IF NOT EXISTS idx_tax_profiles_client ON tax_profiles(client_id); +CREATE INDEX IF NOT EXISTS idx_tax_filing_schedules_client ON tax_filing_schedules(client_id); +CREATE INDEX IF NOT EXISTS idx_tax_filing_schedules_due_date ON tax_filing_schedules(due_date); +CREATE INDEX IF NOT EXISTS idx_consulting_activities_client ON consulting_activities(client_id); +CREATE INDEX IF NOT EXISTS idx_contracts_client ON contracts(client_id); +CREATE INDEX IF NOT EXISTS idx_revenue_tracking_client ON revenue_tracking(client_id);