Files
taxbaik/TaxBaik.Application/Services/CompanyService.cs
T
kjh2064 7dd51a1169
TaxBaik CI/CD / build-and-deploy (push) Successful in 48s
feat: implement multi-tenant company management system
Architecture:
- Create companies table with company_code as unique identifier
- Add company_id foreign key to admin_users for multi-tenant support
- Implement backward compatibility with DEFAULT company for existing users

Core Components:
- Company entity with full CRUD operations
- ICompanyRepository interface following Repository pattern
- CompanyRepository with Dapper implementation
- CompanyService with business logic and validation
- CompanyController with REST API endpoints

Admin UI:
- CompanyForm reusable component (Create/Edit pattern)
- CompanyList.razor with pagination and company overview
- CompanyCreate.razor for registering new companies
- CompanyEdit.razor for managing existing companies with delete
- All pages follow admin-page-hero pattern for consistency

SOLID Principles:
- Single Responsibility: Each component has one reason to change
- Open/Closed: Extensible without modifying existing code
- Interface Segregation: Clean repository and service contracts
- Dependency Inversion: All layers depend on abstractions

Database Migration (V014):
- Creates companies table with active/inactive status
- Assigns existing admin users to DEFAULT company
- Provides foundation for role-based access control

Future Enhancement:
- Admin users can belong to specific companies
- Data filtering based on company_id (multi-tenant isolation)
- Company-based permission model
2026-06-28 16:52:22 +09:00

96 lines
4.2 KiB
C#

namespace TaxBaik.Application.Services;
using TaxBaik.Domain.Entities;
using TaxBaik.Domain.Interfaces;
public class CompanyService(ICompanyRepository repository)
{
public async Task<int> CreateAsync(string companyCode, string companyName, string? contactPerson = null,
string? phone = null, string? email = null, string? memo = null, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(companyCode))
throw new ValidationException("회사 코드를 입력하세요.");
if (string.IsNullOrWhiteSpace(companyName))
throw new ValidationException("회사명을 입력하세요.");
var existing = await repository.GetByCodeAsync(companyCode.Trim(), ct);
if (existing != null)
throw new ValidationException("이미 존재하는 회사 코드입니다.");
var company = new Company
{
CompanyCode = companyCode.Trim(),
CompanyName = companyName.Trim(),
ContactPerson = string.IsNullOrWhiteSpace(contactPerson) ? null : contactPerson.Trim(),
Phone = string.IsNullOrWhiteSpace(phone) ? null : phone.Trim(),
Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim(),
Memo = string.IsNullOrWhiteSpace(memo) ? null : memo.Trim(),
IsActive = true,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
return await repository.CreateAsync(company, ct);
}
public async Task<Company?> GetByIdAsync(int id, CancellationToken ct = default) =>
await repository.GetByIdAsync(id, ct);
public async Task<Company?> GetByCodeAsync(string code, CancellationToken ct = default) =>
await repository.GetByCodeAsync(code, ct);
public async Task<IEnumerable<Company>> GetAllActiveAsync(CancellationToken ct = default) =>
await repository.GetAllActiveAsync(ct);
public async Task<(IEnumerable<Company>, int)> GetPagedAsync(int page, int pageSize, CancellationToken ct = default)
{
var (items, total) = await repository.GetPagedAsync(NormalizePage(page), NormalizePageSize(pageSize), ct);
return (items, total);
}
public async Task UpdateAsync(int id, string companyCode, string companyName, string? contactPerson = null,
string? phone = null, string? email = null, string? memo = null, bool isActive = true, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(companyCode))
throw new ValidationException("회사 코드를 입력하세요.");
if (string.IsNullOrWhiteSpace(companyName))
throw new ValidationException("회사명을 입력하세요.");
var company = await repository.GetByIdAsync(id, ct);
if (company == null)
throw new ValidationException("회사를 찾을 수 없습니다.");
var existing = await repository.GetByCodeAsync(companyCode.Trim(), ct);
if (existing != null && existing.Id != id)
throw new ValidationException("이미 존재하는 회사 코드입니다.");
company.CompanyCode = companyCode.Trim();
company.CompanyName = companyName.Trim();
company.ContactPerson = string.IsNullOrWhiteSpace(contactPerson) ? null : contactPerson.Trim();
company.Phone = string.IsNullOrWhiteSpace(phone) ? null : phone.Trim();
company.Email = string.IsNullOrWhiteSpace(email) ? null : email.Trim();
company.Memo = string.IsNullOrWhiteSpace(memo) ? null : memo.Trim();
company.IsActive = isActive;
company.UpdatedAt = DateTime.UtcNow;
await repository.UpdateAsync(company, ct);
}
public async Task DeleteAsync(int id, CancellationToken ct = default)
{
var company = await repository.GetByIdAsync(id, ct);
if (company == null)
throw new ValidationException("회사를 찾을 수 없습니다.");
if (company.CompanyCode == "DEFAULT")
throw new ValidationException("기본 회사는 삭제할 수 없습니다.");
await repository.DeleteAsync(id, ct);
}
private static int NormalizePage(int page) => Math.Max(1, page);
private static int NormalizePageSize(int pageSize) => Math.Clamp(pageSize, 1, 100);
}