c2955ad02f
TaxBaik CI/CD / build-and-deploy (push) Successful in 49s
Phase 2: Repository Implementation (Dapper) - TaxProfileRepository: tax profile CRUD + risk level analysis + filing due dates - TaxFilingScheduleRepository: schedule tracking + upcoming due dates + completion marking - ConsultingActivityRepository: CRM activity history + pending followups + consultant tracking - ContractRepository: contract lifecycle + active contracts + expiring alerts + MRR calculation - RevenueTrackingRepository: invoice tracking + payment status + revenue analysis Phase 3: Service Layer (Business Logic) - TaxProfileService: profile creation, risk assessment, upcoming filing detection - TaxFilingScheduleService: schedule management, deadline tracking, completion workflow - ConsultingActivityService: activity logging, followup management, consultant productivity - ContractService: contract management, MRR calculation, expiring contract alerts - RevenueTrackingService: invoice creation, payment tracking, revenue analytics Phase 4: API Controller (REST Endpoints) - TaxProfileController: CRUD operations + high-risk filtering + upcoming filings query Architecture Highlights: - SOLID principles: each layer has clear responsibility - Dapper-based repositories for data access - Comprehensive service layer for business logic - RESTful API design with proper error handling - Ready for Blazor UI implementation and deployment Database Migration V015 executed: - 5 new specialized tables for CRM and tax accounting - Appropriate indexes for query performance - Foreign key constraints for data integrity
75 lines
3.9 KiB
C#
75 lines
3.9 KiB
C#
namespace TaxBaik.Infrastructure.Repositories;
|
|
|
|
using Dapper;
|
|
using TaxBaik.Domain.Entities;
|
|
using TaxBaik.Domain.Interfaces;
|
|
|
|
public class ContractRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IContractRepository
|
|
{
|
|
public async Task<int> CreateAsync(Contract contract, CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
return await conn.QueryFirstAsync<int>(
|
|
@"INSERT INTO contracts (client_id, contract_number, service_type, contract_date, start_date, end_date, monthly_fee, total_amount, payment_status, status, notes, created_at, updated_at)
|
|
VALUES (@ClientId, @ContractNumber, @ServiceType, @ContractDate, @StartDate, @EndDate, @MonthlyFee, @TotalAmount, @PaymentStatus, @Status, @Notes, NOW(), NOW())
|
|
RETURNING id",
|
|
contract);
|
|
}
|
|
|
|
public async Task<Contract?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
return await conn.QueryFirstOrDefaultAsync<Contract>(
|
|
@"SELECT id, client_id, contract_number, service_type, contract_date, start_date, end_date, monthly_fee, total_amount, payment_status, status, notes, created_at, updated_at
|
|
FROM contracts WHERE id = @Id",
|
|
new { Id = id });
|
|
}
|
|
|
|
public async Task<IEnumerable<Contract>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
return await conn.QueryAsync<Contract>(
|
|
@"SELECT id, client_id, contract_number, service_type, contract_date, start_date, end_date, monthly_fee, total_amount, payment_status, status, notes, created_at, updated_at
|
|
FROM contracts WHERE client_id = @ClientId ORDER BY contract_date DESC",
|
|
new { ClientId = clientId });
|
|
}
|
|
|
|
public async Task<IEnumerable<Contract>> GetActiveContractsAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
return await conn.QueryAsync<Contract>(
|
|
@"SELECT id, client_id, contract_number, service_type, contract_date, start_date, end_date, monthly_fee, total_amount, payment_status, status, notes, created_at, updated_at
|
|
FROM contracts WHERE status = 'active' ORDER BY client_id");
|
|
}
|
|
|
|
public async Task<IEnumerable<Contract>> GetExpiringContractsAsync(int daysAhead = 30, CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
return await conn.QueryAsync<Contract>(
|
|
@"SELECT id, client_id, contract_number, service_type, contract_date, start_date, end_date, monthly_fee, total_amount, payment_status, status, notes, created_at, updated_at
|
|
FROM contracts
|
|
WHERE status = 'active' AND end_date IS NOT NULL AND end_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day' * @DaysAhead
|
|
ORDER BY end_date ASC",
|
|
new { DaysAhead = daysAhead });
|
|
}
|
|
|
|
public async Task UpdateAsync(Contract contract, CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
await conn.ExecuteAsync(
|
|
@"UPDATE contracts SET contract_number = @ContractNumber, service_type = @ServiceType, contract_date = @ContractDate,
|
|
start_date = @StartDate, end_date = @EndDate, monthly_fee = @MonthlyFee, total_amount = @TotalAmount,
|
|
payment_status = @PaymentStatus, status = @Status, notes = @Notes, updated_at = NOW()
|
|
WHERE id = @Id",
|
|
contract);
|
|
}
|
|
|
|
public async Task<decimal> GetMonthlyRecurringRevenueAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
using var conn = Conn();
|
|
var result = await conn.QueryFirstAsync<decimal>(
|
|
@"SELECT COALESCE(SUM(monthly_fee), 0) FROM contracts WHERE status = 'active' AND monthly_fee IS NOT NULL");
|
|
return result;
|
|
}
|
|
}
|