Feat: Novas Implementações
This commit is contained in:
parent
208c201156
commit
82dd0bf2d0
|
|
@ -10,7 +10,7 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,financeiro")]
|
||||||
public class BillingController : ControllerBase
|
public class BillingController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
@ -197,7 +197,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateBillingClientRequest req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateBillingClientRequest req)
|
||||||
{
|
{
|
||||||
var x = await _db.BillingClients.FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.BillingClients.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
|
|
@ -230,7 +230,7 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id:guid}")]
|
[HttpDelete("{id:guid}")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<IActionResult> Delete(Guid id)
|
public async Task<IActionResult> Delete(Guid id)
|
||||||
{
|
{
|
||||||
var x = await _db.BillingClients.FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.BillingClients.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/chips-virgens")]
|
[Route("api/chips-virgens")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,gestor,financeiro")]
|
||||||
public class ChipsVirgensController : ControllerBase
|
public class ChipsVirgensController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/controle-recebidos")]
|
[Route("api/controle-recebidos")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,gestor,financeiro")]
|
||||||
public class ControleRecebidosController : ControllerBase
|
public class ControleRecebidosController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using line_gestao_api.Data;
|
using line_gestao_api.Data;
|
||||||
using line_gestao_api.Dtos;
|
using line_gestao_api.Dtos;
|
||||||
|
using line_gestao_api.Models;
|
||||||
using line_gestao_api.Services;
|
using line_gestao_api.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
@ -11,9 +12,18 @@ namespace line_gestao_api.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/historico")]
|
[Route("api/historico")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,gestor,financeiro")]
|
||||||
public class HistoricoController : ControllerBase
|
public class HistoricoController : ControllerBase
|
||||||
{
|
{
|
||||||
|
private static readonly HashSet<string> LineRelatedEntities = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
nameof(MobileLine),
|
||||||
|
nameof(MuregLine),
|
||||||
|
nameof(TrocaNumeroLine),
|
||||||
|
nameof(VigenciaLine),
|
||||||
|
nameof(ParcelamentoLine)
|
||||||
|
};
|
||||||
|
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
||||||
public HistoricoController(AppDbContext db)
|
public HistoricoController(AppDbContext db)
|
||||||
|
|
@ -121,11 +131,118 @@ public class HistoricoController : ControllerBase
|
||||||
Page = page,
|
Page = page,
|
||||||
PageSize = pageSize,
|
PageSize = pageSize,
|
||||||
Total = total,
|
Total = total,
|
||||||
Items = items.Select(ToDto).ToList()
|
Items = items.Select(log => ToDto(log)).ToList()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuditLogDto ToDto(Models.AuditLog log)
|
[HttpGet("linhas")]
|
||||||
|
public async Task<ActionResult<PagedResult<AuditLogDto>>> GetLineHistory(
|
||||||
|
[FromQuery] string? line,
|
||||||
|
[FromQuery] string? pageName,
|
||||||
|
[FromQuery] string? action,
|
||||||
|
[FromQuery] string? user,
|
||||||
|
[FromQuery] string? search,
|
||||||
|
[FromQuery] DateTime? dateFrom,
|
||||||
|
[FromQuery] DateTime? dateTo,
|
||||||
|
[FromQuery] int page = 1,
|
||||||
|
[FromQuery] int pageSize = 20)
|
||||||
|
{
|
||||||
|
page = page < 1 ? 1 : page;
|
||||||
|
pageSize = pageSize < 1 ? 20 : pageSize;
|
||||||
|
|
||||||
|
var lineTerm = (line ?? string.Empty).Trim();
|
||||||
|
var normalizedLineDigits = DigitsOnly(lineTerm);
|
||||||
|
if (string.IsNullOrWhiteSpace(lineTerm) && string.IsNullOrWhiteSpace(normalizedLineDigits))
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = "Informe uma linha para consultar o histórico." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var q = _db.AuditLogs
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => LineRelatedEntities.Contains(x.EntityName))
|
||||||
|
.Where(x =>
|
||||||
|
!EF.Functions.ILike(x.RequestPath ?? "", "%import-excel%") ||
|
||||||
|
x.Page == AuditLogBuilder.SpreadsheetImportPageName);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(pageName))
|
||||||
|
{
|
||||||
|
var p = pageName.Trim();
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Page, $"%{p}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(action))
|
||||||
|
{
|
||||||
|
var a = action.Trim().ToUpperInvariant();
|
||||||
|
q = q.Where(x => x.Action == a);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(user))
|
||||||
|
{
|
||||||
|
var u = user.Trim();
|
||||||
|
q = q.Where(x =>
|
||||||
|
EF.Functions.ILike(x.UserName ?? "", $"%{u}%") ||
|
||||||
|
EF.Functions.ILike(x.UserEmail ?? "", $"%{u}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateFrom.HasValue)
|
||||||
|
{
|
||||||
|
var fromUtc = ToUtc(dateFrom.Value);
|
||||||
|
q = q.Where(x => x.OccurredAtUtc >= fromUtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateTo.HasValue)
|
||||||
|
{
|
||||||
|
var toUtc = ToUtc(dateTo.Value);
|
||||||
|
if (dateTo.Value.TimeOfDay == TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
toUtc = toUtc.Date.AddDays(1).AddTicks(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
q = q.Where(x => x.OccurredAtUtc <= toUtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidateLogs = await q
|
||||||
|
.OrderByDescending(x => x.OccurredAtUtc)
|
||||||
|
.ThenByDescending(x => x.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var searchTerm = (search ?? string.Empty).Trim();
|
||||||
|
var searchDigits = DigitsOnly(searchTerm);
|
||||||
|
var matchedLogs = new List<(AuditLog Log, List<AuditFieldChangeDto> Changes)>();
|
||||||
|
|
||||||
|
foreach (var log in candidateLogs)
|
||||||
|
{
|
||||||
|
var changes = ParseChanges(log.ChangesJson);
|
||||||
|
if (!MatchesLine(log, changes, lineTerm, normalizedLineDigits))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(searchTerm) && !MatchesSearch(log, changes, searchTerm, searchDigits))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedLogs.Add((log, changes));
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = matchedLogs.Count;
|
||||||
|
var items = matchedLogs
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.Select(x => ToDto(x.Log, x.Changes))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Ok(new PagedResult<AuditLogDto>
|
||||||
|
{
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
Total = total,
|
||||||
|
Items = items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuditLogDto ToDto(AuditLog log, List<AuditFieldChangeDto>? parsedChanges = null)
|
||||||
{
|
{
|
||||||
return new AuditLogDto
|
return new AuditLogDto
|
||||||
{
|
{
|
||||||
|
|
@ -142,7 +259,7 @@ public class HistoricoController : ControllerBase
|
||||||
RequestPath = log.RequestPath,
|
RequestPath = log.RequestPath,
|
||||||
RequestMethod = log.RequestMethod,
|
RequestMethod = log.RequestMethod,
|
||||||
IpAddress = log.IpAddress,
|
IpAddress = log.IpAddress,
|
||||||
Changes = ParseChanges(log.ChangesJson)
|
Changes = parsedChanges ?? ParseChanges(log.ChangesJson)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +290,102 @@ public class HistoricoController : ControllerBase
|
||||||
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
|
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool MatchesLine(
|
||||||
|
AuditLog log,
|
||||||
|
List<AuditFieldChangeDto> changes,
|
||||||
|
string lineTerm,
|
||||||
|
string normalizedLineDigits)
|
||||||
|
{
|
||||||
|
if (MatchesTerm(log.EntityLabel, lineTerm, normalizedLineDigits) ||
|
||||||
|
MatchesTerm(log.EntityId, lineTerm, normalizedLineDigits))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var change in changes)
|
||||||
|
{
|
||||||
|
if (MatchesTerm(change.Field, lineTerm, normalizedLineDigits) ||
|
||||||
|
MatchesTerm(change.OldValue, lineTerm, normalizedLineDigits) ||
|
||||||
|
MatchesTerm(change.NewValue, lineTerm, normalizedLineDigits))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesSearch(
|
||||||
|
AuditLog log,
|
||||||
|
List<AuditFieldChangeDto> changes,
|
||||||
|
string searchTerm,
|
||||||
|
string searchDigits)
|
||||||
|
{
|
||||||
|
if (MatchesTerm(log.UserName, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.UserEmail, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.Action, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.Page, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.EntityName, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.EntityId, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.EntityLabel, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.RequestMethod, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.RequestPath, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(log.IpAddress, searchTerm, searchDigits))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var change in changes)
|
||||||
|
{
|
||||||
|
if (MatchesTerm(change.Field, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(change.OldValue, searchTerm, searchDigits) ||
|
||||||
|
MatchesTerm(change.NewValue, searchTerm, searchDigits))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesTerm(string? source, string term, string digitsTerm)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(source))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(term) &&
|
||||||
|
source.Contains(term, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(digitsTerm))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceDigits = DigitsOnly(source);
|
||||||
|
if (string.IsNullOrWhiteSpace(sourceDigits))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceDigits.Contains(digitsTerm, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DigitsOnly(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chars = value.Where(char.IsDigit).ToArray();
|
||||||
|
return chars.Length == 0 ? string.Empty : new string(chars);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryParseSearchDateUtcStart(string value, out DateTime utcStart)
|
private static bool TryParseSearchDateUtcStart(string value, out DateTime utcStart)
|
||||||
{
|
{
|
||||||
utcStart = default;
|
utcStart = default;
|
||||||
|
|
|
||||||
|
|
@ -811,6 +811,7 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
var previousLinha = lineToPersist!.Linha;
|
var previousLinha = lineToPersist!.Linha;
|
||||||
ApplyCreateRequestToLine(lineToPersist, req, linhaLimpa, chipLimpo, franquiaVivo, valorPlanoVivo, now);
|
ApplyCreateRequestToLine(lineToPersist, req, linhaLimpa, chipLimpo, franquiaVivo, valorPlanoVivo, now);
|
||||||
|
ApplyBlockedLineToReservaContext(lineToPersist);
|
||||||
ApplyReservaRule(lineToPersist);
|
ApplyReservaRule(lineToPersist);
|
||||||
var ensuredTenant = await EnsureTenantForClientAsync(lineToPersist.Cliente);
|
var ensuredTenant = await EnsureTenantForClientAsync(lineToPersist.Cliente);
|
||||||
if (ensuredTenant != null)
|
if (ensuredTenant != null)
|
||||||
|
|
@ -1323,6 +1324,166 @@ namespace line_gestao_api.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// ✅ 5.5. BLOQUEIO / DESBLOQUEIO EM LOTE (GERAL)
|
||||||
|
// ==========================================================
|
||||||
|
[HttpPost("batch-status-update")]
|
||||||
|
[Authorize(Roles = "sysadmin,gestor")]
|
||||||
|
public async Task<ActionResult<BatchLineStatusUpdateResultDto>> BatchStatusUpdate([FromBody] BatchLineStatusUpdateRequestDto req)
|
||||||
|
{
|
||||||
|
var action = (req?.Action ?? "").Trim().ToLowerInvariant();
|
||||||
|
var isBlockAction = action is "block" or "bloquear";
|
||||||
|
var isUnblockAction = action is "unblock" or "desbloquear";
|
||||||
|
|
||||||
|
if (!isBlockAction && !isUnblockAction)
|
||||||
|
return BadRequest(new { message = "Ação inválida. Use 'block' ou 'unblock'." });
|
||||||
|
|
||||||
|
var blockStatus = NormalizeOptionalText(req?.BlockStatus);
|
||||||
|
if (isBlockAction && string.IsNullOrWhiteSpace(blockStatus))
|
||||||
|
return BadRequest(new { message = "Informe o tipo de bloqueio para bloqueio em lote." });
|
||||||
|
|
||||||
|
var applyToAllFiltered = req?.ApplyToAllFiltered ?? false;
|
||||||
|
var ids = (req?.LineIds ?? new List<Guid>())
|
||||||
|
.Where(x => x != Guid.Empty)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!applyToAllFiltered)
|
||||||
|
{
|
||||||
|
if (ids.Count == 0)
|
||||||
|
return BadRequest(new { message = "Selecione ao menos uma linha para processar." });
|
||||||
|
if (ids.Count > 5000)
|
||||||
|
return BadRequest(new { message = "Limite de 5000 linhas por operação em lote." });
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryable<MobileLine> baseQuery;
|
||||||
|
if (applyToAllFiltered)
|
||||||
|
{
|
||||||
|
baseQuery = BuildBatchStatusTargetQuery(req);
|
||||||
|
const int filteredLimit = 20000;
|
||||||
|
var overLimit = await baseQuery.OrderBy(x => x.Item).Take(filteredLimit + 1).CountAsync();
|
||||||
|
if (overLimit > filteredLimit)
|
||||||
|
return BadRequest(new { message = "Muitos registros filtrados. Refine os filtros (máximo 20000 por operação)." });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
baseQuery = _db.MobileLines.Where(x => ids.Contains(x.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
var userFilter = (req?.Usuario ?? "").Trim();
|
||||||
|
if (!applyToAllFiltered && !string.IsNullOrWhiteSpace(userFilter))
|
||||||
|
{
|
||||||
|
baseQuery = baseQuery.Where(x => EF.Functions.ILike(x.Usuario ?? "", $"%{userFilter}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetLines = await baseQuery
|
||||||
|
.OrderBy(x => x.Item)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var result = new BatchLineStatusUpdateResultDto
|
||||||
|
{
|
||||||
|
Requested = applyToAllFiltered ? targetLines.Count : ids.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.Requested <= 0)
|
||||||
|
return Ok(result);
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (applyToAllFiltered)
|
||||||
|
{
|
||||||
|
foreach (var line in targetLines)
|
||||||
|
{
|
||||||
|
var previousStatus = NormalizeOptionalText(line.Status);
|
||||||
|
var newStatus = isBlockAction ? blockStatus! : "ATIVO";
|
||||||
|
|
||||||
|
line.Status = newStatus;
|
||||||
|
line.DataBloqueio = isBlockAction
|
||||||
|
? (line.DataBloqueio ?? now)
|
||||||
|
: null;
|
||||||
|
if (isBlockAction)
|
||||||
|
ApplyBlockedLineToReservaContext(line);
|
||||||
|
ApplyReservaRule(line);
|
||||||
|
line.UpdatedAt = now;
|
||||||
|
|
||||||
|
result.Items.Add(new BatchLineStatusUpdateItemResultDto
|
||||||
|
{
|
||||||
|
Id = line.Id,
|
||||||
|
Item = line.Item,
|
||||||
|
Linha = line.Linha,
|
||||||
|
Usuario = line.Usuario,
|
||||||
|
StatusAnterior = previousStatus,
|
||||||
|
StatusNovo = newStatus,
|
||||||
|
Success = true,
|
||||||
|
Message = isBlockAction ? "Linha bloqueada com sucesso." : "Linha desbloqueada com sucesso."
|
||||||
|
});
|
||||||
|
|
||||||
|
result.Updated++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var byId = targetLines.ToDictionary(x => x.Id, x => x);
|
||||||
|
foreach (var id in ids)
|
||||||
|
{
|
||||||
|
if (!byId.TryGetValue(id, out var line))
|
||||||
|
{
|
||||||
|
result.Items.Add(new BatchLineStatusUpdateItemResultDto
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Success = false,
|
||||||
|
Message = "Linha não encontrada para o contexto atual."
|
||||||
|
});
|
||||||
|
result.Failed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousStatus = NormalizeOptionalText(line.Status);
|
||||||
|
var newStatus = isBlockAction ? blockStatus! : "ATIVO";
|
||||||
|
|
||||||
|
line.Status = newStatus;
|
||||||
|
line.DataBloqueio = isBlockAction
|
||||||
|
? (line.DataBloqueio ?? now)
|
||||||
|
: null;
|
||||||
|
if (isBlockAction)
|
||||||
|
ApplyBlockedLineToReservaContext(line);
|
||||||
|
ApplyReservaRule(line);
|
||||||
|
line.UpdatedAt = now;
|
||||||
|
|
||||||
|
result.Items.Add(new BatchLineStatusUpdateItemResultDto
|
||||||
|
{
|
||||||
|
Id = line.Id,
|
||||||
|
Item = line.Item,
|
||||||
|
Linha = line.Linha,
|
||||||
|
Usuario = line.Usuario,
|
||||||
|
StatusAnterior = previousStatus,
|
||||||
|
StatusNovo = newStatus,
|
||||||
|
Success = true,
|
||||||
|
Message = isBlockAction ? "Linha bloqueada com sucesso." : "Linha desbloqueada com sucesso."
|
||||||
|
});
|
||||||
|
result.Updated++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Failed = result.Requested - result.Updated;
|
||||||
|
|
||||||
|
if (result.Updated <= 0)
|
||||||
|
return Ok(result);
|
||||||
|
|
||||||
|
await using var tx = await _db.Database.BeginTransactionAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
await AddBatchStatusUpdateHistoryAsync(req, result, isBlockAction, blockStatus);
|
||||||
|
await tx.CommitAsync();
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (DbUpdateException)
|
||||||
|
{
|
||||||
|
await tx.RollbackAsync();
|
||||||
|
return StatusCode(500, new { message = "Erro ao processar bloqueio/desbloqueio em lote." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ 6. UPDATE
|
// ✅ 6. UPDATE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
|
|
@ -1415,6 +1576,7 @@ namespace line_gestao_api.Controllers
|
||||||
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
||||||
x.VencConta = NormalizeOptionalText(req.VencConta);
|
x.VencConta = NormalizeOptionalText(req.VencConta);
|
||||||
x.TipoDeChip = NormalizeOptionalText(req.TipoDeChip);
|
x.TipoDeChip = NormalizeOptionalText(req.TipoDeChip);
|
||||||
|
ApplyBlockedLineToReservaContext(x);
|
||||||
|
|
||||||
var previousClienteNormalized = string.IsNullOrWhiteSpace(previousCliente) ? null : previousCliente.Trim();
|
var previousClienteNormalized = string.IsNullOrWhiteSpace(previousCliente) ? null : previousCliente.Trim();
|
||||||
var clienteAtualIsReserva = IsReservaValue(x.Cliente);
|
var clienteAtualIsReserva = IsReservaValue(x.Cliente);
|
||||||
|
|
@ -1859,6 +2021,132 @@ namespace line_gestao_api.Controllers
|
||||||
await _db.SaveChangesAsync();
|
await _db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IQueryable<MobileLine> BuildBatchStatusTargetQuery(BatchLineStatusUpdateRequestDto? req)
|
||||||
|
{
|
||||||
|
var q = _db.MobileLines.AsQueryable();
|
||||||
|
var reservaFilter = false;
|
||||||
|
|
||||||
|
var skil = (req?.Skil ?? "").Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(skil))
|
||||||
|
{
|
||||||
|
if (skil.Equals("RESERVA", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
reservaFilter = true;
|
||||||
|
q = q.Where(x =>
|
||||||
|
EF.Functions.ILike((x.Usuario ?? "").Trim(), "RESERVA") ||
|
||||||
|
EF.Functions.ILike((x.Skil ?? "").Trim(), "RESERVA") ||
|
||||||
|
EF.Functions.ILike((x.Cliente ?? "").Trim(), "RESERVA"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Skil ?? "", $"%{skil}%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reservaFilter)
|
||||||
|
q = ExcludeReservaContext(q);
|
||||||
|
|
||||||
|
q = ApplyAdditionalFilters(q, req?.AdditionalMode, req?.AdditionalServices);
|
||||||
|
|
||||||
|
var clients = (req?.Clients ?? new List<string>())
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (clients.Count > 0)
|
||||||
|
{
|
||||||
|
var normalizedClients = clients
|
||||||
|
.Select(x => x.ToUpperInvariant())
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
q = q.Where(x => normalizedClients.Contains((x.Cliente ?? "").Trim().ToUpper()));
|
||||||
|
}
|
||||||
|
|
||||||
|
var search = (req?.Search ?? "").Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
q = q.Where(x =>
|
||||||
|
EF.Functions.ILike(x.Linha ?? "", $"%{search}%") ||
|
||||||
|
EF.Functions.ILike(x.Chip ?? "", $"%{search}%") ||
|
||||||
|
EF.Functions.ILike(x.Cliente ?? "", $"%{search}%") ||
|
||||||
|
EF.Functions.ILike(x.Usuario ?? "", $"%{search}%") ||
|
||||||
|
EF.Functions.ILike(x.Conta ?? "", $"%{search}%") ||
|
||||||
|
EF.Functions.ILike(x.Status ?? "", $"%{search}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var usuario = (req?.Usuario ?? "").Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(usuario))
|
||||||
|
{
|
||||||
|
q = q.Where(x => EF.Functions.ILike(x.Usuario ?? "", $"%{usuario}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddBatchStatusUpdateHistoryAsync(
|
||||||
|
BatchLineStatusUpdateRequestDto req,
|
||||||
|
BatchLineStatusUpdateResultDto result,
|
||||||
|
bool isBlockAction,
|
||||||
|
string? blockStatus)
|
||||||
|
{
|
||||||
|
var tenantId = _tenantProvider.TenantId;
|
||||||
|
if (!tenantId.HasValue)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimNameId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
var userId = Guid.TryParse(claimNameId, out var parsedUserId) ? parsedUserId : (Guid?)null;
|
||||||
|
|
||||||
|
var userName = User.FindFirst("name")?.Value
|
||||||
|
?? User.FindFirst(ClaimTypes.Name)?.Value
|
||||||
|
?? User.Identity?.Name;
|
||||||
|
|
||||||
|
var userEmail = User.FindFirst(ClaimTypes.Email)?.Value
|
||||||
|
?? User.FindFirst("email")?.Value;
|
||||||
|
|
||||||
|
var clientFilter = string.Join(", ", (req.Clients ?? new List<string>())
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
var changes = new List<AuditFieldChangeDto>
|
||||||
|
{
|
||||||
|
new() { Field = "AcaoLote", ChangeType = "batch", NewValue = isBlockAction ? "BLOCK" : "UNBLOCK" },
|
||||||
|
new() { Field = "Escopo", ChangeType = "batch", NewValue = req.ApplyToAllFiltered ? "TODOS_FILTRADOS" : "SELECAO_MANUAL" },
|
||||||
|
new() { Field = "StatusAplicado", ChangeType = "batch", NewValue = isBlockAction ? (blockStatus ?? "-") : "ATIVO" },
|
||||||
|
new() { Field = "QuantidadeSolicitada", ChangeType = "batch", NewValue = result.Requested.ToString(CultureInfo.InvariantCulture) },
|
||||||
|
new() { Field = "QuantidadeAtualizada", ChangeType = "batch", NewValue = result.Updated.ToString(CultureInfo.InvariantCulture) },
|
||||||
|
new() { Field = "QuantidadeFalha", ChangeType = "batch", NewValue = result.Failed.ToString(CultureInfo.InvariantCulture) },
|
||||||
|
new() { Field = "FiltroSkil", ChangeType = "filter", NewValue = string.IsNullOrWhiteSpace(req.Skil) ? "-" : req.Skil!.Trim() },
|
||||||
|
new() { Field = "FiltroCliente", ChangeType = "filter", NewValue = string.IsNullOrWhiteSpace(clientFilter) ? "-" : clientFilter },
|
||||||
|
new() { Field = "FiltroUsuario", ChangeType = "filter", NewValue = string.IsNullOrWhiteSpace(req.Usuario) ? "-" : req.Usuario!.Trim() },
|
||||||
|
new() { Field = "FiltroBusca", ChangeType = "filter", NewValue = string.IsNullOrWhiteSpace(req.Search) ? "-" : req.Search!.Trim() }
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.AuditLogs.Add(new AuditLog
|
||||||
|
{
|
||||||
|
TenantId = tenantId.Value,
|
||||||
|
OccurredAtUtc = DateTime.UtcNow,
|
||||||
|
UserId = userId,
|
||||||
|
UserName = string.IsNullOrWhiteSpace(userName) ? "USUARIO" : userName,
|
||||||
|
UserEmail = userEmail,
|
||||||
|
Action = isBlockAction ? "BATCH_BLOCK" : "BATCH_UNBLOCK",
|
||||||
|
Page = "Geral",
|
||||||
|
EntityName = "MobileLineBatchStatus",
|
||||||
|
EntityId = null,
|
||||||
|
EntityLabel = "Bloqueio/Desbloqueio em Lote",
|
||||||
|
ChangesJson = JsonSerializer.Serialize(changes),
|
||||||
|
RequestPath = HttpContext.Request.Path.Value,
|
||||||
|
RequestMethod = HttpContext.Request.Method,
|
||||||
|
IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ IMPORTAÇÃO DA ABA MUREG
|
// ✅ IMPORTAÇÃO DA ABA MUREG
|
||||||
// ✅ NOVA REGRA:
|
// ✅ NOVA REGRA:
|
||||||
|
|
@ -1873,13 +2161,37 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
if (wsM == null) return;
|
if (wsM == null) return;
|
||||||
|
|
||||||
var headerRow = wsM.RowsUsed().FirstOrDefault(r => r.CellsUsed().Any(c => NormalizeHeader(c.GetString()) == "ITEM"));
|
var headerRow = wsM.RowsUsed().FirstOrDefault(r =>
|
||||||
|
{
|
||||||
|
var keys = r.CellsUsed()
|
||||||
|
.Select(c => NormalizeHeader(c.GetString()))
|
||||||
|
.Where(k => !string.IsNullOrWhiteSpace(k))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (keys.Length == 0) return false;
|
||||||
|
|
||||||
|
var hasItem = keys.Any(k => k == "ITEM" || k == "ITEMID" || k.Contains("ITEM"));
|
||||||
|
var hasMuregColumns = keys.Any(k =>
|
||||||
|
k.Contains("LINHAANTIGA") ||
|
||||||
|
k.Contains("LINHANOVA") ||
|
||||||
|
k.Contains("ICCID") ||
|
||||||
|
k.Contains("DATAMUREG") ||
|
||||||
|
k.Contains("DATADAMUREG"));
|
||||||
|
|
||||||
|
return hasItem && hasMuregColumns;
|
||||||
|
});
|
||||||
if (headerRow == null) return;
|
if (headerRow == null) return;
|
||||||
|
|
||||||
var map = BuildHeaderMap(headerRow);
|
var lastCol = GetLastUsedColumn(wsM, headerRow.RowNumber());
|
||||||
|
|
||||||
int colItem = GetCol(map, "ITEM");
|
var colItem = FindColByAny(headerRow, lastCol, "ITEM", "ITEM ID", "ITEM(ID)", "ITEMID", "ITÉM", "ITÉM (ID)");
|
||||||
if (colItem == 0) return;
|
var colLinhaAntiga = FindColByAny(headerRow, lastCol, "LINHA ANTIGA", "LINHA ANTERIOR", "LINHA ANT.", "LINHA ANT");
|
||||||
|
var colLinhaNova = FindColByAny(headerRow, lastCol, "LINHA NOVA", "NOVA LINHA");
|
||||||
|
var colIccid = FindColByAny(headerRow, lastCol, "ICCID", "CHIP");
|
||||||
|
var colDataMureg = FindColByAny(headerRow, lastCol, "DATA DA MUREG", "DATA MUREG", "DT MUREG");
|
||||||
|
|
||||||
|
if (colLinhaAntiga == 0 && colLinhaNova == 0 && colIccid == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var startRow = headerRow.RowNumber() + 1;
|
var startRow = headerRow.RowNumber() + 1;
|
||||||
|
|
||||||
|
|
@ -1889,14 +2201,18 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ dicionários para resolver MobileLineId por Linha/Chip
|
// ✅ dicionários para resolver MobileLineId por Linha/Chip
|
||||||
var mobilePairs = await _db.MobileLines
|
var mobilePairs = await _db.MobileLines
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Select(x => new { x.Id, x.Linha, x.Chip })
|
.Select(x => new { x.Id, x.Item, x.Linha, x.Chip })
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var mobileByLinha = new Dictionary<string, Guid>(StringComparer.Ordinal);
|
var mobileByLinha = new Dictionary<string, Guid>(StringComparer.Ordinal);
|
||||||
var mobileByChip = new Dictionary<string, Guid>(StringComparer.Ordinal);
|
var mobileByChip = new Dictionary<string, Guid>(StringComparer.Ordinal);
|
||||||
|
var mobileByItem = new Dictionary<int, Guid>();
|
||||||
|
|
||||||
foreach (var m in mobilePairs)
|
foreach (var m in mobilePairs)
|
||||||
{
|
{
|
||||||
|
if (m.Item > 0 && !mobileByItem.ContainsKey(m.Item))
|
||||||
|
mobileByItem[m.Item] = m.Id;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(m.Linha))
|
if (!string.IsNullOrWhiteSpace(m.Linha))
|
||||||
{
|
{
|
||||||
var k = OnlyDigits(m.Linha);
|
var k = OnlyDigits(m.Linha);
|
||||||
|
|
@ -1920,21 +2236,41 @@ namespace line_gestao_api.Controllers
|
||||||
|
|
||||||
for (int r = startRow; r <= lastRow; r++)
|
for (int r = startRow; r <= lastRow; r++)
|
||||||
{
|
{
|
||||||
var itemStr = GetCellString(wsM, r, colItem);
|
var itemStr = colItem > 0 ? GetCellString(wsM, r, colItem) : "";
|
||||||
if (string.IsNullOrWhiteSpace(itemStr)) break;
|
var linhaAntigaRaw = colLinhaAntiga > 0 ? GetCellString(wsM, r, colLinhaAntiga) : "";
|
||||||
|
var linhaNovaRaw = colLinhaNova > 0 ? GetCellString(wsM, r, colLinhaNova) : "";
|
||||||
|
var iccidRaw = colIccid > 0 ? GetCellString(wsM, r, colIccid) : "";
|
||||||
|
|
||||||
var linhaAntiga = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA ANTIGA"));
|
if (string.IsNullOrWhiteSpace(itemStr)
|
||||||
var linhaNova = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "LINHA NOVA"));
|
&& string.IsNullOrWhiteSpace(linhaAntigaRaw)
|
||||||
var iccid = NullIfEmptyDigits(GetCellByHeader(wsM, r, map, "ICCID"));
|
&& string.IsNullOrWhiteSpace(linhaNovaRaw)
|
||||||
var dataMureg = TryDate(wsM, r, map, "DATA DA MUREG");
|
&& string.IsNullOrWhiteSpace(iccidRaw))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ resolve MobileLineId (prioridade: LinhaAntiga, depois ICCID)
|
var linhaAntiga = NullIfEmptyDigits(linhaAntigaRaw);
|
||||||
|
var linhaNova = NullIfEmptyDigits(linhaNovaRaw);
|
||||||
|
var iccid = NullIfEmptyDigits(iccidRaw);
|
||||||
|
var dataMureg = colDataMureg > 0 ? TryDateCell(wsM, r, colDataMureg) : null;
|
||||||
|
var item = TryInt(itemStr);
|
||||||
|
var hasSourceItem = item > 0;
|
||||||
|
if (!hasSourceItem)
|
||||||
|
{
|
||||||
|
item = (r - startRow) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ resolve MobileLineId (prioridade: LinhaAntiga, ICCID, LinhaNova, Item)
|
||||||
Guid mobileLineId = Guid.Empty;
|
Guid mobileLineId = Guid.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(linhaAntiga) && mobileByLinha.TryGetValue(linhaAntiga, out var idPorLinha))
|
if (!string.IsNullOrWhiteSpace(linhaAntiga) && mobileByLinha.TryGetValue(linhaAntiga, out var idPorLinha))
|
||||||
mobileLineId = idPorLinha;
|
mobileLineId = idPorLinha;
|
||||||
else if (!string.IsNullOrWhiteSpace(iccid) && mobileByChip.TryGetValue(iccid, out var idPorChip))
|
else if (!string.IsNullOrWhiteSpace(iccid) && mobileByChip.TryGetValue(iccid, out var idPorChip))
|
||||||
mobileLineId = idPorChip;
|
mobileLineId = idPorChip;
|
||||||
|
else if (!string.IsNullOrWhiteSpace(linhaNova) && mobileByLinha.TryGetValue(linhaNova, out var idPorLinhaNova))
|
||||||
|
mobileLineId = idPorLinhaNova;
|
||||||
|
else if (hasSourceItem && mobileByItem.TryGetValue(item, out var idPorItem))
|
||||||
|
mobileLineId = idPorItem;
|
||||||
|
|
||||||
// Se não encontrou correspondência na GERAL, não dá pra salvar (MobileLineId é obrigatório)
|
// Se não encontrou correspondência na GERAL, não dá pra salvar (MobileLineId é obrigatório)
|
||||||
if (mobileLineId == Guid.Empty)
|
if (mobileLineId == Guid.Empty)
|
||||||
|
|
@ -1959,7 +2295,7 @@ namespace line_gestao_api.Controllers
|
||||||
var e = new MuregLine
|
var e = new MuregLine
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
Item = TryInt(itemStr),
|
Item = item,
|
||||||
MobileLineId = mobileLineId,
|
MobileLineId = mobileLineId,
|
||||||
LinhaAntiga = linhaAntigaSnapshot,
|
LinhaAntiga = linhaAntigaSnapshot,
|
||||||
LinhaNova = linhaNova,
|
LinhaNova = linhaNova,
|
||||||
|
|
@ -4983,6 +5319,25 @@ namespace line_gestao_api.Controllers
|
||||||
if (IsReservaValue(x.Skil)) x.Skil = "RESERVA";
|
if (IsReservaValue(x.Skil)) x.Skil = "RESERVA";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplyBlockedLineToReservaContext(MobileLine line)
|
||||||
|
{
|
||||||
|
if (!ShouldAutoMoveBlockedLineToReserva(line?.Status)) return;
|
||||||
|
line.Usuario = "RESERVA";
|
||||||
|
line.Skil = "RESERVA";
|
||||||
|
if (string.IsNullOrWhiteSpace(line.Cliente))
|
||||||
|
line.Cliente = "RESERVA";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldAutoMoveBlockedLineToReserva(string? status)
|
||||||
|
{
|
||||||
|
var normalizedStatus = NormalizeHeader(status);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalizedStatus)) return false;
|
||||||
|
|
||||||
|
return normalizedStatus.Contains("PERDA")
|
||||||
|
|| normalizedStatus.Contains("ROUBO")
|
||||||
|
|| (normalizedStatus.Contains("BLOQUEIO") && normalizedStatus.Contains("120"));
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsReservaValue(string? value)
|
private static bool IsReservaValue(string? value)
|
||||||
=> string.Equals(value?.Trim(), "RESERVA", StringComparison.OrdinalIgnoreCase);
|
=> string.Equals(value?.Trim(), "RESERVA", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ namespace line_gestao_api.Controllers
|
||||||
public string? LinhaAntiga { get; set; } // opcional (snapshot)
|
public string? LinhaAntiga { get; set; } // opcional (snapshot)
|
||||||
public string? LinhaNova { get; set; } // opcional
|
public string? LinhaNova { get; set; } // opcional
|
||||||
public string? ICCID { get; set; } // opcional
|
public string? ICCID { get; set; } // opcional
|
||||||
public DateTime? DataDaMureg { get; set; } // opcional
|
public DateTime? DataDaMureg { get; set; } // ignorado no create (sistema define automaticamente)
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|
@ -234,7 +234,8 @@ namespace line_gestao_api.Controllers
|
||||||
LinhaAntiga = linhaAntigaSnapshot,
|
LinhaAntiga = linhaAntigaSnapshot,
|
||||||
LinhaNova = linhaNova,
|
LinhaNova = linhaNova,
|
||||||
ICCID = iccid,
|
ICCID = iccid,
|
||||||
DataDaMureg = ToUtc(req.DataDaMureg),
|
// Data automática no momento da criação da Mureg
|
||||||
|
DataDaMureg = now,
|
||||||
CreatedAt = now,
|
CreatedAt = now,
|
||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ namespace line_gestao_api.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/parcelamentos")]
|
[Route("api/parcelamentos")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,financeiro")]
|
||||||
public class ParcelamentosController : ControllerBase
|
public class ParcelamentosController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly AppDbContext _db;
|
private readonly AppDbContext _db;
|
||||||
|
|
@ -165,7 +165,7 @@ public class ParcelamentosController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<ActionResult<ParcelamentoDetailDto>> Create([FromBody] ParcelamentoUpsertDto req)
|
public async Task<ActionResult<ParcelamentoDetailDto>> Create([FromBody] ParcelamentoUpsertDto req)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
@ -202,7 +202,7 @@ public class ParcelamentosController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] ParcelamentoUpsertDto req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] ParcelamentoUpsertDto req)
|
||||||
{
|
{
|
||||||
var entity = await _db.ParcelamentoLines
|
var entity = await _db.ParcelamentoLines
|
||||||
|
|
@ -239,7 +239,7 @@ public class ParcelamentosController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id:guid}")]
|
[HttpDelete("{id:guid}")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin")]
|
||||||
public async Task<IActionResult> Delete(Guid id)
|
public async Task<IActionResult> Delete(Guid id)
|
||||||
{
|
{
|
||||||
var entity = await _db.ParcelamentoLines.FirstOrDefaultAsync(x => x.Id == id);
|
var entity = await _db.ParcelamentoLines.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace line_gestao_api.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/templates")]
|
[Route("api/templates")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,gestor,financeiro")]
|
||||||
public class TemplatesController : ControllerBase
|
public class TemplatesController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly GeralSpreadsheetTemplateService _geralSpreadsheetTemplateService;
|
private readonly GeralSpreadsheetTemplateService _geralSpreadsheetTemplateService;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ public class UsersController : ControllerBase
|
||||||
{
|
{
|
||||||
AppRoles.SysAdmin,
|
AppRoles.SysAdmin,
|
||||||
AppRoles.Gestor,
|
AppRoles.Gestor,
|
||||||
|
AppRoles.Financeiro,
|
||||||
AppRoles.Cliente
|
AppRoles.Cliente
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,4 +71,42 @@ namespace line_gestao_api.Dtos
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
public string Message { get; set; } = string.Empty;
|
public string Message { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class BatchLineStatusUpdateRequestDto
|
||||||
|
{
|
||||||
|
// "block" | "unblock"
|
||||||
|
public string? Action { get; set; }
|
||||||
|
public string? BlockStatus { get; set; }
|
||||||
|
public bool ApplyToAllFiltered { get; set; }
|
||||||
|
|
||||||
|
public List<Guid> LineIds { get; set; } = new();
|
||||||
|
|
||||||
|
// Filtros da tela Geral
|
||||||
|
public string? Search { get; set; }
|
||||||
|
public string? Skil { get; set; }
|
||||||
|
public List<string> Clients { get; set; } = new();
|
||||||
|
public string? AdditionalMode { get; set; }
|
||||||
|
public string? AdditionalServices { get; set; }
|
||||||
|
public string? Usuario { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BatchLineStatusUpdateResultDto
|
||||||
|
{
|
||||||
|
public int Requested { get; set; }
|
||||||
|
public int Updated { get; set; }
|
||||||
|
public int Failed { get; set; }
|
||||||
|
public List<BatchLineStatusUpdateItemResultDto> Items { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BatchLineStatusUpdateItemResultDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public int Item { get; set; }
|
||||||
|
public string? Linha { get; set; }
|
||||||
|
public string? Usuario { get; set; }
|
||||||
|
public string? StatusAnterior { get; set; }
|
||||||
|
public string? StatusNovo { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ public static class AppRoles
|
||||||
{
|
{
|
||||||
public const string SysAdmin = "sysadmin";
|
public const string SysAdmin = "sysadmin";
|
||||||
public const string Gestor = "gestor";
|
public const string Gestor = "gestor";
|
||||||
|
public const string Financeiro = "financeiro";
|
||||||
public const string Cliente = "cliente";
|
public const string Cliente = "cliente";
|
||||||
|
|
||||||
public static readonly string[] All = [SysAdmin, Gestor, Cliente];
|
public static readonly string[] All = [SysAdmin, Gestor, Financeiro, Cliente];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ public class TenantProvider : ITenantProvider
|
||||||
|
|
||||||
public bool HasGlobalViewAccess =>
|
public bool HasGlobalViewAccess =>
|
||||||
HasRole(AppRoles.SysAdmin) ||
|
HasRole(AppRoles.SysAdmin) ||
|
||||||
HasRole(AppRoles.Gestor);
|
HasRole(AppRoles.Gestor) ||
|
||||||
|
HasRole(AppRoles.Financeiro);
|
||||||
|
|
||||||
public void SetTenantId(Guid? tenantId)
|
public void SetTenantId(Guid? tenantId)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue