refactor: move buildable .NET source into src/, update CI/doc paths
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m7s
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m7s
Groups the repo root into src (buildable source), docs (already existed), and everything else (db/, scripts/, tests/, deploy/ - deployment/ops/test assets that aren't compiled, already organized as their own folders). CI now only needs src/ to build: dotnet restore/build/test/publish all point at src/TaxBaik.sln, src/TaxBaik.Web/, src/TaxBaik.Proxy/. - git mv every project (Domain, Infrastructure, Application, Application.Tests, Web, Web.Client, Proxy) and TaxBaik.sln into src/ as a unit, so relative ProjectReference/.sln paths stay valid unchanged. - .gitea/workflows/deploy.yml: 6 dotnet restore/clean/build/test/publish invocations now point at src/. db/migrations and scripts/ stay at root (deploy_gb.sh and browser-e2e.yml only touch published output and the deployed URL, not source paths - verified, no changes needed there). - scripts/validate_admin_render.sh: admin render-mode file paths now src/TaxBaik.Web.Client/... - scripts/validate_kst_timestamps.sh: dropped deploy.sh from its target list - that script was removed in the prior cleanup commit (dead, no CI workflow referenced it) but this validator still expected it to exist. - CLAUDE.md, docs/ENGINEERING_HARNESS.md, docs/ADMIN_PATTERN_CRITIQUE_WBS.md: updated project-structure diagram, dotnet run/build commands, and grep targets to the new src/ paths (also fixed a pre-existing stale path in ADMIN_PATTERN_CRITIQUE_WBS.md that still said TaxBaik.Web/Components/Admin from before that ever moved to TaxBaik.Web.Client). - Added a Repo Root harness rule + Architecture Guardrail entries: new files belong under src/docs/tests/scripts/db/deploy, not loose at root; temp work stays outside the repo (or under a gitignored .scratch/) and is never committed. Verified locally: dotnet build/test src/TaxBaik.sln (26/26 tests), and all three scripts/validate_*.sh pass against the new layout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
public class AdminUserRepository : BaseRepository, IAdminUserRepository
|
||||
{
|
||||
public AdminUserRepository(IDbConnectionFactory connectionFactory) : base(connectionFactory) { }
|
||||
|
||||
public async Task<AdminUser?> GetByUsernameAsync(string username)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryFirstOrDefaultAsync<AdminUser>(
|
||||
"""
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
password_hash AS PasswordHash,
|
||||
last_login_at AS LastLoginAt,
|
||||
created_at AS CreatedAt
|
||||
FROM admin_users
|
||||
WHERE username = @username
|
||||
""",
|
||||
new { username });
|
||||
}
|
||||
|
||||
public async Task<AdminUser?> GetByIdAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryFirstOrDefaultAsync<AdminUser>(
|
||||
"""
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
password_hash AS PasswordHash,
|
||||
last_login_at AS LastLoginAt,
|
||||
created_at AS CreatedAt
|
||||
FROM admin_users
|
||||
WHERE id = @id
|
||||
""",
|
||||
new { id });
|
||||
}
|
||||
|
||||
public async Task CreateAsync(AdminUser user)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"INSERT INTO admin_users (username, password_hash, created_at) VALUES (@username, @passwordHash, NOW())",
|
||||
new { username = user.Username, passwordHash = user.PasswordHash });
|
||||
}
|
||||
|
||||
public async Task UpdatePasswordHashAsync(int id, string passwordHash)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE admin_users SET password_hash = @passwordHash WHERE id = @id",
|
||||
new { id, passwordHash });
|
||||
}
|
||||
|
||||
public async Task UpdateLastLoginAtAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE admin_users SET last_login_at = NOW() WHERE id = @id",
|
||||
new { id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class AnnouncementRepository(IDbConnectionFactory connectionFactory)
|
||||
: BaseRepository(connectionFactory), IAnnouncementRepository
|
||||
{
|
||||
private const string SelectColumns =
|
||||
"id, title, content, display_type, is_active, starts_at, ends_at, sort_order, created_at, updated_at";
|
||||
|
||||
public async Task<IEnumerable<Announcement>> GetActiveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Announcement>(
|
||||
$@"SELECT {SelectColumns}
|
||||
FROM announcements
|
||||
WHERE is_active = TRUE
|
||||
AND (starts_at IS NULL OR starts_at <= NOW())
|
||||
AND (ends_at IS NULL OR ends_at >= NOW())
|
||||
ORDER BY sort_order DESC, created_at DESC");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Announcement>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Announcement>(
|
||||
$"SELECT {SelectColumns} FROM announcements ORDER BY sort_order DESC, created_at DESC");
|
||||
}
|
||||
|
||||
public async Task<Announcement?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Announcement>(
|
||||
$"SELECT {SelectColumns} FROM announcements WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(Announcement announcement, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO announcements
|
||||
(title, content, display_type, is_active, starts_at, ends_at, sort_order, created_at, updated_at)
|
||||
VALUES
|
||||
(@Title, @Content, @DisplayType, @IsActive, @StartsAt, @EndsAt, @SortOrder, NOW(), NOW())
|
||||
RETURNING id",
|
||||
announcement);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Announcement announcement, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE announcements
|
||||
SET title = @Title,
|
||||
content = @Content,
|
||||
display_type = @DisplayType,
|
||||
is_active = @IsActive,
|
||||
starts_at = @StartsAt,
|
||||
ends_at = @EndsAt,
|
||||
sort_order = @SortOrder,
|
||||
updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
announcement);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM announcements WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using System.Data;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public abstract class BaseRepository(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
protected readonly IDbConnectionFactory _connectionFactory = connectionFactory;
|
||||
|
||||
protected IDbConnection Conn() => _connectionFactory.CreateConnection();
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class BlogPostRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IBlogPostRepository
|
||||
{
|
||||
public async Task<BlogPost?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<BlogPost>(
|
||||
@"SELECT bp.id, bp.title, bp.content, bp.slug, bp.category_id, bp.tags, bp.author_id,
|
||||
bp.published_at, bp.view_count, bp.seo_title, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, bp.deleted_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.id = @Id AND bp.deleted_at IS NULL",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<BlogPost>(
|
||||
@"SELECT bp.id, bp.title, bp.content, bp.slug, bp.category_id, bp.tags, bp.author_id,
|
||||
bp.published_at, bp.view_count, bp.seo_title, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, bp.deleted_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.slug = @Slug AND bp.is_published = TRUE AND bp.deleted_at IS NULL",
|
||||
new { Slug = slug });
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<BlogPost> Items, int Total)> GetPublishedPagedAsync(
|
||||
int page, int pageSize, int? categoryId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var offset = (page - 1) * pageSize;
|
||||
|
||||
using var reader = await conn.QueryMultipleAsync(
|
||||
@"SELECT bp.id, bp.title, bp.content, bp.slug, bp.category_id, bp.tags, bp.author_id,
|
||||
bp.published_at, bp.view_count, bp.seo_title, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, bp.deleted_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.is_published = TRUE AND bp.deleted_at IS NULL AND (@CategoryId::int IS NULL OR bp.category_id = @CategoryId)
|
||||
ORDER BY bp.published_at DESC
|
||||
LIMIT @PageSize OFFSET @Offset;
|
||||
|
||||
SELECT COUNT(*) FROM blog_posts
|
||||
WHERE is_published = TRUE AND deleted_at IS NULL AND (@CategoryId::int IS NULL OR category_id = @CategoryId);",
|
||||
new { CategoryId = categoryId, PageSize = pageSize, Offset = offset });
|
||||
|
||||
var items = (await reader.ReadAsync<BlogPost>()).ToList();
|
||||
var total = await reader.ReadFirstAsync<int>();
|
||||
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BlogPost>> GetByCategorySlugAsync(string categorySlug, int limit, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<BlogPost>(
|
||||
@"SELECT bp.id, bp.title, bp.slug, bp.category_id, bp.tags,
|
||||
bp.published_at, bp.view_count, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, bp.deleted_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.is_published = TRUE AND bp.deleted_at IS NULL AND c.slug = @CategorySlug
|
||||
ORDER BY bp.published_at DESC
|
||||
LIMIT @Limit",
|
||||
new { CategorySlug = categorySlug, Limit = limit });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BlogPost>> GetAllForAdminAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<BlogPost>(
|
||||
@"SELECT bp.id, bp.title, bp.content, bp.slug, bp.category_id, bp.tags, bp.author_id,
|
||||
bp.published_at, bp.view_count, bp.seo_title, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.deleted_at IS NULL
|
||||
ORDER BY bp.created_at DESC");
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<BlogPost> Items, int Total)> GetAdminPagedAsync(
|
||||
int page, int pageSize, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var offset = (page - 1) * pageSize;
|
||||
|
||||
using var reader = await conn.QueryMultipleAsync(
|
||||
@"SELECT bp.id, bp.title, bp.content, bp.slug, bp.category_id, bp.tags, bp.author_id,
|
||||
bp.published_at, bp.view_count, bp.seo_title, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, bp.deleted_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.deleted_at IS NULL
|
||||
ORDER BY bp.created_at DESC
|
||||
LIMIT @PageSize OFFSET @Offset;
|
||||
|
||||
SELECT COUNT(*) FROM blog_posts WHERE deleted_at IS NULL;",
|
||||
new { PageSize = pageSize, Offset = offset });
|
||||
|
||||
var items = (await reader.ReadAsync<BlogPost>()).ToList();
|
||||
var total = await reader.ReadFirstAsync<int>();
|
||||
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<BlogPost> Items, int Total)> GetArchivedPagedAsync(
|
||||
int page, int pageSize, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var offset = (page - 1) * pageSize;
|
||||
|
||||
using var reader = await conn.QueryMultipleAsync(
|
||||
@"SELECT bp.id, bp.title, bp.content, bp.slug, bp.category_id, bp.tags, bp.author_id,
|
||||
bp.published_at, bp.view_count, bp.seo_title, bp.seo_description, bp.thumbnail_url,
|
||||
bp.is_published, bp.created_at, bp.updated_at, bp.deleted_at, c.name AS category_name
|
||||
FROM blog_posts bp
|
||||
LEFT JOIN categories c ON bp.category_id = c.id
|
||||
WHERE bp.deleted_at IS NOT NULL
|
||||
ORDER BY bp.deleted_at DESC
|
||||
LIMIT @PageSize OFFSET @Offset;
|
||||
|
||||
SELECT COUNT(*) FROM blog_posts WHERE deleted_at IS NOT NULL;",
|
||||
new { PageSize = pageSize, Offset = offset });
|
||||
|
||||
var items = (await reader.ReadAsync<BlogPost>()).ToList();
|
||||
var total = await reader.ReadFirstAsync<int>();
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(BlogPost post, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO blog_posts (title, content, slug, category_id, tags, author_id, published_at,
|
||||
seo_title, seo_description, thumbnail_url, is_published, created_at, updated_at)
|
||||
VALUES (@Title, @Content, @Slug, @CategoryId, @Tags, @AuthorId, @PublishedAt,
|
||||
@SeoTitle, @SeoDescription, @ThumbnailUrl, @IsPublished, NOW(), NOW())
|
||||
RETURNING id",
|
||||
post);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(BlogPost post, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE blog_posts
|
||||
SET title = @Title, content = @Content, slug = @Slug, category_id = @CategoryId,
|
||||
tags = @Tags, author_id = @AuthorId, published_at = @PublishedAt,
|
||||
seo_title = @SeoTitle, seo_description = @SeoDescription,
|
||||
thumbnail_url = @ThumbnailUrl, is_published = @IsPublished, updated_at = NOW()
|
||||
WHERE id = @Id AND deleted_at IS NULL",
|
||||
post);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ArchiveAsync(id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ArchiveAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE blog_posts SET deleted_at = NOW(), updated_at = NOW() WHERE id = @Id AND deleted_at IS NULL",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task RestoreAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE blog_posts SET deleted_at = NULL, updated_at = NOW() WHERE id = @Id AND deleted_at IS NOT NULL",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task IncrementViewCountAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("UPDATE blog_posts SET view_count = view_count + 1 WHERE id = @Id AND deleted_at IS NULL", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class CategoryRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ICategoryRepository
|
||||
{
|
||||
public async Task<IEnumerable<Category>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Category>(
|
||||
"SELECT id, name, slug, sort_order FROM categories ORDER BY sort_order");
|
||||
}
|
||||
|
||||
public async Task<Category?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Category>(
|
||||
"SELECT id, name, slug, sort_order FROM categories WHERE slug = @Slug",
|
||||
new { Slug = slug });
|
||||
}
|
||||
|
||||
public async Task<Category?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Category>(
|
||||
"SELECT id, name, slug, sort_order FROM categories WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(Category category, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO categories (name, slug, sort_order)
|
||||
VALUES (@Name, @Slug, @SortOrder)
|
||||
RETURNING id",
|
||||
category);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Category category, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE categories SET name = @Name, slug = @Slug, sort_order = @SortOrder WHERE id = @Id",
|
||||
category);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM categories WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class ClientRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IClientRepository
|
||||
{
|
||||
private const string SelectColumns =
|
||||
"id, name, company_name, phone, email, service_type, tax_type, status, source, memo, created_at, updated_at";
|
||||
|
||||
public async Task<(IEnumerable<Client> Items, int Total)> GetPagedAsync(
|
||||
int page, int pageSize, string? status = null, string? search = null, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var offset = (page - 1) * pageSize;
|
||||
|
||||
using var reader = await conn.QueryMultipleAsync(
|
||||
$@"SELECT {SelectColumns} FROM clients
|
||||
WHERE (@Status::text IS NULL OR status = @Status)
|
||||
AND (@Search::text IS NULL OR name ILIKE @SearchLike OR phone ILIKE @SearchLike OR company_name ILIKE @SearchLike)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT @PageSize OFFSET @Offset;
|
||||
|
||||
SELECT COUNT(*) FROM clients
|
||||
WHERE (@Status::text IS NULL OR status = @Status)
|
||||
AND (@Search::text IS NULL OR name ILIKE @SearchLike OR phone ILIKE @SearchLike OR company_name ILIKE @SearchLike);",
|
||||
new { Status = status, Search = search, SearchLike = string.IsNullOrEmpty(search) ? null : $"%{search}%", PageSize = pageSize, Offset = offset });
|
||||
|
||||
var items = (await reader.ReadAsync<Client>()).ToList();
|
||||
var total = await reader.ReadFirstAsync<int>();
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<Client?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Client>(
|
||||
$"SELECT {SelectColumns} FROM clients WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<Client?> GetByEmailAsync(string email, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Client>(
|
||||
$"SELECT {SelectColumns} FROM clients WHERE email = @Email",
|
||||
new { Email = email });
|
||||
}
|
||||
|
||||
public async Task<Client?> GetByPhoneAsync(string phone, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Client>(
|
||||
$"SELECT {SelectColumns} FROM clients WHERE phone = @Phone",
|
||||
new { Phone = phone });
|
||||
}
|
||||
|
||||
public async Task<int> CountByCreatedAtRangeAsync(DateTime startDateUtc, DateTime endDateUtc, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.ExecuteScalarAsync<int>(
|
||||
@"SELECT COUNT(*)
|
||||
FROM clients
|
||||
WHERE created_at >= @StartDateUtc
|
||||
AND created_at <= @EndDateUtc",
|
||||
new { StartDateUtc = startDateUtc, EndDateUtc = endDateUtc });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(Client client, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO clients (name, company_name, phone, email, service_type, tax_type, status, source, memo, created_at, updated_at)
|
||||
VALUES (@Name, @CompanyName, @Phone, @Email, @ServiceType, @TaxType, @Status, @Source, @Memo, NOW(), NOW())
|
||||
RETURNING id",
|
||||
client);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Client client, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE clients
|
||||
SET name = @Name, company_name = @CompanyName, phone = @Phone, email = @Email,
|
||||
service_type = @ServiceType, tax_type = @TaxType, status = @Status,
|
||||
source = @Source, memo = @Memo, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
client);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM clients WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
public class CommonCodeRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ICommonCodeRepository
|
||||
{
|
||||
public async Task<IEnumerable<string>> GetAllGroupsAsync(CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<string>(
|
||||
"SELECT DISTINCT code_group FROM common_codes WHERE is_active = TRUE ORDER BY code_group");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CommonCode>> GetByGroupAsync(string codeGroup, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<CommonCode>(
|
||||
@"SELECT code_group as CodeGroup, code_value as CodeValue, code_name as CodeName, sort_order as SortOrder, is_active as IsActive
|
||||
FROM common_codes
|
||||
WHERE code_group = @CodeGroup AND is_active = TRUE
|
||||
ORDER BY sort_order",
|
||||
new { CodeGroup = codeGroup });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CommonCode>> GetAllActiveAsync(CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<CommonCode>(
|
||||
@"SELECT code_group as CodeGroup, code_value as CodeValue, code_name as CodeName, sort_order as SortOrder, is_active as IsActive
|
||||
FROM common_codes
|
||||
WHERE is_active = TRUE
|
||||
ORDER BY code_group, sort_order");
|
||||
}
|
||||
|
||||
public async Task<CommonCode?> GetAsync(string codeGroup, string codeValue, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QuerySingleOrDefaultAsync<CommonCode>(
|
||||
@"SELECT code_group as CodeGroup, code_value as CodeValue, code_name as CodeName, sort_order as SortOrder, is_active as IsActive
|
||||
FROM common_codes
|
||||
WHERE code_group = @CodeGroup AND code_value = @CodeValue",
|
||||
new { CodeGroup = codeGroup, CodeValue = codeValue });
|
||||
}
|
||||
|
||||
public async Task UpsertAsync(CommonCode code, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"INSERT INTO common_codes (code_group, code_value, code_name, sort_order, is_active)
|
||||
VALUES (@CodeGroup, @CodeValue, @CodeName, @SortOrder, @IsActive)
|
||||
ON CONFLICT (code_group, code_value) DO UPDATE
|
||||
SET code_name = EXCLUDED.code_name,
|
||||
sort_order = EXCLUDED.sort_order,
|
||||
is_active = EXCLUDED.is_active",
|
||||
code);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string codeGroup, string codeValue, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"DELETE FROM common_codes
|
||||
WHERE code_group = @CodeGroup AND code_value = @CodeValue",
|
||||
new { CodeGroup = codeGroup, CodeValue = codeValue });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class CompanyRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ICompanyRepository
|
||||
{
|
||||
public async Task<int> CreateAsync(Company company, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO companies (company_code, company_name, contact_person, phone, email, memo, is_active, created_at, updated_at)
|
||||
VALUES (@CompanyCode, @CompanyName, @ContactPerson, @Phone, @Email, @Memo, @IsActive, NOW(), NOW())
|
||||
RETURNING id",
|
||||
company);
|
||||
}
|
||||
|
||||
public async Task<Company?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Company>(
|
||||
@"SELECT id, company_code, company_name, contact_person, phone, email, memo, is_active, created_at, updated_at
|
||||
FROM companies WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<Company?> GetByCodeAsync(string code, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Company>(
|
||||
@"SELECT id, company_code, company_name, contact_person, phone, email, memo, is_active, created_at, updated_at
|
||||
FROM companies WHERE company_code = @Code",
|
||||
new { Code = code });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Company>> GetAllActiveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Company>(
|
||||
@"SELECT id, company_code, company_name, contact_person, phone, email, memo, is_active, created_at, updated_at
|
||||
FROM companies WHERE is_active = TRUE ORDER BY company_name");
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Company> Items, int Total)> GetPagedAsync(int page, int pageSize, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var offset = (page - 1) * pageSize;
|
||||
|
||||
using var reader = await conn.QueryMultipleAsync(
|
||||
@"SELECT id, company_code, company_name, contact_person, phone, email, memo, is_active, created_at, updated_at
|
||||
FROM companies
|
||||
ORDER BY company_name
|
||||
LIMIT @PageSize OFFSET @Offset;
|
||||
|
||||
SELECT COUNT(*) FROM companies;",
|
||||
new { PageSize = pageSize, Offset = offset });
|
||||
|
||||
var items = (await reader.ReadAsync<Company>()).ToList();
|
||||
var total = await reader.ReadFirstAsync<int>();
|
||||
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Company company, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE companies
|
||||
SET company_code = @CompanyCode, company_name = @CompanyName,
|
||||
contact_person = @ContactPerson, phone = @Phone, email = @Email,
|
||||
memo = @Memo, is_active = @IsActive, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
company);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM companies WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class ConsultationRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IConsultationRepository
|
||||
{
|
||||
public async Task<IEnumerable<Consultation>> GetByClientIdAsync(int clientId, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Consultation>(
|
||||
@"SELECT id, client_id, consultation_date, service_type, summary, result, fee, created_at
|
||||
FROM consultations
|
||||
WHERE client_id = @ClientId
|
||||
ORDER BY consultation_date DESC, id DESC",
|
||||
new { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(Consultation consultation, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO consultations (client_id, consultation_date, service_type, summary, result, fee, created_at)
|
||||
VALUES (@ClientId, @ConsultationDate, @ServiceType, @Summary, @Result, @Fee, NOW())
|
||||
RETURNING id",
|
||||
consultation);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM consultations WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class ConsultingActivityRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IConsultingActivityRepository
|
||||
{
|
||||
public async Task<int> CreateAsync(ConsultingActivity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO consulting_activities (client_id, activity_type, activity_date, activity_time, assigned_consultant, description, outcome, next_followup_date, notes, created_at, updated_at)
|
||||
VALUES (@ClientId, @ActivityType, @ActivityDate, @ActivityTime, @AssignedConsultantId, @Description, @Outcome, @NextFollowupDate, @Notes, NOW(), NOW())
|
||||
RETURNING id",
|
||||
activity);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConsultingActivity>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<ConsultingActivity>(
|
||||
@"SELECT id, client_id, activity_type, activity_date, activity_time, assigned_consultant, description, outcome, next_followup_date, notes, created_at, updated_at
|
||||
FROM consulting_activities ORDER BY activity_date DESC");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConsultingActivity>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<ConsultingActivity>(
|
||||
@"SELECT id, client_id, activity_type, activity_date, activity_time, assigned_consultant, description, outcome, next_followup_date, notes, created_at, updated_at
|
||||
FROM consulting_activities WHERE client_id = @ClientId ORDER BY activity_date DESC",
|
||||
new { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConsultingActivity>> GetPendingFollowupsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<ConsultingActivity>(
|
||||
@"SELECT id, client_id, activity_type, activity_date, activity_time, assigned_consultant, description, outcome, next_followup_date, notes, created_at, updated_at
|
||||
FROM consulting_activities WHERE next_followup_date IS NOT NULL AND next_followup_date <= CURRENT_DATE
|
||||
ORDER BY next_followup_date ASC");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConsultingActivity>> GetByConsultantAsync(int consultantId, DateTime fromDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<ConsultingActivity>(
|
||||
@"SELECT id, client_id, activity_type, activity_date, activity_time, assigned_consultant, description, outcome, next_followup_date, notes, created_at, updated_at
|
||||
FROM consulting_activities WHERE assigned_consultant = @ConsultantId AND activity_date >= @FromDate
|
||||
ORDER BY activity_date DESC",
|
||||
new { ConsultantId = consultantId, FromDate = fromDate });
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ConsultingActivity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE consulting_activities SET activity_type = @ActivityType, activity_date = @ActivityDate,
|
||||
activity_time = @ActivityTime, assigned_consultant = @AssignedConsultantId, description = @Description,
|
||||
outcome = @Outcome, next_followup_date = @NextFollowupDate, notes = @Notes, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
activity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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<IEnumerable<Contract>> GetAllAsync(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 ORDER BY contract_date DESC");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class FaqRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IFaqRepository
|
||||
{
|
||||
private const string SelectColumns =
|
||||
"id, question, answer, category, sort_order, is_active, created_at, updated_at";
|
||||
|
||||
public async Task<IEnumerable<Faq>> GetActiveAsync(CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Faq>(
|
||||
$"SELECT {SelectColumns} FROM faqs WHERE is_active = TRUE ORDER BY sort_order, id");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Faq>> GetAllAsync(CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<Faq>(
|
||||
$"SELECT {SelectColumns} FROM faqs ORDER BY sort_order, id");
|
||||
}
|
||||
|
||||
public async Task<Faq?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Faq>(
|
||||
$"SELECT {SelectColumns} FROM faqs WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(Faq faq, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO faqs (question, answer, category, sort_order, is_active, created_at, updated_at)
|
||||
VALUES (@Question, @Answer, @Category, @SortOrder, @IsActive, NOW(), NOW())
|
||||
RETURNING id",
|
||||
faq);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Faq faq, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE faqs
|
||||
SET question = @Question, answer = @Answer, category = @Category,
|
||||
sort_order = @SortOrder, is_active = @IsActive, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
faq);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM faqs WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class InquiryRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IInquiryRepository
|
||||
{
|
||||
public async Task<int> CreateAsync(Inquiry inquiry, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO inquiries (name, phone, email, service_type, message, status, ip_address, created_at)
|
||||
VALUES (@Name, @Phone, @Email, @ServiceType, @Message, @Status, @IpAddress, NOW())
|
||||
RETURNING id",
|
||||
inquiry);
|
||||
}
|
||||
|
||||
public async Task<Inquiry?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<Inquiry>(
|
||||
@"SELECT id, name, phone, email, service_type, message, status, ip_address,
|
||||
client_id, admin_memo, created_at, updated_at
|
||||
FROM inquiries WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Inquiry> Items, int Total)> GetPagedAsync(
|
||||
int page, int pageSize, string? status = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var offset = (page - 1) * pageSize;
|
||||
|
||||
using var reader = await conn.QueryMultipleAsync(
|
||||
@"SELECT id, name, phone, email, service_type, message, status, ip_address,
|
||||
client_id, admin_memo, created_at, updated_at
|
||||
FROM inquiries
|
||||
WHERE @Status::text IS NULL OR status = @Status
|
||||
ORDER BY created_at DESC
|
||||
LIMIT @PageSize OFFSET @Offset;
|
||||
|
||||
SELECT COUNT(*) FROM inquiries
|
||||
WHERE @Status::text IS NULL OR status = @Status;",
|
||||
new { Status = status, PageSize = pageSize, Offset = offset });
|
||||
|
||||
var items = (await reader.ReadAsync<Inquiry>()).ToList();
|
||||
var total = await reader.ReadFirstAsync<int>();
|
||||
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM inquiries");
|
||||
}
|
||||
|
||||
public async Task<int> CountThisMonthAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.ExecuteScalarAsync<int>(
|
||||
@"SELECT COUNT(*)
|
||||
FROM inquiries
|
||||
WHERE created_at >= date_trunc('month', NOW())
|
||||
AND created_at < date_trunc('month', NOW()) + INTERVAL '1 month'");
|
||||
}
|
||||
|
||||
public async Task<int> CountByStatusAsync(string status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.ExecuteScalarAsync<int>(
|
||||
"SELECT COUNT(*) FROM inquiries WHERE status = @Status",
|
||||
new { Status = status });
|
||||
}
|
||||
|
||||
public async Task<int> CountByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.ExecuteScalarAsync<int>(
|
||||
@"SELECT COUNT(*)
|
||||
FROM inquiries
|
||||
WHERE created_at >= @StartDate AND created_at <= @EndDate",
|
||||
new { StartDate = startDate, EndDate = endDate });
|
||||
}
|
||||
|
||||
public async Task<int> CountByStatusAndDateAsync(string status, DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.ExecuteScalarAsync<int>(
|
||||
@"SELECT COUNT(*)
|
||||
FROM inquiries
|
||||
WHERE status = @Status
|
||||
AND created_at >= @StartDate
|
||||
AND created_at <= @EndDate",
|
||||
new { Status = status, StartDate = startDate, EndDate = endDate });
|
||||
}
|
||||
|
||||
public async Task UpdateStatusAsync(int id, string status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE inquiries SET status = @Status, updated_at = NOW() WHERE id = @Id",
|
||||
new { Id = id, Status = status });
|
||||
}
|
||||
|
||||
public async Task UpdateAdminMemoAsync(int id, string? adminMemo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE inquiries SET admin_memo = @AdminMemo, updated_at = NOW() WHERE id = @Id",
|
||||
new { Id = id, AdminMemo = adminMemo });
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Inquiry inquiry, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE inquiries
|
||||
SET name = @Name,
|
||||
phone = @Phone,
|
||||
email = @Email,
|
||||
service_type = @ServiceType,
|
||||
message = @Message,
|
||||
status = @Status,
|
||||
admin_memo = @AdminMemo,
|
||||
updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
inquiry);
|
||||
}
|
||||
|
||||
public async Task LinkClientAsync(int inquiryId, int clientId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE inquiries SET client_id = @ClientId, updated_at = NOW() WHERE id = @Id",
|
||||
new { Id = inquiryId, ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM inquiries WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class PortalUserRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IPortalUserRepository
|
||||
{
|
||||
public async Task<PortalUser?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<PortalUser>(
|
||||
@"SELECT id, client_id, email, name, phone, provider, provider_id, password_hash, created_at
|
||||
FROM portal_users
|
||||
WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<PortalUser?> GetByEmailAsync(string email, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<PortalUser>(
|
||||
@"SELECT id, client_id, email, name, phone, provider, provider_id, password_hash, created_at
|
||||
FROM portal_users
|
||||
WHERE email = @Email",
|
||||
new { Email = email });
|
||||
}
|
||||
|
||||
public async Task<PortalUser?> GetByProviderAsync(string provider, string providerId, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<PortalUser>(
|
||||
@"SELECT id, client_id, email, name, phone, provider, provider_id, password_hash, created_at
|
||||
FROM portal_users
|
||||
WHERE provider = @Provider AND provider_id = @ProviderId",
|
||||
new { Provider = provider, ProviderId = providerId });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(PortalUser user, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO portal_users (client_id, email, name, phone, provider, provider_id, password_hash, created_at)
|
||||
VALUES (@ClientId, @Email, @Name, @Phone, @Provider, @ProviderId, @PasswordHash, NOW())
|
||||
RETURNING id",
|
||||
user);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(PortalUser user, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE portal_users
|
||||
SET client_id = @ClientId,
|
||||
email = @Email,
|
||||
name = @Name,
|
||||
phone = @Phone,
|
||||
provider = @Provider,
|
||||
provider_id = @ProviderId,
|
||||
password_hash = @PasswordHash
|
||||
WHERE id = @Id",
|
||||
user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class RevenueTrackingRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), IRevenueTrackingRepository
|
||||
{
|
||||
public async Task<int> CreateAsync(RevenueTracking revenue, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO revenue_tracking (client_id, invoice_number, invoice_date, service_type, amount, payment_status, payment_date, due_date, notes, created_at, updated_at)
|
||||
VALUES (@ClientId, @InvoiceNumber, @InvoiceDate, @ServiceType, @Amount, @PaymentStatus, @PaymentDate, @DueDate, @Notes, NOW(), NOW())
|
||||
RETURNING id",
|
||||
revenue);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RevenueTracking>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<RevenueTracking>(
|
||||
@"SELECT id, client_id, invoice_number, invoice_date, service_type, amount, payment_status, payment_date, due_date, notes, created_at, updated_at
|
||||
FROM revenue_tracking ORDER BY invoice_date DESC");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RevenueTracking>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<RevenueTracking>(
|
||||
@"SELECT id, client_id, invoice_number, invoice_date, service_type, amount, payment_status, payment_date, due_date, notes, created_at, updated_at
|
||||
FROM revenue_tracking WHERE client_id = @ClientId ORDER BY invoice_date DESC",
|
||||
new { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RevenueTracking>> GetPendingPaymentsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<RevenueTracking>(
|
||||
@"SELECT id, client_id, invoice_number, invoice_date, service_type, amount, payment_status, payment_date, due_date, notes, created_at, updated_at
|
||||
FROM revenue_tracking WHERE payment_status = 'pending' ORDER BY due_date ASC");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RevenueTracking>> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<RevenueTracking>(
|
||||
@"SELECT id, client_id, invoice_number, invoice_date, service_type, amount, payment_status, payment_date, due_date, notes, created_at, updated_at
|
||||
FROM revenue_tracking WHERE invoice_date BETWEEN @StartDate AND @EndDate ORDER BY invoice_date DESC",
|
||||
new { StartDate = startDate, EndDate = endDate });
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(RevenueTracking revenue, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE revenue_tracking SET invoice_number = @InvoiceNumber, invoice_date = @InvoiceDate,
|
||||
service_type = @ServiceType, amount = @Amount, payment_status = @PaymentStatus,
|
||||
payment_date = @PaymentDate, due_date = @DueDate, notes = @Notes, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
revenue);
|
||||
}
|
||||
|
||||
public async Task MarkPaidAsync(int id, DateTime paymentDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE revenue_tracking SET payment_status = 'paid', payment_date = @PaymentDate, updated_at = NOW() WHERE id = @Id",
|
||||
new { Id = id, PaymentDate = paymentDate });
|
||||
}
|
||||
|
||||
public async Task<decimal> GetTotalRevenueAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var result = await conn.QueryFirstAsync<decimal>(
|
||||
@"SELECT COALESCE(SUM(amount), 0) FROM revenue_tracking WHERE invoice_date BETWEEN @StartDate AND @EndDate",
|
||||
new { StartDate = startDate, EndDate = endDate });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
public class SiteSettingRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ISiteSettingRepository
|
||||
{
|
||||
public async Task<IReadOnlyDictionary<string, string>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
var rows = await conn.QueryAsync<SiteSetting>(
|
||||
"SELECT key, value, updated_at AS UpdatedAt FROM site_settings ORDER BY key");
|
||||
return rows.ToDictionary(x => x.Key, x => x.Value);
|
||||
}
|
||||
|
||||
public async Task UpsertAsync(IEnumerable<SiteSetting> settings, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
await conn.ExecuteAsync(
|
||||
@"INSERT INTO site_settings (key, value, updated_at)
|
||||
VALUES (@Key, @Value, NOW())
|
||||
ON CONFLICT (key)
|
||||
DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()",
|
||||
setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class TaxFilingRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ITaxFilingRepository
|
||||
{
|
||||
private const string SelectColumns = @"
|
||||
tf.id, tf.client_id, c.name AS client_name, tf.filing_type, tf.due_date,
|
||||
tf.status, tf.memo, tf.created_at, tf.updated_at";
|
||||
|
||||
public async Task<IEnumerable<TaxFiling>> GetByClientIdAsync(int clientId, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxFiling>(
|
||||
$@"SELECT {SelectColumns}
|
||||
FROM tax_filings tf
|
||||
JOIN clients c ON c.id = tf.client_id
|
||||
WHERE tf.client_id = @ClientId
|
||||
ORDER BY tf.due_date ASC",
|
||||
new { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxFiling>> GetUpcomingAsync(int daysAhead, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxFiling>(
|
||||
$@"SELECT {SelectColumns}
|
||||
FROM tax_filings tf
|
||||
JOIN clients c ON c.id = tf.client_id
|
||||
WHERE tf.status = 'pending'
|
||||
AND tf.due_date <= CURRENT_DATE + @DaysAhead::int
|
||||
AND tf.due_date >= CURRENT_DATE
|
||||
ORDER BY tf.due_date ASC",
|
||||
new { DaysAhead = daysAhead });
|
||||
}
|
||||
|
||||
public async Task<TaxFiling?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<TaxFiling>(
|
||||
$@"SELECT {SelectColumns}
|
||||
FROM tax_filings tf
|
||||
JOIN clients c ON c.id = tf.client_id
|
||||
WHERE tf.id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(TaxFiling filing, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO tax_filings (client_id, filing_type, due_date, status, memo, created_at, updated_at)
|
||||
VALUES (@ClientId, @FilingType, @DueDate, @Status, @Memo, NOW(), NOW())
|
||||
RETURNING id",
|
||||
filing);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(TaxFiling filing, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE tax_filings
|
||||
SET filing_type = @FilingType, due_date = @DueDate, status = @Status,
|
||||
memo = @Memo, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
filing);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync("DELETE FROM tax_filings WHERE id = @Id", new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class TaxFilingScheduleRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ITaxFilingScheduleRepository
|
||||
{
|
||||
public async Task<int> CreateAsync(TaxFilingSchedule schedule, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO tax_filing_schedules (client_id, filing_type, due_date, filing_year, status, assigned_to, notes, created_at, updated_at)
|
||||
VALUES (@ClientId, @FilingType, @DueDate, @FilingYear, @Status, @AssignedToId, @Notes, NOW(), NOW())
|
||||
RETURNING id",
|
||||
schedule);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxFilingSchedule>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxFilingSchedule>(
|
||||
@"SELECT id, client_id, filing_type, due_date, filing_year, status, assigned_to, completed_date, notes, created_at, updated_at
|
||||
FROM tax_filing_schedules ORDER BY due_date DESC");
|
||||
}
|
||||
|
||||
public async Task<TaxFilingSchedule?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<TaxFilingSchedule>(
|
||||
@"SELECT id, client_id, filing_type, due_date, filing_year, status, assigned_to, completed_date, notes, created_at, updated_at
|
||||
FROM tax_filing_schedules WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxFilingSchedule>> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxFilingSchedule>(
|
||||
@"SELECT id, client_id, filing_type, due_date, filing_year, status, assigned_to, completed_date, notes, created_at, updated_at
|
||||
FROM tax_filing_schedules WHERE client_id = @ClientId ORDER BY due_date DESC",
|
||||
new { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxFilingSchedule>> GetUpcomingDuesAsync(int daysAhead = 30, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxFilingSchedule>(
|
||||
@"SELECT id, client_id, filing_type, due_date, filing_year, status, assigned_to, completed_date, notes, created_at, updated_at
|
||||
FROM tax_filing_schedules
|
||||
WHERE status = 'pending' AND due_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '1 day' * @DaysAhead
|
||||
ORDER BY due_date ASC",
|
||||
new { DaysAhead = daysAhead });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxFilingSchedule>> GetByStatusAsync(string status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxFilingSchedule>(
|
||||
@"SELECT id, client_id, filing_type, due_date, filing_year, status, assigned_to, completed_date, notes, created_at, updated_at
|
||||
FROM tax_filing_schedules WHERE status = @Status ORDER BY due_date",
|
||||
new { Status = status });
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(TaxFilingSchedule schedule, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE tax_filing_schedules SET filing_type = @FilingType, due_date = @DueDate, status = @Status,
|
||||
assigned_to = @AssignedToId, notes = @Notes, updated_at = NOW() WHERE id = @Id",
|
||||
schedule);
|
||||
}
|
||||
|
||||
public async Task MarkCompletedAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE tax_filing_schedules SET status = 'completed', completed_date = NOW(), updated_at = NOW() WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
namespace TaxBaik.Infrastructure.Repositories;
|
||||
|
||||
using Dapper;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class TaxProfileRepository(IDbConnectionFactory connectionFactory) : BaseRepository(connectionFactory), ITaxProfileRepository
|
||||
{
|
||||
public async Task<int> CreateAsync(TaxProfile profile, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstAsync<int>(
|
||||
@"INSERT INTO tax_profiles (client_id, business_registration, business_type, establishment_date,
|
||||
annual_revenue_range, employee_count, accounting_method, fiscal_year_end, last_filing_date,
|
||||
next_filing_due_date, tax_risk_level, previous_audit_history, special_notes, created_at, updated_at)
|
||||
VALUES (@ClientId, @BusinessRegistration, @BusinessType, @EstablishmentDate, @AnnualRevenueRange,
|
||||
@EmployeeCount, @AccountingMethod, @FiscalYearEnd, @LastFilingDate, @NextFilingDueDate,
|
||||
@TaxRiskLevel, @PreviousAuditHistory, @SpecialNotes, NOW(), NOW())
|
||||
RETURNING id",
|
||||
profile);
|
||||
}
|
||||
|
||||
public async Task<TaxProfile?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<TaxProfile>(
|
||||
@"SELECT id, client_id, business_registration, business_type, establishment_date,
|
||||
annual_revenue_range, employee_count, accounting_method, fiscal_year_end, last_filing_date,
|
||||
next_filing_due_date, tax_risk_level, previous_audit_history, special_notes, created_at, updated_at
|
||||
FROM tax_profiles WHERE id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxProfile>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxProfile>(
|
||||
@"SELECT id, client_id, business_registration, business_type, establishment_date,
|
||||
annual_revenue_range, employee_count, accounting_method, fiscal_year_end, last_filing_date,
|
||||
next_filing_due_date, tax_risk_level, previous_audit_history, special_notes, created_at, updated_at
|
||||
FROM tax_profiles ORDER BY id DESC");
|
||||
}
|
||||
|
||||
public async Task<TaxProfile?> GetByClientIdAsync(int clientId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryFirstOrDefaultAsync<TaxProfile>(
|
||||
@"SELECT id, client_id, business_registration, business_type, establishment_date,
|
||||
annual_revenue_range, employee_count, accounting_method, fiscal_year_end, last_filing_date,
|
||||
next_filing_due_date, tax_risk_level, previous_audit_history, special_notes, created_at, updated_at
|
||||
FROM tax_profiles WHERE client_id = @ClientId",
|
||||
new { ClientId = clientId });
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(TaxProfile profile, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
await conn.ExecuteAsync(
|
||||
@"UPDATE tax_profiles SET business_registration = @BusinessRegistration, business_type = @BusinessType,
|
||||
establishment_date = @EstablishmentDate, annual_revenue_range = @AnnualRevenueRange,
|
||||
employee_count = @EmployeeCount, accounting_method = @AccountingMethod, fiscal_year_end = @FiscalYearEnd,
|
||||
last_filing_date = @LastFilingDate, next_filing_due_date = @NextFilingDueDate,
|
||||
tax_risk_level = @TaxRiskLevel, previous_audit_history = @PreviousAuditHistory,
|
||||
special_notes = @SpecialNotes, updated_at = NOW()
|
||||
WHERE id = @Id",
|
||||
profile);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxProfile>> GetByRiskLevelAsync(string riskLevel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxProfile>(
|
||||
@"SELECT id, client_id, business_registration, business_type, establishment_date,
|
||||
annual_revenue_range, employee_count, accounting_method, fiscal_year_end, last_filing_date,
|
||||
next_filing_due_date, tax_risk_level, previous_audit_history, special_notes, created_at, updated_at
|
||||
FROM tax_profiles WHERE tax_risk_level = @RiskLevel ORDER BY client_id",
|
||||
new { RiskLevel = riskLevel });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaxProfile>> GetUpcomingFilingDuesAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var conn = Conn();
|
||||
return await conn.QueryAsync<TaxProfile>(
|
||||
@"SELECT id, client_id, business_registration, business_type, establishment_date,
|
||||
annual_revenue_range, employee_count, accounting_method, fiscal_year_end, last_filing_date,
|
||||
next_filing_due_date, tax_risk_level, previous_audit_history, special_notes, created_at, updated_at
|
||||
FROM tax_profiles WHERE next_filing_due_date BETWEEN @StartDate AND @EndDate
|
||||
ORDER BY next_filing_due_date",
|
||||
new { StartDate = startDate, EndDate = endDate });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user