Feat Varios 3
This commit is contained in:
@@ -37,4 +37,19 @@ public class AuditRepository
|
||||
ORDER BY a.CreatedAt DESC";
|
||||
return await conn.QueryAsync<AuditLog>(sql, new { UserId = userId, Limit = limit });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AuditLog>> GetFilteredLogsAsync(DateTime from, DateTime to, int? userId = null)
|
||||
{
|
||||
using var conn = _db.CreateConnection();
|
||||
var sql = @"SELECT a.*, u.Username
|
||||
FROM AuditLogs a
|
||||
JOIN Users u ON a.UserId = u.Id
|
||||
WHERE a.CreatedAt >= @From AND a.CreatedAt <= @To";
|
||||
|
||||
if (userId.HasValue) sql += " AND a.UserId = @UserId";
|
||||
|
||||
sql += " ORDER BY a.CreatedAt DESC";
|
||||
|
||||
return await conn.QueryAsync<AuditLog>(sql, new { From = from, To = to, UserId = userId });
|
||||
}
|
||||
}
|
||||
@@ -13,59 +13,63 @@ public class ClientRepository
|
||||
_db = db;
|
||||
}
|
||||
|
||||
// Búsqueda inteligente con protección de nulos
|
||||
// Búsqueda inteligente redireccionada a Users
|
||||
public async Task<IEnumerable<Client>> SearchAsync(string query)
|
||||
{
|
||||
using var conn = _db.CreateConnection();
|
||||
var sql = @"
|
||||
SELECT TOP 10
|
||||
Id,
|
||||
ISNULL(Name, 'Sin Nombre') as Name,
|
||||
ISNULL(DniOrCuit, '') as DniOrCuit,
|
||||
Email, Phone, Address
|
||||
FROM Clients
|
||||
WHERE Name LIKE @Query OR DniOrCuit LIKE @Query
|
||||
ORDER BY Name";
|
||||
ISNULL(BillingName, Username) as Name,
|
||||
ISNULL(BillingTaxId, '') as DniOrCuit,
|
||||
Email, Phone, BillingAddress as Address
|
||||
FROM Users
|
||||
WHERE BillingName LIKE @Query OR BillingTaxId LIKE @Query OR Username LIKE @Query
|
||||
ORDER BY BillingName";
|
||||
return await conn.QueryAsync<Client>(sql, new { Query = $"%{query}%" });
|
||||
}
|
||||
|
||||
// Asegurar existencia (Upsert)
|
||||
// Asegurar existencia (Upsert en la tabla Users)
|
||||
public async Task<int> EnsureClientExistsAsync(string name, string dni)
|
||||
{
|
||||
using var conn = _db.CreateConnection();
|
||||
|
||||
var existingId = await conn.ExecuteScalarAsync<int?>(
|
||||
"SELECT Id FROM Clients WHERE DniOrCuit = @Dni", new { Dni = dni });
|
||||
"SELECT Id FROM Users WHERE BillingTaxId = @Dni", new { Dni = dni });
|
||||
|
||||
if (existingId.HasValue)
|
||||
{
|
||||
await conn.ExecuteAsync("UPDATE Clients SET Name = @Name WHERE Id = @Id", new { Name = name, Id = existingId });
|
||||
await conn.ExecuteAsync("UPDATE Users SET BillingName = @Name WHERE Id = @Id", new { Name = name, Id = existingId });
|
||||
return existingId.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Si no existe, creamos un usuario con rol Cliente (sin password por ahora, es solo para gestión de mostrador)
|
||||
var sql = @"
|
||||
INSERT INTO Clients (Name, DniOrCuit) VALUES (@Name, @Dni);
|
||||
INSERT INTO Users (Username, Role, BillingName, BillingTaxId, PasswordHash, MustChangePassword)
|
||||
VALUES (@Username, 'Client', @Name, @Dni, 'N/A', 0);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, new { Name = name, Dni = dni });
|
||||
// El username será el DNI para asegurar unicidad si no hay otro dato
|
||||
return await conn.QuerySingleAsync<int>(sql, new { Username = dni, Name = name, Dni = dni });
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener todos con estadísticas (ISNULL agregado para seguridad)
|
||||
// Obtener todos con estadísticas desde Users
|
||||
public async Task<IEnumerable<dynamic>> GetAllWithStatsAsync()
|
||||
{
|
||||
using var conn = _db.CreateConnection();
|
||||
var sql = @"
|
||||
SELECT
|
||||
c.Id as id,
|
||||
ISNULL(c.Name, 'Sin Nombre') as name,
|
||||
ISNULL(c.DniOrCuit, 'S/D') as dniOrCuit,
|
||||
ISNULL(c.Email, 'Sin correo') as email,
|
||||
ISNULL(c.Phone, 'Sin teléfono') as phone,
|
||||
(SELECT COUNT(1) FROM Listings l WHERE l.ClientId = c.Id) as totalAds,
|
||||
ISNULL((SELECT SUM(AdFee) FROM Listings l WHERE l.ClientId = c.Id), 0) as totalSpent
|
||||
FROM Clients c
|
||||
ORDER BY c.Name";
|
||||
u.Id as id,
|
||||
ISNULL(u.BillingName, u.Username) as name,
|
||||
ISNULL(u.BillingTaxId, 'S/D') as dniOrCuit,
|
||||
ISNULL(u.Email, 'Sin correo') as email,
|
||||
ISNULL(u.Phone, 'Sin teléfono') as phone,
|
||||
(SELECT COUNT(1) FROM Listings l WHERE l.ClientId = u.Id) as totalAds,
|
||||
ISNULL((SELECT SUM(AdFee) FROM Listings l WHERE l.ClientId = u.Id), 0) as totalSpent
|
||||
FROM Users u
|
||||
WHERE Role IN ('Client', 'User') -- Mostramos tanto clientes puros como usuarios web
|
||||
ORDER BY name";
|
||||
return await conn.QueryAsync(sql);
|
||||
}
|
||||
|
||||
@@ -73,12 +77,12 @@ public class ClientRepository
|
||||
{
|
||||
using var conn = _db.CreateConnection();
|
||||
var sql = @"
|
||||
UPDATE Clients
|
||||
SET Name = @Name,
|
||||
DniOrCuit = @DniOrCuit,
|
||||
UPDATE Users
|
||||
SET BillingName = @Name,
|
||||
BillingTaxId = @DniOrCuit,
|
||||
Email = @Email,
|
||||
Phone = @Phone,
|
||||
Address = @Address
|
||||
BillingAddress = @Address
|
||||
WHERE Id = @Id";
|
||||
await conn.ExecuteAsync(sql, client);
|
||||
}
|
||||
@@ -88,21 +92,23 @@ public class ClientRepository
|
||||
using var conn = _db.CreateConnection();
|
||||
var sql = @"
|
||||
SELECT
|
||||
c.Id, c.Name, c.DniOrCuit, c.Email, c.Phone, c.Address,
|
||||
(SELECT COUNT(1) FROM Listings WHERE ClientId = c.Id) as TotalAds,
|
||||
ISNULL((SELECT SUM(AdFee) FROM Listings WHERE ClientId = c.Id), 0) as TotalInvested,
|
||||
(SELECT MAX(CreatedAt) FROM Listings WHERE ClientId = c.Id) as LastAdDate,
|
||||
(SELECT COUNT(1) FROM Listings WHERE ClientId = c.Id AND Status = 'Published') as ActiveAds,
|
||||
u.Id,
|
||||
ISNULL(u.BillingName, u.Username) as Name,
|
||||
u.BillingTaxId as DniOrCuit, u.Email, u.Phone, u.BillingAddress as Address,
|
||||
(SELECT COUNT(1) FROM Listings WHERE ClientId = u.Id) as TotalAds,
|
||||
ISNULL((SELECT SUM(AdFee) FROM Listings WHERE ClientId = u.Id), 0) as TotalInvested,
|
||||
(SELECT MAX(CreatedAt) FROM Listings WHERE ClientId = u.Id) as LastAdDate,
|
||||
(SELECT COUNT(1) FROM Listings WHERE ClientId = u.Id AND Status = 'Published') as ActiveAds,
|
||||
ISNULL((
|
||||
SELECT TOP 1 cat.Name
|
||||
FROM Listings l
|
||||
JOIN Categories cat ON l.CategoryId = cat.Id
|
||||
WHERE l.ClientId = c.Id
|
||||
WHERE l.ClientId = u.Id
|
||||
GROUP BY cat.Name
|
||||
ORDER BY COUNT(l.Id) DESC
|
||||
), 'N/A') as PreferredCategory
|
||||
FROM Clients c
|
||||
WHERE c.Id = @Id";
|
||||
FROM Users u
|
||||
WHERE u.Id = @Id";
|
||||
|
||||
return await conn.QuerySingleOrDefaultAsync<dynamic>(sql, new { Id = clientId });
|
||||
}
|
||||
|
||||
63
src/SIGCM.Infrastructure/Repositories/CouponRepository.cs
Normal file
63
src/SIGCM.Infrastructure/Repositories/CouponRepository.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Dapper;
|
||||
using SIGCM.Domain.Entities;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
using SIGCM.Infrastructure.Data;
|
||||
|
||||
namespace SIGCM.Infrastructure.Repositories;
|
||||
|
||||
public class CouponRepository : ICouponRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public CouponRepository(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Coupon>> GetAllAsync()
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QueryAsync<Coupon>("SELECT * FROM Coupons ORDER BY CreatedAt DESC");
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(Coupon coupon)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
INSERT INTO Coupons (Code, DiscountType, DiscountValue, ExpiryDate, MaxUsages, MaxUsagesPerUser, IsActive, CreatedAt)
|
||||
VALUES (@Code, @DiscountType, @DiscountValue, @ExpiryDate, @MaxUsages, @MaxUsagesPerUser, @IsActive, GETUTCDATE());
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, coupon);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync("DELETE FROM Coupons WHERE Id = @Id", new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<Coupon?> GetByCodeAsync(string code)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
return await conn.QuerySingleOrDefaultAsync<Coupon>(
|
||||
"SELECT * FROM Coupons WHERE Code = @Code AND IsActive = 1",
|
||||
new { Code = code });
|
||||
}
|
||||
|
||||
public async Task IncrementUsageAsync(int id)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync(
|
||||
"UPDATE Coupons SET UsageCount = UsageCount + 1 WHERE Id = @Id",
|
||||
new { Id = id });
|
||||
}
|
||||
|
||||
public async Task<int> CountUserUsageAsync(int userId, string couponCode)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
// Check listings for this user with this coupon code
|
||||
// Note: CouponCode column was added to Listings
|
||||
var sql = "SELECT COUNT(1) FROM Listings WHERE UserId = @UserId AND CouponCode = @CouponCode";
|
||||
return await conn.ExecuteScalarAsync<int>(sql, new { UserId = userId, CouponCode = couponCode });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Dapper;
|
||||
using SIGCM.Domain.Entities;
|
||||
using SIGCM.Domain.Interfaces;
|
||||
using SIGCM.Infrastructure.Data;
|
||||
|
||||
namespace SIGCM.Infrastructure.Repositories;
|
||||
|
||||
public class ListingNoteRepository : IListingNoteRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public ListingNoteRepository(IDbConnectionFactory connectionFactory)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public async Task<int> CreateAsync(ListingNote note)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
INSERT INTO ListingNotes (ListingId, SenderId, IsFromModerator, Message, CreatedAt, IsRead)
|
||||
VALUES (@ListingId, @SenderId, @IsFromModerator, @Message, GETUTCDATE(), 0);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, note);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ListingNote>> GetByListingIdAsync(int listingId)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = "SELECT * FROM ListingNotes WHERE ListingId = @ListingId ORDER BY CreatedAt ASC";
|
||||
return await conn.QueryAsync<ListingNote>(sql, new { ListingId = listingId });
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ListingNote>> GetByUserIdAsync(int userId)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
SELECT ln.*
|
||||
FROM ListingNotes ln
|
||||
INNER JOIN Listings l ON ln.ListingId = l.Id
|
||||
WHERE l.UserId = @UserId
|
||||
ORDER BY ln.CreatedAt DESC";
|
||||
return await conn.QueryAsync<ListingNote>(sql, new { UserId = userId });
|
||||
}
|
||||
|
||||
public async Task MarkAsReadAsync(int noteId)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync("UPDATE ListingNotes SET IsRead = 1 WHERE Id = @Id", new { Id = noteId });
|
||||
}
|
||||
|
||||
public async Task<int> GetUnreadCountAsync(int userId, bool isForModerator)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = "";
|
||||
if (isForModerator)
|
||||
{
|
||||
// Para moderadores: Mensajes que NO son de moderador y NO están leídos
|
||||
sql = @"SELECT COUNT(*) FROM ListingNotes WHERE IsFromModerator = 0 AND IsRead = 0";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Para usuarios: Mensajes que SON de moderador, para sus avisos, y NO están leídos
|
||||
sql = @"
|
||||
SELECT COUNT(*)
|
||||
FROM ListingNotes ln
|
||||
INNER JOIN Listings l ON ln.ListingId = l.Id
|
||||
WHERE l.UserId = @UserId AND ln.IsFromModerator = 1 AND ln.IsRead = 0";
|
||||
}
|
||||
return await conn.ExecuteScalarAsync<int>(sql, new { UserId = userId });
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,14 @@ public class ListingRepository : IListingRepository
|
||||
INSERT INTO Listings (
|
||||
CategoryId, OperationId, Title, Description, Price, Currency,
|
||||
CreatedAt, Status, UserId, PrintText, PrintStartDate, PrintDaysCount,
|
||||
IsBold, IsFrame, PrintFontSize, PrintAlignment, AdFee, ClientId
|
||||
IsBold, IsFrame, PrintFontSize, PrintAlignment, AdFee, ClientId,
|
||||
PublicationStartDate, IsFeatured, FeaturedExpiry, AllowContact, CouponCode, Origin
|
||||
)
|
||||
VALUES (
|
||||
@CategoryId, @OperationId, @Title, @Description, @Price, @Currency,
|
||||
@CreatedAt, @Status, @UserId, @PrintText, @PrintStartDate, @PrintDaysCount,
|
||||
@IsBold, @IsFrame, @PrintFontSize, @PrintAlignment, @AdFee, @ClientId
|
||||
@IsBold, @IsFrame, @PrintFontSize, @PrintAlignment, @AdFee, @ClientId,
|
||||
@PublicationStartDate, @IsFeatured, @FeaturedExpiry, @AllowContact, @CouponCode, @Origin
|
||||
);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
|
||||
@@ -101,11 +103,15 @@ public class ListingRepository : IListingRepository
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
SELECT
|
||||
l.*, c.Name as CategoryName, cl.Name as ClientName, cl.DniOrCuit as ClientDni,
|
||||
l.*,
|
||||
c.Name as CategoryName,
|
||||
p.Name as ParentCategoryName,
|
||||
cl.BillingName as ClientName, cl.BillingTaxId as ClientDni,
|
||||
u.Username as CashierName
|
||||
FROM Listings l
|
||||
LEFT JOIN Categories c ON l.CategoryId = c.Id
|
||||
LEFT JOIN Clients cl ON l.ClientId = cl.Id
|
||||
LEFT JOIN Categories p ON c.ParentId = p.Id
|
||||
LEFT JOIN Users cl ON l.ClientId = cl.Id
|
||||
LEFT JOIN Users u ON l.UserId = u.Id
|
||||
WHERE l.Id = @Id;
|
||||
|
||||
@@ -142,10 +148,16 @@ public class ListingRepository : IListingRepository
|
||||
CreatedAt = listingData.CreatedAt,
|
||||
UserId = listingData.UserId,
|
||||
CategoryName = listingData.CategoryName,
|
||||
ParentCategoryName = listingData.ParentCategoryName,
|
||||
PrintDaysCount = (int)(listingData.PrintDaysCount ?? 0),
|
||||
PrintText = listingData.PrintText,
|
||||
IsBold = listingData.IsBold != null && Convert.ToBoolean(listingData.IsBold),
|
||||
IsFrame = listingData.IsFrame != null && Convert.ToBoolean(listingData.IsFrame),
|
||||
PublicationStartDate = listingData.PublicationStartDate,
|
||||
ApprovedAt = listingData.ApprovedAt,
|
||||
IsFeatured = listingData.IsFeatured != null && Convert.ToBoolean(listingData.IsFeatured),
|
||||
FeaturedExpiry = listingData.FeaturedExpiry,
|
||||
AllowContact = listingData.AllowContact != null && Convert.ToBoolean(listingData.AllowContact),
|
||||
},
|
||||
Attributes = attributes,
|
||||
Images = images,
|
||||
@@ -162,6 +174,8 @@ public class ListingRepository : IListingRepository
|
||||
FROM Listings l
|
||||
JOIN Categories c ON l.CategoryId = c.Id
|
||||
WHERE l.Status = 'Published'
|
||||
AND (l.PublicationStartDate IS NULL OR l.PublicationStartDate <= GETUTCDATE())
|
||||
AND (l.PrintDaysCount = 0 OR DATEADD(day, l.PrintDaysCount, COALESCE(l.PublicationStartDate, l.ApprovedAt, l.CreatedAt)) >= GETUTCDATE())
|
||||
ORDER BY l.CreatedAt DESC";
|
||||
|
||||
return await conn.QueryAsync<Listing>(sql);
|
||||
@@ -190,7 +204,7 @@ public class ListingRepository : IListingRepository
|
||||
|
||||
public async Task<IEnumerable<Listing>> SearchAsync(string? query, int? categoryId)
|
||||
{
|
||||
return await SearchFacetedAsync(query, categoryId, null);
|
||||
return await SearchFacetedAsync(query, categoryId, null, null, null, null, null, true);
|
||||
}
|
||||
|
||||
// Búsqueda Avanzada Facetada
|
||||
@@ -201,23 +215,24 @@ public class ListingRepository : IListingRepository
|
||||
DateTime? from = null,
|
||||
DateTime? to = null,
|
||||
string? origin = null,
|
||||
string? status = null)
|
||||
string? status = null,
|
||||
bool onlyActive = false)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var parameters = new DynamicParameters();
|
||||
|
||||
string sql = @"
|
||||
SELECT l.*, c.Name as CategoryName, cl.Name as ClientName, cl.DniOrCuit as ClientDni,
|
||||
SELECT l.*, c.Name as CategoryName, cl.BillingName as ClientName, cl.BillingTaxId as ClientDni,
|
||||
(SELECT TOP 1 Url FROM ListingImages li WHERE li.ListingId = l.Id ORDER BY IsMainInfo DESC, DisplayOrder ASC) as MainImageUrl
|
||||
FROM Listings l
|
||||
JOIN Categories c ON l.CategoryId = c.Id
|
||||
LEFT JOIN Clients cl ON l.ClientId = cl.Id
|
||||
LEFT JOIN Users cl ON l.ClientId = cl.Id
|
||||
WHERE 1=1";
|
||||
|
||||
// --- FILTROS EXISTENTES ---
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
sql += " AND (l.Title LIKE @Query OR l.Description LIKE @Query OR cl.DniOrCuit = @ExactQuery)";
|
||||
sql += " AND (l.Title LIKE @Query OR l.Description LIKE @Query OR cl.BillingTaxId = @ExactQuery OR cl.BillingName LIKE @Query)";
|
||||
parameters.Add("Query", $"%{query}%");
|
||||
parameters.Add("ExactQuery", query);
|
||||
}
|
||||
@@ -253,7 +268,16 @@ public class ListingRepository : IListingRepository
|
||||
parameters.Add("Status", status);
|
||||
}
|
||||
|
||||
sql += " ORDER BY l.CreatedAt DESC";
|
||||
// --- FILTRO DE VISIBILIDAD (Solo para búsquedas públicas) ---
|
||||
if (onlyActive)
|
||||
{
|
||||
// Solo avisos publicados y dentro de su rango de vigencia
|
||||
sql += @" AND l.Status = 'Published'
|
||||
AND (l.PublicationStartDate IS NULL OR l.PublicationStartDate <= GETUTCDATE())
|
||||
AND (l.PrintDaysCount = 0 OR DATEADD(day, l.PrintDaysCount, COALESCE(l.PublicationStartDate, l.ApprovedAt, l.CreatedAt)) >= GETUTCDATE())";
|
||||
}
|
||||
|
||||
sql += " ORDER BY l.IsFeatured DESC, l.CreatedAt DESC";
|
||||
|
||||
return await conn.QueryAsync<Listing>(sql, parameters);
|
||||
}
|
||||
@@ -277,15 +301,28 @@ public class ListingRepository : IListingRepository
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
// Avisos que NO están publicados ni rechazados ni borrados.
|
||||
// Asumimos 'Pending' o 'Draft' si vienen del Wizard y requieren revisión.
|
||||
// Para este ejemplo, buscamos 'Pending'.
|
||||
return await conn.QueryAsync<Listing>("SELECT * FROM Listings WHERE Status = 'Pending' ORDER BY CreatedAt ASC");
|
||||
// Asumimos 'Pending'. Incluimos conteo de notas no leídas del usuario.
|
||||
var sql = @"
|
||||
SELECT l.*,
|
||||
(SELECT COUNT(*) FROM ListingNotes ln WHERE ln.ListingId = l.Id AND ln.IsFromModerator = 0 AND ln.IsRead = 0) as UnreadNotesCount
|
||||
FROM Listings l
|
||||
WHERE l.Status = 'Pending'
|
||||
ORDER BY l.CreatedAt ASC";
|
||||
return await conn.QueryAsync<Listing>(sql);
|
||||
}
|
||||
|
||||
public async Task UpdateStatusAsync(int id, string status)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.ExecuteAsync("UPDATE Listings SET Status = @Status WHERE Id = @Id", new { Id = id, Status = status });
|
||||
var sql = @"
|
||||
UPDATE Listings
|
||||
SET Status = @Status,
|
||||
ApprovedAt = CASE
|
||||
WHEN (@Status = 'Published' OR @Status = 'Approved') AND ApprovedAt IS NULL THEN GETUTCDATE()
|
||||
ELSE ApprovedAt
|
||||
END
|
||||
WHERE Id = @Id";
|
||||
await conn.ExecuteAsync(sql, new { Id = id, Status = status });
|
||||
}
|
||||
|
||||
public async Task<int> CountByCategoryIdAsync(int categoryId)
|
||||
@@ -562,10 +599,11 @@ public class ListingRepository : IListingRepository
|
||||
SELECT
|
||||
l.Id, l.CreatedAt as Date, l.Title,
|
||||
c.Name as Category, u.Username as Cashier, l.AdFee as Amount,
|
||||
l.Origin as Source
|
||||
l.Origin as Source, cl.BillingName as ClientName
|
||||
FROM Listings l
|
||||
JOIN Categories c ON l.CategoryId = c.Id
|
||||
LEFT JOIN Users u ON l.UserId = u.Id
|
||||
LEFT JOIN Users cl ON l.ClientId = cl.Id
|
||||
WHERE CAST(l.CreatedAt AS DATE) BETWEEN @Start AND @End
|
||||
AND l.Status = 'Published'
|
||||
AND (@UserId IS NULL OR l.UserId = @UserId)
|
||||
|
||||
@@ -46,8 +46,10 @@ public class UserRepository : IUserRepository
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
var sql = @"
|
||||
INSERT INTO Users (Username, PasswordHash, Role, Email, FailedLoginAttempts, LockoutEnd, MustChangePassword, IsActive, LastLogin, GoogleId, IsMfaEnabled, MfaSecret)
|
||||
VALUES (@Username, @PasswordHash, @Role, @Email, @FailedLoginAttempts, @LockoutEnd, @MustChangePassword, @IsActive, @LastLogin, @GoogleId, @IsMfaEnabled, @MfaSecret);
|
||||
INSERT INTO Users (Username, PasswordHash, Role, Email, FailedLoginAttempts, LockoutEnd, MustChangePassword, IsActive, LastLogin, GoogleId, IsMfaEnabled, MfaSecret,
|
||||
BillingName, BillingAddress, BillingTaxId, BillingTaxType, ClientId, Phone)
|
||||
VALUES (@Username, @PasswordHash, @Role, @Email, @FailedLoginAttempts, @LockoutEnd, @MustChangePassword, @IsActive, @LastLogin, @GoogleId, @IsMfaEnabled, @MfaSecret,
|
||||
@BillingName, @BillingAddress, @BillingTaxId, @BillingTaxType, @ClientId, @Phone);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
return await conn.QuerySingleAsync<int>(sql, user);
|
||||
}
|
||||
@@ -75,7 +77,9 @@ public class UserRepository : IUserRepository
|
||||
SET Username = @Username, Role = @Role, Email = @Email, PasswordHash = @PasswordHash,
|
||||
FailedLoginAttempts = @FailedLoginAttempts, LockoutEnd = @LockoutEnd,
|
||||
MustChangePassword = @MustChangePassword, IsActive = @IsActive, LastLogin = @LastLogin,
|
||||
GoogleId = @GoogleId, IsMfaEnabled = @IsMfaEnabled, MfaSecret = @MfaSecret
|
||||
GoogleId = @GoogleId, IsMfaEnabled = @IsMfaEnabled, MfaSecret = @MfaSecret,
|
||||
BillingName = @BillingName, BillingAddress = @BillingAddress, BillingTaxId = @BillingTaxId, BillingTaxType = @BillingTaxType,
|
||||
ClientId = @ClientId, Phone = @Phone
|
||||
WHERE Id = @Id";
|
||||
await conn.ExecuteAsync(sql, user);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user