feat: foundation for CRM and tax accounting specialized features
TaxBaik CI/CD / build-and-deploy (push) Successful in 50s
TaxBaik CI/CD / build-and-deploy (push) Successful in 50s
Domain Layer (SOLID Foundation): - 5 New Entities: TaxProfile, TaxFilingSchedule, ConsultingActivity, Contract, RevenueTracking - Client entity extended with tax-specific fields - Multi-tenant support (company_id) Database Migration (V015): - Create tax_profiles table for detailed tax info - Create tax_filing_schedules for deadline tracking - Create consulting_activities for CRM (activity history) - Create contracts for contract management - Create revenue_tracking for invoice and payment tracking - Add indexes for performance optimization Repository Interfaces: - ITaxProfileRepository (tax profile CRUD + risk analysis) - ITaxFilingScheduleRepository (schedule management + deadline tracking) - IConsultingActivityRepository (CRM activity tracking) - IContractRepository (contract lifecycle + MRR calculation) - IRevenueTrackingRepository (invoice + payment tracking + revenue analysis) Architecture: - Follows Repository Pattern with clear separation of concerns - SOLID principles: each repo = one responsibility - Extensible design for multi-tenant support - Supports specialized tax accounting and CRM workflows
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace TaxBaik.Domain.Interfaces;
|
||||
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
public interface IConsultingActivityRepository
|
||||
{
|
||||
Task<int> CreateAsync(ConsultingActivity activity, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<ConsultingActivity>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<ConsultingActivity>> GetPendingFollowupsAsync(CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<ConsultingActivity>> GetByConsultantAsync(int consultantId, DateTime fromDate, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(ConsultingActivity activity, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace TaxBaik.Domain.Interfaces;
|
||||
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
public interface IContractRepository
|
||||
{
|
||||
Task<int> CreateAsync(Contract contract, CancellationToken cancellationToken = default);
|
||||
Task<Contract?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<Contract>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<Contract>> GetActiveContractsAsync(CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<Contract>> GetExpiringContractsAsync(int daysAhead = 30, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(Contract contract, CancellationToken cancellationToken = default);
|
||||
Task<decimal> GetMonthlyRecurringRevenueAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace TaxBaik.Domain.Interfaces;
|
||||
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
public interface IRevenueTrackingRepository
|
||||
{
|
||||
Task<int> CreateAsync(RevenueTracking revenue, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<RevenueTracking>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<RevenueTracking>> GetPendingPaymentsAsync(CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<RevenueTracking>> 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<decimal> GetTotalRevenueAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace TaxBaik.Domain.Interfaces;
|
||||
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
public interface ITaxFilingScheduleRepository
|
||||
{
|
||||
Task<int> CreateAsync(TaxFilingSchedule schedule, CancellationToken cancellationToken = default);
|
||||
Task<TaxFilingSchedule?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<TaxFilingSchedule>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<TaxFilingSchedule>> GetUpcomingDuesAsync(int daysAhead = 30, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<TaxFilingSchedule>> GetByStatusAsync(string status, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(TaxFilingSchedule schedule, CancellationToken cancellationToken = default);
|
||||
Task MarkCompletedAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace TaxBaik.Domain.Interfaces;
|
||||
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
public interface ITaxProfileRepository
|
||||
{
|
||||
Task<int> CreateAsync(TaxProfile profile, CancellationToken cancellationToken = default);
|
||||
Task<TaxProfile?> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(TaxProfile profile, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<TaxProfile>> GetByRiskLevelAsync(string riskLevel, CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<TaxProfile>> GetUpcomingFilingDuesAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user